From 7215ffe8a43d55536ca46e37f9b5241f35f2fa5b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 13 May 2009 14:04:29 +0000 Subject: [PATCH] Fixed #10243, #11043 -- Corrected handling of formsets over a ForeignKey that uses to_field, and by extension, fixed the admin for handling fields of that type. Thanks to apollo13 for the initial patch. git-svn-id: http://code.djangoproject.com/svn/django/trunk@10756 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/forms/models.py | 21 ++++++++++++---- .../model_formsets_regress/__init__.py | 0 .../model_formsets_regress/models.py | 9 +++++++ .../model_formsets_regress/tests.py | 24 +++++++++++++++++++ 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 tests/regressiontests/model_formsets_regress/__init__.py create mode 100644 tests/regressiontests/model_formsets_regress/models.py create mode 100644 tests/regressiontests/model_formsets_regress/tests.py diff --git a/django/forms/models.py b/django/forms/models.py index d42b98483b..705ef10f17 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -698,7 +698,11 @@ class BaseInlineFormSet(BaseModelFormSet): 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() - qs = self.model._default_manager.filter(**{self.fk.name: self.instance}) + if self.fk.rel.field_name == self.fk.rel.to._meta.pk.name: + backlink_value = self.instance + else: + backlink_value = getattr(self.instance, self.fk.rel.field_name) + qs = self.model._default_manager.filter(**{self.fk.name: backlink_value}) super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix, queryset=qs) @@ -733,7 +737,7 @@ class BaseInlineFormSet(BaseModelFormSet): # Use commit=False so we can assign the parent key afterwards, then # save the object. obj = form.save(commit=False) - setattr(obj, self.fk.get_attname(), self.instance.pk) + setattr(obj, self.fk.get_attname(), getattr(self.instance, self.fk.rel.field_name)) if commit: obj.save() # form.save_m2m() can be called via the formset later on if commit=False @@ -749,6 +753,7 @@ class BaseInlineFormSet(BaseModelFormSet): # The foreign key field might not be on the form, so we poke at the # Model field to get the label, since we need that for error messages. form.fields[self.fk.name] = InlineForeignKeyField(self.instance, + to_field=self.fk.rel.field_name, label=getattr(form.fields.get(self.fk.name), 'label', capfirst(self.fk.verbose_name)) ) @@ -845,8 +850,12 @@ class InlineForeignKeyField(Field): def __init__(self, parent_instance, *args, **kwargs): self.parent_instance = parent_instance self.pk_field = kwargs.pop("pk_field", False) + self.to_field = kwargs.pop("to_field", None) if self.parent_instance is not None: - kwargs["initial"] = self.parent_instance.pk + if self.to_field: + kwargs["initial"] = getattr(self.parent_instance, self.to_field) + else: + kwargs["initial"] = self.parent_instance.pk kwargs["required"] = False kwargs["widget"] = InlineForeignKeyHiddenInput super(InlineForeignKeyField, self).__init__(*args, **kwargs) @@ -858,7 +867,11 @@ class InlineForeignKeyField(Field): # if there is no value act as we did before. return self.parent_instance # ensure the we compare the values as equal types. - if force_unicode(value) != force_unicode(self.parent_instance.pk): + if self.to_field: + orig = getattr(self.parent_instance, self.to_field) + else: + orig = self.parent_instance.pk + if force_unicode(value) != force_unicode(orig): raise ValidationError(self.error_messages['invalid_choice']) return self.parent_instance diff --git a/tests/regressiontests/model_formsets_regress/__init__.py b/tests/regressiontests/model_formsets_regress/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/model_formsets_regress/models.py b/tests/regressiontests/model_formsets_regress/models.py new file mode 100644 index 0000000000..51fc6a49ab --- /dev/null +++ b/tests/regressiontests/model_formsets_regress/models.py @@ -0,0 +1,9 @@ +from django.db import models + +class User(models.Model): + username = models.CharField(max_length=12, unique=True) + serial = models.IntegerField() + +class UserSite(models.Model): + user = models.ForeignKey(User, to_field="username") + data = models.IntegerField() diff --git a/tests/regressiontests/model_formsets_regress/tests.py b/tests/regressiontests/model_formsets_regress/tests.py new file mode 100644 index 0000000000..c20c1f4077 --- /dev/null +++ b/tests/regressiontests/model_formsets_regress/tests.py @@ -0,0 +1,24 @@ +from django.forms.models import inlineformset_factory +from django.test import TestCase + +from models import User, UserSite + +class InlineFormsetTests(TestCase): + def test_formset_over_to_field(self): + "A formset over a ForeignKey with a to_field can be saved. Regression for #10243" + FormSet = inlineformset_factory(User, UserSite) + user = User.objects.create(serial=1, username='apollo13') + user.save() + data = { + 'usersite_set-TOTAL_FORMS': u'1', + 'usersite_set-INITIAL_FORMS': u'0', + 'usersite_set-0-data': u'10', + 'usersite_set-0-user': u'apollo13' + } + form_set = FormSet(data, instance=user) + if form_set.is_valid(): + form_set.save() + usersite = UserSite.objects.all().values()[0] + self.assertEqual(usersite, {'data': 10, 'user_id': u'apollo13', 'id': 1}) + else: + self.fail('Errors found on form:%s' % form_set.errors) \ No newline at end of file