From 92563ff53532eb9480139fdc95186085246e22ba Mon Sep 17 00:00:00 2001 From: Serafeim Papastefanos Date: Thu, 20 Mar 2014 23:04:44 +0200 Subject: [PATCH] Add form builder and the basic form workflow also add the implementation of all form field types. The form will save the submitted data to the database (using the FormSubmission) model. --- wagtail/wagtailforms/forms.py | 67 ++++++++++++++++++++++ wagtail/wagtailforms/models.py | 102 +++++++++++++++++++++++++-------- 2 files changed, 145 insertions(+), 24 deletions(-) create mode 100644 wagtail/wagtailforms/forms.py diff --git a/wagtail/wagtailforms/forms.py b/wagtail/wagtailforms/forms.py new file mode 100644 index 0000000000..b444895c58 --- /dev/null +++ b/wagtail/wagtailforms/forms.py @@ -0,0 +1,67 @@ +import django.forms +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 + field_name = str(slugify(unicode(unidecode(field.label)))) + self.formfields[field_name] = f + + def get_options(self, field): + options = {} + options['label'] = field.label + options['help_text'] = field.help_text + options['required'] = field.required + options['initial'] = field.default_value + return options + + def create_singleline_field(self, field, options): + # TODO: This is a default value - it may need to be changed + options['max_length'] = 255 + return django.forms.CharField(**options) + + def create_multiline_field(self, field, options): + return django.forms.CharField(widget=django.forms.Textarea, **options) + + 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(',')) + return django.forms.ChoiceField(**options) + + def create_radio_field(self, field, options): + 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'] = map(lambda x: (x.strip(),x.strip()), field.choices.split(',')) + options['initial'] = 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 ) + diff --git a/wagtail/wagtailforms/models.py b/wagtail/wagtailforms/models.py index e4be5a3790..ff758497c3 100644 --- a/wagtail/wagtailforms/models.py +++ b/wagtail/wagtailforms/models.py @@ -1,32 +1,53 @@ +from django.conf import settings from django.db import models from django.shortcuts import render from django.utils.translation import ugettext_lazy as _ +import json +import re + from wagtail.wagtailcore.models import Page, Orderable from wagtail.wagtailadmin.edit_handlers import FieldPanel, InlinePanel from modelcluster.fields import ParentalKey +from .forms import FormBuilder + FORM_FIELD_CHOICES = ( - ('SINGLELINE', _('Single line text')), - ('MULTILINE', _('Multi-line text')), - ('EMAIL', _('Email')), - ('NUMBER', _('Number')), - ('URL', _('URL')), - ('CHECKBOX', _('Checkbox')), - ('CHECKBOXES', _('Checkboxes')), - ('DROPDOWN', _('Drop down')), - ('RADIO', _('Radio buttons')), - ('DATE', _('Date')), - ('DATETIME', _('Date/time')), + ('singleline', _('Single line text')), + ('multiline', _('Multi-line text')), + ('email', _('Email')), + ('number', _('Number')), + ('url', _('URL')), + ('checkbox', _('Checkbox')), + ('checkboxes', _('Checkboxes')), + ('dropdown', _('Drop down')), + ('radio', _('Radio buttons')), + ('date', _('Date')), + ('datetime', _('Date/time')), ) + +HTML_EXTENSION_RE = re.compile(r"(.*)\.html") + + +class FormSubmission(models.Model): + """Data for a Form submission.""" + form_data = models.TextField() + form_page = models.ForeignKey('wagtailcore.Page',related_name='+') + submit_time = models.DateTimeField(auto_now_add=True) + user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True) + + def __unicode__(self): + return self.form_data + class AbstractFormFields(models.Model): - #page = ParentalKey('wagtailforms.AbstractForm', related_name='form_fields') - label = models.CharField(max_length=255) + """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) - required = models.BooleanField( default=True) - choices = models.CharField(max_length=512, blank=True, help_text='Comma seperated list of choices') + required = models.BooleanField(default=True) + choices = models.CharField(max_length=512, blank=True, help_text=_('Comma seperated list of choices')) default_value = models.CharField(max_length=255, blank=True) help_text = models.CharField(max_length=255, blank=True) @@ -42,31 +63,64 @@ class AbstractFormFields(models.Model): class Meta: abstract = True - - + class AbstractForm(Page): - is_abstract = True #Don't display me in "Add" + """A Form Page. Pages with form should inhert from it""" + form_builder = FormBuilder + is_abstract = True # Don't display me in "Add" + + def __init__(self, *args, **kwargs): + super(Page, self).__init__(*args, **kwargs) + 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 - + def serve(self, request): - # Get fields - form_fields = self.form_fields + 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(): + # remove csrf_token from form.data + form_data = dict( + i for i in self.form.data.items() + if i[0] != 'csrfmiddlewaretoken' + ) + FormSubmission.objects.create( + form_data = json.dumps(form_data), + form_page = self.page_ptr, + user = request.user, + ) + # TODO: Do other things like sending email + # render the landing_page + # TODO: It is much better to redirect to it + return render(request, self.landing_page_template, { + 'self': self, + }) + else: + self.form = form_class() return render(request, self.template, { 'self': self, + 'form': self.form, }) - + + +######## TEST class ConcreteFormFields(Orderable, AbstractFormFields): page = ParentalKey('wagtailforms.ConcreteForm', related_name='form_fields') - class ConcreteForm(AbstractForm): - pass + 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"), ]