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
This commit is contained in:
Russell Keith-Magee 2010-04-27 15:05:38 +00:00
parent b031fa2376
commit dd07c23545
4 changed files with 55 additions and 9 deletions

View File

@ -824,13 +824,8 @@ class ForeignKey(RelatedField, Field):
def validate(self, value, model_instance): def validate(self, value, model_instance):
if self.rel.parent_link: if self.rel.parent_link:
return 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) super(ForeignKey, self).validate(value, model_instance)
if not value: if value is None:
return return
qs = self.rel.to._default_manager.filter(**{self.rel.field_name:value}) qs = self.rel.to._default_manager.filter(**{self.rel.field_name:value})

View File

@ -316,12 +316,23 @@ class BaseModelForm(BaseForm):
return self.cleaned_data return self.cleaned_data
def _post_clean(self): def _post_clean(self):
exclude = self._get_validation_exclusions()
opts = self._meta opts = self._meta
# Update the model instance with self.cleaned_data. # Update the model instance with self.cleaned_data.
self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude) 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. # Clean the model instance's fields.
try: try:
self.instance.clean_fields(exclude=exclude) 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] unique_check = [field for field in unique_check if field != self.fk.name]
return super(BaseInlineFormSet, self).get_unique_error_message(unique_check) return super(BaseInlineFormSet, self).get_unique_error_message(unique_check)
def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False): 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 Finds and returns the ForeignKey from model to parent if there is one

View File

@ -102,6 +102,29 @@ admin.site.register(Holder2, HolderAdmin, inlines=[InnerInline2])
# only Inline media # only Inline media
admin.site.register(Holder3, inlines=[InnerInline3]) 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': """ __test__ = {'API_TESTS': """
# Regression test for #9362 # Regression test for #9362

View File

@ -3,7 +3,7 @@ from django.test import TestCase
# local test models # local test models
from models import Holder, Inner, InnerInline from models import Holder, Inner, InnerInline
from models import Holder2, Inner2, Holder3, Inner3 from models import Holder2, Inner2, Holder3, Inner3
from models import Person, OutfitItem, Fashionista
class TestInline(TestCase): class TestInline(TestCase):
fixtures = ['admin-views-users.xml'] fixtures = ['admin-views-users.xml']
@ -48,6 +48,22 @@ class TestInline(TestCase):
# The '+' is dropped from the autogenerated form prefix (Author_books+) # The '+' is dropped from the autogenerated form prefix (Author_books+)
self.assertContains(response, 'id="id_Author_books-TOTAL_FORMS"') 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): class TestInlineMedia(TestCase):
fixtures = ['admin-views-users.xml'] fixtures = ['admin-views-users.xml']