/
models.py
201 lines (153 loc) · 6.36 KB
/
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
from __future__ import unicode_literals
import json
import re
from six import text_type
from unidecode import unidecode
from django.db import models
from django.shortcuts import render
from django.utils.translation import ugettext_lazy as _
from django.utils.text import slugify
from django.utils.encoding import python_2_unicode_compatible
from django.core.serializers.json import DjangoJSONEncoder
from wagtail.wagtailcore.models import Page, Orderable, UserPagePermissionsProxy, get_page_types
from wagtail.wagtailadmin.edit_handlers import FieldPanel
from wagtail.wagtailadmin.utils import send_mail
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')),
)
HTML_EXTENSION_RE = re.compile(r"(.*)\.html")
@python_2_unicode_compatible
class FormSubmission(models.Model):
"""Data for a Form submission."""
form_data = models.TextField()
page = models.ForeignKey(Page)
submit_time = models.DateTimeField(auto_now_add=True)
def get_data(self):
return json.loads(self.form_data)
def __str__(self):
return self.form_data
class AbstractFormField(Orderable):
"""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 separated list of choices. Only applicable in checkboxes, radio and dropdown.')
)
default_value = models.CharField(
max_length=255,
blank=True,
help_text=_('Default value. Comma separated values supported for checkboxes.')
)
help_text = models.CharField(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(text_type(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"),
]
class Meta:
abstract = True
ordering = ['sort_order']
_FORM_CONTENT_TYPES = None
def get_form_types():
global _FORM_CONTENT_TYPES
if _FORM_CONTENT_TYPES is None:
_FORM_CONTENT_TYPES = [
ct for ct in get_page_types()
if issubclass(ct.model_class(), AbstractForm)
]
return _FORM_CONTENT_TYPES
def get_forms_for_user(user):
"""Return a queryset of form pages that this user is allowed to access the submissions for"""
editable_pages = UserPagePermissionsProxy(user).editable_pages()
return editable_pages.filter(content_type__in=get_form_types())
class AbstractForm(Page):
"""A Form Page. Pages implementing a form should inhert from it"""
form_builder = FormBuilder
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'):
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 get_form_parameters(self):
return {}
def process_form_submission(self, form):
FormSubmission.objects.create(
form_data=json.dumps(form.cleaned_data, cls=DjangoJSONEncoder),
page=self,
)
def serve(self, request):
fb = self.form_builder(self.form_fields.all())
form_class = fb.get_form_class()
form_params = self.get_form_parameters()
if request.method == 'POST':
form = form_class(request.POST, **form_params)
if form.is_valid():
self.process_form_submission(form)
# If we have a form_processing_backend call its process method
if hasattr(self, 'form_processing_backend'):
form_processor = self.form_processing_backend()
form_processor.process(self, form)
# render the landing_page
# TODO: It is much better to redirect to it
return render(request, self.landing_page_template, {
'self': self,
})
else:
form = form_class(**form_params)
return render(request, self.template, {
'self': self,
'form': form,
})
preview_modes = [
('form', 'Form'),
('landing', 'Landing page'),
]
def serve_preview(self, request, mode):
if mode == 'landing':
return render(request, self.landing_page_template, {
'self': self,
})
else:
return super(AbstractForm, self).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"""
is_abstract = True # Don't display me in "Add"
to_address = models.CharField(max_length=255, blank=True, help_text=_("Optional - form submissions will be emailed to this address"))
from_address = models.CharField(max_length=255, blank=True)
subject = models.CharField(max_length=255, blank=True)
def process_form_submission(self, form):
super(AbstractEmailForm, self).process_form_submission(form)
if self.to_address:
content = '\n'.join([x[1].label + ': ' + form.data.get(x[0]) for x in form.fields.items()])
send_mail(self.subject, content, [self.to_address], self.from_address,)
class Meta:
abstract = True