Fixed #13085 -- Don't fail on creation of model with GFK to a model with __len__() returning zero.
Also, according to the comments on the ticket and its duplicates, added tests execising saving an instance of a model with a GFK to: * An unsaved object -- This actually doesn't generate the same failure but another ORM-level exception. The test verifies it's the case. * An instance of a model with a __nonzero__() method thant returns False for it. This doesn't fail because that code path isn't executed. * An instance of a model with a CharField PK and an empty value for it. This doesn't fail.
This commit is contained in:
parent
2b916895a1
commit
04d9730b12
|
@ -43,7 +43,7 @@ class GenericForeignKey(object):
|
||||||
|
|
||||||
def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs):
|
def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs):
|
||||||
"""
|
"""
|
||||||
Handles initializing an object with the generic FK instaed of
|
Handles initializing an object with the generic FK instead of
|
||||||
content-type/object-id fields.
|
content-type/object-id fields.
|
||||||
"""
|
"""
|
||||||
if self.name in kwargs:
|
if self.name in kwargs:
|
||||||
|
@ -52,7 +52,7 @@ class GenericForeignKey(object):
|
||||||
kwargs[self.fk_field] = value._get_pk_val()
|
kwargs[self.fk_field] = value._get_pk_val()
|
||||||
|
|
||||||
def get_content_type(self, obj=None, id=None, using=None):
|
def get_content_type(self, obj=None, id=None, using=None):
|
||||||
if obj:
|
if obj is not None:
|
||||||
return ContentType.objects.db_manager(obj._state.db).get_for_model(obj)
|
return ContentType.objects.db_manager(obj._state.db).get_for_model(obj)
|
||||||
elif id:
|
elif id:
|
||||||
return ContentType.objects.db_manager(using).get_for_id(id)
|
return ContentType.objects.db_manager(using).get_for_id(id)
|
||||||
|
|
|
@ -91,3 +91,34 @@ class Company(models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Company: %s" % self.name
|
return "Company: %s" % self.name
|
||||||
|
|
||||||
|
# For testing #13085 fix, we also use Note model defined above
|
||||||
|
class Developer(models.Model):
|
||||||
|
name = models.CharField(max_length=15)
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class Team(models.Model):
|
||||||
|
name = models.CharField(max_length=15)
|
||||||
|
members = models.ManyToManyField(Developer)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s team" % self.name
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.members.count()
|
||||||
|
|
||||||
|
class Guild(models.Model):
|
||||||
|
name = models.CharField(max_length=15)
|
||||||
|
members = models.ManyToManyField(Developer)
|
||||||
|
|
||||||
|
def __nonzero__(self):
|
||||||
|
return self.members.count()
|
||||||
|
|
||||||
|
class Tag(models.Model):
|
||||||
|
content_type = models.ForeignKey(ContentType, related_name='g_r_r_tags')
|
||||||
|
object_id = models.CharField(max_length=15)
|
||||||
|
content_object = generic.GenericForeignKey()
|
||||||
|
label = models.CharField(max_length=15)
|
||||||
|
|
||||||
|
class Board(models.Model):
|
||||||
|
name = models.CharField(primary_key=True, max_length=15)
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.db.utils import IntegrityError
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from .models import (Address, Place, Restaurant, Link, CharLink, TextLink,
|
from .models import (Address, Place, Restaurant, Link, CharLink, TextLink,
|
||||||
Person, Contact, Note, Organization, OddRelation1, OddRelation2, Company)
|
Person, Contact, Note, Organization, OddRelation1, OddRelation2, Company,
|
||||||
|
Developer, Team, Guild, Tag, Board)
|
||||||
|
|
||||||
|
|
||||||
class GenericRelationTests(TestCase):
|
class GenericRelationTests(TestCase):
|
||||||
|
@ -98,3 +100,39 @@ class GenericRelationTests(TestCase):
|
||||||
self.assertEqual(len(places), 2)
|
self.assertEqual(len(places), 2)
|
||||||
self.assertEqual(count_places(p1), 1)
|
self.assertEqual(count_places(p1), 1)
|
||||||
self.assertEqual(count_places(p2), 1)
|
self.assertEqual(count_places(p2), 1)
|
||||||
|
|
||||||
|
def test_target_model_is_unsaved(self):
|
||||||
|
"""Test related to #13085"""
|
||||||
|
# Fails with another, ORM-level error
|
||||||
|
dev1 = Developer(name='Joe')
|
||||||
|
note = Note(note='Deserves promotion', content_object=dev1)
|
||||||
|
self.assertRaisesMessage(IntegrityError,
|
||||||
|
"generic_relations_regress_note.object_id may not be NULL",
|
||||||
|
note.save)
|
||||||
|
|
||||||
|
def test_target_model_len_zero(self):
|
||||||
|
"""Test for #13085 -- __len__() returns 0"""
|
||||||
|
team1 = Team.objects.create(name='Backend devs')
|
||||||
|
try:
|
||||||
|
note = Note(note='Deserve a bonus', content_object=team1)
|
||||||
|
except Exception as e:
|
||||||
|
if issubclass(type(e), Exception) and str(e) == 'Impossible arguments to GFK.get_content_type!':
|
||||||
|
self.fail("Saving model with GenericForeignKey to model instance whose __len__ method returns 0 shouldn't fail.")
|
||||||
|
raise e
|
||||||
|
note.save()
|
||||||
|
|
||||||
|
def test_target_model_nonzero_false(self):
|
||||||
|
"""Test related to #13085"""
|
||||||
|
# __nonzero__() returns False -- This actually doesn't currently fail.
|
||||||
|
# This test validates that
|
||||||
|
g1 = Guild.objects.create(name='First guild')
|
||||||
|
note = Note(note='Note for guild', content_object=g1)
|
||||||
|
note.save()
|
||||||
|
|
||||||
|
def test_gfk_to_model_with_empty_pk(self):
|
||||||
|
"""Test related to #13085"""
|
||||||
|
# Saving model with GenericForeignKey to model instance with an
|
||||||
|
# empty CharField PK
|
||||||
|
b1 = Board.objects.create(name='')
|
||||||
|
tag = Tag(label='VP', content_object=b1)
|
||||||
|
tag.save()
|
||||||
|
|
Loading…
Reference in New Issue