Skip to content

Commit 7e67b40

Browse files
committed
Merged in branch/amsl/trunkmerge@5449 from rcross@amsl.com, with some tweaks. This provides the secretariat apps.
- Legacy-Id: 5482
1 parent bf7b128 commit 7e67b40

360 files changed

Lines changed: 314803 additions & 3 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

form_utils/__init__.py

Whitespace-only changes.

form_utils/admin.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from django.contrib import admin
2+
from django import forms
3+
4+
from form_utils.fields import ClearableFileField
5+
6+
class ClearableFileFieldsAdmin(admin.ModelAdmin):
7+
def formfield_for_dbfield(self, db_field, **kwargs):
8+
field = super(ClearableFileFieldsAdmin, self).formfield_for_dbfield(
9+
db_field, **kwargs)
10+
if isinstance(field, forms.FileField):
11+
field = ClearableFileField(field)
12+
return field

form_utils/fields.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from django import forms
2+
3+
from form_utils.widgets import ClearableFileInput
4+
5+
class FakeEmptyFieldFile(object):
6+
"""
7+
A fake FieldFile that will convice a FileField model field to
8+
actually replace an existing file name with an empty string.
9+
10+
FileField.save_form_data only overwrites its instance data if the
11+
incoming form data evaluates to True in a boolean context (because
12+
an empty file input is assumed to mean "no change"). We want to be
13+
able to clear it without requiring the use of a model FileField
14+
subclass (keeping things at the form level only). In order to do
15+
this we need our form field to return a value that evaluates to
16+
True in a boolean context, but to the empty string when coerced to
17+
unicode. This object fulfills that requirement.
18+
19+
It also needs the _committed attribute to satisfy the test in
20+
FileField.pre_save.
21+
22+
This is, of course, hacky and fragile, and depends on internal
23+
knowledge of the FileField and FieldFile classes. But it will
24+
serve until Django FileFields acquire a native ability to be
25+
cleared (ticket 7048).
26+
27+
"""
28+
def __unicode__(self):
29+
return u''
30+
_committed = True
31+
32+
class ClearableFileField(forms.MultiValueField):
33+
default_file_field_class = forms.FileField
34+
widget = ClearableFileInput
35+
36+
def __init__(self, file_field=None, template=None, *args, **kwargs):
37+
file_field = file_field or self.default_file_field_class(*args,
38+
**kwargs)
39+
fields = (file_field, forms.BooleanField(required=False))
40+
kwargs['required'] = file_field.required
41+
kwargs['widget'] = self.widget(file_widget=file_field.widget,
42+
template=template)
43+
super(ClearableFileField, self).__init__(fields, *args, **kwargs)
44+
45+
def compress(self, data_list):
46+
if data_list[1] and not data_list[0]:
47+
return FakeEmptyFieldFile()
48+
return data_list[0]
49+
50+
class ClearableImageField(ClearableFileField):
51+
default_file_field_class = forms.ImageField

form_utils/forms.py

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
"""
2+
forms for django-form-utils
3+
4+
Time-stamp: <2010-04-28 02:57:16 carljm forms.py>
5+
6+
"""
7+
from copy import deepcopy
8+
9+
from django import forms
10+
from django.forms.util import flatatt, ErrorDict
11+
from django.utils.safestring import mark_safe
12+
13+
class Fieldset(object):
14+
"""
15+
An iterable Fieldset with a legend and a set of BoundFields.
16+
17+
"""
18+
def __init__(self, form, name, boundfields, legend='', classes='', description=''):
19+
self.form = form
20+
self.boundfields = boundfields
21+
if legend is None: legend = name
22+
self.legend = legend and mark_safe(legend)
23+
self.classes = classes
24+
self.description = mark_safe(description)
25+
self.name = name
26+
27+
28+
def _errors(self):
29+
return ErrorDict(((k, v) for (k, v) in self.form.errors.iteritems()
30+
if k in [f.name for f in self.boundfields]))
31+
errors = property(_errors)
32+
33+
def __iter__(self):
34+
for bf in self.boundfields:
35+
yield _mark_row_attrs(bf, self.form)
36+
37+
def __repr__(self):
38+
return "%s('%s', %s, legend='%s', classes='%s', description='%s')" % (
39+
self.__class__.__name__, self.name,
40+
[f.name for f in self.boundfields], self.legend, self.classes, self.description)
41+
42+
class FieldsetCollection(object):
43+
def __init__(self, form, fieldsets):
44+
self.form = form
45+
self.fieldsets = fieldsets
46+
self._cached_fieldsets = []
47+
48+
def __len__(self):
49+
return len(self.fieldsets) or 1
50+
51+
def __iter__(self):
52+
if not self._cached_fieldsets:
53+
self._gather_fieldsets()
54+
for field in self._cached_fieldsets:
55+
yield field
56+
57+
def __getitem__(self, key):
58+
if not self._cached_fieldsets:
59+
self._gather_fieldsets()
60+
for field in self._cached_fieldsets:
61+
if field.name == key:
62+
return field
63+
raise KeyError
64+
65+
def _gather_fieldsets(self):
66+
if not self.fieldsets:
67+
self.fieldsets = (('main', {'fields': self.form.fields.keys(),
68+
'legend': ''}),)
69+
for name, options in self.fieldsets:
70+
try:
71+
field_names = [n for n in options['fields']
72+
if n in self.form.fields]
73+
except KeyError:
74+
raise ValueError("Fieldset definition must include 'fields' option." )
75+
boundfields = [forms.forms.BoundField(self.form, self.form.fields[n], n)
76+
for n in field_names]
77+
self._cached_fieldsets.append(Fieldset(self.form, name,
78+
boundfields, options.get('legend', None),
79+
' '.join(options.get('classes', ())),
80+
options.get('description', '')))
81+
82+
def _get_meta_attr(attrs, attr, default):
83+
try:
84+
ret = getattr(attrs['Meta'], attr)
85+
except (KeyError, AttributeError):
86+
ret = default
87+
return ret
88+
89+
def _set_meta_attr(attrs, attr, value):
90+
try:
91+
setattr(attrs['Meta'], attr, value)
92+
return True
93+
except KeyError:
94+
return False
95+
96+
def get_fieldsets(bases, attrs):
97+
"""
98+
Get the fieldsets definition from the inner Meta class.
99+
100+
"""
101+
fieldsets = _get_meta_attr(attrs, 'fieldsets', None)
102+
if fieldsets is None:
103+
#grab the fieldsets from the first base class that has them
104+
for base in bases:
105+
fieldsets = getattr(base, 'base_fieldsets', None)
106+
if fieldsets is not None:
107+
break
108+
fieldsets = fieldsets or []
109+
return fieldsets
110+
111+
def get_fields_from_fieldsets(fieldsets):
112+
"""
113+
Get a list of all fields included in a fieldsets definition.
114+
115+
"""
116+
fields = []
117+
try:
118+
for name, options in fieldsets:
119+
fields.extend(options['fields'])
120+
except (TypeError, KeyError):
121+
raise ValueError('"fieldsets" must be an iterable of two-tuples, '
122+
'and the second tuple must be a dictionary '
123+
'with a "fields" key')
124+
return fields
125+
126+
def get_row_attrs(bases, attrs):
127+
"""
128+
Get the row_attrs definition from the inner Meta class.
129+
130+
"""
131+
return _get_meta_attr(attrs, 'row_attrs', {})
132+
133+
def _mark_row_attrs(bf, form):
134+
row_attrs = deepcopy(form._row_attrs.get(bf.name, {}))
135+
if bf.field.required:
136+
req_class = 'required'
137+
else:
138+
req_class = 'optional'
139+
if 'class' in row_attrs:
140+
row_attrs['class'] = row_attrs['class'] + ' ' + req_class
141+
else:
142+
row_attrs['class'] = req_class
143+
bf.row_attrs = mark_safe(flatatt(row_attrs))
144+
return bf
145+
146+
class BetterFormBaseMetaclass(type):
147+
def __new__(cls, name, bases, attrs):
148+
attrs['base_fieldsets'] = get_fieldsets(bases, attrs)
149+
fields = get_fields_from_fieldsets(attrs['base_fieldsets'])
150+
if (_get_meta_attr(attrs, 'fields', None) is None and
151+
_get_meta_attr(attrs, 'exclude', None) is None):
152+
_set_meta_attr(attrs, 'fields', fields)
153+
attrs['base_row_attrs'] = get_row_attrs(bases, attrs)
154+
new_class = super(BetterFormBaseMetaclass,
155+
cls).__new__(cls, name, bases, attrs)
156+
return new_class
157+
158+
class BetterFormMetaclass(BetterFormBaseMetaclass,
159+
forms.forms.DeclarativeFieldsMetaclass):
160+
pass
161+
162+
class BetterModelFormMetaclass(BetterFormBaseMetaclass,
163+
forms.models.ModelFormMetaclass):
164+
pass
165+
166+
class BetterBaseForm(object):
167+
"""
168+
``BetterForm`` and ``BetterModelForm`` are subclasses of Form
169+
and ModelForm that allow for declarative definition of fieldsets
170+
and row_attrs in an inner Meta class.
171+
172+
The row_attrs declaration is a dictionary mapping field names to
173+
dictionaries of attribute/value pairs. The attribute/value
174+
dictionaries will be flattened into HTML-style attribute/values
175+
(i.e. {'style': 'display: none'} will become ``style="display:
176+
none"``), and will be available as the ``row_attrs`` attribute of
177+
the ``BoundField``. Also, a CSS class of "required" or "optional"
178+
will automatically be added to the row_attrs of each
179+
``BoundField``, depending on whether the field is required.
180+
181+
There is no automatic inheritance of ``row_attrs``.
182+
183+
The fieldsets declaration is a list of two-tuples very similar to
184+
the ``fieldsets`` option on a ModelAdmin class in
185+
``django.contrib.admin``.
186+
187+
The first item in each two-tuple is a name for the fieldset, and
188+
the second is a dictionary of fieldset options.
189+
190+
Valid fieldset options in the dictionary include:
191+
192+
``fields`` (required): A tuple of field names to display in this
193+
fieldset.
194+
195+
``classes``: A list of extra CSS classes to apply to the fieldset.
196+
197+
``legend``: This value, if present, will be the contents of a ``legend``
198+
tag to open the fieldset.
199+
200+
``description``: A string of optional extra text to be displayed
201+
under the ``legend`` of the fieldset.
202+
203+
When iterated over, the ``fieldsets`` attribute of a
204+
``BetterForm`` (or ``BetterModelForm``) yields ``Fieldset``s.
205+
Each ``Fieldset`` has a ``name`` attribute, a ``legend``
206+
attribute, , a ``classes`` attribute (the ``classes`` tuple
207+
collapsed into a space-separated string), and a description
208+
attribute, and when iterated over yields its ``BoundField``s.
209+
210+
Subclasses of a ``BetterForm`` will inherit their parent's
211+
fieldsets unless they define their own.
212+
213+
A ``BetterForm`` or ``BetterModelForm`` can still be iterated over
214+
directly to yield all of its ``BoundField``s, regardless of
215+
fieldsets.
216+
217+
"""
218+
def __init__(self, *args, **kwargs):
219+
self._fieldsets = deepcopy(self.base_fieldsets)
220+
self._row_attrs = deepcopy(self.base_row_attrs)
221+
self._fieldset_collection = None
222+
super(BetterBaseForm, self).__init__(*args, **kwargs)
223+
224+
@property
225+
def fieldsets(self):
226+
if not self._fieldset_collection:
227+
self._fieldset_collection = FieldsetCollection(self,
228+
self._fieldsets)
229+
return self._fieldset_collection
230+
231+
def __iter__(self):
232+
for bf in super(BetterBaseForm, self).__iter__():
233+
yield _mark_row_attrs(bf, self)
234+
235+
def __getitem__(self, name):
236+
bf = super(BetterBaseForm, self).__getitem__(name)
237+
return _mark_row_attrs(bf, self)
238+
239+
class BetterForm(BetterBaseForm, forms.Form):
240+
__metaclass__ = BetterFormMetaclass
241+
__doc__ = BetterBaseForm.__doc__
242+
243+
class BetterModelForm(BetterBaseForm, forms.ModelForm):
244+
__metaclass__ = BetterModelFormMetaclass
245+
__doc__ = BetterBaseForm.__doc__
246+
247+
248+
class BasePreviewForm (object):
249+
"""
250+
Mixin to add preview functionality to a form. If the form is submitted with
251+
the following k/v pair in its ``data`` dictionary:
252+
253+
'submit': 'preview' (value string is case insensitive)
254+
255+
Then ``PreviewForm.preview`` will be marked ``True`` and the form will
256+
be marked invalid (though this invalidation will not put an error in
257+
its ``errors`` dictionary).
258+
259+
"""
260+
def __init__(self, *args, **kwargs):
261+
super(BasePreviewForm, self).__init__(*args, **kwargs)
262+
self.preview = self.check_preview(kwargs.get('data', None))
263+
264+
def check_preview(self, data):
265+
if data and data.get('submit', '').lower() == u'preview':
266+
return True
267+
return False
268+
269+
def is_valid(self, *args, **kwargs):
270+
if self.preview:
271+
return False
272+
return super(BasePreviewForm, self).is_valid()
273+
274+
class PreviewModelForm(BasePreviewForm, BetterModelForm):
275+
pass
276+
277+
class PreviewForm(BasePreviewForm, BetterForm):
278+
pass
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
$(document).ready(function() {
2+
$('textarea.autoresize').autogrow();
3+
});

0 commit comments

Comments
 (0)