Commit 04d9730b authored by Ramiro Morales's avatar Ramiro Morales
Browse files

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.
parent 2b916895
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -43,7 +43,7 @@ class GenericForeignKey(object):

    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.
        """
        if self.name in kwargs:
@@ -52,7 +52,7 @@ class GenericForeignKey(object):
            kwargs[self.fk_field] = value._get_pk_val()

    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)
        elif id:
            return ContentType.objects.db_manager(using).get_for_id(id)
+31 −0
Original line number Diff line number Diff line
@@ -91,3 +91,34 @@ class Company(models.Model):

    def __str__(self):
        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)
+39 −1
Original line number Diff line number Diff line
from django.db.models import Q
from django.db.utils import IntegrityError
from django.test import TestCase

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):
@@ -98,3 +100,39 @@ class GenericRelationTests(TestCase):
        self.assertEqual(len(places), 2)
        self.assertEqual(count_places(p1), 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()