cc1  v2.1
CC1 source code docs
 All Classes Namespaces Files Functions Variables Pages
forms.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 # @COPYRIGHT_begin
3 #
4 # Copyright [2010-2014] Institute of Nuclear Physics PAN, Krakow, Poland
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #
18 # @COPYRIGHT_end
19 
20 ##
21 # @package src.wi.utils.forms
22 #
23 # @author Piotr Wójcik
24 # @date 24.03.2011
25 #
26 
27 from copy import deepcopy
28 import hashlib
29 
30 from django import forms
31 from django.forms.util import flatatt
32 from django.utils.safestring import mark_safe
33 from django.utils.translation import ugettext_lazy as _
34 
35 from wi.utils.regexp import regexp, regexp_text
36 
37 
38 attrs_dict = {'class': 'required'}
39 
40 
41 ##
42 #
43 # Form checking if all values of all required selectboxes have been set.
44 #
45 class RequiredSelectValidation(forms.Form):
46  ##
47  #
48  # Perfoms the check.
49  #
50  def clean(self):
51  if any([v.required and int(self.cleaned_data[k]) == -1 for k, v in self.fields.iteritems()]):
52  raise forms.ValidationError(_('Please fill in all required form fields.'))
53  return self.cleaned_data
54 
55 
56 ##
57 #
58 # Used in number selection inputs.
59 #
60 class NumberChoice(object):
61  def __init__(self, maximum=10):
62  self.max_choices = maximum + 1
63 
64  def __iter__(self):
65  return iter((n, n) for n in range(1, self.max_choices))
66 
67 
68 ##
69 #
70 # Class for <b>setting password</b> form.
71 #
72 class PasswordForm(forms.Form):
73  new_password = forms.RegexField(regex=regexp['password'],
74  max_length=255,
75  widget=forms.PasswordInput(attrs=dict(attrs_dict)),
76  label=_("Password"),
77  error_messages={'invalid': regexp_text['password']})
78 
79  password2 = forms.RegexField(regex=regexp['password'],
80  max_length=255,
81  widget=forms.PasswordInput(attrs=dict(attrs_dict)),
82  label=_("Password confirmation"),
83  error_messages={'invalid': regexp_text['password']})
84 
85  ##
86  #
87  # Checks if given passwords match.
88  #
89  def clean(self):
90  if 'new_password' in self.cleaned_data and 'password2' in self.cleaned_data:
91  if self.cleaned_data['new_password'] != self.cleaned_data['password2']:
92  raise forms.ValidationError(_("The two password fields didn't match."))
93 
94  self.cleaned_data['new_password'] = hashlib.sha1(self.cleaned_data['new_password']).hexdigest()
95  del self.cleaned_data['password2']
96  return self.cleaned_data
97 
98 """
99 Copyright (c) 2008, Carl J Meyer
100 All rights reserved.
101 
102 Redistribution and use in source and binary forms, with or without modification,
103 are permitted provided that the following conditions are met:
104 
105  * Redistributions of source code must retain the above copyright notice,
106  this list of conditions and the following disclaimer.
107  * Redistributions in binary form must reproduce the above copyright notice,
108  this list of conditions and the following disclaimer in the documentation
109  and/or other materials provided with the distribution.
110  * The names of its contributors may not be used to endorse or promote products
111  derived from this software without specific prior written permission.
112 
113 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
114 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
115 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
116 SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
117 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
118 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
119 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
120 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
121 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
122 
123 Time-stamp: <2008-11-21 01:54:45 carljm forms.py>
124 
125 """
126 
127 
128 ##
129 #
130 # An iterable Fieldset with a legend and a set of BoundFields.
131 #
132 class Fieldset(object):
133  def __init__(self, form, name, boundfields, legend=None, description=''):
134  self.form = form
135  self.boundfields = boundfields
136  if legend is None:
137  legend = name
138  self.legend = mark_safe(legend)
139  self.description = mark_safe(description)
140  self.name = name
141 
142  def __iter__(self):
143  for bfi in self.boundfields:
144  yield _mark_row_attrs(bfi, self.form)
145 
146  def __repr__(self):
147  return "%s('%s', %s, legend='%s', description='%s')" % (
148  self.__class__.__name__, self.name,
149  [f.name for f in self.boundfields], self.legend, self.description)
150 
151 
152 class FieldsetCollection(object):
153  def __init__(self, form, fieldsets):
154  self.form = form
155  self.fieldsets = fieldsets
156 
157  def __len__(self):
158  return len(self.fieldsets) or 1
159 
160  def __iter__(self):
161  if not self.fieldsets:
162  self.fieldsets = (('main', {'fields': self.form.fields.keys(),
163  'legend': ''}),)
164  for name, options in self.fieldsets:
165  try:
166  field_names = [n for n in options['fields']
167  if n in self.form.fields]
168  except KeyError:
169  raise ValueError("Fieldset definition must include 'fields' option.")
170  boundfields = [forms.forms.BoundField(self.form, self.form.fields[n], n)
171  for n in field_names]
172  yield Fieldset(self.form, name, boundfields,
173  options.get('legend', None),
174  options.get('description', ''))
175 
176 
177 def _get_meta_attr(attrs, attr, default):
178  try:
179  ret = getattr(attrs['Meta'], attr)
180  except (KeyError, AttributeError):
181  ret = default
182  return ret
183 
184 
185 ##
186 #
187 # Get the fieldsets definition from the inner Meta class, mapping it
188 # on top of the fieldsets from any base classes.
189 #
190 #
191 def get_fieldsets(bases, attrs):
192  fieldsets = _get_meta_attr(attrs, 'fieldsets', ())
193 
194  new_fieldsets = {}
195  order = []
196 
197  for base in bases:
198  for bfs in getattr(base, 'base_fieldsets', ()):
199  new_fieldsets[bfs[0]] = bfs
200  order.append(bfs[0])
201 
202  for bfs in fieldsets:
203  new_fieldsets[bfs[0]] = bfs
204  if bfs[0] not in order:
205  order.append(bfs[0])
206 
207  return [new_fieldsets[name] for name in order]
208 
209 
210 ##
211 #
212 # Get the row_attrs definition from the inner Meta class.
213 #
214 #
215 def get_row_attrs(bases, attrs):
216  return _get_meta_attr(attrs, 'row_attrs', {})
217 
218 
219 def _mark_row_attrs(bf, form):
220  row_attrs = deepcopy(form._row_attrs.get(bf.name, {}))
221  if bf.field.required:
222  req_class = 'required'
223  else:
224  req_class = 'optional'
225  if 'class' in row_attrs:
226  row_attrs['class'] = row_attrs['class'] + ' ' + req_class
227  else:
228  row_attrs['class'] = req_class
229  bf.row_attrs = mark_safe(flatatt(row_attrs))
230  return bf
231 
232 
234  def __new__(cls, name, bases, attrs):
235  attrs['base_fieldsets'] = get_fieldsets(bases, attrs)
236  attrs['base_row_attrs'] = get_row_attrs(bases, attrs)
237  new_class = super(BetterFormBaseMetaclass,
238  cls).__new__(cls, name, bases, attrs)
239  return new_class
240 
241 
242 class BetterFormMetaclass(BetterFormBaseMetaclass, forms.forms.DeclarativeFieldsMetaclass):
243  pass
244 
245 
246 class BetterModelFormMetaclass(BetterFormBaseMetaclass, forms.models.ModelFormMetaclass):
247  pass
248 
249 
250 ##
251 #
252 # \c BetterForm and \c BetterModelForm are subclasses of Form
253 # and ModelForm that allow for declarative definition of fieldsets
254 # and row_attrs in an inner Meta class.
255 #
256 # The row_attrs declaration is a dictionary mapping field names to
257 # dictionaries of attribute/value pairs. The attribute/value
258 # dictionaries will be flattened into HTML-style attribute/values
259 # (i.e. {'style': 'display: none'} will become \c style="display:
260 # none"), and will be available as the \c row_attrs attribute of
261 # the \c BoundField. Also, a CSS class of "required" or "optional"
262 # will automatically be added to the row_attrs of each
263 # \c BoundField, depending on whether the field is required.
264 #
265 # The fieldsets declaration is a list of two-tuples very similar to
266 # the \c fieldsets option on a ModelAdmin class in
267 # \c django.contrib.admin.
268 #
269 # The first item in each two-tuple is a name for the fieldset (must
270 # be unique, so that overriding fieldsets of superclasses works),
271 # and the second is a dictionary of fieldset options
272 #
273 # Valid fieldset options in the dictionary include:
274 #
275 # \c fields (required): A tuple of field names to display in this
276 # fieldset.
277 #
278 # \c classes: A list of extra CSS classes to apply to the fieldset.
279 #
280 # \c legend: This value, if present, will be the contents of a
281 # \c legend tag to open the fieldset. If not present the unique
282 # name of the fieldset will be used (so a value of '' for legend
283 # must be used if no legend is desired.)
284 #
285 # \c description: A string of optional extra text to be displayed
286 # under the \c legend of the fieldset.
287 #
288 # When iterated over, the \c fieldsets attribute of a
289 # \c BetterForm (or \c BetterModelForm) yields \c Fieldsets.
290 # Each \c Fieldset has a name attribute, a legend attribute, and a
291 # description attribute, and when iterated over yields its
292 # \c BoundFields.
293 #
294 # For backwards compatibility, a \c BetterForm or
295 # \c BetterModelForm can still be iterated over directly to yield
296 # all of its \c BoundFields, regardless of fieldsets.
297 #
298 # For more detailed examples, see the doctests in tests/__init__.py.
299 #
300 #
301 class BetterBaseForm(object):
302  def __init__(self, *args, **kwargs):
303  self._fieldsets = deepcopy(self.base_fieldsets)
304  self._row_attrs = deepcopy(self.base_row_attrs)
305  super(BetterBaseForm, self).__init__(*args, **kwargs)
306 
307  @property
308  def fieldsets(self):
309  return FieldsetCollection(self, self._fieldsets)
310 
311  def __iter__(self):
312  for bf in super(BetterBaseForm, self).__iter__():
313  yield _mark_row_attrs(bf, self)
314 
315 
316 class BetterForm(BetterBaseForm, forms.Form):
317  __metaclass__ = BetterFormMetaclass
318  __doc__ = BetterBaseForm.__doc__
319 
320 
321 class BetterModelForm(BetterBaseForm, forms.ModelForm):
322  __metaclass__ = BetterModelFormMetaclass
323  __doc__ = BetterBaseForm.__doc__
324