diff --git a/wagtail/wagtailforms/backends/email.py b/wagtail/wagtailforms/backends/email.py index 063a2ce65..2dc0b84f7 100644 --- a/wagtail/wagtailforms/backends/email.py +++ b/wagtail/wagtailforms/backends/email.py @@ -1,4 +1,4 @@ -import datetime +import datetime from django.core.exceptions import ImproperlyConfigured from .base import BaseFormProcessor @@ -8,7 +8,7 @@ from wagtail.wagtailadmin import tasks class EmailFormProcessor(BaseFormProcessor): def __init__(self): pass - + @staticmethod def validate_usage(page): try: @@ -17,7 +17,7 @@ class EmailFormProcessor(BaseFormProcessor): page._meta.get_field('from_address') except: raise ImproperlyConfigured("To use the EmailFormProcessor your Page must define the fields: subject, to_address and from_address.") - + def process(self, page, form): - content = ', '.join([ x[1].label +': '+ form.data.get(x[0]) for x in form.fields.items() ]) - tasks.send_email_task.delay(page.subject, content, [page.to_address], page.from_address, ) + content = ', '.join([x[1].label + ': ' + form.data.get(x[0]) for x in form.fields.items()]) + tasks.send_email_task.delay(page.subject, content, [page.to_address], page.from_address,) diff --git a/wagtail/wagtailforms/forms.py b/wagtail/wagtailforms/forms.py index 2543bc3cd..e1e5dbdcd 100644 --- a/wagtail/wagtailforms/forms.py +++ b/wagtail/wagtailforms/forms.py @@ -3,15 +3,17 @@ from django.utils.datastructures import SortedDict from django.utils.text import slugify from unidecode import unidecode + class FormBuilder(): formfields = SortedDict() + def __init__(self, fields): for field in fields: options = self.get_options(field) - f = getattr(self, "create_"+field.field_type+"_field" )(field, options) - # 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 + f = getattr(self, "create_"+field.field_type+"_field")(field, options) + # 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 field_name = str(slugify(unicode(unidecode(field.label)))) self.formfields[field_name] = f @@ -25,7 +27,7 @@ class FormBuilder(): def create_singleline_field(self, field, options): # TODO: This is a default value - it may need to be changed - options['max_length'] = 255 + options['max_length'] = 255 return django.forms.CharField(**options) def create_multiline_field(self, field, options): @@ -33,38 +35,53 @@ class FormBuilder(): def create_date_field(self, field, options): return django.forms.DateField(**options) - + def create_datetime_field(self, field, options): return django.forms.DateTimeField(**options) def create_email_field(self, field, options): return django.forms.EmailField(**options) - + def create_url_field(self, field, options): return django.forms.URLField(**options) def create_number_field(self, field, options): return django.forms.DecimalField(**options) - + def create_dropdown_field(self, field, options): - options['choices'] = map(lambda x: (x.strip(),x.strip()), field.choices.split(',')) + options['choices'] = map( + lambda x: (x.strip(), x.strip()), + field.choices.split(',') + ) return django.forms.ChoiceField(**options) - + def create_radio_field(self, field, options): - options['choices'] = map(lambda x: (x.strip(),x.strip()), field.choices.split(',')) + options['choices'] = map( + lambda x: (x.strip(), x.strip()), + field.choices.split(',') + ) return django.forms.ChoiceField(widget=django.forms.RadioSelect, **options) - + def create_checkboxes_field(self, field, options): - options['choices'] = [ (x.strip(), x.strip()) for x in field.choices.split(',')] - options['initial'] = [ x.strip() for x in field.default_value.split(',') ] - return django.forms.MultipleChoiceField(widget=django.forms.CheckboxSelectMultiple, **options) - + options['choices'] = [(x.strip(), x.strip()) for x in field.choices.split(',')] + options['initial'] = [x.strip() for x in field.default_value.split(',')] + return django.forms.MultipleChoiceField( + widget=django.forms.CheckboxSelectMultiple, **options + ) + def create_checkbox_field(self, field, options): return django.forms.BooleanField(**options) - - def get_form_class(self): - return type('WagtailForm', (django.forms.Form,), self.formfields ) - + + def get_form_class(self): + return type('WagtailForm', (django.forms.Form,), self.formfields) + + class SelectDateForm(django.forms.Form): - date_from = django.forms.DateField(required=False, widget=django.forms.DateInput(attrs={'placeholder':'Date from'})) - date_to = django.forms.DateField(required=False, widget=django.forms.DateInput(attrs={'placeholder':'Date to'})) \ No newline at end of file + date_from = django.forms.DateField( + required=False, + widget=django.forms.DateInput(attrs={'placeholder': 'Date from'}) + ) + date_to = django.forms.DateField( + required=False, + widget=django.forms.DateInput(attrs={'placeholder': 'Date to'}) + ) diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index 7a92f4a3d..b84a6b351 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -1,4 +1,3 @@ - from django.conf import settings from django.contrib.contenttypes import generic from django.contrib.contenttypes.models import ContentType @@ -11,13 +10,12 @@ import re from wagtail.wagtailcore.models import PageBase, Page, Orderable from wagtail.wagtailadmin.edit_handlers import FieldPanel, InlinePanel - from wagtail.wagtailforms.backends.email import EmailFormProcessor from modelcluster.fields import ParentalKey - from .forms import FormBuilder + FORM_FIELD_CHOICES = ( ('singleline', _('Single line text')), ('multiline', _('Multi-line text')), @@ -32,12 +30,13 @@ FORM_FIELD_CHOICES = ( ('datetime', _('Date/time')), ) + HTML_EXTENSION_RE = re.compile(r"(.*)\.html") class FormSubmission(models.Model): """Data for a Form submission.""" - + form_data = models.TextField() content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() @@ -45,23 +44,35 @@ class FormSubmission(models.Model): submit_time = models.DateTimeField(auto_now_add=True) user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True) - + def get_data(self): return json.loads(self.form_data) - + def __unicode__(self): return self.form_data + class AbstractFormFields(models.Model): """Database Fields required for building a Django Form field.""" - - label = models.CharField(max_length=255, help_text=_('The label of the form field') ) - field_type = models.CharField(max_length=16, choices = FORM_FIELD_CHOICES) + + label = models.CharField( + max_length=255, + help_text=_('The label of the form field') + ) + field_type = models.CharField(max_length=16, choices=FORM_FIELD_CHOICES) required = models.BooleanField(default=True) - choices = models.CharField(max_length=512, blank=True, help_text=_('Comma seperated list of choices. Only applicable in checkboxes, radio and dropdown.')) - default_value = models.CharField(max_length=255, blank=True, help_text=_('Default value. Comma seperated values supported for checkboxes.')) + choices = models.CharField( + max_length=512, + blank=True, + help_text=_('Comma seperated list of choices. Only applicable in checkboxes, radio and dropdown.') + ) + default_value = models.CharField( + max_length=255, + blank=True, + help_text=_('Default value. Comma seperated values supported for checkboxes.') + ) help_text = models.CharField(max_length=255, blank=True) - + panels = [ FieldPanel('label'), FieldPanel('field_type'), @@ -70,14 +81,15 @@ class AbstractFormFields(models.Model): FieldPanel('default_value'), FieldPanel('help_text'), ] - - class Meta: - abstract = True - + class Meta: + abstract = True + + FORM_MODEL_CLASSES = [] _FORM_CONTENT_TYPES = [] + def get_form_types(): global _FORM_CONTENT_TYPES if len(_FORM_CONTENT_TYPES) != len(FORM_MODEL_CLASSES): @@ -85,58 +97,58 @@ def get_form_types(): ContentType.objects.get_for_model(cls) for cls in FORM_MODEL_CLASSES ] return _FORM_CONTENT_TYPES - + class FormBase(PageBase): """Metaclass for Forms""" def __init__(cls, name, bases, dct): super(FormBase, cls).__init__(name, bases, dct) - + if not cls.is_abstract: # register this type in the list of page content types FORM_MODEL_CLASSES.append(cls) # Check if form_processing_backend is ok if hasattr(cls, 'form_processing_backend'): cls.form_processing_backend.validate_usage(cls) - - + + class AbstractForm(Page): """A Form Page. Pages implementing a form should inhert from it""" __metaclass__ = FormBase - + form_builder = FormBuilder - is_abstract = True # Don't display me in "Add" - + is_abstract = True # Don't display me in "Add" + def __init__(self, *args, **kwargs): super(AbstractForm, self).__init__(*args, **kwargs) - if not hasattr(self, 'landing_page_template'): + if not hasattr(self, 'landing_page_template'): template_wo_ext = re.match(HTML_EXTENSION_RE, self.template).group(1) self.landing_page_template = template_wo_ext + '_landing.html' - + class Meta: - abstract = True - + abstract = True + def serve(self, request): - fb = self.form_builder(self.form_fields.all() ) + fb = self.form_builder(self.form_fields.all()) form_class = fb.get_form_class() - + if request.method == 'POST': self.form = form_class(request.POST) - - if self.form.is_valid(): + + if self.form.is_valid(): # remove csrf_token from form.data form_data = dict( - i for i in self.form.data.items() - if i[0] != 'csrfmiddlewaretoken' + i for i in self.form.data.items() + if i[0] != 'csrfmiddlewaretoken' ) - + submission = FormSubmission.objects.create( - form_data = json.dumps(form_data), - form_page = self, - user = request.user, + form_data=json.dumps(form_data), + form_page=self, + user=request.user, ) - + # If we have a form_processing_backend call its process method if hasattr(self, 'form_processing_backend'): form_processor = self.form_processing_backend() @@ -154,38 +166,40 @@ class AbstractForm(Page): 'self': self, 'form': self.form, }) - + class AbstractEmailForm(AbstractForm): """A Form Page that sends email. Pages implementing a form to be send to an email should inherit from it""" - is_abstract = True # Don't display me in "Add" + is_abstract = True # Don't display me in "Add" form_processing_backend = EmailFormProcessor - + to_address = models.CharField(max_length=255, ) from_address = models.CharField(max_length=255, blank=True) subject = models.CharField(max_length=255, ) - + class Meta: abstract = True - - -######## TEST + + +# TEST class ConcreteFormFields(Orderable, AbstractFormFields): page = ParentalKey('wagtailforms.ConcreteForm', related_name='form_fields') -class ConcreteForm(AbstractForm): + +class ConcreteForm(AbstractForm): thank_you = models.CharField(max_length=255) - + ConcreteForm.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('thank_you', classname="full"), InlinePanel(ConcreteForm, 'form_fields', label="Form Fields"), ] -######## + class ConcreteEmailFormFields(Orderable, AbstractFormFields): page = ParentalKey('wagtailforms.ConcreteEmailForm', related_name='form_fields') + class ConcreteEmailForm(AbstractEmailForm): thank_you = models.CharField(max_length=255) diff --git a/wagtail/wagtailforms/views.py b/wagtail/wagtailforms/views.py index 566043e2e..c8006d87e 100644 --- a/wagtail/wagtailforms/views.py +++ b/wagtail/wagtailforms/views.py @@ -15,6 +15,7 @@ from wagtail.wagtailcore.models import Page from wagtail.wagtailforms.models import FormSubmission, get_form_types from wagtail.wagtailforms.forms import SelectDateForm + def get_form_type_from_url_params(app_name, model_name): """ Retrieve a form type from an app_name / model_name combo. @@ -34,20 +35,21 @@ def get_form_type_from_url_params(app_name, model_name): def index(request): form_types = get_form_types() form_pages = Page.objects.filter(content_type__in=form_types) - + return render(request, 'wagtailforms/index.html', { 'form_pages': form_pages, }) + @permission_required('wagtailadmin.access_admin') def list_submissions(request, app_label, model, id): - model = get_form_type_from_url_params(app_label, model).model_class() form_page = get_object_or_404(model, id=id) - #submissions = FormSubmission.objects.filter(form_page=form_page) - submissions = FormSubmission.objects.filter(content_type = form_page.content_type, object_id=form_page.id) - + submissions = FormSubmission.objects.filter( + content_type=form_page.content_type, object_id=form_page.id + ) + select_date_form = SelectDateForm(request.GET) if select_date_form.is_valid(): date_from = select_date_form.cleaned_data.get('date_from') @@ -57,35 +59,35 @@ def list_submissions(request, app_label, model, id): if date_to: date_to += datetime.timedelta(days=1) if date_from and date_to: - submissions=submissions.filter(submit_time__range=[date_from, date_to] ) + submissions = submissions.filter(submit_time__range=[date_from, date_to]) elif date_from and not date_to: - submissions=submissions.filter(submit_time__gte=date_from) + submissions = submissions.filter(submit_time__gte=date_from) elif not date_from and date_to: - submissions=submissions.filter(submit_time__lte=date_to) - - if request.GET.get('action')=='CSV': + submissions = submissions.filter(submit_time__lte=date_to) + + if request.GET.get('action') == 'CSV': # return a CSV instead response = HttpResponse(content_type='text/csv; charset=utf-8') response['Content-Disposition'] = 'attachment;filename=export.csv' writer = unicodecsv.writer(response, encoding='utf-8') - + if submissions: extra_keys = json.loads(submissions[0].form_data).keys() - - header_row = ['Submission date', 'user',] + + header_row = ['Submission date', 'user'] header_row.extend(extra_keys) - writer.writerow(header_row) + writer.writerow(header_row) for s in submissions: data_row = [s.submit_time, s.user] form_data = json.loads(s.form_data) for ek in extra_keys: - data_row.append( form_data.get(ek) ) + data_row.append(form_data.get(ek)) writer.writerow(data_row) return response - + p = request.GET.get('p', 1) paginator = Paginator(submissions, 20) - + try: submissions = paginator.page(p) except PageNotAnInteger: