summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell Keith-Magee <russell@keith-magee.com>2010-04-27 15:05:38 +0000
committerRussell Keith-Magee <russell@keith-magee.com>2010-04-27 15:05:38 +0000
commitdd07c235454baea0c74fa373cc07187942f3aed0 (patch)
treef0cc411a11bdca546d16b7c78921a9511e66410a
parentb031fa2376f489406b44f0dabed18b5d318c2e83 (diff)
Fixed #12749 -- Corrected a problem with validation of inline primary keys. Thanks to Chris.Wesseling@cwi.nl for the report, and nessita for the test case.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@13034 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/db/models/fields/related.py7
-rw-r--r--django/forms/models.py16
-rw-r--r--tests/regressiontests/admin_inlines/models.py23
-rw-r--r--tests/regressiontests/admin_inlines/tests.py18
4 files changed, 55 insertions, 9 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 54c064a56e..57c8bdefd0 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -824,13 +824,8 @@ class ForeignKey(RelatedField, Field):
def validate(self, value, model_instance):
if self.rel.parent_link:
return
- # Don't validate the field if a value wasn't supplied. This is
- # generally the case when saving new inlines in the admin.
- # See #12507.
- if value is None:
- return
super(ForeignKey, self).validate(value, model_instance)
- if not value:
+ if value is None:
return
qs = self.rel.to._default_manager.filter(**{self.rel.field_name:value})
diff --git a/django/forms/models.py b/django/forms/models.py
index 4ac3f950ba..a0ef2de67f 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -316,12 +316,23 @@ class BaseModelForm(BaseForm):
return self.cleaned_data
def _post_clean(self):
- exclude = self._get_validation_exclusions()
opts = self._meta
-
# Update the model instance with self.cleaned_data.
self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
+ exclude = self._get_validation_exclusions()
+
+ # Foreign Keys being used to represent inline relationships
+ # are excluded from basic field value validation. This is for two
+ # reasons: firstly, the value may not be supplied (#12507; the
+ # case of providing new values to the admin); secondly the
+ # object being referred to may not yet fully exist (#12749).
+ # However, these fields *must* be included in uniqueness checks,
+ # so this can't be part of _get_validation_exclusions().
+ for f_name, field in self.fields.items():
+ if isinstance(field, InlineForeignKeyField):
+ exclude.append(f_name)
+
# Clean the model instance's fields.
try:
self.instance.clean_fields(exclude=exclude)
@@ -762,6 +773,7 @@ class BaseInlineFormSet(BaseModelFormSet):
unique_check = [field for field in unique_check if field != self.fk.name]
return super(BaseInlineFormSet, self).get_unique_error_message(unique_check)
+
def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False):
"""
Finds and returns the ForeignKey from model to parent if there is one
diff --git a/tests/regressiontests/admin_inlines/models.py b/tests/regressiontests/admin_inlines/models.py
index bca582b9ab..5a12e0743c 100644
--- a/tests/regressiontests/admin_inlines/models.py
+++ b/tests/regressiontests/admin_inlines/models.py
@@ -102,6 +102,29 @@ admin.site.register(Holder2, HolderAdmin, inlines=[InnerInline2])
# only Inline media
admin.site.register(Holder3, inlines=[InnerInline3])
+# Models for #12749
+
+class Person(models.Model):
+ firstname = models.CharField(max_length=15)
+
+class OutfitItem(models.Model):
+ name = models.CharField(max_length=15)
+
+class Fashionista(models.Model):
+ person = models.OneToOneField(Person, primary_key=True)
+ weaknesses = models.ManyToManyField(OutfitItem, through='ShoppingWeakness', blank=True)
+
+class ShoppingWeakness(models.Model):
+ fashionista = models.ForeignKey(Fashionista)
+ item = models.ForeignKey(OutfitItem)
+
+class InlineWeakness(admin.TabularInline):
+ model = ShoppingWeakness
+ extra = 1
+
+admin.site.register(Fashionista, inlines=[InlineWeakness])
+
+
__test__ = {'API_TESTS': """
# Regression test for #9362
diff --git a/tests/regressiontests/admin_inlines/tests.py b/tests/regressiontests/admin_inlines/tests.py
index ffcbe1e4ba..c27873ceec 100644
--- a/tests/regressiontests/admin_inlines/tests.py
+++ b/tests/regressiontests/admin_inlines/tests.py
@@ -3,7 +3,7 @@ from django.test import TestCase
# local test models
from models import Holder, Inner, InnerInline
from models import Holder2, Inner2, Holder3, Inner3
-
+from models import Person, OutfitItem, Fashionista
class TestInline(TestCase):
fixtures = ['admin-views-users.xml']
@@ -48,6 +48,22 @@ class TestInline(TestCase):
# The '+' is dropped from the autogenerated form prefix (Author_books+)
self.assertContains(response, 'id="id_Author_books-TOTAL_FORMS"')
+ def test_inline_primary(self):
+ person = Person.objects.create(firstname='Imelda')
+ item = OutfitItem.objects.create(name='Shoes')
+ # Imelda likes shoes, but can't cary her own bags.
+ data = {
+ 'shoppingweakness_set-TOTAL_FORMS': 1,
+ 'shoppingweakness_set-INITIAL_FORMS': 0,
+ 'shoppingweakness_set-MAX_NUM_FORMS': 0,
+ '_save': u'Save',
+ 'person': person.id,
+ 'max_weight': 0,
+ 'shoppingweakness_set-0-item': item.id,
+ }
+ response = self.client.post('/test_admin/admin/admin_inlines/fashionista/add/', data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(len(Fashionista.objects.filter(person__firstname='Imelda')), 1)
class TestInlineMedia(TestCase):
fixtures = ['admin-views-users.xml']