forked from pydanny/django-uni-form
/
helpers.py
308 lines (232 loc) · 9.49 KB
/
helpers.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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
"""
Utilities for helping developers use python for adding various attributes,
elements, and UI elements to forms generated via the uni_form template tag.
"""
from django.core.urlresolvers import reverse, NoReverseMatch
from django.forms.forms import BoundField
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe
from uni_form.util import BaseInput, Toggle
class FormHelpersException(Exception):
""" This is raised when building a form via helpers throws an error.
We want to catch form helper errors as soon as possible because
debugging templatetags is never fun.
"""
pass
class Submit(BaseInput):
"""
Used to create a Submit button descriptor for the uni_form template tag:
submit = Submit('Search the Site','search this site')
Note: The first argument is also slugified and turned into the id for the submit button.
"""
input_type = 'submit'
field_classes = 'submit submitButton'
class Button(BaseInput):
"""
Used to create a Submit input descriptor for the uni_form template tag:
button = Button('Button 1','Press Me!')
Note: The first argument is also slugified and turned into the id for the button.
"""
input_type = 'button'
field_classes = 'button'
class Hidden(BaseInput):
"""
Used to create a Hidden input descriptor for the uni_form template tag.
"""
input_type = 'hidden'
field_classes = 'hidden'
class Reset(BaseInput):
"""
Used to create a Hidden input descriptor for the uni_form template tag.
reset = Reset('Reset This Form','Revert Me!')
Note: The first argument is also slugified and turned into the id for the reset.
"""
input_type = 'reset'
field_classes = 'reset resetButton'
def render_field(field, form):
if isinstance(field, str):
return render_form_field(form, field)
else:
return field.render(form)
def render_form_field(form, field):
try:
field_instance = form.fields[field]
except KeyError:
raise Exception("Could not resolve form field '%s'." % field)
bound_field = BoundField(form, field_instance, field)
html = render_to_string("uni_form/field.html", {'field': bound_field})
if not hasattr(form, 'rendered_fields'):
form.rendered_fields = []
if not field in form.rendered_fields:
form.rendered_fields.append(field)
else:
raise Exception("A field should only be rendered once: %s" % field)
return html
class Layout(object):
'''
Form Layout, add fieldsets, rows, fields and html
example:
>>> layout = Layout(Fieldset('', 'is_company'),
... Fieldset(_('Contact details'),
... 'email',
... Row('password1','password2'),
... 'first_name',
... 'last_name',
... HTML('<img src="/media/somepicture.jpg"/>'),
... 'company'))
>>> helper.add_layout(layout)
'''
def __init__(self, *fields):
self.fields = fields
def render(self, form):
html = ""
for field in self.fields:
html += render_field(field, form)
for field in form.fields.keys():
if not field in form.rendered_fields:
html += render_field(field, form)
return html
class Fieldset(object):
''' Fieldset container. Renders to a <fieldset>. '''
def __init__(self, legend, *fields, **args):
if 'css_class' in args.keys():
self.css = args['css_class']
else:
self.css = None
self.legend_html = legend and ('<legend>%s</legend>' % unicode(legend)) or ''
self.fields = fields
def render(self, form):
if self.css:
html = u'<fieldset class="%s">' % self.css
else:
html = u'<fieldset>'
html += self.legend_html
for field in self.fields:
html += render_field(field, form)
html += u'</fieldset>'
return html
class Row(object):
''' row container. Renders to a set of <div>'''
def __init__(self, *fields, **kwargs):
self.fields = fields
if 'css_class' in kwargs.keys():
self.css = kwargs['css_class']
else:
self.css = "formRow"
def render(self, form):
output = u'<div class="%s">' % self.css
for field in self.fields:
output += render_field(field, form)
output += u'</div>'
return u''.join(output)
class Column(object):
''' column container. Renders to a set of <div>'''
def __init__(self, *fields, **kwargs):
self.fields = fields
if 'css_class' in kwargs.keys():
self.css = kwargs['css_class']
else:
self.css = "formColumn"
def render(self, form):
output = u'<div class="%s">' % self.css
for field in self.fields:
output += render_field(field, form)
output += u'</div>'
return u''.join(output)
class HTML(object):
''' HTML container '''
def __init__(self, html):
self.html = unicode(html)
def render(self, form):
return self.html
class FormHelper(object):
"""
By setting attributes to me you can easily create the text that goes
into the uni_form template tag. One use case is to add to your form
class.
Special attribute behavior:
method: Defaults to POST but you can also do 'GET'
form_action: applied to the form action attribute. Can be a named url in
your urlconf that can be executed via the *url* default template tag or can
simply point to another URL.
id: Generates a form id for dom identification.
If no id provided then no id attribute is created on the form.
class: add space seperated classes to the class list.
Defaults to uniForm.
Always starts with uniForm even do specify classes.
form_tag: Defaults to True. If set to False it renders the form without the form tags.
use_csrf_protection: Defaults to False. If set to True a CSRF protection token is
rendered in the form. This should only be left as False for forms targeting
external sites or internal sites without CSRF protection (as described in the
Django documentation).
Requires the presence of a csrf token in the current context with the identifier
"csrf_token" (which is automatically added to your context when using RequestContext).
Demonstration:
First we create a MyForm class and instantiate it
>>> from django import forms
>>> from uni_form.helpers import FormHelper, Submit, Reset
>>> from django.utils.translation import ugettext_lazy as _
>>> class MyForm(forms.Form):
... title = forms.CharField(label=_("Title"), max_length=30, widget=forms.TextInput())
... # this displays how to attach a formHelper to your forms class.
... helper = FormHelper()
... helper.form_id = 'this-form-rocks'
... helper.form_class = 'search'
... submit = Submit('search','search this site')
... helper.add_input(submit)
... reset = Reset('reset','reset button')
... helper.add_input(reset)
After this in the template::
{% load uni_form_tags %}
{% uni_form form form.helper %}
"""
def __init__(self):
self._form_method = 'post'
self._form_action = ''
self.form_id = ''
self.form_class = ''
self.inputs = []
self.toggle = Toggle()
self.layout = None
self.form_tag = True
self.use_csrf_protection = False
def get_form_method(self):
return self._form_method
def set_form_method(self, method):
if method.lower() not in ('get','post'):
raise FormHelpersException('Only GET and POST are valid in the \
form_method helper attribute')
self._form_method = method.lower()
# we set properties the old way because we want to support pre-2.6 python
form_method = property(get_form_method, set_form_method)
def get_form_action(self):
return self._form_action
def set_form_action(self, action):
try:
self._form_action = reverse(action)
except NoReverseMatch:
self._form_action = action
# we set properties the old way because we want to support pre-2.6 python
form_action = property(get_form_action, set_form_action)
def add_input(self, input_object):
self.inputs.append(input_object)
def add_layout(self, layout):
self.layout = layout
def render_layout(self, form):
return mark_safe(self.layout.render(form))
def get_attr(self):
items = {}
items['form_method'] = self.form_method.strip()
items['form_tag'] = self.form_tag
items['use_csrf_protection'] = self.use_csrf_protection
if self.form_action:
items['form_action'] = self.form_action.strip()
if self.form_id:
items['id'] = self.form_id.strip()
if self.form_class:
items['class'] = self.form_class.strip()
if self.inputs:
items['inputs'] = self.inputs
if self.toggle.fields:
items['toggle_fields'] = self.toggle.fields
return items