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
|
from forms import Form, ValidationError
from fields import IntegerField, BooleanField
from widgets import HiddenInput, Media
# special field names
FORM_COUNT_FIELD_NAME = 'COUNT'
ORDERING_FIELD_NAME = 'ORDER'
DELETION_FIELD_NAME = 'DELETE'
class ManagementForm(Form):
"""
``ManagementForm`` is used to keep track of how many form instances
are displayed on the page. If adding new forms via javascript, you should
increment the count field of this form as well.
"""
def __init__(self, *args, **kwargs):
self.base_fields[FORM_COUNT_FIELD_NAME] = IntegerField(widget=HiddenInput)
super(ManagementForm, self).__init__(*args, **kwargs)
class BaseFormSet(object):
"""A collection of instances of the same Form class."""
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None):
self.is_bound = data is not None or files is not None
self.prefix = prefix or 'form'
self.auto_id = auto_id
self.data = data
self.files = files
self.initial = initial
# initialization is different depending on whether we recieved data, initial, or nothing
if data or files:
self.management_form = ManagementForm(data, files, auto_id=self.auto_id, prefix=self.prefix)
if self.management_form.is_valid():
self.total_forms = self.management_form.cleaned_data[FORM_COUNT_FIELD_NAME]
self.required_forms = self.total_forms - self.num_extra
self.change_form_count = self.total_forms - self.num_extra
else:
# not sure that ValidationError is the best thing to raise here
raise ValidationError('ManagementForm data is missing or has been tampered with')
elif initial:
self.change_form_count = len(initial)
self.required_forms = len(initial)
self.total_forms = self.required_forms + self.num_extra
self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix)
else:
self.change_form_count = 0
self.required_forms = 0
self.total_forms = self.num_extra
self.management_form = ManagementForm(initial={FORM_COUNT_FIELD_NAME: self.total_forms}, auto_id=self.auto_id, prefix=self.prefix)
def _get_add_forms(self):
"""Return a list of all the add forms in this ``FormSet``."""
FormClass = self.form_class
if not hasattr(self, '_add_forms'):
add_forms = []
for i in range(self.change_form_count, self.total_forms):
kwargs = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)}
if self.data:
kwargs['data'] = self.data
if self.files:
kwargs['files'] = self.files
add_form = FormClass(**kwargs)
self.add_fields(add_form, i)
add_forms.append(add_form)
self._add_forms = add_forms
return self._add_forms
add_forms = property(_get_add_forms)
def _get_change_forms(self):
"""Return a list of all the change forms in this ``FormSet``."""
FormClass = self.form_class
if not hasattr(self, '_add_forms'):
change_forms = []
for i in range(0, self.change_form_count):
kwargs = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)}
if self.data:
kwargs['data'] = self.data
if self.files:
kwargs['files'] = self.files
if self.initial:
kwargs['initial'] = self.initial[i]
change_form = FormClass(**kwargs)
self.add_fields(change_form, i)
change_forms.append(change_form)
self._change_forms= change_forms
return self._change_forms
change_forms = property(_get_change_forms)
def _forms(self):
return self.change_forms + self.add_forms
forms = property(_forms)
def full_clean(self):
"""Cleans all of self.data and populates self.__errors and self.cleaned_data."""
is_valid = True
errors = []
if not self.is_bound: # Stop further processing.
self.__errors = errors
return
cleaned_data = []
deleted_data = []
# Process change forms
for form in self.change_forms:
if form.is_valid():
if self.deletable and form.cleaned_data[DELETION_FIELD_NAME]:
deleted_data.append(form.cleaned_data)
else:
cleaned_data.append(form.cleaned_data)
else:
is_valid = False
errors.append(form.errors)
# Process add forms in reverse so we can easily tell when the remaining
# ones should be required.
required = False
add_errors = []
for i in range(len(self.add_forms)-1, -1, -1):
form = self.add_forms[i]
# If an add form is empty, reset it so it won't have any errors
if form.is_empty([ORDERING_FIELD_NAME]) and not required:
form.reset()
continue
else:
required = True
if form.is_valid():
cleaned_data.append(form.cleaned_data)
else:
is_valid = False
add_errors.append(form.errors)
add_errors.reverse()
errors.extend(add_errors)
if self.orderable:
cleaned_data.sort(lambda x,y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME])
if is_valid:
self.cleaned_data = cleaned_data
self.deleted_data = deleted_data
self.errors = errors
self._is_valid = is_valid
def add_fields(self, form, index):
"""A hook for adding extra fields on to each form instance."""
if self.orderable:
form.fields[ORDERING_FIELD_NAME] = IntegerField(label='Order', initial=index+1)
if self.deletable:
form.fields[DELETION_FIELD_NAME] = BooleanField(label='Delete', required=False)
def add_prefix(self, index):
return '%s-%s' % (self.prefix, index)
def is_valid(self):
self.full_clean()
return self._is_valid
def _get_media(self):
# All the forms on a FormSet are the same, so you only need to
# interrogate the first form for media.
if self.forms:
return self.forms[0].media
else:
return Media()
media = property(_get_media)
def formset_for_form(form, formset=BaseFormSet, num_extra=1, orderable=False, deletable=False):
"""Return a FormSet for the given form class."""
attrs = {'form_class': form, 'num_extra': num_extra, 'orderable': orderable, 'deletable': deletable}
return type(form.__name__ + 'FormSet', (formset,), attrs)
def all_valid(formsets):
"""Returns true if every formset in formsets is valid."""
for formset in formsets:
if not formset.is_valid():
return False
return True
|