2006-11-25 14:33:59 +08:00
|
|
|
"""
|
2006-12-15 13:46:11 +08:00
|
|
|
Helper functions for creating Form classes from Django models
|
|
|
|
and database field objects.
|
2006-11-25 14:33:59 +08:00
|
|
|
"""
|
|
|
|
|
2007-11-19 04:25:23 +08:00
|
|
|
from django.utils.translation import ugettext_lazy as _
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
from django.utils.encoding import smart_unicode
|
2007-11-11 12:44:20 +08:00
|
|
|
from django.utils.datastructures import SortedDict
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
|
2007-02-21 13:14:28 +08:00
|
|
|
from util import ValidationError
|
2007-11-11 12:44:20 +08:00
|
|
|
from forms import BaseForm
|
2007-11-19 12:07:03 +08:00
|
|
|
from fields import Field, ChoiceField, EMPTY_VALUES
|
2007-02-21 13:14:28 +08:00
|
|
|
from widgets import Select, SelectMultiple, MultipleHiddenInput
|
2006-12-15 13:46:11 +08:00
|
|
|
|
2007-05-17 05:20:35 +08:00
|
|
|
__all__ = (
|
|
|
|
'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
|
|
|
|
'ModelChoiceField', 'ModelMultipleChoiceField'
|
|
|
|
)
|
2006-11-25 14:33:59 +08:00
|
|
|
|
2007-10-30 07:52:17 +08:00
|
|
|
def save_instance(form, instance, fields=None, fail_message='saved',
|
|
|
|
commit=True):
|
2007-01-09 13:49:47 +08:00
|
|
|
"""
|
2007-05-15 00:24:51 +08:00
|
|
|
Saves bound Form ``form``'s cleaned_data into model instance ``instance``.
|
2007-01-09 13:49:47 +08:00
|
|
|
|
2007-06-15 08:26:30 +08:00
|
|
|
If commit=True, then the changes to ``instance`` will be saved to the
|
|
|
|
database. Returns ``instance``.
|
2007-01-09 13:49:47 +08:00
|
|
|
"""
|
2006-12-28 10:34:53 +08:00
|
|
|
from django.db import models
|
2007-01-09 13:49:47 +08:00
|
|
|
opts = instance.__class__._meta
|
|
|
|
if form.errors:
|
2007-10-30 07:52:17 +08:00
|
|
|
raise ValueError("The %s could not be %s because the data didn't"
|
|
|
|
" validate." % (opts.object_name, fail_message))
|
2007-05-15 00:24:51 +08:00
|
|
|
cleaned_data = form.cleaned_data
|
2007-01-28 12:56:54 +08:00
|
|
|
for f in opts.fields:
|
2007-10-30 07:52:17 +08:00
|
|
|
if not f.editable or isinstance(f, models.AutoField) \
|
|
|
|
or not f.name in cleaned_data:
|
2007-01-09 13:49:47 +08:00
|
|
|
continue
|
2007-05-12 22:42:46 +08:00
|
|
|
if fields and f.name not in fields:
|
|
|
|
continue
|
2007-10-30 07:52:17 +08:00
|
|
|
f.save_form_data(instance, cleaned_data[f.name])
|
|
|
|
# Wrap up the saving of m2m data as a function.
|
2007-08-05 15:39:36 +08:00
|
|
|
def save_m2m():
|
|
|
|
opts = instance.__class__._meta
|
|
|
|
cleaned_data = form.cleaned_data
|
2007-01-28 12:56:54 +08:00
|
|
|
for f in opts.many_to_many:
|
2007-05-12 22:42:46 +08:00
|
|
|
if fields and f.name not in fields:
|
|
|
|
continue
|
2007-05-15 00:24:51 +08:00
|
|
|
if f.name in cleaned_data:
|
2007-08-06 21:58:56 +08:00
|
|
|
f.save_form_data(instance, cleaned_data[f.name])
|
2007-08-05 15:39:36 +08:00
|
|
|
if commit:
|
2007-10-30 07:52:17 +08:00
|
|
|
# If we are committing, save the instance and the m2m data immediately.
|
2007-08-05 15:39:36 +08:00
|
|
|
instance.save()
|
|
|
|
save_m2m()
|
|
|
|
else:
|
2007-10-30 07:52:17 +08:00
|
|
|
# We're not committing. Add a method to the form to allow deferred
|
|
|
|
# saving of m2m data.
|
2007-08-05 15:39:36 +08:00
|
|
|
form.save_m2m = save_m2m
|
2007-01-09 13:49:47 +08:00
|
|
|
return instance
|
|
|
|
|
2007-05-12 22:42:46 +08:00
|
|
|
def make_model_save(model, fields, fail_message):
|
2007-10-30 07:52:17 +08:00
|
|
|
"""Returns the save() method for a Form."""
|
2007-01-09 13:49:47 +08:00
|
|
|
def save(self, commit=True):
|
2007-05-12 22:42:46 +08:00
|
|
|
return save_instance(self, model(), fields, fail_message, commit)
|
|
|
|
return save
|
2007-10-30 07:52:17 +08:00
|
|
|
|
2007-05-12 22:42:46 +08:00
|
|
|
def make_instance_save(instance, fields, fail_message):
|
2007-10-30 07:52:17 +08:00
|
|
|
"""Returns the save() method for a Form."""
|
2007-05-12 22:42:46 +08:00
|
|
|
def save(self, commit=True):
|
|
|
|
return save_instance(self, instance, fields, fail_message, commit)
|
2007-01-09 13:49:47 +08:00
|
|
|
return save
|
2006-12-28 10:34:53 +08:00
|
|
|
|
2007-10-30 07:52:17 +08:00
|
|
|
def form_for_model(model, form=BaseForm, fields=None,
|
|
|
|
formfield_callback=lambda f: f.formfield()):
|
2006-12-17 13:12:53 +08:00
|
|
|
"""
|
|
|
|
Returns a Form class for the given Django model class.
|
|
|
|
|
2007-01-09 13:49:47 +08:00
|
|
|
Provide ``form`` if you want to use a custom BaseForm subclass.
|
2007-01-22 14:10:47 +08:00
|
|
|
|
|
|
|
Provide ``formfield_callback`` if you want to define different logic for
|
|
|
|
determining the formfield for a given database field. It's a callable that
|
|
|
|
takes a database Field instance and returns a form Field instance.
|
2006-12-17 13:12:53 +08:00
|
|
|
"""
|
2006-12-15 13:46:11 +08:00
|
|
|
opts = model._meta
|
2006-12-16 05:22:13 +08:00
|
|
|
field_list = []
|
|
|
|
for f in opts.fields + opts.many_to_many:
|
2007-02-20 10:59:16 +08:00
|
|
|
if not f.editable:
|
|
|
|
continue
|
2007-05-12 22:42:46 +08:00
|
|
|
if fields and not f.name in fields:
|
|
|
|
continue
|
2007-01-22 14:10:47 +08:00
|
|
|
formfield = formfield_callback(f)
|
2006-12-16 05:22:13 +08:00
|
|
|
if formfield:
|
|
|
|
field_list.append((f.name, formfield))
|
2007-11-11 12:44:20 +08:00
|
|
|
base_fields = SortedDict(field_list)
|
2007-10-30 07:52:17 +08:00
|
|
|
return type(opts.object_name + 'Form', (form,),
|
|
|
|
{'base_fields': base_fields, '_model': model,
|
|
|
|
'save': make_model_save(model, fields, 'created')})
|
2006-11-25 14:33:59 +08:00
|
|
|
|
2007-10-30 07:52:17 +08:00
|
|
|
def form_for_instance(instance, form=BaseForm, fields=None,
|
|
|
|
formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
|
2006-12-28 09:16:29 +08:00
|
|
|
"""
|
|
|
|
Returns a Form class for the given Django model instance.
|
|
|
|
|
2007-01-09 13:49:47 +08:00
|
|
|
Provide ``form`` if you want to use a custom BaseForm subclass.
|
2007-01-22 14:10:47 +08:00
|
|
|
|
|
|
|
Provide ``formfield_callback`` if you want to define different logic for
|
|
|
|
determining the formfield for a given database field. It's a callable that
|
|
|
|
takes a database Field instance, plus **kwargs, and returns a form Field
|
|
|
|
instance with the given kwargs (i.e. 'initial').
|
2006-12-28 09:16:29 +08:00
|
|
|
"""
|
|
|
|
model = instance.__class__
|
|
|
|
opts = model._meta
|
|
|
|
field_list = []
|
|
|
|
for f in opts.fields + opts.many_to_many:
|
2007-02-20 10:59:16 +08:00
|
|
|
if not f.editable:
|
|
|
|
continue
|
2007-05-12 22:42:46 +08:00
|
|
|
if fields and not f.name in fields:
|
|
|
|
continue
|
2006-12-28 10:34:53 +08:00
|
|
|
current_value = f.value_from_object(instance)
|
2007-01-22 14:10:47 +08:00
|
|
|
formfield = formfield_callback(f, initial=current_value)
|
2006-12-28 09:16:29 +08:00
|
|
|
if formfield:
|
|
|
|
field_list.append((f.name, formfield))
|
2007-11-11 12:44:20 +08:00
|
|
|
base_fields = SortedDict(field_list)
|
2006-12-28 10:34:53 +08:00
|
|
|
return type(opts.object_name + 'InstanceForm', (form,),
|
2007-10-30 07:52:17 +08:00
|
|
|
{'base_fields': base_fields, '_model': model,
|
|
|
|
'save': make_instance_save(instance, fields, 'changed')})
|
2006-12-28 09:16:29 +08:00
|
|
|
|
2006-11-25 14:33:59 +08:00
|
|
|
def form_for_fields(field_list):
|
2007-10-30 07:52:17 +08:00
|
|
|
"""
|
|
|
|
Returns a Form class for the given list of Django database field instances.
|
|
|
|
"""
|
2007-11-11 12:44:20 +08:00
|
|
|
fields = SortedDict([(f.name, f.formfield())
|
|
|
|
for f in field_list if f.editable])
|
2007-01-28 06:06:56 +08:00
|
|
|
return type('FormForFields', (BaseForm,), {'base_fields': fields})
|
2007-02-20 10:42:07 +08:00
|
|
|
|
2007-02-21 13:14:28 +08:00
|
|
|
class QuerySetIterator(object):
|
|
|
|
def __init__(self, queryset, empty_label, cache_choices):
|
2007-10-30 07:52:17 +08:00
|
|
|
self.queryset = queryset
|
|
|
|
self.empty_label = empty_label
|
|
|
|
self.cache_choices = cache_choices
|
2007-02-21 13:14:28 +08:00
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
if self.empty_label is not None:
|
|
|
|
yield (u"", self.empty_label)
|
|
|
|
for obj in self.queryset:
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
yield (obj._get_pk_val(), smart_unicode(obj))
|
2007-02-21 13:14:28 +08:00
|
|
|
# Clear the QuerySet cache if required.
|
|
|
|
if not self.cache_choices:
|
|
|
|
self.queryset._result_cache = None
|
|
|
|
|
2007-02-20 10:42:07 +08:00
|
|
|
class ModelChoiceField(ChoiceField):
|
2007-10-30 07:52:17 +08:00
|
|
|
"""A ChoiceField whose choices are a model QuerySet."""
|
2007-02-21 13:14:28 +08:00
|
|
|
# This class is a subclass of ChoiceField for purity, but it doesn't
|
|
|
|
# actually use any of ChoiceField's implementation.
|
2007-11-19 04:25:23 +08:00
|
|
|
default_error_messages = {
|
|
|
|
'invalid_choice': _(u'Select a valid choice. That choice is not one of'
|
|
|
|
u' the available choices.'),
|
|
|
|
}
|
2007-10-30 07:52:17 +08:00
|
|
|
|
2007-02-21 13:14:28 +08:00
|
|
|
def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
|
2007-10-30 07:52:17 +08:00
|
|
|
required=True, widget=Select, label=None, initial=None,
|
2007-11-19 04:25:23 +08:00
|
|
|
help_text=None, *args, **kwargs):
|
2007-02-21 13:14:28 +08:00
|
|
|
self.empty_label = empty_label
|
|
|
|
self.cache_choices = cache_choices
|
|
|
|
# Call Field instead of ChoiceField __init__() because we don't need
|
|
|
|
# ChoiceField.__init__().
|
2007-11-19 04:25:23 +08:00
|
|
|
Field.__init__(self, required, widget, label, initial, help_text,
|
|
|
|
*args, **kwargs)
|
2007-11-13 22:36:29 +08:00
|
|
|
self.queryset = queryset
|
|
|
|
|
|
|
|
def _get_queryset(self):
|
|
|
|
return self._queryset
|
|
|
|
|
|
|
|
def _set_queryset(self, queryset):
|
|
|
|
self._queryset = queryset
|
2007-02-21 13:14:28 +08:00
|
|
|
self.widget.choices = self.choices
|
|
|
|
|
2007-11-13 22:36:29 +08:00
|
|
|
queryset = property(_get_queryset, _set_queryset)
|
|
|
|
|
2007-02-21 13:14:28 +08:00
|
|
|
def _get_choices(self):
|
|
|
|
# If self._choices is set, then somebody must have manually set
|
|
|
|
# the property self.choices. In this case, just return self._choices.
|
|
|
|
if hasattr(self, '_choices'):
|
|
|
|
return self._choices
|
|
|
|
# Otherwise, execute the QuerySet in self.queryset to determine the
|
2007-02-21 13:18:34 +08:00
|
|
|
# choices dynamically. Return a fresh QuerySetIterator that has not
|
|
|
|
# been consumed. Note that we're instantiating a new QuerySetIterator
|
|
|
|
# *each* time _get_choices() is called (and, thus, each time
|
|
|
|
# self.choices is accessed) so that we can ensure the QuerySet has not
|
|
|
|
# been consumed.
|
2007-10-30 07:52:17 +08:00
|
|
|
return QuerySetIterator(self.queryset, self.empty_label,
|
|
|
|
self.cache_choices)
|
2007-02-21 13:14:28 +08:00
|
|
|
|
|
|
|
def _set_choices(self, value):
|
|
|
|
# This method is copied from ChoiceField._set_choices(). It's necessary
|
|
|
|
# because property() doesn't allow a subclass to overwrite only
|
|
|
|
# _get_choices without implementing _set_choices.
|
|
|
|
self._choices = self.widget.choices = list(value)
|
|
|
|
|
|
|
|
choices = property(_get_choices, _set_choices)
|
2007-02-20 10:42:07 +08:00
|
|
|
|
|
|
|
def clean(self, value):
|
2007-02-21 13:14:28 +08:00
|
|
|
Field.clean(self, value)
|
2007-11-19 12:07:03 +08:00
|
|
|
if value in EMPTY_VALUES:
|
2007-02-20 10:42:07 +08:00
|
|
|
return None
|
|
|
|
try:
|
2007-11-13 22:36:29 +08:00
|
|
|
value = self.queryset.get(pk=value)
|
2007-02-21 13:14:28 +08:00
|
|
|
except self.queryset.model.DoesNotExist:
|
2007-11-19 04:25:23 +08:00
|
|
|
raise ValidationError(self.error_messages['invalid_choice'])
|
2007-02-20 10:42:07 +08:00
|
|
|
return value
|
|
|
|
|
2007-02-21 13:14:28 +08:00
|
|
|
class ModelMultipleChoiceField(ModelChoiceField):
|
2007-10-30 07:52:17 +08:00
|
|
|
"""A MultipleChoiceField whose choices are a model QuerySet."""
|
2007-02-21 13:14:28 +08:00
|
|
|
hidden_widget = MultipleHiddenInput
|
2007-11-19 04:25:23 +08:00
|
|
|
default_error_messages = {
|
|
|
|
'list': _(u'Enter a list of values.'),
|
|
|
|
'invalid_choice': _(u'Select a valid choice. %s is not one of the'
|
|
|
|
u' available choices.'),
|
|
|
|
}
|
2007-10-30 07:52:17 +08:00
|
|
|
|
2007-02-21 13:14:28 +08:00
|
|
|
def __init__(self, queryset, cache_choices=False, required=True,
|
2007-10-30 07:52:17 +08:00
|
|
|
widget=SelectMultiple, label=None, initial=None,
|
2007-11-19 04:25:23 +08:00
|
|
|
help_text=None, *args, **kwargs):
|
2007-10-30 07:52:17 +08:00
|
|
|
super(ModelMultipleChoiceField, self).__init__(queryset, None,
|
2007-11-19 04:25:23 +08:00
|
|
|
cache_choices, required, widget, label, initial, help_text,
|
|
|
|
*args, **kwargs)
|
2007-02-20 10:42:07 +08:00
|
|
|
|
|
|
|
def clean(self, value):
|
2007-02-21 13:14:28 +08:00
|
|
|
if self.required and not value:
|
2007-11-19 04:25:23 +08:00
|
|
|
raise ValidationError(self.error_messages['required'])
|
2007-02-21 13:14:28 +08:00
|
|
|
elif not self.required and not value:
|
2007-02-20 10:42:07 +08:00
|
|
|
return []
|
2007-02-21 13:14:28 +08:00
|
|
|
if not isinstance(value, (list, tuple)):
|
2007-11-19 04:25:23 +08:00
|
|
|
raise ValidationError(self.error_messages['list'])
|
2007-02-21 13:14:28 +08:00
|
|
|
final_values = []
|
|
|
|
for val in value:
|
|
|
|
try:
|
2007-11-13 22:36:29 +08:00
|
|
|
obj = self.queryset.get(pk=val)
|
2007-02-21 13:14:28 +08:00
|
|
|
except self.queryset.model.DoesNotExist:
|
2007-11-19 04:25:23 +08:00
|
|
|
raise ValidationError(self.error_messages['invalid_choice'] % val)
|
2007-02-21 13:14:28 +08:00
|
|
|
else:
|
|
|
|
final_values.append(obj)
|
|
|
|
return final_values
|