From f68769e1c6c5cf1ac5d3a338380c352d922febda Mon Sep 17 00:00:00 2001 From: Joseph Kocherhans Date: Sat, 15 Sep 2007 18:13:50 +0000 Subject: [PATCH] newforms-admin: Fixed #5488. inlines with multiple ForeignKeys to the same parent don't work. Thanks jdetaeye. Also, some whitespace cleanup. Sorry for the mess. git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@6301 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/newforms/models.py | 30 ++++++---- .../inline_formsets/__init__.py | 0 .../regressiontests/inline_formsets/models.py | 55 +++++++++++++++++++ 3 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 tests/regressiontests/inline_formsets/__init__.py create mode 100644 tests/regressiontests/inline_formsets/models.py diff --git a/django/newforms/models.py b/django/newforms/models.py index bfbc2c20a9..4d2b3106c9 100644 --- a/django/newforms/models.py +++ b/django/newforms/models.py @@ -36,7 +36,7 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True continue if fields and f.name not in fields: continue - f.save_form_data(instance, cleaned_data[f.name]) + 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 @@ -51,7 +51,7 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True instance.save() save_m2m() else: - # We're not committing. Add a method to the form to allow deferred + # We're not committing. Add a method to the form to allow deferred # saving of m2m data form.save_m2m = save_m2m return instance @@ -61,7 +61,7 @@ def make_model_save(model, fields, fail_message): def save(self, commit=True): return save_instance(self, model(), fields, fail_message, commit) return save - + def make_instance_save(instance, fields, fail_message): "Returns the save() method for a Form." def save(self, commit=True): @@ -89,7 +89,7 @@ def form_for_model(model, form=BaseForm, fields=None, formfield_callback=lambda if formfield: field_list.append((f.name, formfield)) base_fields = SortedDictFromList(field_list) - return type(opts.object_name + 'Form', (form,), + return type(opts.object_name + 'Form', (form,), {'base_fields': base_fields, '_model': model, 'save': make_model_save(model, fields, 'created')}) def form_for_instance(instance, form=BaseForm, fields=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)): @@ -211,9 +211,9 @@ class ModelMultipleChoiceField(ModelChoiceField): def initial_data(instance, fields=None): """ - Return a dictionary from data in ``instance`` that is suitable for + Return a dictionary from data in ``instance`` that is suitable for use as a ``Form`` constructor's ``initial`` argument. - + Provide ``fields`` to specify the names of specific fields to return. All field values in the instance will be returned if ``fields`` is not provided. @@ -234,7 +234,7 @@ class BaseModelFormSet(BaseFormSet): A ``FormSet`` attatched to a particular model or sequence of model instances. """ model = None - + def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, instances=None): self.instances = instances kwargs = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} @@ -308,7 +308,7 @@ class InlineFormset(BaseModelFormSet): def inline_formset(parent_model, model, fk_name=None, fields=None, extra=3, orderable=False, deletable=True, 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``. """ @@ -323,10 +323,18 @@ def inline_formset(parent_model, model, fk_name=None, fields=None, extra=3, orde 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)) + else: + 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)) # let the formset handle object deletion by default - FormSet = formset_for_model(model, formset=InlineFormset, fields=fields, - formfield_callback=formfield_callback, - extra=extra, orderable=orderable, + FormSet = formset_for_model(model, formset=InlineFormset, fields=fields, + formfield_callback=formfield_callback, + extra=extra, orderable=orderable, deletable=deletable) # HACK: remove the ForeignKey to the parent from every form # This should be done a line above before we pass 'fields' to formset_for_model diff --git a/tests/regressiontests/inline_formsets/__init__.py b/tests/regressiontests/inline_formsets/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/inline_formsets/models.py b/tests/regressiontests/inline_formsets/models.py new file mode 100644 index 0000000000..f84be84c0d --- /dev/null +++ b/tests/regressiontests/inline_formsets/models.py @@ -0,0 +1,55 @@ +# coding: utf-8 +from django.db import models + +class School(models.Model): + name = models.CharField(max_length=100) + +class Parent(models.Model): + name = models.CharField(max_length=100) + +class Child(models.Model): + mother = models.ForeignKey(Parent, related_name='mothers_children') + father = models.ForeignKey(Parent, related_name='fathers_children') + school = models.ForeignKey(School) + name = models.CharField(max_length=100) + +__test__ = {'API_TESTS': """ + +>>> from django.newforms.models import inline_formset + + +Child has two ForeignKeys to Parent, so if we don't specify which one to use +for the inline formset, we should get an exception. + +>>> ifs = inline_formset(Parent, Child) +Traceback (most recent call last): + ... +Exception: has more than 1 ForeignKey to + + +These two should both work without a problem. + +>>> ifs = inline_formset(Parent, Child, fk_name='mother') +>>> ifs = inline_formset(Parent, Child, fk_name='father') + + +If we specify fk_name, but it isn't a ForeignKey from the child model to the +parent model, we should get an exception. + +>>> ifs = inline_formset(Parent, Child, fk_name='school') +Traceback (most recent call last): + ... +Exception: fk_name 'school' is not a ForeignKey to + + +If the field specified in fk_name is not a ForeignKey, we should get an +exception. + +>>> ifs = inline_formset(Parent, Child, fk_name='test') +Traceback (most recent call last): + ... +Exception: has no field named 'test' + + +""" +}