Reputation: 25
In order to be able to automatically use the email sent by the wagtail email form, I need to put some tags in front of the generated lines. So in short, a line inside the email should look like this: "#Start# descr: somethingsomething. #Ende#". I managed to add a "start_tag" and a "end_tag" field to the wagtail "form creator" but now I get a error when I try to submit the form.
The admin form editor:
UPDATE
The new error:
NameError at /anforderungsformular/
name 'start_tag' is not defined
Request Method: POST
Request URL: http://localhost:8000/anforderungsformular/
Django Version: 3.0.8
Exception Type: NameError
Exception Value:
name 'start_tag' is not defined
Exception Location: C:\Program Files (x86)\Python38-32\lib\site-packages\wagtail\contrib\forms\models.py in send_mail, line 293
Python Executable: C:\Program Files (x86)\Python38-32\python.exe
Python Version: 3.8.3
Python Path:
['U:\\IT\\itseite_design',
'C:\\Program Files (x86)\\Python38-32\\python38.zip',
'C:\\Program Files (x86)\\Python38-32\\DLLs',
'C:\\Program Files (x86)\\Python38-32\\lib',
'C:\\Program Files (x86)\\Python38-32',
'C:\\Users\\priwitzl\\AppData\\Roaming\\Python\\Python38\\site-packages',
'C:\\Program Files (x86)\\Python38-32\\lib\\site-packages']
Server time: Di, 11 Aug 2020 12:32:38 +0000
I changed wagtail/contrib/forms/models.py
(Basically I added start_tag
and end_tag
as charFields
and added them to the panel.
import json
import os
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.template.response import TemplateResponse
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from unidecode import unidecode
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.admin.mail import send_mail
from wagtail.core.models import Orderable, Page
from .forms import FormBuilder, WagtailAdminFormPageForm
FORM_FIELD_CHOICES = (
('singleline', _('Single line text')),
('multiline', _('Multi-line text')),
('email', _('Email')),
('number', _('Number')),
('url', _('URL')),
('checkbox', _('Checkbox')),
('checkboxes', _('Checkboxes')),
('dropdown', _('Drop down')),
('multiselect', _('Multiple select')),
('radio', _('Radio buttons')),
('date', _('Date')),
('datetime', _('Date/time')),
('hidden', _('Hidden field')),
)
class AbstractFormSubmission(models.Model):
"""
Data for a form submission.
You can create custom submission model based on this abstract model.
For example, if you need to save additional data or a reference to a user.
"""
form_data = models.TextField()
page = models.ForeignKey(Page, on_delete=models.CASCADE)
submit_time = models.DateTimeField(verbose_name=_('submit time'), auto_now_add=True)
def get_data(self):
"""
Returns dict with form data.
You can override this method to add additional data.
"""
form_data = json.loads(self.form_data)
form_data.update({
'submit_time': self.submit_time,
})
return form_data
def __str__(self):
return self.form_data
class Meta:
abstract = True
verbose_name = _('form submission')
verbose_name_plural = _('form submissions')
class FormSubmission(AbstractFormSubmission):
"""Data for a Form submission."""
class AbstractFormField(Orderable):
"""
Database Fields required for building a Django Form field.
"""
label = models.CharField(
verbose_name=_('label'),
max_length=255,
help_text=_('The label of the form field')
)
field_type = models.CharField(verbose_name=_('field type'), max_length=16, choices=FORM_FIELD_CHOICES)
required = models.BooleanField(verbose_name=_('required'), default=True)
choices = models.TextField(
verbose_name=_('choices'),
blank=True,
help_text=_('Comma separated list of choices. Only applicable in checkboxes, radio and dropdown.')
)
default_value = models.CharField(
verbose_name=_('default value'),
max_length=255,
blank=True,
help_text=_('Default value. Comma separated values supported for checkboxes.')
)
help_text = models.CharField(verbose_name=_('help text'), max_length=255, blank=True)
start_tag = models.CharField(verbose_name=_('start_tag'), max_length=255, blank=True)
end_tag = models.CharField(verbose_name =_('end_tag'), max_length=255, blank=True)
@property
def clean_name(self):
# unidecode will return an ascii string while slugify wants a
# unicode string on the other hand, slugify returns a safe-string
# which will be converted to a normal str
return str(slugify(str(unidecode(self.label))))
panels = [
FieldPanel('label'),
FieldPanel('help_text'),
FieldPanel('required'),
FieldPanel('field_type', classname="formbuilder-type"),
FieldPanel('choices', classname="formbuilder-choices"),
FieldPanel('default_value', classname="formbuilder-default"),
FieldPanel('start_tag'),
FieldPanel('end_tag'),
]
class Meta:
abstract = True
ordering = ['sort_order']
class AbstractForm(Page):
"""
A Form Page. Pages implementing a form should inherit from it
"""
base_form_class = WagtailAdminFormPageForm
form_builder = FormBuilder
submissions_list_view_class = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not hasattr(self, 'landing_page_template'):
name, ext = os.path.splitext(self.template)
self.landing_page_template = name + '_landing' + ext
class Meta:
abstract = True
def get_form_fields(self):
"""
Form page expects `form_fields` to be declared.
If you want to change backwards relation name,
you need to override this method.
"""
return self.form_fields.all()
def get_data_fields(self):
"""
Returns a list of tuples with (field_name, field_label).
"""
data_fields = [
('submit_time', _('Submission date')),
]
data_fields += [
(field.clean_name, field.label)
for field in self.get_form_fields()
]
return data_fields
def get_form_class(self):
fb = self.form_builder(self.get_form_fields())
return fb.get_form_class()
def get_form_parameters(self):
return {}
def get_form(self, *args, **kwargs):
form_class = self.get_form_class()
form_params = self.get_form_parameters()
form_params.update(kwargs)
return form_class(*args, **form_params)
def get_landing_page_template(self, request, *args, **kwargs):
return self.landing_page_template
def get_submission_class(self):
"""
Returns submission class.
You can override this method to provide custom submission class.
Your class must be inherited from AbstractFormSubmission.
"""
return FormSubmission
def get_submissions_list_view_class(self):
from .views import SubmissionsListView
return self.submissions_list_view_class or SubmissionsListView
def process_form_submission(self, form):
"""
Accepts form instance with submitted data, user and page.
Creates submission instance.
You can override this method if you want to have custom creation logic.
For example, if you want to save reference to a user.
"""
return self.get_submission_class().objects.create(
form_data=json.dumps(form.cleaned_data, cls=DjangoJSONEncoder),
page=self,
)
def render_landing_page(self, request, form_submission=None, *args, **kwargs):
"""
Renders the landing page.
You can override this method to return a different HttpResponse as
landing page. E.g. you could return a redirect to a separate page.
"""
context = self.get_context(request)
context['form_submission'] = form_submission
return TemplateResponse(
request,
self.get_landing_page_template(request),
context
)
def serve_submissions_list_view(self, request, *args, **kwargs):
"""
Returns list submissions view for admin.
`list_submissions_view_class` can bse set to provide custom view class.
Your class must be inherited from SubmissionsListView.
"""
view = self.get_submissions_list_view_class().as_view()
return view(request, form_page=self, *args, **kwargs)
def serve(self, request, *args, **kwargs):
if request.method == 'POST':
form = self.get_form(request.POST, request.FILES, page=self, user=request.user)
if form.is_valid():
form_submission = self.process_form_submission(form)
return self.render_landing_page(request, form_submission, *args, **kwargs)
else:
form = self.get_form(page=self, user=request.user)
context = self.get_context(request)
context['form'] = form
return TemplateResponse(
request,
self.get_template(request),
context
)
preview_modes = [
('form', _('Form')),
('landing', _('Landing page')),
]
def serve_preview(self, request, mode):
if mode == 'landing':
request.is_preview = True
return self.render_landing_page(request)
else:
return super().serve_preview(request, mode)
class AbstractEmailForm(AbstractForm):
"""
A Form Page that sends email. Pages implementing a form to be send to an email should inherit from it
"""
to_address = models.CharField(
verbose_name=_('to address'), max_length=255, blank=True,
help_text=_("Optional - form submissions will be emailed to these addresses. Separate multiple addresses by comma.")
)
from_address = models.CharField(verbose_name=_('from address'), max_length=255, blank=True)
subject = models.CharField(verbose_name=_('subject'), max_length=255, blank=True)
def process_form_submission(self, form):
submission = super().process_form_submission(form)
if self.to_address:
self.send_mail(form)
return submission
def send_mail(self, form):
addresses = [x.strip() for x in self.to_address.split(',')]
content = []
for field in form:
value = field.value()
if isinstance(value, list):
value = ', '.join(value)
content.append('{}: {}'.format(start_tag, field.label, value, end_tag))
content = '\n'.join(content)
send_mail(self.subject, content, addresses, self.from_address,)
class Meta:
abstract = True
I guess the problem occurs when trying to send the mail. The form-website loads and I can fill in any fields. But when I try to submit I get the error.
content.append('{}: {}'.format(start_tag, field.label, value, end_tag))
My guess is that I call the variables in a wrong way, but I can't figure how to do it correctly.
I hope I explained my problem well. I'm happy about any help I can get.
Upvotes: 2
Views: 726
Reputation: 66
for this
(and so on. Now comes the tricky part I guess: I want to use the "start_tag" and "end_tag" when generating the email. So instead of this: "label": "value" linebreak "label": "value"..., I want to do this: "start_tag""label": "value""end_tag" linebreak "start_tag""label": "value""end_tag"... Since my problem occured when the system tries to send the email, I know that I call the variables "start/end_tag" in a wrong way. As u said, I try to call a variable of a different module. I am confused since the variable "field.label" can be called, but not "field.start_tag",etc. I hope this helps)
the best way (my opinion) is to create your decorator and wrap your latest needed dict into it.
For your info: the Emailing process has nothing to do with what you need..!
Upvotes: 0
Reputation: 66
As I see..
the first issue is in this
content.append('{}: {}'.format(start_tag, field.label, value, end_tag))
got 4 arguments while you provided only two Curly brackets only,plus, you didn't import the ((start_tag)) nor ((end_tag)), because you defined them in a different model (as fields) but you called them in the different model that you combined them with field.label and the value,
hint: if still you get an error,you should probably check tyopos (writing mistakes)
Upvotes: 1