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
|
|
|
"""
|
|
|
|
|
2006-12-15 13:46:11 +08:00
|
|
|
from forms import BaseForm, DeclarativeFieldsMetaclass, SortedDictFromList
|
2007-02-20 10:42:07 +08:00
|
|
|
from fields import ChoiceField, MultipleChoiceField
|
|
|
|
from widgets import Select, SelectMultiple
|
2006-12-15 13:46:11 +08:00
|
|
|
|
2007-02-20 10:42:07 +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-01-09 13:22:48 +08:00
|
|
|
def model_save(self, commit=True):
|
2006-12-28 09:16:29 +08:00
|
|
|
"""
|
|
|
|
Creates and returns model instance according to self.clean_data.
|
|
|
|
|
|
|
|
This method is created for any form_for_model Form.
|
|
|
|
"""
|
2006-12-16 06:33:24 +08:00
|
|
|
if self.errors:
|
|
|
|
raise ValueError("The %s could not be created because the data didn't validate." % self._model._meta.object_name)
|
2007-01-28 12:56:54 +08:00
|
|
|
return save_instance(self, self._model(), commit)
|
2006-12-16 06:33:24 +08:00
|
|
|
|
2007-01-09 13:49:47 +08:00
|
|
|
def save_instance(form, instance, commit=True):
|
|
|
|
"""
|
|
|
|
Saves bound Form ``form``'s clean_data into model instance ``instance``.
|
|
|
|
|
|
|
|
Assumes ``form`` has a field for every non-AutoField database field in
|
|
|
|
``instance``. If commit=True, then the changes to ``instance`` will be
|
|
|
|
saved to the database. Returns ``instance``.
|
|
|
|
"""
|
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:
|
|
|
|
raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name)
|
|
|
|
clean_data = form.clean_data
|
2007-01-28 12:56:54 +08:00
|
|
|
for f in opts.fields:
|
2007-01-09 13:49:47 +08:00
|
|
|
if isinstance(f, models.AutoField):
|
|
|
|
continue
|
2007-02-20 10:42:07 +08:00
|
|
|
setattr(instance, f.name, clean_data[f.name])
|
2007-01-09 13:49:47 +08:00
|
|
|
if commit:
|
|
|
|
instance.save()
|
2007-01-28 12:56:54 +08:00
|
|
|
for f in opts.many_to_many:
|
2007-01-30 00:12:17 +08:00
|
|
|
setattr(instance, f.attname, clean_data[f.name])
|
2007-01-28 12:56:54 +08:00
|
|
|
# GOTCHA: If many-to-many data is given and commit=False, the many-to-many
|
|
|
|
# data will be lost. This happens because a many-to-many options cannot be
|
|
|
|
# set on an object until after it's saved. Maybe we should raise an
|
|
|
|
# exception in that case.
|
2007-01-09 13:49:47 +08:00
|
|
|
return instance
|
|
|
|
|
|
|
|
def make_instance_save(instance):
|
|
|
|
"Returns the save() method for a form_for_instance Form."
|
|
|
|
def save(self, commit=True):
|
|
|
|
return save_instance(self, instance, commit)
|
|
|
|
return save
|
2006-12-28 10:34:53 +08:00
|
|
|
|
2007-01-22 14:10:47 +08:00
|
|
|
def form_for_model(model, form=BaseForm, 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-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))
|
|
|
|
fields = SortedDictFromList(field_list)
|
2007-01-28 06:06:56 +08:00
|
|
|
return type(opts.object_name + 'Form', (form,), {'base_fields': fields, '_model': model, 'save': model_save})
|
2006-11-25 14:33:59 +08:00
|
|
|
|
2007-01-22 14:10:47 +08:00
|
|
|
def form_for_instance(instance, form=BaseForm, 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:
|
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))
|
|
|
|
fields = SortedDictFromList(field_list)
|
2006-12-28 10:34:53 +08:00
|
|
|
return type(opts.object_name + 'InstanceForm', (form,),
|
2007-01-28 06:06:56 +08:00
|
|
|
{'base_fields': fields, '_model': model, 'save': make_instance_save(instance)})
|
2006-12-28 09:16:29 +08:00
|
|
|
|
2006-11-25 14:33:59 +08:00
|
|
|
def form_for_fields(field_list):
|
2006-12-15 13:46:11 +08:00
|
|
|
"Returns a Form class for the given list of Django database field instances."
|
|
|
|
fields = SortedDictFromList([(f.name, f.formfield()) for f in field_list])
|
2007-01-28 06:06:56 +08:00
|
|
|
return type('FormForFields', (BaseForm,), {'base_fields': fields})
|
2007-02-20 10:42:07 +08:00
|
|
|
|
|
|
|
class ModelChoiceField(ChoiceField):
|
|
|
|
"A ChoiceField whose choices are a model QuerySet."
|
|
|
|
def __init__(self, queryset, empty_label=u"---------", **kwargs):
|
|
|
|
self.model = queryset.model
|
|
|
|
choices = [(obj._get_pk_val(), str(obj)) for obj in queryset]
|
|
|
|
if empty_label is not None:
|
|
|
|
choices = [(u"", empty_label)] + choices
|
|
|
|
ChoiceField.__init__(self, choices=choices, **kwargs)
|
|
|
|
|
|
|
|
def clean(self, value):
|
|
|
|
value = ChoiceField.clean(self, value)
|
|
|
|
if not value:
|
|
|
|
return None
|
|
|
|
try:
|
|
|
|
value = self.model._default_manager.get(pk=value)
|
|
|
|
except self.model.DoesNotExist:
|
|
|
|
raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.'))
|
|
|
|
return value
|
|
|
|
|
|
|
|
class ModelMultipleChoiceField(MultipleChoiceField):
|
|
|
|
"A MultipleChoiceField whose choices are a model QuerySet."
|
|
|
|
def __init__(self, queryset, **kwargs):
|
|
|
|
self.model = queryset.model
|
|
|
|
MultipleChoiceField.__init__(self, choices=[(obj._get_pk_val(), str(obj)) for obj in queryset], **kwargs)
|
|
|
|
|
|
|
|
def clean(self, value):
|
|
|
|
value = MultipleChoiceField.clean(self, value)
|
|
|
|
if not value:
|
|
|
|
return []
|
|
|
|
return self.model._default_manager.filter(pk__in=value)
|