2
Helper functions for creating Form classes from Django models
3
and database field objects.
6
from django.utils.translation import gettext
7
from util import ValidationError
8
from forms import BaseForm, DeclarativeFieldsMetaclass, SortedDictFromList
9
from fields import Field, ChoiceField
10
from widgets import Select, SelectMultiple, MultipleHiddenInput
12
__all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
13
'ModelChoiceField', 'ModelMultipleChoiceField')
15
def model_save(self, commit=True):
17
Creates and returns model instance according to self.clean_data.
19
This method is created for any form_for_model Form.
22
raise ValueError("The %s could not be created because the data didn't validate." % self._model._meta.object_name)
23
return save_instance(self, self._model(), commit)
25
def save_instance(form, instance, commit=True):
27
Saves bound Form ``form``'s clean_data into model instance ``instance``.
29
Assumes ``form`` has a field for every non-AutoField database field in
30
``instance``. If commit=True, then the changes to ``instance`` will be
31
saved to the database. Returns ``instance``.
33
from django.db import models
34
opts = instance.__class__._meta
36
raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name)
37
clean_data = form.clean_data
39
if not f.editable or isinstance(f, models.AutoField):
41
setattr(instance, f.name, clean_data[f.name])
44
for f in opts.many_to_many:
45
setattr(instance, f.attname, clean_data[f.name])
46
# GOTCHA: If many-to-many data is given and commit=False, the many-to-many
47
# data will be lost. This happens because a many-to-many options cannot be
48
# set on an object until after it's saved. Maybe we should raise an
49
# exception in that case.
52
def make_instance_save(instance):
53
"Returns the save() method for a form_for_instance Form."
54
def save(self, commit=True):
55
return save_instance(self, instance, commit)
58
def form_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield()):
60
Returns a Form class for the given Django model class.
62
Provide ``form`` if you want to use a custom BaseForm subclass.
64
Provide ``formfield_callback`` if you want to define different logic for
65
determining the formfield for a given database field. It's a callable that
66
takes a database Field instance and returns a form Field instance.
70
for f in opts.fields + opts.many_to_many:
73
formfield = formfield_callback(f)
75
field_list.append((f.name, formfield))
76
fields = SortedDictFromList(field_list)
77
return type(opts.object_name + 'Form', (form,), {'base_fields': fields, '_model': model, 'save': model_save})
79
def form_for_instance(instance, form=BaseForm, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
81
Returns a Form class for the given Django model instance.
83
Provide ``form`` if you want to use a custom BaseForm subclass.
85
Provide ``formfield_callback`` if you want to define different logic for
86
determining the formfield for a given database field. It's a callable that
87
takes a database Field instance, plus **kwargs, and returns a form Field
88
instance with the given kwargs (i.e. 'initial').
90
model = instance.__class__
93
for f in opts.fields + opts.many_to_many:
96
current_value = f.value_from_object(instance)
97
formfield = formfield_callback(f, initial=current_value)
99
field_list.append((f.name, formfield))
100
fields = SortedDictFromList(field_list)
101
return type(opts.object_name + 'InstanceForm', (form,),
102
{'base_fields': fields, '_model': model, 'save': make_instance_save(instance)})
104
def form_for_fields(field_list):
105
"Returns a Form class for the given list of Django database field instances."
106
fields = SortedDictFromList([(f.name, f.formfield()) for f in field_list if f.editable])
107
return type('FormForFields', (BaseForm,), {'base_fields': fields})
109
class QuerySetIterator(object):
110
def __init__(self, queryset, empty_label, cache_choices):
111
self.queryset, self.empty_label, self.cache_choices = queryset, empty_label, cache_choices
114
if self.empty_label is not None:
115
yield (u"", self.empty_label)
116
for obj in self.queryset:
117
yield (obj._get_pk_val(), str(obj))
118
# Clear the QuerySet cache if required.
119
if not self.cache_choices:
120
self.queryset._result_cache = None
122
class ModelChoiceField(ChoiceField):
123
"A ChoiceField whose choices are a model QuerySet."
124
# This class is a subclass of ChoiceField for purity, but it doesn't
125
# actually use any of ChoiceField's implementation.
126
def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
127
required=True, widget=Select, label=None, initial=None, help_text=None):
128
self.queryset = queryset
129
self.empty_label = empty_label
130
self.cache_choices = cache_choices
131
# Call Field instead of ChoiceField __init__() because we don't need
132
# ChoiceField.__init__().
133
Field.__init__(self, required, widget, label, initial, help_text)
134
self.widget.choices = self.choices
136
def _get_choices(self):
137
# If self._choices is set, then somebody must have manually set
138
# the property self.choices. In this case, just return self._choices.
139
if hasattr(self, '_choices'):
141
# Otherwise, execute the QuerySet in self.queryset to determine the
142
# choices dynamically. Return a fresh QuerySetIterator that has not
143
# been consumed. Note that we're instantiating a new QuerySetIterator
144
# *each* time _get_choices() is called (and, thus, each time
145
# self.choices is accessed) so that we can ensure the QuerySet has not
147
return QuerySetIterator(self.queryset, self.empty_label, self.cache_choices)
149
def _set_choices(self, value):
150
# This method is copied from ChoiceField._set_choices(). It's necessary
151
# because property() doesn't allow a subclass to overwrite only
152
# _get_choices without implementing _set_choices.
153
self._choices = self.widget.choices = list(value)
155
choices = property(_get_choices, _set_choices)
157
def clean(self, value):
158
Field.clean(self, value)
159
if value in ('', None):
162
value = self.queryset.model._default_manager.get(pk=value)
163
except self.queryset.model.DoesNotExist:
164
raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.'))
167
class ModelMultipleChoiceField(ModelChoiceField):
168
"A MultipleChoiceField whose choices are a model QuerySet."
169
hidden_widget = MultipleHiddenInput
170
def __init__(self, queryset, cache_choices=False, required=True,
171
widget=SelectMultiple, label=None, initial=None, help_text=None):
172
super(ModelMultipleChoiceField, self).__init__(queryset, None, cache_choices,
173
required, widget, label, initial, help_text)
175
def clean(self, value):
176
if self.required and not value:
177
raise ValidationError(gettext(u'This field is required.'))
178
elif not self.required and not value:
180
if not isinstance(value, (list, tuple)):
181
raise ValidationError(gettext(u'Enter a list of values.'))
185
obj = self.queryset.model._default_manager.get(pk=val)
186
except self.queryset.model.DoesNotExist:
187
raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val)
189
final_values.append(obj)