|
0
|
1 |
""" |
|
|
2 |
Helper functions for creating Form classes from Django models |
|
|
3 |
and database field objects. |
|
|
4 |
""" |
|
|
5 |
|
|
|
6 |
from django.utils.encoding import smart_unicode, force_unicode |
|
|
7 |
from django.utils.datastructures import SortedDict |
|
|
8 |
from django.utils.text import get_text_list, capfirst |
|
|
9 |
from django.utils.translation import ugettext_lazy as _, ugettext |
|
|
10 |
|
|
|
11 |
from util import ValidationError, ErrorList |
|
|
12 |
from forms import BaseForm, get_declared_fields, NON_FIELD_ERRORS |
|
|
13 |
from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES |
|
|
14 |
from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput |
|
|
15 |
from widgets import media_property |
|
|
16 |
from formsets import BaseFormSet, formset_factory, DELETION_FIELD_NAME |
|
|
17 |
|
|
|
18 |
try: |
|
|
19 |
set |
|
|
20 |
except NameError: |
|
|
21 |
from sets import Set as set # Python 2.3 fallback |
|
|
22 |
|
|
|
23 |
__all__ = ( |
|
|
24 |
'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model', |
|
|
25 |
'save_instance', 'form_for_fields', 'ModelChoiceField', |
|
|
26 |
'ModelMultipleChoiceField', |
|
|
27 |
) |
|
|
28 |
|
|
|
29 |
|
|
|
30 |
def save_instance(form, instance, fields=None, fail_message='saved', |
|
|
31 |
commit=True, exclude=None): |
|
|
32 |
""" |
|
|
33 |
Saves bound Form ``form``'s cleaned_data into model instance ``instance``. |
|
|
34 |
|
|
|
35 |
If commit=True, then the changes to ``instance`` will be saved to the |
|
|
36 |
database. Returns ``instance``. |
|
|
37 |
""" |
|
|
38 |
from django.db import models |
|
|
39 |
opts = instance._meta |
|
|
40 |
if form.errors: |
|
|
41 |
raise ValueError("The %s could not be %s because the data didn't" |
|
|
42 |
" validate." % (opts.object_name, fail_message)) |
|
|
43 |
cleaned_data = form.cleaned_data |
|
|
44 |
file_field_list = [] |
|
|
45 |
for f in opts.fields: |
|
|
46 |
if not f.editable or isinstance(f, models.AutoField) \ |
|
|
47 |
or not f.name in cleaned_data: |
|
|
48 |
continue |
|
|
49 |
if fields and f.name not in fields: |
|
|
50 |
continue |
|
|
51 |
if exclude and f.name in exclude: |
|
|
52 |
continue |
|
|
53 |
# OneToOneField doesn't allow assignment of None. Guard against that |
|
|
54 |
# instead of allowing it and throwing an error. |
|
|
55 |
if isinstance(f, models.OneToOneField) and cleaned_data[f.name] is None: |
|
|
56 |
continue |
|
|
57 |
# Defer saving file-type fields until after the other fields, so a |
|
|
58 |
# callable upload_to can use the values from other fields. |
|
|
59 |
if isinstance(f, models.FileField): |
|
|
60 |
file_field_list.append(f) |
|
|
61 |
else: |
|
|
62 |
f.save_form_data(instance, cleaned_data[f.name]) |
|
|
63 |
|
|
|
64 |
for f in file_field_list: |
|
|
65 |
f.save_form_data(instance, cleaned_data[f.name]) |
|
|
66 |
|
|
|
67 |
# Wrap up the saving of m2m data as a function. |
|
|
68 |
def save_m2m(): |
|
|
69 |
opts = instance._meta |
|
|
70 |
cleaned_data = form.cleaned_data |
|
|
71 |
for f in opts.many_to_many: |
|
|
72 |
if fields and f.name not in fields: |
|
|
73 |
continue |
|
|
74 |
if f.name in cleaned_data: |
|
|
75 |
f.save_form_data(instance, cleaned_data[f.name]) |
|
|
76 |
if commit: |
|
|
77 |
# If we are committing, save the instance and the m2m data immediately. |
|
|
78 |
instance.save() |
|
|
79 |
save_m2m() |
|
|
80 |
else: |
|
|
81 |
# We're not committing. Add a method to the form to allow deferred |
|
|
82 |
# saving of m2m data. |
|
|
83 |
form.save_m2m = save_m2m |
|
|
84 |
return instance |
|
|
85 |
|
|
|
86 |
def make_model_save(model, fields, fail_message): |
|
|
87 |
"""Returns the save() method for a Form.""" |
|
|
88 |
def save(self, commit=True): |
|
|
89 |
return save_instance(self, model(), fields, fail_message, commit) |
|
|
90 |
return save |
|
|
91 |
|
|
|
92 |
def make_instance_save(instance, fields, fail_message): |
|
|
93 |
"""Returns the save() method for a Form.""" |
|
|
94 |
def save(self, commit=True): |
|
|
95 |
return save_instance(self, instance, fields, fail_message, commit) |
|
|
96 |
return save |
|
|
97 |
|
|
|
98 |
def form_for_fields(field_list): |
|
|
99 |
""" |
|
|
100 |
Returns a Form class for the given list of Django database field instances. |
|
|
101 |
""" |
|
|
102 |
fields = SortedDict([(f.name, f.formfield()) |
|
|
103 |
for f in field_list if f.editable]) |
|
|
104 |
return type('FormForFields', (BaseForm,), {'base_fields': fields}) |
|
|
105 |
|
|
|
106 |
|
|
|
107 |
# ModelForms ################################################################# |
|
|
108 |
|
|
|
109 |
def model_to_dict(instance, fields=None, exclude=None): |
|
|
110 |
""" |
|
|
111 |
Returns a dict containing the data in ``instance`` suitable for passing as |
|
|
112 |
a Form's ``initial`` keyword argument. |
|
|
113 |
|
|
|
114 |
``fields`` is an optional list of field names. If provided, only the named |
|
|
115 |
fields will be included in the returned dict. |
|
|
116 |
|
|
|
117 |
``exclude`` is an optional list of field names. If provided, the named |
|
|
118 |
fields will be excluded from the returned dict, even if they are listed in |
|
|
119 |
the ``fields`` argument. |
|
|
120 |
""" |
|
|
121 |
# avoid a circular import |
|
|
122 |
from django.db.models.fields.related import ManyToManyField, OneToOneField |
|
|
123 |
opts = instance._meta |
|
|
124 |
data = {} |
|
|
125 |
for f in opts.fields + opts.many_to_many: |
|
|
126 |
if not f.editable: |
|
|
127 |
continue |
|
|
128 |
if fields and not f.name in fields: |
|
|
129 |
continue |
|
|
130 |
if exclude and f.name in exclude: |
|
|
131 |
continue |
|
|
132 |
if isinstance(f, ManyToManyField): |
|
|
133 |
# If the object doesn't have a primry key yet, just use an empty |
|
|
134 |
# list for its m2m fields. Calling f.value_from_object will raise |
|
|
135 |
# an exception. |
|
|
136 |
if instance.pk is None: |
|
|
137 |
data[f.name] = [] |
|
|
138 |
else: |
|
|
139 |
# MultipleChoiceWidget needs a list of pks, not object instances. |
|
|
140 |
data[f.name] = [obj.pk for obj in f.value_from_object(instance)] |
|
|
141 |
else: |
|
|
142 |
data[f.name] = f.value_from_object(instance) |
|
|
143 |
return data |
|
|
144 |
|
|
|
145 |
def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda f: f.formfield()): |
|
|
146 |
""" |
|
|
147 |
Returns a ``SortedDict`` containing form fields for the given model. |
|
|
148 |
|
|
|
149 |
``fields`` is an optional list of field names. If provided, only the named |
|
|
150 |
fields will be included in the returned fields. |
|
|
151 |
|
|
|
152 |
``exclude`` is an optional list of field names. If provided, the named |
|
|
153 |
fields will be excluded from the returned fields, even if they are listed |
|
|
154 |
in the ``fields`` argument. |
|
|
155 |
""" |
|
|
156 |
field_list = [] |
|
|
157 |
opts = model._meta |
|
|
158 |
for f in opts.fields + opts.many_to_many: |
|
|
159 |
if not f.editable: |
|
|
160 |
continue |
|
|
161 |
if fields and not f.name in fields: |
|
|
162 |
continue |
|
|
163 |
if exclude and f.name in exclude: |
|
|
164 |
continue |
|
|
165 |
formfield = formfield_callback(f) |
|
|
166 |
if formfield: |
|
|
167 |
field_list.append((f.name, formfield)) |
|
|
168 |
field_dict = SortedDict(field_list) |
|
|
169 |
if fields: |
|
|
170 |
field_dict = SortedDict([(f, field_dict.get(f)) for f in fields if (not exclude) or (exclude and f not in exclude)]) |
|
|
171 |
return field_dict |
|
|
172 |
|
|
|
173 |
class ModelFormOptions(object): |
|
|
174 |
def __init__(self, options=None): |
|
|
175 |
self.model = getattr(options, 'model', None) |
|
|
176 |
self.fields = getattr(options, 'fields', None) |
|
|
177 |
self.exclude = getattr(options, 'exclude', None) |
|
|
178 |
|
|
|
179 |
|
|
|
180 |
class ModelFormMetaclass(type): |
|
|
181 |
def __new__(cls, name, bases, attrs): |
|
|
182 |
formfield_callback = attrs.pop('formfield_callback', |
|
|
183 |
lambda f: f.formfield()) |
|
|
184 |
try: |
|
|
185 |
parents = [b for b in bases if issubclass(b, ModelForm)] |
|
|
186 |
except NameError: |
|
|
187 |
# We are defining ModelForm itself. |
|
|
188 |
parents = None |
|
|
189 |
declared_fields = get_declared_fields(bases, attrs, False) |
|
|
190 |
new_class = super(ModelFormMetaclass, cls).__new__(cls, name, bases, |
|
|
191 |
attrs) |
|
|
192 |
if not parents: |
|
|
193 |
return new_class |
|
|
194 |
|
|
|
195 |
if 'media' not in attrs: |
|
|
196 |
new_class.media = media_property(new_class) |
|
|
197 |
opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None)) |
|
|
198 |
if opts.model: |
|
|
199 |
# If a model is defined, extract form fields from it. |
|
|
200 |
fields = fields_for_model(opts.model, opts.fields, |
|
|
201 |
opts.exclude, formfield_callback) |
|
|
202 |
# Override default model fields with any custom declared ones |
|
|
203 |
# (plus, include all the other declared fields). |
|
|
204 |
fields.update(declared_fields) |
|
|
205 |
else: |
|
|
206 |
fields = declared_fields |
|
|
207 |
new_class.declared_fields = declared_fields |
|
|
208 |
new_class.base_fields = fields |
|
|
209 |
return new_class |
|
|
210 |
|
|
|
211 |
class BaseModelForm(BaseForm): |
|
|
212 |
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, |
|
|
213 |
initial=None, error_class=ErrorList, label_suffix=':', |
|
|
214 |
empty_permitted=False, instance=None): |
|
|
215 |
opts = self._meta |
|
|
216 |
if instance is None: |
|
|
217 |
# if we didn't get an instance, instantiate a new one |
|
|
218 |
self.instance = opts.model() |
|
|
219 |
object_data = {} |
|
|
220 |
else: |
|
|
221 |
self.instance = instance |
|
|
222 |
object_data = model_to_dict(instance, opts.fields, opts.exclude) |
|
|
223 |
# if initial was provided, it should override the values from instance |
|
|
224 |
if initial is not None: |
|
|
225 |
object_data.update(initial) |
|
|
226 |
super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data, |
|
|
227 |
error_class, label_suffix, empty_permitted) |
|
|
228 |
|
|
|
229 |
def clean(self): |
|
|
230 |
self.validate_unique() |
|
|
231 |
return self.cleaned_data |
|
|
232 |
|
|
|
233 |
def validate_unique(self): |
|
|
234 |
unique_checks, date_checks = self._get_unique_checks() |
|
|
235 |
form_errors = [] |
|
|
236 |
bad_fields = set() |
|
|
237 |
|
|
|
238 |
field_errors, global_errors = self._perform_unique_checks(unique_checks) |
|
|
239 |
bad_fields.union(field_errors) |
|
|
240 |
form_errors.extend(global_errors) |
|
|
241 |
|
|
|
242 |
field_errors, global_errors = self._perform_date_checks(date_checks) |
|
|
243 |
bad_fields.union(field_errors) |
|
|
244 |
form_errors.extend(global_errors) |
|
|
245 |
|
|
|
246 |
for field_name in bad_fields: |
|
|
247 |
del self.cleaned_data[field_name] |
|
|
248 |
if form_errors: |
|
|
249 |
# Raise the unique together errors since they are considered |
|
|
250 |
# form-wide. |
|
|
251 |
raise ValidationError(form_errors) |
|
|
252 |
|
|
|
253 |
def _get_unique_checks(self): |
|
|
254 |
from django.db.models.fields import FieldDoesNotExist, Field as ModelField |
|
|
255 |
|
|
|
256 |
# Gather a list of checks to perform. We only perform unique checks |
|
|
257 |
# for fields present and not None in cleaned_data. Since this is a |
|
|
258 |
# ModelForm, some fields may have been excluded; we can't perform a unique |
|
|
259 |
# check on a form that is missing fields involved in that check. It also does |
|
|
260 |
# not make sense to check data that didn't validate, and since NULL does not |
|
|
261 |
# equal NULL in SQL we should not do any unique checking for NULL values. |
|
|
262 |
unique_checks = [] |
|
|
263 |
# these are checks for the unique_for_<date/year/month> |
|
|
264 |
date_checks = [] |
|
|
265 |
for check in self.instance._meta.unique_together[:]: |
|
|
266 |
fields_on_form = [field for field in check if self.cleaned_data.get(field) is not None] |
|
|
267 |
if len(fields_on_form) == len(check): |
|
|
268 |
unique_checks.append(check) |
|
|
269 |
|
|
|
270 |
# Gather a list of checks for fields declared as unique and add them to |
|
|
271 |
# the list of checks. Again, skip empty fields and any that did not validate. |
|
|
272 |
for name in self.fields: |
|
|
273 |
try: |
|
|
274 |
f = self.instance._meta.get_field_by_name(name)[0] |
|
|
275 |
except FieldDoesNotExist: |
|
|
276 |
# This is an extra field that's not on the ModelForm, ignore it |
|
|
277 |
continue |
|
|
278 |
if not isinstance(f, ModelField): |
|
|
279 |
# This is an extra field that happens to have a name that matches, |
|
|
280 |
# for example, a related object accessor for this model. So |
|
|
281 |
# get_field_by_name found it, but it is not a Field so do not proceed |
|
|
282 |
# to use it as if it were. |
|
|
283 |
continue |
|
|
284 |
if self.cleaned_data.get(name) is None: |
|
|
285 |
continue |
|
|
286 |
if f.unique: |
|
|
287 |
unique_checks.append((name,)) |
|
|
288 |
if f.unique_for_date and self.cleaned_data.get(f.unique_for_date) is not None: |
|
|
289 |
date_checks.append(('date', name, f.unique_for_date)) |
|
|
290 |
if f.unique_for_year and self.cleaned_data.get(f.unique_for_year) is not None: |
|
|
291 |
date_checks.append(('year', name, f.unique_for_year)) |
|
|
292 |
if f.unique_for_month and self.cleaned_data.get(f.unique_for_month) is not None: |
|
|
293 |
date_checks.append(('month', name, f.unique_for_month)) |
|
|
294 |
return unique_checks, date_checks |
|
|
295 |
|
|
|
296 |
|
|
|
297 |
def _perform_unique_checks(self, unique_checks): |
|
|
298 |
bad_fields = set() |
|
|
299 |
form_errors = [] |
|
|
300 |
|
|
|
301 |
for unique_check in unique_checks: |
|
|
302 |
# Try to look up an existing object with the same values as this |
|
|
303 |
# object's values for all the unique field. |
|
|
304 |
|
|
|
305 |
lookup_kwargs = {} |
|
|
306 |
for field_name in unique_check: |
|
|
307 |
lookup_value = self.cleaned_data[field_name] |
|
|
308 |
# ModelChoiceField will return an object instance rather than |
|
|
309 |
# a raw primary key value, so convert it to a pk value before |
|
|
310 |
# using it in a lookup. |
|
|
311 |
if isinstance(self.fields[field_name], ModelChoiceField): |
|
|
312 |
lookup_value = lookup_value.pk |
|
|
313 |
lookup_kwargs[str(field_name)] = lookup_value |
|
|
314 |
|
|
|
315 |
qs = self.instance.__class__._default_manager.filter(**lookup_kwargs) |
|
|
316 |
|
|
|
317 |
# Exclude the current object from the query if we are editing an |
|
|
318 |
# instance (as opposed to creating a new one) |
|
|
319 |
if self.instance.pk is not None: |
|
|
320 |
qs = qs.exclude(pk=self.instance.pk) |
|
|
321 |
|
|
|
322 |
# This cute trick with extra/values is the most efficient way to |
|
|
323 |
# tell if a particular query returns any results. |
|
|
324 |
if qs.extra(select={'a': 1}).values('a').order_by(): |
|
|
325 |
if len(unique_check) == 1: |
|
|
326 |
self._errors[unique_check[0]] = ErrorList([self.unique_error_message(unique_check)]) |
|
|
327 |
else: |
|
|
328 |
form_errors.append(self.unique_error_message(unique_check)) |
|
|
329 |
|
|
|
330 |
# Mark these fields as needing to be removed from cleaned data |
|
|
331 |
# later. |
|
|
332 |
for field_name in unique_check: |
|
|
333 |
bad_fields.add(field_name) |
|
|
334 |
return bad_fields, form_errors |
|
|
335 |
|
|
|
336 |
def _perform_date_checks(self, date_checks): |
|
|
337 |
bad_fields = set() |
|
|
338 |
for lookup_type, field, unique_for in date_checks: |
|
|
339 |
lookup_kwargs = {} |
|
|
340 |
# there's a ticket to add a date lookup, we can remove this special |
|
|
341 |
# case if that makes it's way in |
|
|
342 |
if lookup_type == 'date': |
|
|
343 |
date = self.cleaned_data[unique_for] |
|
|
344 |
lookup_kwargs['%s__day' % unique_for] = date.day |
|
|
345 |
lookup_kwargs['%s__month' % unique_for] = date.month |
|
|
346 |
lookup_kwargs['%s__year' % unique_for] = date.year |
|
|
347 |
else: |
|
|
348 |
lookup_kwargs['%s__%s' % (unique_for, lookup_type)] = getattr(self.cleaned_data[unique_for], lookup_type) |
|
|
349 |
lookup_kwargs[field] = self.cleaned_data[field] |
|
|
350 |
|
|
|
351 |
qs = self.instance.__class__._default_manager.filter(**lookup_kwargs) |
|
|
352 |
# Exclude the current object from the query if we are editing an |
|
|
353 |
# instance (as opposed to creating a new one) |
|
|
354 |
if self.instance.pk is not None: |
|
|
355 |
qs = qs.exclude(pk=self.instance.pk) |
|
|
356 |
|
|
|
357 |
# This cute trick with extra/values is the most efficient way to |
|
|
358 |
# tell if a particular query returns any results. |
|
|
359 |
if qs.extra(select={'a': 1}).values('a').order_by(): |
|
|
360 |
self._errors[field] = ErrorList([ |
|
|
361 |
self.date_error_message(lookup_type, field, unique_for) |
|
|
362 |
]) |
|
|
363 |
bad_fields.add(field) |
|
|
364 |
return bad_fields, [] |
|
|
365 |
|
|
|
366 |
def date_error_message(self, lookup_type, field, unique_for): |
|
|
367 |
return _(u"%(field_name)s must be unique for %(date_field)s %(lookup)s.") % { |
|
|
368 |
'field_name': unicode(self.fields[field].label), |
|
|
369 |
'date_field': unicode(self.fields[unique_for].label), |
|
|
370 |
'lookup': lookup_type, |
|
|
371 |
} |
|
|
372 |
|
|
|
373 |
def unique_error_message(self, unique_check): |
|
|
374 |
model_name = capfirst(self.instance._meta.verbose_name) |
|
|
375 |
|
|
|
376 |
# A unique field |
|
|
377 |
if len(unique_check) == 1: |
|
|
378 |
field_name = unique_check[0] |
|
|
379 |
field_label = self.fields[field_name].label |
|
|
380 |
# Insert the error into the error dict, very sneaky |
|
|
381 |
return _(u"%(model_name)s with this %(field_label)s already exists.") % { |
|
|
382 |
'model_name': unicode(model_name), |
|
|
383 |
'field_label': unicode(field_label) |
|
|
384 |
} |
|
|
385 |
# unique_together |
|
|
386 |
else: |
|
|
387 |
field_labels = [self.fields[field_name].label for field_name in unique_check] |
|
|
388 |
field_labels = get_text_list(field_labels, _('and')) |
|
|
389 |
return _(u"%(model_name)s with this %(field_label)s already exists.") % { |
|
|
390 |
'model_name': unicode(model_name), |
|
|
391 |
'field_label': unicode(field_labels) |
|
|
392 |
} |
|
|
393 |
|
|
|
394 |
def save(self, commit=True): |
|
|
395 |
""" |
|
|
396 |
Saves this ``form``'s cleaned_data into model instance |
|
|
397 |
``self.instance``. |
|
|
398 |
|
|
|
399 |
If commit=True, then the changes to ``instance`` will be saved to the |
|
|
400 |
database. Returns ``instance``. |
|
|
401 |
""" |
|
|
402 |
if self.instance.pk is None: |
|
|
403 |
fail_message = 'created' |
|
|
404 |
else: |
|
|
405 |
fail_message = 'changed' |
|
|
406 |
return save_instance(self, self.instance, self._meta.fields, |
|
|
407 |
fail_message, commit, exclude=self._meta.exclude) |
|
|
408 |
|
|
|
409 |
save.alters_data = True |
|
|
410 |
|
|
|
411 |
class ModelForm(BaseModelForm): |
|
|
412 |
__metaclass__ = ModelFormMetaclass |
|
|
413 |
|
|
|
414 |
def modelform_factory(model, form=ModelForm, fields=None, exclude=None, |
|
|
415 |
formfield_callback=lambda f: f.formfield()): |
|
|
416 |
# Create the inner Meta class. FIXME: ideally, we should be able to |
|
|
417 |
# construct a ModelForm without creating and passing in a temporary |
|
|
418 |
# inner class. |
|
|
419 |
|
|
|
420 |
# Build up a list of attributes that the Meta object will have. |
|
|
421 |
attrs = {'model': model} |
|
|
422 |
if fields is not None: |
|
|
423 |
attrs['fields'] = fields |
|
|
424 |
if exclude is not None: |
|
|
425 |
attrs['exclude'] = exclude |
|
|
426 |
|
|
|
427 |
# If parent form class already has an inner Meta, the Meta we're |
|
|
428 |
# creating needs to inherit from the parent's inner meta. |
|
|
429 |
parent = (object,) |
|
|
430 |
if hasattr(form, 'Meta'): |
|
|
431 |
parent = (form.Meta, object) |
|
|
432 |
Meta = type('Meta', parent, attrs) |
|
|
433 |
|
|
|
434 |
# Give this new form class a reasonable name. |
|
|
435 |
class_name = model.__name__ + 'Form' |
|
|
436 |
|
|
|
437 |
# Class attributes for the new form class. |
|
|
438 |
form_class_attrs = { |
|
|
439 |
'Meta': Meta, |
|
|
440 |
'formfield_callback': formfield_callback |
|
|
441 |
} |
|
|
442 |
|
|
|
443 |
return ModelFormMetaclass(class_name, (form,), form_class_attrs) |
|
|
444 |
|
|
|
445 |
|
|
|
446 |
# ModelFormSets ############################################################## |
|
|
447 |
|
|
|
448 |
class BaseModelFormSet(BaseFormSet): |
|
|
449 |
""" |
|
|
450 |
A ``FormSet`` for editing a queryset and/or adding new objects to it. |
|
|
451 |
""" |
|
|
452 |
model = None |
|
|
453 |
|
|
|
454 |
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, |
|
|
455 |
queryset=None, **kwargs): |
|
|
456 |
self.queryset = queryset |
|
|
457 |
defaults = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} |
|
|
458 |
defaults.update(kwargs) |
|
|
459 |
super(BaseModelFormSet, self).__init__(**defaults) |
|
|
460 |
|
|
|
461 |
def initial_form_count(self): |
|
|
462 |
"""Returns the number of forms that are required in this FormSet.""" |
|
|
463 |
if not (self.data or self.files): |
|
|
464 |
return len(self.get_queryset()) |
|
|
465 |
return super(BaseModelFormSet, self).initial_form_count() |
|
|
466 |
|
|
|
467 |
def _existing_object(self, pk): |
|
|
468 |
if not hasattr(self, '_object_dict'): |
|
|
469 |
self._object_dict = dict([(o.pk, o) for o in self.get_queryset()]) |
|
|
470 |
return self._object_dict.get(pk) |
|
|
471 |
|
|
|
472 |
def _construct_form(self, i, **kwargs): |
|
|
473 |
if self.is_bound and i < self.initial_form_count(): |
|
|
474 |
pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name) |
|
|
475 |
pk = self.data[pk_key] |
|
|
476 |
pk_field = self.model._meta.pk |
|
|
477 |
pk = pk_field.get_db_prep_lookup('exact', pk) |
|
|
478 |
if isinstance(pk, list): |
|
|
479 |
pk = pk[0] |
|
|
480 |
kwargs['instance'] = self._existing_object(pk) |
|
|
481 |
if i < self.initial_form_count() and not kwargs.get('instance'): |
|
|
482 |
kwargs['instance'] = self.get_queryset()[i] |
|
|
483 |
return super(BaseModelFormSet, self)._construct_form(i, **kwargs) |
|
|
484 |
|
|
|
485 |
def get_queryset(self): |
|
|
486 |
if not hasattr(self, '_queryset'): |
|
|
487 |
if self.queryset is not None: |
|
|
488 |
qs = self.queryset |
|
|
489 |
else: |
|
|
490 |
qs = self.model._default_manager.get_query_set() |
|
|
491 |
|
|
|
492 |
# If the queryset isn't already ordered we need to add an |
|
|
493 |
# artificial ordering here to make sure that all formsets |
|
|
494 |
# constructed from this queryset have the same form order. |
|
|
495 |
if not qs.ordered: |
|
|
496 |
qs = qs.order_by(self.model._meta.pk.name) |
|
|
497 |
|
|
|
498 |
if self.max_num > 0: |
|
|
499 |
self._queryset = qs[:self.max_num] |
|
|
500 |
else: |
|
|
501 |
self._queryset = qs |
|
|
502 |
return self._queryset |
|
|
503 |
|
|
|
504 |
def save_new(self, form, commit=True): |
|
|
505 |
"""Saves and returns a new model instance for the given form.""" |
|
|
506 |
return form.save(commit=commit) |
|
|
507 |
|
|
|
508 |
def save_existing(self, form, instance, commit=True): |
|
|
509 |
"""Saves and returns an existing model instance for the given form.""" |
|
|
510 |
return form.save(commit=commit) |
|
|
511 |
|
|
|
512 |
def save(self, commit=True): |
|
|
513 |
"""Saves model instances for every form, adding and changing instances |
|
|
514 |
as necessary, and returns the list of instances. |
|
|
515 |
""" |
|
|
516 |
if not commit: |
|
|
517 |
self.saved_forms = [] |
|
|
518 |
def save_m2m(): |
|
|
519 |
for form in self.saved_forms: |
|
|
520 |
form.save_m2m() |
|
|
521 |
self.save_m2m = save_m2m |
|
|
522 |
return self.save_existing_objects(commit) + self.save_new_objects(commit) |
|
|
523 |
|
|
|
524 |
def clean(self): |
|
|
525 |
self.validate_unique() |
|
|
526 |
|
|
|
527 |
def validate_unique(self): |
|
|
528 |
# Iterate over the forms so that we can find one with potentially valid |
|
|
529 |
# data from which to extract the error checks |
|
|
530 |
for form in self.forms: |
|
|
531 |
if hasattr(form, 'cleaned_data'): |
|
|
532 |
break |
|
|
533 |
else: |
|
|
534 |
return |
|
|
535 |
unique_checks, date_checks = form._get_unique_checks() |
|
|
536 |
errors = [] |
|
|
537 |
# Do each of the unique checks (unique and unique_together) |
|
|
538 |
for unique_check in unique_checks: |
|
|
539 |
seen_data = set() |
|
|
540 |
for form in self.forms: |
|
|
541 |
# if the form doesn't have cleaned_data then we ignore it, |
|
|
542 |
# it's already invalid |
|
|
543 |
if not hasattr(form, "cleaned_data"): |
|
|
544 |
continue |
|
|
545 |
# get each of the fields for which we have data on this form |
|
|
546 |
if [f for f in unique_check if f in form.cleaned_data and form.cleaned_data[f] is not None]: |
|
|
547 |
# get the data itself |
|
|
548 |
row_data = tuple([form.cleaned_data[field] for field in unique_check]) |
|
|
549 |
# if we've aready seen it then we have a uniqueness failure |
|
|
550 |
if row_data in seen_data: |
|
|
551 |
# poke error messages into the right places and mark |
|
|
552 |
# the form as invalid |
|
|
553 |
errors.append(self.get_unique_error_message(unique_check)) |
|
|
554 |
form._errors[NON_FIELD_ERRORS] = self.get_form_error() |
|
|
555 |
del form.cleaned_data |
|
|
556 |
break |
|
|
557 |
# mark the data as seen |
|
|
558 |
seen_data.add(row_data) |
|
|
559 |
# iterate over each of the date checks now |
|
|
560 |
for date_check in date_checks: |
|
|
561 |
seen_data = set() |
|
|
562 |
lookup, field, unique_for = date_check |
|
|
563 |
for form in self.forms: |
|
|
564 |
# if the form doesn't have cleaned_data then we ignore it, |
|
|
565 |
# it's already invalid |
|
|
566 |
if not hasattr(self, 'cleaned_data'): |
|
|
567 |
continue |
|
|
568 |
# see if we have data for both fields |
|
|
569 |
if (form.cleaned_data and form.cleaned_data[field] is not None |
|
|
570 |
and form.cleaned_data[unique_for] is not None): |
|
|
571 |
# if it's a date lookup we need to get the data for all the fields |
|
|
572 |
if lookup == 'date': |
|
|
573 |
date = form.cleaned_data[unique_for] |
|
|
574 |
date_data = (date.year, date.month, date.day) |
|
|
575 |
# otherwise it's just the attribute on the date/datetime |
|
|
576 |
# object |
|
|
577 |
else: |
|
|
578 |
date_data = (getattr(form.cleaned_data[unique_for], lookup),) |
|
|
579 |
data = (form.cleaned_data[field],) + date_data |
|
|
580 |
# if we've aready seen it then we have a uniqueness failure |
|
|
581 |
if data in seen_data: |
|
|
582 |
# poke error messages into the right places and mark |
|
|
583 |
# the form as invalid |
|
|
584 |
errors.append(self.get_date_error_message(date_check)) |
|
|
585 |
form._errors[NON_FIELD_ERRORS] = self.get_form_error() |
|
|
586 |
del form.cleaned_data |
|
|
587 |
break |
|
|
588 |
seen_data.add(data) |
|
|
589 |
if errors: |
|
|
590 |
raise ValidationError(errors) |
|
|
591 |
|
|
|
592 |
def get_unique_error_message(self, unique_check): |
|
|
593 |
if len(unique_check) == 1: |
|
|
594 |
return ugettext("Please correct the duplicate data for %(field)s.") % { |
|
|
595 |
"field": unique_check[0], |
|
|
596 |
} |
|
|
597 |
else: |
|
|
598 |
return ugettext("Please correct the duplicate data for %(field)s, " |
|
|
599 |
"which must be unique.") % { |
|
|
600 |
"field": get_text_list(unique_check, unicode(_("and"))), |
|
|
601 |
} |
|
|
602 |
|
|
|
603 |
def get_date_error_message(self, date_check): |
|
|
604 |
return ugettext("Please correct the duplicate data for %(field_name)s " |
|
|
605 |
"which must be unique for the %(lookup)s in %(date_field)s.") % { |
|
|
606 |
'field_name': date_check[1], |
|
|
607 |
'date_field': date_check[2], |
|
|
608 |
'lookup': unicode(date_check[0]), |
|
|
609 |
} |
|
|
610 |
|
|
|
611 |
def get_form_error(self): |
|
|
612 |
return ugettext("Please correct the duplicate values below.") |
|
|
613 |
|
|
|
614 |
def save_existing_objects(self, commit=True): |
|
|
615 |
self.changed_objects = [] |
|
|
616 |
self.deleted_objects = [] |
|
|
617 |
if not self.get_queryset(): |
|
|
618 |
return [] |
|
|
619 |
|
|
|
620 |
saved_instances = [] |
|
|
621 |
for form in self.initial_forms: |
|
|
622 |
pk_name = self._pk_field.name |
|
|
623 |
raw_pk_value = form._raw_value(pk_name) |
|
|
624 |
|
|
|
625 |
# clean() for different types of PK fields can sometimes return |
|
|
626 |
# the model instance, and sometimes the PK. Handle either. |
|
|
627 |
pk_value = form.fields[pk_name].clean(raw_pk_value) |
|
|
628 |
pk_value = getattr(pk_value, 'pk', pk_value) |
|
|
629 |
|
|
|
630 |
obj = self._existing_object(pk_value) |
|
|
631 |
if self.can_delete: |
|
|
632 |
raw_delete_value = form._raw_value(DELETION_FIELD_NAME) |
|
|
633 |
should_delete = form.fields[DELETION_FIELD_NAME].clean(raw_delete_value) |
|
|
634 |
if should_delete: |
|
|
635 |
self.deleted_objects.append(obj) |
|
|
636 |
obj.delete() |
|
|
637 |
continue |
|
|
638 |
if form.has_changed(): |
|
|
639 |
self.changed_objects.append((obj, form.changed_data)) |
|
|
640 |
saved_instances.append(self.save_existing(form, obj, commit=commit)) |
|
|
641 |
if not commit: |
|
|
642 |
self.saved_forms.append(form) |
|
|
643 |
return saved_instances |
|
|
644 |
|
|
|
645 |
def save_new_objects(self, commit=True): |
|
|
646 |
self.new_objects = [] |
|
|
647 |
for form in self.extra_forms: |
|
|
648 |
if not form.has_changed(): |
|
|
649 |
continue |
|
|
650 |
# If someone has marked an add form for deletion, don't save the |
|
|
651 |
# object. |
|
|
652 |
if self.can_delete: |
|
|
653 |
raw_delete_value = form._raw_value(DELETION_FIELD_NAME) |
|
|
654 |
should_delete = form.fields[DELETION_FIELD_NAME].clean(raw_delete_value) |
|
|
655 |
if should_delete: |
|
|
656 |
continue |
|
|
657 |
self.new_objects.append(self.save_new(form, commit=commit)) |
|
|
658 |
if not commit: |
|
|
659 |
self.saved_forms.append(form) |
|
|
660 |
return self.new_objects |
|
|
661 |
|
|
|
662 |
def add_fields(self, form, index): |
|
|
663 |
"""Add a hidden field for the object's primary key.""" |
|
|
664 |
from django.db.models import AutoField, OneToOneField, ForeignKey |
|
|
665 |
self._pk_field = pk = self.model._meta.pk |
|
|
666 |
# If a pk isn't editable, then it won't be on the form, so we need to |
|
|
667 |
# add it here so we can tell which object is which when we get the |
|
|
668 |
# data back. Generally, pk.editable should be false, but for some |
|
|
669 |
# reason, auto_created pk fields and AutoField's editable attribute is |
|
|
670 |
# True, so check for that as well. |
|
|
671 |
def pk_is_not_editable(pk): |
|
|
672 |
return ((not pk.editable) or (pk.auto_created or isinstance(pk, AutoField)) |
|
|
673 |
or (pk.rel and pk.rel.parent_link and pk_is_not_editable(pk.rel.to._meta.pk))) |
|
|
674 |
if pk_is_not_editable(pk) or pk.name not in form.fields: |
|
|
675 |
if form.is_bound: |
|
|
676 |
pk_value = form.instance.pk |
|
|
677 |
else: |
|
|
678 |
try: |
|
|
679 |
pk_value = self.get_queryset()[index].pk |
|
|
680 |
except IndexError: |
|
|
681 |
pk_value = None |
|
|
682 |
if isinstance(pk, OneToOneField) or isinstance(pk, ForeignKey): |
|
|
683 |
qs = pk.rel.to._default_manager.get_query_set() |
|
|
684 |
else: |
|
|
685 |
qs = self.model._default_manager.get_query_set() |
|
|
686 |
form.fields[self._pk_field.name] = ModelChoiceField(qs, initial=pk_value, required=False, widget=HiddenInput) |
|
|
687 |
super(BaseModelFormSet, self).add_fields(form, index) |
|
|
688 |
|
|
|
689 |
def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(), |
|
|
690 |
formset=BaseModelFormSet, |
|
|
691 |
extra=1, can_delete=False, can_order=False, |
|
|
692 |
max_num=0, fields=None, exclude=None): |
|
|
693 |
""" |
|
|
694 |
Returns a FormSet class for the given Django model class. |
|
|
695 |
""" |
|
|
696 |
form = modelform_factory(model, form=form, fields=fields, exclude=exclude, |
|
|
697 |
formfield_callback=formfield_callback) |
|
|
698 |
FormSet = formset_factory(form, formset, extra=extra, max_num=max_num, |
|
|
699 |
can_order=can_order, can_delete=can_delete) |
|
|
700 |
FormSet.model = model |
|
|
701 |
return FormSet |
|
|
702 |
|
|
|
703 |
|
|
|
704 |
# InlineFormSets ############################################################# |
|
|
705 |
|
|
|
706 |
class BaseInlineFormSet(BaseModelFormSet): |
|
|
707 |
"""A formset for child objects related to a parent.""" |
|
|
708 |
def __init__(self, data=None, files=None, instance=None, |
|
|
709 |
save_as_new=False, prefix=None): |
|
|
710 |
from django.db.models.fields.related import RelatedObject |
|
|
711 |
if instance is None: |
|
|
712 |
self.instance = self.model() |
|
|
713 |
else: |
|
|
714 |
self.instance = instance |
|
|
715 |
self.save_as_new = save_as_new |
|
|
716 |
# is there a better way to get the object descriptor? |
|
|
717 |
self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name() |
|
|
718 |
if self.fk.rel.field_name == self.fk.rel.to._meta.pk.name: |
|
|
719 |
backlink_value = self.instance |
|
|
720 |
else: |
|
|
721 |
backlink_value = getattr(self.instance, self.fk.rel.field_name) |
|
|
722 |
qs = self.model._default_manager.filter(**{self.fk.name: backlink_value}) |
|
|
723 |
super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix, |
|
|
724 |
queryset=qs) |
|
|
725 |
|
|
|
726 |
def initial_form_count(self): |
|
|
727 |
if self.save_as_new: |
|
|
728 |
return 0 |
|
|
729 |
return super(BaseInlineFormSet, self).initial_form_count() |
|
|
730 |
|
|
|
731 |
def total_form_count(self): |
|
|
732 |
if self.save_as_new: |
|
|
733 |
return super(BaseInlineFormSet, self).initial_form_count() |
|
|
734 |
return super(BaseInlineFormSet, self).total_form_count() |
|
|
735 |
|
|
|
736 |
def _construct_form(self, i, **kwargs): |
|
|
737 |
form = super(BaseInlineFormSet, self)._construct_form(i, **kwargs) |
|
|
738 |
if self.save_as_new: |
|
|
739 |
# Remove the primary key from the form's data, we are only |
|
|
740 |
# creating new instances |
|
|
741 |
form.data[form.add_prefix(self._pk_field.name)] = None |
|
|
742 |
|
|
|
743 |
# Remove the foreign key from the form's data |
|
|
744 |
form.data[form.add_prefix(self.fk.name)] = None |
|
|
745 |
return form |
|
|
746 |
|
|
|
747 |
#@classmethod |
|
|
748 |
def get_default_prefix(cls): |
|
|
749 |
from django.db.models.fields.related import RelatedObject |
|
|
750 |
return RelatedObject(cls.fk.rel.to, cls.model, cls.fk).get_accessor_name() |
|
|
751 |
get_default_prefix = classmethod(get_default_prefix) |
|
|
752 |
|
|
|
753 |
def save_new(self, form, commit=True): |
|
|
754 |
# Use commit=False so we can assign the parent key afterwards, then |
|
|
755 |
# save the object. |
|
|
756 |
obj = form.save(commit=False) |
|
|
757 |
pk_value = getattr(self.instance, self.fk.rel.field_name) |
|
|
758 |
setattr(obj, self.fk.get_attname(), getattr(pk_value, 'pk', pk_value)) |
|
|
759 |
if commit: |
|
|
760 |
obj.save() |
|
|
761 |
# form.save_m2m() can be called via the formset later on if commit=False |
|
|
762 |
if commit and hasattr(form, 'save_m2m'): |
|
|
763 |
form.save_m2m() |
|
|
764 |
return obj |
|
|
765 |
|
|
|
766 |
def add_fields(self, form, index): |
|
|
767 |
super(BaseInlineFormSet, self).add_fields(form, index) |
|
|
768 |
if self._pk_field == self.fk: |
|
|
769 |
form.fields[self._pk_field.name] = InlineForeignKeyField(self.instance, pk_field=True) |
|
|
770 |
else: |
|
|
771 |
# The foreign key field might not be on the form, so we poke at the |
|
|
772 |
# Model field to get the label, since we need that for error messages. |
|
|
773 |
kwargs = { |
|
|
774 |
'label': getattr(form.fields.get(self.fk.name), 'label', capfirst(self.fk.verbose_name)) |
|
|
775 |
} |
|
|
776 |
if self.fk.rel.field_name != self.fk.rel.to._meta.pk.name: |
|
|
777 |
kwargs['to_field'] = self.fk.rel.field_name |
|
|
778 |
form.fields[self.fk.name] = InlineForeignKeyField(self.instance, **kwargs) |
|
|
779 |
|
|
|
780 |
def get_unique_error_message(self, unique_check): |
|
|
781 |
unique_check = [field for field in unique_check if field != self.fk.name] |
|
|
782 |
return super(BaseInlineFormSet, self).get_unique_error_message(unique_check) |
|
|
783 |
|
|
|
784 |
def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False): |
|
|
785 |
""" |
|
|
786 |
Finds and returns the ForeignKey from model to parent if there is one |
|
|
787 |
(returns None if can_fail is True and no such field exists). If fk_name is |
|
|
788 |
provided, assume it is the name of the ForeignKey field. Unles can_fail is |
|
|
789 |
True, an exception is raised if there is no ForeignKey from model to |
|
|
790 |
parent_model. |
|
|
791 |
""" |
|
|
792 |
# avoid circular import |
|
|
793 |
from django.db.models import ForeignKey |
|
|
794 |
opts = model._meta |
|
|
795 |
if fk_name: |
|
|
796 |
fks_to_parent = [f for f in opts.fields if f.name == fk_name] |
|
|
797 |
if len(fks_to_parent) == 1: |
|
|
798 |
fk = fks_to_parent[0] |
|
|
799 |
if not isinstance(fk, ForeignKey) or \ |
|
|
800 |
(fk.rel.to != parent_model and |
|
|
801 |
fk.rel.to not in parent_model._meta.get_parent_list()): |
|
|
802 |
raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model)) |
|
|
803 |
elif len(fks_to_parent) == 0: |
|
|
804 |
raise Exception("%s has no field named '%s'" % (model, fk_name)) |
|
|
805 |
else: |
|
|
806 |
# Try to discover what the ForeignKey from model to parent_model is |
|
|
807 |
fks_to_parent = [ |
|
|
808 |
f for f in opts.fields |
|
|
809 |
if isinstance(f, ForeignKey) |
|
|
810 |
and (f.rel.to == parent_model |
|
|
811 |
or f.rel.to in parent_model._meta.get_parent_list()) |
|
|
812 |
] |
|
|
813 |
if len(fks_to_parent) == 1: |
|
|
814 |
fk = fks_to_parent[0] |
|
|
815 |
elif len(fks_to_parent) == 0: |
|
|
816 |
if can_fail: |
|
|
817 |
return |
|
|
818 |
raise Exception("%s has no ForeignKey to %s" % (model, parent_model)) |
|
|
819 |
else: |
|
|
820 |
raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model)) |
|
|
821 |
return fk |
|
|
822 |
|
|
|
823 |
|
|
|
824 |
def inlineformset_factory(parent_model, model, form=ModelForm, |
|
|
825 |
formset=BaseInlineFormSet, fk_name=None, |
|
|
826 |
fields=None, exclude=None, |
|
|
827 |
extra=3, can_order=False, can_delete=True, max_num=0, |
|
|
828 |
formfield_callback=lambda f: f.formfield()): |
|
|
829 |
""" |
|
|
830 |
Returns an ``InlineFormSet`` for the given kwargs. |
|
|
831 |
|
|
|
832 |
You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey`` |
|
|
833 |
to ``parent_model``. |
|
|
834 |
""" |
|
|
835 |
fk = _get_foreign_key(parent_model, model, fk_name=fk_name) |
|
|
836 |
# enforce a max_num=1 when the foreign key to the parent model is unique. |
|
|
837 |
if fk.unique: |
|
|
838 |
max_num = 1 |
|
|
839 |
kwargs = { |
|
|
840 |
'form': form, |
|
|
841 |
'formfield_callback': formfield_callback, |
|
|
842 |
'formset': formset, |
|
|
843 |
'extra': extra, |
|
|
844 |
'can_delete': can_delete, |
|
|
845 |
'can_order': can_order, |
|
|
846 |
'fields': fields, |
|
|
847 |
'exclude': exclude, |
|
|
848 |
'max_num': max_num, |
|
|
849 |
} |
|
|
850 |
FormSet = modelformset_factory(model, **kwargs) |
|
|
851 |
FormSet.fk = fk |
|
|
852 |
return FormSet |
|
|
853 |
|
|
|
854 |
|
|
|
855 |
# Fields ##################################################################### |
|
|
856 |
|
|
|
857 |
class InlineForeignKeyHiddenInput(HiddenInput): |
|
|
858 |
def _has_changed(self, initial, data): |
|
|
859 |
return False |
|
|
860 |
|
|
|
861 |
class InlineForeignKeyField(Field): |
|
|
862 |
""" |
|
|
863 |
A basic integer field that deals with validating the given value to a |
|
|
864 |
given parent instance in an inline. |
|
|
865 |
""" |
|
|
866 |
default_error_messages = { |
|
|
867 |
'invalid_choice': _(u'The inline foreign key did not match the parent instance primary key.'), |
|
|
868 |
} |
|
|
869 |
|
|
|
870 |
def __init__(self, parent_instance, *args, **kwargs): |
|
|
871 |
self.parent_instance = parent_instance |
|
|
872 |
self.pk_field = kwargs.pop("pk_field", False) |
|
|
873 |
self.to_field = kwargs.pop("to_field", None) |
|
|
874 |
if self.parent_instance is not None: |
|
|
875 |
if self.to_field: |
|
|
876 |
kwargs["initial"] = getattr(self.parent_instance, self.to_field) |
|
|
877 |
else: |
|
|
878 |
kwargs["initial"] = self.parent_instance.pk |
|
|
879 |
kwargs["required"] = False |
|
|
880 |
kwargs["widget"] = InlineForeignKeyHiddenInput |
|
|
881 |
super(InlineForeignKeyField, self).__init__(*args, **kwargs) |
|
|
882 |
|
|
|
883 |
def clean(self, value): |
|
|
884 |
if value in EMPTY_VALUES: |
|
|
885 |
if self.pk_field: |
|
|
886 |
return None |
|
|
887 |
# if there is no value act as we did before. |
|
|
888 |
return self.parent_instance |
|
|
889 |
# ensure the we compare the values as equal types. |
|
|
890 |
if self.to_field: |
|
|
891 |
orig = getattr(self.parent_instance, self.to_field) |
|
|
892 |
else: |
|
|
893 |
orig = self.parent_instance.pk |
|
|
894 |
if force_unicode(value) != force_unicode(orig): |
|
|
895 |
raise ValidationError(self.error_messages['invalid_choice']) |
|
|
896 |
return self.parent_instance |
|
|
897 |
|
|
|
898 |
class ModelChoiceIterator(object): |
|
|
899 |
def __init__(self, field): |
|
|
900 |
self.field = field |
|
|
901 |
self.queryset = field.queryset |
|
|
902 |
|
|
|
903 |
def __iter__(self): |
|
|
904 |
if self.field.empty_label is not None: |
|
|
905 |
yield (u"", self.field.empty_label) |
|
|
906 |
if self.field.cache_choices: |
|
|
907 |
if self.field.choice_cache is None: |
|
|
908 |
self.field.choice_cache = [ |
|
|
909 |
self.choice(obj) for obj in self.queryset.all() |
|
|
910 |
] |
|
|
911 |
for choice in self.field.choice_cache: |
|
|
912 |
yield choice |
|
|
913 |
else: |
|
|
914 |
for obj in self.queryset.all(): |
|
|
915 |
yield self.choice(obj) |
|
|
916 |
|
|
|
917 |
def choice(self, obj): |
|
|
918 |
if self.field.to_field_name: |
|
|
919 |
key = obj.serializable_value(self.field.to_field_name) |
|
|
920 |
else: |
|
|
921 |
key = obj.pk |
|
|
922 |
return (key, self.field.label_from_instance(obj)) |
|
|
923 |
|
|
|
924 |
|
|
|
925 |
class ModelChoiceField(ChoiceField): |
|
|
926 |
"""A ChoiceField whose choices are a model QuerySet.""" |
|
|
927 |
# This class is a subclass of ChoiceField for purity, but it doesn't |
|
|
928 |
# actually use any of ChoiceField's implementation. |
|
|
929 |
default_error_messages = { |
|
|
930 |
'invalid_choice': _(u'Select a valid choice. That choice is not one of' |
|
|
931 |
u' the available choices.'), |
|
|
932 |
} |
|
|
933 |
|
|
|
934 |
def __init__(self, queryset, empty_label=u"---------", cache_choices=False, |
|
|
935 |
required=True, widget=None, label=None, initial=None, |
|
|
936 |
help_text=None, to_field_name=None, *args, **kwargs): |
|
|
937 |
if required and (initial is not None): |
|
|
938 |
self.empty_label = None |
|
|
939 |
else: |
|
|
940 |
self.empty_label = empty_label |
|
|
941 |
self.cache_choices = cache_choices |
|
|
942 |
|
|
|
943 |
# Call Field instead of ChoiceField __init__() because we don't need |
|
|
944 |
# ChoiceField.__init__(). |
|
|
945 |
Field.__init__(self, required, widget, label, initial, help_text, |
|
|
946 |
*args, **kwargs) |
|
|
947 |
self.queryset = queryset |
|
|
948 |
self.choice_cache = None |
|
|
949 |
self.to_field_name = to_field_name |
|
|
950 |
|
|
|
951 |
def _get_queryset(self): |
|
|
952 |
return self._queryset |
|
|
953 |
|
|
|
954 |
def _set_queryset(self, queryset): |
|
|
955 |
self._queryset = queryset |
|
|
956 |
self.widget.choices = self.choices |
|
|
957 |
|
|
|
958 |
queryset = property(_get_queryset, _set_queryset) |
|
|
959 |
|
|
|
960 |
# this method will be used to create object labels by the QuerySetIterator. |
|
|
961 |
# Override it to customize the label. |
|
|
962 |
def label_from_instance(self, obj): |
|
|
963 |
""" |
|
|
964 |
This method is used to convert objects into strings; it's used to |
|
|
965 |
generate the labels for the choices presented by this object. Subclasses |
|
|
966 |
can override this method to customize the display of the choices. |
|
|
967 |
""" |
|
|
968 |
return smart_unicode(obj) |
|
|
969 |
|
|
|
970 |
def _get_choices(self): |
|
|
971 |
# If self._choices is set, then somebody must have manually set |
|
|
972 |
# the property self.choices. In this case, just return self._choices. |
|
|
973 |
if hasattr(self, '_choices'): |
|
|
974 |
return self._choices |
|
|
975 |
|
|
|
976 |
# Otherwise, execute the QuerySet in self.queryset to determine the |
|
|
977 |
# choices dynamically. Return a fresh QuerySetIterator that has not been |
|
|
978 |
# consumed. Note that we're instantiating a new QuerySetIterator *each* |
|
|
979 |
# time _get_choices() is called (and, thus, each time self.choices is |
|
|
980 |
# accessed) so that we can ensure the QuerySet has not been consumed. This |
|
|
981 |
# construct might look complicated but it allows for lazy evaluation of |
|
|
982 |
# the queryset. |
|
|
983 |
return ModelChoiceIterator(self) |
|
|
984 |
|
|
|
985 |
choices = property(_get_choices, ChoiceField._set_choices) |
|
|
986 |
|
|
|
987 |
def clean(self, value): |
|
|
988 |
Field.clean(self, value) |
|
|
989 |
if value in EMPTY_VALUES: |
|
|
990 |
return None |
|
|
991 |
try: |
|
|
992 |
key = self.to_field_name or 'pk' |
|
|
993 |
value = self.queryset.get(**{key: value}) |
|
|
994 |
except self.queryset.model.DoesNotExist: |
|
|
995 |
raise ValidationError(self.error_messages['invalid_choice']) |
|
|
996 |
return value |
|
|
997 |
|
|
|
998 |
class ModelMultipleChoiceField(ModelChoiceField): |
|
|
999 |
"""A MultipleChoiceField whose choices are a model QuerySet.""" |
|
|
1000 |
widget = SelectMultiple |
|
|
1001 |
hidden_widget = MultipleHiddenInput |
|
|
1002 |
default_error_messages = { |
|
|
1003 |
'list': _(u'Enter a list of values.'), |
|
|
1004 |
'invalid_choice': _(u'Select a valid choice. %s is not one of the' |
|
|
1005 |
u' available choices.'), |
|
|
1006 |
'invalid_pk_value': _(u'"%s" is not a valid value for a primary key.') |
|
|
1007 |
} |
|
|
1008 |
|
|
|
1009 |
def __init__(self, queryset, cache_choices=False, required=True, |
|
|
1010 |
widget=None, label=None, initial=None, |
|
|
1011 |
help_text=None, *args, **kwargs): |
|
|
1012 |
super(ModelMultipleChoiceField, self).__init__(queryset, None, |
|
|
1013 |
cache_choices, required, widget, label, initial, help_text, |
|
|
1014 |
*args, **kwargs) |
|
|
1015 |
|
|
|
1016 |
def clean(self, value): |
|
|
1017 |
if self.required and not value: |
|
|
1018 |
raise ValidationError(self.error_messages['required']) |
|
|
1019 |
elif not self.required and not value: |
|
|
1020 |
return [] |
|
|
1021 |
if not isinstance(value, (list, tuple)): |
|
|
1022 |
raise ValidationError(self.error_messages['list']) |
|
|
1023 |
for pk in value: |
|
|
1024 |
try: |
|
|
1025 |
self.queryset.filter(pk=pk) |
|
|
1026 |
except ValueError: |
|
|
1027 |
raise ValidationError(self.error_messages['invalid_pk_value'] % pk) |
|
|
1028 |
qs = self.queryset.filter(pk__in=value) |
|
|
1029 |
pks = set([force_unicode(o.pk) for o in qs]) |
|
|
1030 |
for val in value: |
|
|
1031 |
if force_unicode(val) not in pks: |
|
|
1032 |
raise ValidationError(self.error_messages['invalid_choice'] % val) |
|
|
1033 |
return qs |