1 from forms import Form |
1 from forms import Form |
|
2 from django.core.exceptions import ValidationError |
2 from django.utils.encoding import StrAndUnicode |
3 from django.utils.encoding import StrAndUnicode |
3 from django.utils.safestring import mark_safe |
4 from django.utils.safestring import mark_safe |
4 from django.utils.translation import ugettext as _ |
5 from django.utils.translation import ugettext as _ |
5 from fields import IntegerField, BooleanField |
6 from fields import IntegerField, BooleanField |
6 from widgets import Media, HiddenInput |
7 from widgets import Media, HiddenInput |
7 from util import ErrorList, ErrorDict, ValidationError |
8 from util import ErrorList |
8 |
9 |
9 __all__ = ('BaseFormSet', 'all_valid') |
10 __all__ = ('BaseFormSet', 'all_valid') |
10 |
11 |
11 # special field names |
12 # special field names |
12 TOTAL_FORM_COUNT = 'TOTAL_FORMS' |
13 TOTAL_FORM_COUNT = 'TOTAL_FORMS' |
13 INITIAL_FORM_COUNT = 'INITIAL_FORMS' |
14 INITIAL_FORM_COUNT = 'INITIAL_FORMS' |
|
15 MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS' |
14 ORDERING_FIELD_NAME = 'ORDER' |
16 ORDERING_FIELD_NAME = 'ORDER' |
15 DELETION_FIELD_NAME = 'DELETE' |
17 DELETION_FIELD_NAME = 'DELETE' |
16 |
18 |
17 class ManagementForm(Form): |
19 class ManagementForm(Form): |
18 """ |
20 """ |
21 increment the count field of this form as well. |
23 increment the count field of this form as well. |
22 """ |
24 """ |
23 def __init__(self, *args, **kwargs): |
25 def __init__(self, *args, **kwargs): |
24 self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput) |
26 self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput) |
25 self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput) |
27 self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput) |
|
28 self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput) |
26 super(ManagementForm, self).__init__(*args, **kwargs) |
29 super(ManagementForm, self).__init__(*args, **kwargs) |
27 |
30 |
28 class BaseFormSet(StrAndUnicode): |
31 class BaseFormSet(StrAndUnicode): |
29 """ |
32 """ |
30 A collection of instances of the same Form class. |
33 A collection of instances of the same Form class. |
53 if not form.is_valid(): |
56 if not form.is_valid(): |
54 raise ValidationError('ManagementForm data is missing or has been tampered with') |
57 raise ValidationError('ManagementForm data is missing or has been tampered with') |
55 else: |
58 else: |
56 form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial={ |
59 form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial={ |
57 TOTAL_FORM_COUNT: self.total_form_count(), |
60 TOTAL_FORM_COUNT: self.total_form_count(), |
58 INITIAL_FORM_COUNT: self.initial_form_count() |
61 INITIAL_FORM_COUNT: self.initial_form_count(), |
|
62 MAX_NUM_FORM_COUNT: self.max_num |
59 }) |
63 }) |
60 return form |
64 return form |
61 management_form = property(_management_form) |
65 management_form = property(_management_form) |
62 |
66 |
63 def total_form_count(self): |
67 def total_form_count(self): |
64 """Returns the total number of forms in this FormSet.""" |
68 """Returns the total number of forms in this FormSet.""" |
65 if self.data or self.files: |
69 if self.data or self.files: |
66 return self.management_form.cleaned_data[TOTAL_FORM_COUNT] |
70 return self.management_form.cleaned_data[TOTAL_FORM_COUNT] |
67 else: |
71 else: |
68 total_forms = self.initial_form_count() + self.extra |
72 initial_forms = self.initial_form_count() |
69 if total_forms > self.max_num > 0: |
73 total_forms = initial_forms + self.extra |
|
74 # Allow all existing related objects/inlines to be displayed, |
|
75 # but don't allow extra beyond max_num. |
|
76 if initial_forms > self.max_num >= 0: |
|
77 total_forms = initial_forms |
|
78 elif total_forms > self.max_num >= 0: |
70 total_forms = self.max_num |
79 total_forms = self.max_num |
71 return total_forms |
80 return total_forms |
72 |
81 |
73 def initial_form_count(self): |
82 def initial_form_count(self): |
74 """Returns the number of forms that are required in this FormSet.""" |
83 """Returns the number of forms that are required in this FormSet.""" |
75 if self.data or self.files: |
84 if self.data or self.files: |
76 return self.management_form.cleaned_data[INITIAL_FORM_COUNT] |
85 return self.management_form.cleaned_data[INITIAL_FORM_COUNT] |
77 else: |
86 else: |
78 # Use the length of the inital data if it's there, 0 otherwise. |
87 # Use the length of the inital data if it's there, 0 otherwise. |
79 initial_forms = self.initial and len(self.initial) or 0 |
88 initial_forms = self.initial and len(self.initial) or 0 |
80 if initial_forms > self.max_num > 0: |
89 if initial_forms > self.max_num >= 0: |
81 initial_forms = self.max_num |
90 initial_forms = self.max_num |
82 return initial_forms |
91 return initial_forms |
83 |
92 |
84 def _construct_forms(self): |
93 def _construct_forms(self): |
85 # instantiate all the forms and put them in self.forms |
94 # instantiate all the forms and put them in self.forms |
116 def _get_extra_forms(self): |
125 def _get_extra_forms(self): |
117 """Return a list of all the extra forms in this formset.""" |
126 """Return a list of all the extra forms in this formset.""" |
118 return self.forms[self.initial_form_count():] |
127 return self.forms[self.initial_form_count():] |
119 extra_forms = property(_get_extra_forms) |
128 extra_forms = property(_get_extra_forms) |
120 |
129 |
|
130 def _get_empty_form(self, **kwargs): |
|
131 defaults = { |
|
132 'auto_id': self.auto_id, |
|
133 'prefix': self.add_prefix('__prefix__'), |
|
134 'empty_permitted': True, |
|
135 } |
|
136 if self.data or self.files: |
|
137 defaults['data'] = self.data |
|
138 defaults['files'] = self.files |
|
139 defaults.update(kwargs) |
|
140 form = self.form(**defaults) |
|
141 self.add_fields(form, None) |
|
142 return form |
|
143 empty_form = property(_get_empty_form) |
|
144 |
121 # Maybe this should just go away? |
145 # Maybe this should just go away? |
122 def _get_cleaned_data(self): |
146 def _get_cleaned_data(self): |
123 """ |
147 """ |
124 Returns a list of form.cleaned_data dicts for every form in self.forms. |
148 Returns a list of form.cleaned_data dicts for every form in self.forms. |
125 """ |
149 """ |
142 for i in range(0, self.total_form_count()): |
166 for i in range(0, self.total_form_count()): |
143 form = self.forms[i] |
167 form = self.forms[i] |
144 # if this is an extra form and hasn't changed, don't consider it |
168 # if this is an extra form and hasn't changed, don't consider it |
145 if i >= self.initial_form_count() and not form.has_changed(): |
169 if i >= self.initial_form_count() and not form.has_changed(): |
146 continue |
170 continue |
147 if form.cleaned_data[DELETION_FIELD_NAME]: |
171 if self._should_delete_form(form): |
148 self._deleted_form_indexes.append(i) |
172 self._deleted_form_indexes.append(i) |
149 return [self.forms[i] for i in self._deleted_form_indexes] |
173 return [self.forms[i] for i in self._deleted_form_indexes] |
150 deleted_forms = property(_get_deleted_forms) |
174 deleted_forms = property(_get_deleted_forms) |
151 |
175 |
152 def _get_ordered_forms(self): |
176 def _get_ordered_forms(self): |
166 form = self.forms[i] |
190 form = self.forms[i] |
167 # if this is an extra form and hasn't changed, don't consider it |
191 # if this is an extra form and hasn't changed, don't consider it |
168 if i >= self.initial_form_count() and not form.has_changed(): |
192 if i >= self.initial_form_count() and not form.has_changed(): |
169 continue |
193 continue |
170 # don't add data marked for deletion to self.ordered_data |
194 # don't add data marked for deletion to self.ordered_data |
171 if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]: |
195 if self.can_delete and self._should_delete_form(form): |
172 continue |
196 continue |
173 self._ordering.append((i, form.cleaned_data[ORDERING_FIELD_NAME])) |
197 self._ordering.append((i, form.cleaned_data[ORDERING_FIELD_NAME])) |
174 # After we're done populating self._ordering, sort it. |
198 # After we're done populating self._ordering, sort it. |
175 # A sort function to order things numerically ascending, but |
199 # A sort function to order things numerically ascending, but |
176 # None should be sorted below anything else. Allowing None as |
200 # None should be sorted below anything else. Allowing None as |
210 if self._errors is None: |
234 if self._errors is None: |
211 self.full_clean() |
235 self.full_clean() |
212 return self._errors |
236 return self._errors |
213 errors = property(_get_errors) |
237 errors = property(_get_errors) |
214 |
238 |
|
239 def _should_delete_form(self, form): |
|
240 # The way we lookup the value of the deletion field here takes |
|
241 # more code than we'd like, but the form's cleaned_data will |
|
242 # not exist if the form is invalid. |
|
243 field = form.fields[DELETION_FIELD_NAME] |
|
244 raw_value = form._raw_value(DELETION_FIELD_NAME) |
|
245 should_delete = field.clean(raw_value) |
|
246 return should_delete |
|
247 |
215 def is_valid(self): |
248 def is_valid(self): |
216 """ |
249 """ |
217 Returns True if form.errors is empty for every form in self.forms. |
250 Returns True if form.errors is empty for every form in self.forms. |
218 """ |
251 """ |
219 if not self.is_bound: |
252 if not self.is_bound: |
222 # first failure to make sure validation gets triggered for every form. |
255 # first failure to make sure validation gets triggered for every form. |
223 forms_valid = True |
256 forms_valid = True |
224 for i in range(0, self.total_form_count()): |
257 for i in range(0, self.total_form_count()): |
225 form = self.forms[i] |
258 form = self.forms[i] |
226 if self.can_delete: |
259 if self.can_delete: |
227 # The way we lookup the value of the deletion field here takes |
260 if self._should_delete_form(form): |
228 # more code than we'd like, but the form's cleaned_data will |
|
229 # not exist if the form is invalid. |
|
230 field = form.fields[DELETION_FIELD_NAME] |
|
231 raw_value = form._raw_value(DELETION_FIELD_NAME) |
|
232 should_delete = field.clean(raw_value) |
|
233 if should_delete: |
|
234 # This form is going to be deleted so any of its errors |
261 # This form is going to be deleted so any of its errors |
235 # should not cause the entire formset to be invalid. |
262 # should not cause the entire formset to be invalid. |
236 continue |
263 continue |
237 if bool(self.errors[i]): |
264 if bool(self.errors[i]): |
238 forms_valid = False |
265 forms_valid = False |
250 self._errors.append(form.errors) |
277 self._errors.append(form.errors) |
251 # Give self.clean() a chance to do cross-form validation. |
278 # Give self.clean() a chance to do cross-form validation. |
252 try: |
279 try: |
253 self.clean() |
280 self.clean() |
254 except ValidationError, e: |
281 except ValidationError, e: |
255 self._non_form_errors = e.messages |
282 self._non_form_errors = self.error_class(e.messages) |
256 |
283 |
257 def clean(self): |
284 def clean(self): |
258 """ |
285 """ |
259 Hook for doing any extra formset-wide cleaning after Form.clean() has |
286 Hook for doing any extra formset-wide cleaning after Form.clean() has |
260 been called on every form. Any ValidationError raised by this method |
287 been called on every form. Any ValidationError raised by this method |
265 |
292 |
266 def add_fields(self, form, index): |
293 def add_fields(self, form, index): |
267 """A hook for adding extra fields on to each form instance.""" |
294 """A hook for adding extra fields on to each form instance.""" |
268 if self.can_order: |
295 if self.can_order: |
269 # Only pre-fill the ordering field for initial forms. |
296 # Only pre-fill the ordering field for initial forms. |
270 if index < self.initial_form_count(): |
297 if index is not None and index < self.initial_form_count(): |
271 form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), initial=index+1, required=False) |
298 form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), initial=index+1, required=False) |
272 else: |
299 else: |
273 form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), required=False) |
300 form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), required=False) |
274 if self.can_delete: |
301 if self.can_delete: |
275 form.fields[DELETION_FIELD_NAME] = BooleanField(label=_(u'Delete'), required=False) |
302 form.fields[DELETION_FIELD_NAME] = BooleanField(label=_(u'Delete'), required=False) |
300 # table row with each field as a td. |
327 # table row with each field as a td. |
301 forms = u' '.join([form.as_table() for form in self.forms]) |
328 forms = u' '.join([form.as_table() for form in self.forms]) |
302 return mark_safe(u'\n'.join([unicode(self.management_form), forms])) |
329 return mark_safe(u'\n'.join([unicode(self.management_form), forms])) |
303 |
330 |
304 def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, |
331 def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, |
305 can_delete=False, max_num=0): |
332 can_delete=False, max_num=None): |
306 """Return a FormSet for the given form class.""" |
333 """Return a FormSet for the given form class.""" |
307 attrs = {'form': form, 'extra': extra, |
334 attrs = {'form': form, 'extra': extra, |
308 'can_order': can_order, 'can_delete': can_delete, |
335 'can_order': can_order, 'can_delete': can_delete, |
309 'max_num': max_num} |
336 'max_num': max_num} |
310 return type(form.__name__ + 'FormSet', (formset,), attrs) |
337 return type(form.__name__ + 'FormSet', (formset,), attrs) |