summaryrefslogtreecommitdiff
path: root/django/newforms/models.py
diff options
context:
space:
mode:
authorBrian Rosner <brosner@gmail.com>2008-07-18 23:54:34 +0000
committerBrian Rosner <brosner@gmail.com>2008-07-18 23:54:34 +0000
commita19ed8aea395e8e07164ff7d85bd7dff2f24edca (patch)
treeec5fd01c30abc5fa22c1f02159bf68cfe89313cc /django/newforms/models.py
parentdc375fb0f3b7fbae740e8cfcd791b8bccb8a4e66 (diff)
Merged the newforms-admin branch into trunk.
This is a backward incompatible change. The admin contrib app has been refactored. The newforms module has several improvements including FormSets and Media definitions. git-svn-id: http://code.djangoproject.com/svn/django/trunk@7967 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/newforms/models.py')
-rw-r--r--django/newforms/models.py222
1 files changed, 215 insertions, 7 deletions
diff --git a/django/newforms/models.py b/django/newforms/models.py
index c3938d9ae7..43e2978ba8 100644
--- a/django/newforms/models.py
+++ b/django/newforms/models.py
@@ -12,13 +12,15 @@ from django.core.exceptions import ImproperlyConfigured
from util import ValidationError, ErrorList
from forms import BaseForm, get_declared_fields
-from fields import Field, ChoiceField, EMPTY_VALUES
-from widgets import Select, SelectMultiple, MultipleHiddenInput
+from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES
+from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
+from widgets import media_property
+from formsets import BaseFormSet, formset_factory, DELETION_FIELD_NAME
__all__ = (
'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
- 'ModelChoiceField', 'ModelMultipleChoiceField'
+ 'ModelChoiceField', 'ModelMultipleChoiceField',
)
def save_instance(form, instance, fields=None, fail_message='saved',
@@ -30,7 +32,7 @@ def save_instance(form, instance, fields=None, fail_message='saved',
database. Returns ``instance``.
"""
from django.db import models
- opts = instance.__class__._meta
+ opts = instance._meta
if form.errors:
raise ValueError("The %s could not be %s because the data didn't"
" validate." % (opts.object_name, fail_message))
@@ -44,7 +46,7 @@ def save_instance(form, instance, fields=None, fail_message='saved',
f.save_form_data(instance, cleaned_data[f.name])
# Wrap up the saving of m2m data as a function.
def save_m2m():
- opts = instance.__class__._meta
+ opts = instance._meta
cleaned_data = form.cleaned_data
for f in opts.many_to_many:
if fields and f.name not in fields:
@@ -226,6 +228,8 @@ class ModelFormMetaclass(type):
if not parents:
return new_class
+ if 'media' not in attrs:
+ new_class.media = media_property(new_class)
declared_fields = get_declared_fields(bases, attrs, False)
opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
if opts.model:
@@ -244,7 +248,7 @@ class ModelFormMetaclass(type):
class BaseModelForm(BaseForm):
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=':',
- instance=None):
+ empty_permitted=False, instance=None):
opts = self._meta
if instance is None:
# if we didn't get an instance, instantiate a new one
@@ -256,7 +260,8 @@ class BaseModelForm(BaseForm):
# if initial was provided, it should override the values from instance
if initial is not None:
object_data.update(initial)
- BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix)
+ BaseForm.__init__(self, data, files, auto_id, prefix, object_data,
+ error_class, label_suffix, empty_permitted)
def save(self, commit=True):
"""
@@ -275,6 +280,209 @@ class BaseModelForm(BaseForm):
class ModelForm(BaseModelForm):
__metaclass__ = ModelFormMetaclass
+def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
+ formfield_callback=lambda f: f.formfield()):
+ # HACK: we should be able to construct a ModelForm without creating
+ # and passing in a temporary inner class
+ class Meta:
+ pass
+ setattr(Meta, 'model', model)
+ setattr(Meta, 'fields', fields)
+ setattr(Meta, 'exclude', exclude)
+ class_name = model.__name__ + 'Form'
+ return ModelFormMetaclass(class_name, (form,), {'Meta': Meta,
+ 'formfield_callback': formfield_callback})
+
+
+# ModelFormSets ##############################################################
+
+class BaseModelFormSet(BaseFormSet):
+ """
+ A ``FormSet`` for editing a queryset and/or adding new objects to it.
+ """
+ model = None
+
+ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
+ queryset=None, **kwargs):
+ self.queryset = queryset
+ defaults = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix}
+ if self._max_form_count > 0:
+ qs = self.get_queryset()[:self._max_form_count]
+ else:
+ qs = self.get_queryset()
+ defaults['initial'] = [model_to_dict(obj) for obj in qs]
+ defaults.update(kwargs)
+ super(BaseModelFormSet, self).__init__(**defaults)
+
+ def get_queryset(self):
+ if self.queryset is not None:
+ return self.queryset
+ return self.model._default_manager.get_query_set()
+
+ def save_new(self, form, commit=True):
+ """Saves and returns a new model instance for the given form."""
+ return save_instance(form, self.model(), commit=commit)
+
+ def save_existing(self, form, instance, commit=True):
+ """Saves and returns an existing model instance for the given form."""
+ return save_instance(form, instance, commit=commit)
+
+ def save(self, commit=True):
+ """Saves model instances for every form, adding and changing instances
+ as necessary, and returns the list of instances.
+ """
+ if not commit:
+ self.saved_forms = []
+ def save_m2m():
+ for form in self.saved_forms:
+ form.save_m2m()
+ self.save_m2m = save_m2m
+ return self.save_existing_objects(commit) + self.save_new_objects(commit)
+
+ def save_existing_objects(self, commit=True):
+ self.changed_objects = []
+ self.deleted_objects = []
+ if not self.get_queryset():
+ return []
+
+ # Put the objects from self.get_queryset into a dict so they are easy to lookup by pk
+ existing_objects = {}
+ for obj in self.get_queryset():
+ existing_objects[obj.pk] = obj
+ saved_instances = []
+ for form in self.initial_forms:
+ obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]]
+ if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
+ self.deleted_objects.append(obj)
+ obj.delete()
+ else:
+ if form.changed_data:
+ self.changed_objects.append((obj, form.changed_data))
+ saved_instances.append(self.save_existing(form, obj, commit=commit))
+ if not commit:
+ self.saved_forms.append(form)
+ return saved_instances
+
+ def save_new_objects(self, commit=True):
+ self.new_objects = []
+ for form in self.extra_forms:
+ if not form.has_changed():
+ continue
+ # If someone has marked an add form for deletion, don't save the
+ # object.
+ if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
+ continue
+ self.new_objects.append(self.save_new(form, commit=commit))
+ if not commit:
+ self.saved_forms.append(form)
+ return self.new_objects
+
+ def add_fields(self, form, index):
+ """Add a hidden field for the object's primary key."""
+ self._pk_field_name = self.model._meta.pk.attname
+ form.fields[self._pk_field_name] = IntegerField(required=False, widget=HiddenInput)
+ super(BaseModelFormSet, self).add_fields(form, index)
+
+def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(),
+ formset=BaseModelFormSet,
+ extra=1, can_delete=False, can_order=False,
+ max_num=0, fields=None, exclude=None):
+ """
+ Returns a FormSet class for the given Django model class.
+ """
+ form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
+ formfield_callback=formfield_callback)
+ FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
+ can_order=can_order, can_delete=can_delete)
+ FormSet.model = model
+ return FormSet
+
+
+# InlineFormSets #############################################################
+
+class BaseInlineFormset(BaseModelFormSet):
+ """A formset for child objects related to a parent."""
+ def __init__(self, data=None, files=None, instance=None, save_as_new=False):
+ from django.db.models.fields.related import RelatedObject
+ self.instance = instance
+ self.save_as_new = save_as_new
+ # is there a better way to get the object descriptor?
+ self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name()
+ super(BaseInlineFormset, self).__init__(data, files, prefix=self.rel_name)
+
+ def _construct_forms(self):
+ if self.save_as_new:
+ self._total_form_count = self._initial_form_count
+ self._initial_form_count = 0
+ super(BaseInlineFormset, self)._construct_forms()
+
+ def get_queryset(self):
+ """
+ Returns this FormSet's queryset, but restricted to children of
+ self.instance
+ """
+ kwargs = {self.fk.name: self.instance}
+ return self.model._default_manager.filter(**kwargs)
+
+ def save_new(self, form, commit=True):
+ kwargs = {self.fk.get_attname(): self.instance.pk}
+ new_obj = self.model(**kwargs)
+ return save_instance(form, new_obj, commit=commit)
+
+def _get_foreign_key(parent_model, model, fk_name=None):
+ """
+ Finds and returns the ForeignKey from model to parent if there is one.
+ If fk_name is provided, assume it is the name of the ForeignKey field.
+ """
+ # avoid circular import
+ from django.db.models import ForeignKey
+ opts = model._meta
+ if fk_name:
+ fks_to_parent = [f for f in opts.fields if f.name == fk_name]
+ if len(fks_to_parent) == 1:
+ fk = fks_to_parent[0]
+ if not isinstance(fk, ForeignKey) or fk.rel.to != parent_model:
+ raise Exception("fk_name '%s' is not a ForeignKey to %s" % (fk_name, parent_model))
+ elif len(fks_to_parent) == 0:
+ raise Exception("%s has no field named '%s'" % (model, fk_name))
+ else:
+ # Try to discover what the ForeignKey from model to parent_model is
+ fks_to_parent = [f for f in opts.fields if isinstance(f, ForeignKey) and f.rel.to == parent_model]
+ if len(fks_to_parent) == 1:
+ fk = fks_to_parent[0]
+ elif len(fks_to_parent) == 0:
+ raise Exception("%s has no ForeignKey to %s" % (model, parent_model))
+ else:
+ raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model))
+ return fk
+
+
+def inlineformset_factory(parent_model, model, form=ModelForm,
+ formset=BaseInlineFormset, fk_name=None,
+ fields=None, exclude=None,
+ extra=3, can_order=False, can_delete=True, max_num=0,
+ formfield_callback=lambda f: f.formfield()):
+ """
+ Returns an ``InlineFormset`` for the given kwargs.
+
+ You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey``
+ to ``parent_model``.
+ """
+ fk = _get_foreign_key(parent_model, model, fk_name=fk_name)
+ # let the formset handle object deletion by default
+
+ if exclude is not None:
+ exclude.append(fk.name)
+ else:
+ exclude = [fk.name]
+ FormSet = modelformset_factory(model, form=form,
+ formfield_callback=formfield_callback,
+ formset=formset,
+ extra=extra, can_delete=can_delete, can_order=can_order,
+ fields=fields, exclude=exclude, max_num=max_num)
+ FormSet.fk = fk
+ return FormSet
+
# Fields #####################################################################