|
1 """ |
|
2 Helper functions for creating Form classes from Django models |
|
3 and database field objects. |
|
4 """ |
|
5 |
|
6 from warnings import warn |
|
7 |
|
8 from django.utils.translation import ugettext_lazy as _ |
|
9 from django.utils.encoding import smart_unicode |
|
10 from django.utils.datastructures import SortedDict |
|
11 from django.core.exceptions import ImproperlyConfigured |
|
12 |
|
13 from util import ValidationError, ErrorList |
|
14 from forms import BaseForm, get_declared_fields |
|
15 from fields import Field, ChoiceField, EMPTY_VALUES |
|
16 from widgets import Select, SelectMultiple, MultipleHiddenInput |
|
17 |
|
18 __all__ = ( |
|
19 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model', |
|
20 'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', |
|
21 'ModelChoiceField', 'ModelMultipleChoiceField' |
|
22 ) |
|
23 |
|
24 def save_instance(form, instance, fields=None, fail_message='saved', |
|
25 commit=True): |
|
26 """ |
|
27 Saves bound Form ``form``'s cleaned_data into model instance ``instance``. |
|
28 |
|
29 If commit=True, then the changes to ``instance`` will be saved to the |
|
30 database. Returns ``instance``. |
|
31 """ |
|
32 from django.db import models |
|
33 opts = instance.__class__._meta |
|
34 if form.errors: |
|
35 raise ValueError("The %s could not be %s because the data didn't" |
|
36 " validate." % (opts.object_name, fail_message)) |
|
37 cleaned_data = form.cleaned_data |
|
38 for f in opts.fields: |
|
39 if not f.editable or isinstance(f, models.AutoField) \ |
|
40 or not f.name in cleaned_data: |
|
41 continue |
|
42 if fields and f.name not in fields: |
|
43 continue |
|
44 f.save_form_data(instance, cleaned_data[f.name]) |
|
45 # Wrap up the saving of m2m data as a function. |
|
46 def save_m2m(): |
|
47 opts = instance.__class__._meta |
|
48 cleaned_data = form.cleaned_data |
|
49 for f in opts.many_to_many: |
|
50 if fields and f.name not in fields: |
|
51 continue |
|
52 if f.name in cleaned_data: |
|
53 f.save_form_data(instance, cleaned_data[f.name]) |
|
54 if commit: |
|
55 # If we are committing, save the instance and the m2m data immediately. |
|
56 instance.save() |
|
57 save_m2m() |
|
58 else: |
|
59 # We're not committing. Add a method to the form to allow deferred |
|
60 # saving of m2m data. |
|
61 form.save_m2m = save_m2m |
|
62 return instance |
|
63 |
|
64 def make_model_save(model, fields, fail_message): |
|
65 """Returns the save() method for a Form.""" |
|
66 def save(self, commit=True): |
|
67 return save_instance(self, model(), fields, fail_message, commit) |
|
68 return save |
|
69 |
|
70 def make_instance_save(instance, fields, fail_message): |
|
71 """Returns the save() method for a Form.""" |
|
72 def save(self, commit=True): |
|
73 return save_instance(self, instance, fields, fail_message, commit) |
|
74 return save |
|
75 |
|
76 def form_for_model(model, form=BaseForm, fields=None, |
|
77 formfield_callback=lambda f: f.formfield()): |
|
78 """ |
|
79 Returns a Form class for the given Django model class. |
|
80 |
|
81 Provide ``form`` if you want to use a custom BaseForm subclass. |
|
82 |
|
83 Provide ``formfield_callback`` if you want to define different logic for |
|
84 determining the formfield for a given database field. It's a callable that |
|
85 takes a database Field instance and returns a form Field instance. |
|
86 """ |
|
87 warn("form_for_model is deprecated. Use ModelForm instead.", |
|
88 PendingDeprecationWarning, stacklevel=3) |
|
89 opts = model._meta |
|
90 field_list = [] |
|
91 for f in opts.fields + opts.many_to_many: |
|
92 if not f.editable: |
|
93 continue |
|
94 if fields and not f.name in fields: |
|
95 continue |
|
96 formfield = formfield_callback(f) |
|
97 if formfield: |
|
98 field_list.append((f.name, formfield)) |
|
99 base_fields = SortedDict(field_list) |
|
100 return type(opts.object_name + 'Form', (form,), |
|
101 {'base_fields': base_fields, '_model': model, |
|
102 'save': make_model_save(model, fields, 'created')}) |
|
103 |
|
104 def form_for_instance(instance, form=BaseForm, fields=None, |
|
105 formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)): |
|
106 """ |
|
107 Returns a Form class for the given Django model instance. |
|
108 |
|
109 Provide ``form`` if you want to use a custom BaseForm subclass. |
|
110 |
|
111 Provide ``formfield_callback`` if you want to define different logic for |
|
112 determining the formfield for a given database field. It's a callable that |
|
113 takes a database Field instance, plus **kwargs, and returns a form Field |
|
114 instance with the given kwargs (i.e. 'initial'). |
|
115 """ |
|
116 warn("form_for_instance is deprecated. Use ModelForm instead.", |
|
117 PendingDeprecationWarning, stacklevel=3) |
|
118 model = instance.__class__ |
|
119 opts = model._meta |
|
120 field_list = [] |
|
121 for f in opts.fields + opts.many_to_many: |
|
122 if not f.editable: |
|
123 continue |
|
124 if fields and not f.name in fields: |
|
125 continue |
|
126 current_value = f.value_from_object(instance) |
|
127 formfield = formfield_callback(f, initial=current_value) |
|
128 if formfield: |
|
129 field_list.append((f.name, formfield)) |
|
130 base_fields = SortedDict(field_list) |
|
131 return type(opts.object_name + 'InstanceForm', (form,), |
|
132 {'base_fields': base_fields, '_model': model, |
|
133 'save': make_instance_save(instance, fields, 'changed')}) |
|
134 |
|
135 def form_for_fields(field_list): |
|
136 """ |
|
137 Returns a Form class for the given list of Django database field instances. |
|
138 """ |
|
139 fields = SortedDict([(f.name, f.formfield()) |
|
140 for f in field_list if f.editable]) |
|
141 return type('FormForFields', (BaseForm,), {'base_fields': fields}) |
|
142 |
|
143 |
|
144 # ModelForms ################################################################# |
|
145 |
|
146 def model_to_dict(instance, fields=None, exclude=None): |
|
147 """ |
|
148 Returns a dict containing the data in ``instance`` suitable for passing as |
|
149 a Form's ``initial`` keyword argument. |
|
150 |
|
151 ``fields`` is an optional list of field names. If provided, only the named |
|
152 fields will be included in the returned dict. |
|
153 |
|
154 ``exclude`` is an optional list of field names. If provided, the named |
|
155 fields will be excluded from the returned dict, even if they are listed in |
|
156 the ``fields`` argument. |
|
157 """ |
|
158 # avoid a circular import |
|
159 from django.db.models.fields.related import ManyToManyField |
|
160 opts = instance._meta |
|
161 data = {} |
|
162 for f in opts.fields + opts.many_to_many: |
|
163 if not f.editable: |
|
164 continue |
|
165 if fields and not f.name in fields: |
|
166 continue |
|
167 if exclude and f.name in exclude: |
|
168 continue |
|
169 if isinstance(f, ManyToManyField): |
|
170 # If the object doesn't have a primry key yet, just use an empty |
|
171 # list for its m2m fields. Calling f.value_from_object will raise |
|
172 # an exception. |
|
173 if instance.pk is None: |
|
174 data[f.name] = [] |
|
175 else: |
|
176 # MultipleChoiceWidget needs a list of pks, not object instances. |
|
177 data[f.name] = [obj.pk for obj in f.value_from_object(instance)] |
|
178 else: |
|
179 data[f.name] = f.value_from_object(instance) |
|
180 return data |
|
181 |
|
182 def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda f: f.formfield()): |
|
183 """ |
|
184 Returns a ``SortedDict`` containing form fields for the given model. |
|
185 |
|
186 ``fields`` is an optional list of field names. If provided, only the named |
|
187 fields will be included in the returned fields. |
|
188 |
|
189 ``exclude`` is an optional list of field names. If provided, the named |
|
190 fields will be excluded from the returned fields, even if they are listed |
|
191 in the ``fields`` argument. |
|
192 """ |
|
193 # TODO: if fields is provided, it would be nice to return fields in that order |
|
194 field_list = [] |
|
195 opts = model._meta |
|
196 for f in opts.fields + opts.many_to_many: |
|
197 if not f.editable: |
|
198 continue |
|
199 if fields and not f.name in fields: |
|
200 continue |
|
201 if exclude and f.name in exclude: |
|
202 continue |
|
203 formfield = formfield_callback(f) |
|
204 if formfield: |
|
205 field_list.append((f.name, formfield)) |
|
206 return SortedDict(field_list) |
|
207 |
|
208 class ModelFormOptions(object): |
|
209 def __init__(self, options=None): |
|
210 self.model = getattr(options, 'model', None) |
|
211 self.fields = getattr(options, 'fields', None) |
|
212 self.exclude = getattr(options, 'exclude', None) |
|
213 |
|
214 |
|
215 class ModelFormMetaclass(type): |
|
216 def __new__(cls, name, bases, attrs, |
|
217 formfield_callback=lambda f: f.formfield()): |
|
218 try: |
|
219 parents = [b for b in bases if issubclass(b, ModelForm)] |
|
220 except NameError: |
|
221 # We are defining ModelForm itself. |
|
222 parents = None |
|
223 if not parents: |
|
224 return super(ModelFormMetaclass, cls).__new__(cls, name, bases, |
|
225 attrs) |
|
226 |
|
227 new_class = type.__new__(cls, name, bases, attrs) |
|
228 declared_fields = get_declared_fields(bases, attrs, False) |
|
229 opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None)) |
|
230 if opts.model: |
|
231 # If a model is defined, extract form fields from it. |
|
232 fields = fields_for_model(opts.model, opts.fields, |
|
233 opts.exclude, formfield_callback) |
|
234 # Override default model fields with any custom declared ones |
|
235 # (plus, include all the other declared fields). |
|
236 fields.update(declared_fields) |
|
237 else: |
|
238 fields = declared_fields |
|
239 new_class.declared_fields = declared_fields |
|
240 new_class.base_fields = fields |
|
241 return new_class |
|
242 |
|
243 class BaseModelForm(BaseForm): |
|
244 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, |
|
245 initial=None, error_class=ErrorList, label_suffix=':', |
|
246 instance=None): |
|
247 opts = self._meta |
|
248 if instance is None: |
|
249 # if we didn't get an instance, instantiate a new one |
|
250 self.instance = opts.model() |
|
251 object_data = {} |
|
252 else: |
|
253 self.instance = instance |
|
254 object_data = model_to_dict(instance, opts.fields, opts.exclude) |
|
255 # if initial was provided, it should override the values from instance |
|
256 if initial is not None: |
|
257 object_data.update(initial) |
|
258 BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix) |
|
259 |
|
260 def save(self, commit=True): |
|
261 """ |
|
262 Saves this ``form``'s cleaned_data into model instance |
|
263 ``self.instance``. |
|
264 |
|
265 If commit=True, then the changes to ``instance`` will be saved to the |
|
266 database. Returns ``instance``. |
|
267 """ |
|
268 if self.instance.pk is None: |
|
269 fail_message = 'created' |
|
270 else: |
|
271 fail_message = 'changed' |
|
272 return save_instance(self, self.instance, self._meta.fields, fail_message, commit) |
|
273 |
|
274 class ModelForm(BaseModelForm): |
|
275 __metaclass__ = ModelFormMetaclass |
|
276 |
|
277 |
|
278 # Fields ##################################################################### |
|
279 |
|
280 class ModelChoiceIterator(object): |
|
281 def __init__(self, field): |
|
282 self.field = field |
|
283 self.queryset = field.queryset |
|
284 |
|
285 def __iter__(self): |
|
286 if self.field.empty_label is not None: |
|
287 yield (u"", self.field.empty_label) |
|
288 for obj in self.queryset: |
|
289 yield (obj.pk, self.field.label_from_instance(obj)) |
|
290 # Clear the QuerySet cache if required. |
|
291 if not self.field.cache_choices: |
|
292 self.queryset._result_cache = None |
|
293 |
|
294 class ModelChoiceField(ChoiceField): |
|
295 """A ChoiceField whose choices are a model QuerySet.""" |
|
296 # This class is a subclass of ChoiceField for purity, but it doesn't |
|
297 # actually use any of ChoiceField's implementation. |
|
298 default_error_messages = { |
|
299 'invalid_choice': _(u'Select a valid choice. That choice is not one of' |
|
300 u' the available choices.'), |
|
301 } |
|
302 |
|
303 def __init__(self, queryset, empty_label=u"---------", cache_choices=False, |
|
304 required=True, widget=Select, label=None, initial=None, |
|
305 help_text=None, *args, **kwargs): |
|
306 self.empty_label = empty_label |
|
307 self.cache_choices = cache_choices |
|
308 |
|
309 # Call Field instead of ChoiceField __init__() because we don't need |
|
310 # ChoiceField.__init__(). |
|
311 Field.__init__(self, required, widget, label, initial, help_text, |
|
312 *args, **kwargs) |
|
313 self.queryset = queryset |
|
314 |
|
315 def _get_queryset(self): |
|
316 return self._queryset |
|
317 |
|
318 def _set_queryset(self, queryset): |
|
319 self._queryset = queryset |
|
320 self.widget.choices = self.choices |
|
321 |
|
322 queryset = property(_get_queryset, _set_queryset) |
|
323 |
|
324 # this method will be used to create object labels by the QuerySetIterator. |
|
325 # Override it to customize the label. |
|
326 def label_from_instance(self, obj): |
|
327 """ |
|
328 This method is used to convert objects into strings; it's used to |
|
329 generate the labels for the choices presented by this object. Subclasses |
|
330 can override this method to customize the display of the choices. |
|
331 """ |
|
332 return smart_unicode(obj) |
|
333 |
|
334 def _get_choices(self): |
|
335 # If self._choices is set, then somebody must have manually set |
|
336 # the property self.choices. In this case, just return self._choices. |
|
337 if hasattr(self, '_choices'): |
|
338 return self._choices |
|
339 |
|
340 # Otherwise, execute the QuerySet in self.queryset to determine the |
|
341 # choices dynamically. Return a fresh QuerySetIterator that has not been |
|
342 # consumed. Note that we're instantiating a new QuerySetIterator *each* |
|
343 # time _get_choices() is called (and, thus, each time self.choices is |
|
344 # accessed) so that we can ensure the QuerySet has not been consumed. This |
|
345 # construct might look complicated but it allows for lazy evaluation of |
|
346 # the queryset. |
|
347 return ModelChoiceIterator(self) |
|
348 |
|
349 def _set_choices(self, value): |
|
350 # This method is copied from ChoiceField._set_choices(). It's necessary |
|
351 # because property() doesn't allow a subclass to overwrite only |
|
352 # _get_choices without implementing _set_choices. |
|
353 self._choices = self.widget.choices = list(value) |
|
354 |
|
355 choices = property(_get_choices, _set_choices) |
|
356 |
|
357 def clean(self, value): |
|
358 Field.clean(self, value) |
|
359 if value in EMPTY_VALUES: |
|
360 return None |
|
361 try: |
|
362 value = self.queryset.get(pk=value) |
|
363 except self.queryset.model.DoesNotExist: |
|
364 raise ValidationError(self.error_messages['invalid_choice']) |
|
365 return value |
|
366 |
|
367 class ModelMultipleChoiceField(ModelChoiceField): |
|
368 """A MultipleChoiceField whose choices are a model QuerySet.""" |
|
369 hidden_widget = MultipleHiddenInput |
|
370 default_error_messages = { |
|
371 'list': _(u'Enter a list of values.'), |
|
372 'invalid_choice': _(u'Select a valid choice. %s is not one of the' |
|
373 u' available choices.'), |
|
374 } |
|
375 |
|
376 def __init__(self, queryset, cache_choices=False, required=True, |
|
377 widget=SelectMultiple, label=None, initial=None, |
|
378 help_text=None, *args, **kwargs): |
|
379 super(ModelMultipleChoiceField, self).__init__(queryset, None, |
|
380 cache_choices, required, widget, label, initial, help_text, |
|
381 *args, **kwargs) |
|
382 |
|
383 def clean(self, value): |
|
384 if self.required and not value: |
|
385 raise ValidationError(self.error_messages['required']) |
|
386 elif not self.required and not value: |
|
387 return [] |
|
388 if not isinstance(value, (list, tuple)): |
|
389 raise ValidationError(self.error_messages['list']) |
|
390 final_values = [] |
|
391 for val in value: |
|
392 try: |
|
393 obj = self.queryset.get(pk=val) |
|
394 except self.queryset.model.DoesNotExist: |
|
395 raise ValidationError(self.error_messages['invalid_choice'] % val) |
|
396 else: |
|
397 final_values.append(obj) |
|
398 return final_values |