Split GenericRelationsTests.test_generic_relations into several tests; refs #18586.

This commit is contained in:
José L. Patiño 2014-05-20 09:38:12 +02:00 committed by Tim Graham
parent 2a5c750ad1
commit 7b064e5390
1 changed files with 137 additions and 101 deletions

View File

@ -4,6 +4,7 @@ from django import forms
from django.contrib.contenttypes.forms import generic_inlineformset_factory from django.contrib.contenttypes.forms import generic_inlineformset_factory
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.db.models import Q
from django.test import TestCase from django.test import TestCase
from django.utils import six from django.utils import six
@ -14,154 +15,189 @@ from .models import (TaggedItem, ValuableTaggedItem, Comparison, Animal,
class GenericRelationsTests(TestCase): class GenericRelationsTests(TestCase):
def test_generic_relations(self): def setUp(self):
# Create the world in 7 lines of code... self.lion = Animal.objects.create(
lion = Animal.objects.create(common_name="Lion", latin_name="Panthera leo") common_name="Lion", latin_name="Panthera leo")
platypus = Animal.objects.create( self.platypus = Animal.objects.create(
common_name="Platypus", latin_name="Ornithorhynchus anatinus" common_name="Platypus", latin_name="Ornithorhynchus anatinus")
)
Vegetable.objects.create(name="Eggplant", is_yucky=True) Vegetable.objects.create(name="Eggplant", is_yucky=True)
bacon = Vegetable.objects.create(name="Bacon", is_yucky=False) self.bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
quartz = Mineral.objects.create(name="Quartz", hardness=7) self.quartz = Mineral.objects.create(name="Quartz", hardness=7)
# Objects with declared GenericRelations can be tagged directly -- the # Tagging stuff.
# API mimics the many-to-many API. self.bacon.tags.create(tag="fatty")
bacon.tags.create(tag="fatty") self.bacon.tags.create(tag="salty")
bacon.tags.create(tag="salty") self.lion.tags.create(tag="yellow")
lion.tags.create(tag="yellow") self.lion.tags.create(tag="hairy")
lion.tags.create(tag="hairy")
platypus.tags.create(tag="fatty") # Original list of tags:
self.assertQuerysetEqual(lion.tags.all(), [ self.comp_func = lambda obj: (
obj.tag, obj.content_type.model_class(), obj.object_id
)
def test_generic_relations_m2m_mimic(self):
"""
Objects with declared GenericRelations can be tagged directly -- the
API mimics the many-to-many API.
"""
self.assertQuerysetEqual(self.lion.tags.all(), [
"<TaggedItem: hairy>", "<TaggedItem: hairy>",
"<TaggedItem: yellow>" "<TaggedItem: yellow>"
]) ])
self.assertQuerysetEqual(bacon.tags.all(), [ self.assertQuerysetEqual(self.bacon.tags.all(), [
"<TaggedItem: fatty>", "<TaggedItem: fatty>",
"<TaggedItem: salty>" "<TaggedItem: salty>"
]) ])
# You can easily access the content object like a foreign key. def test_access_content_object(self):
t = TaggedItem.objects.get(tag="salty") """
self.assertEqual(t.content_object, bacon) Test accessing the content object like a foreign key.
qs = TaggedItem.objects.filter(animal__isnull=False).order_by('animal__common_name', 'tag') """
tagged_item = TaggedItem.objects.get(tag="salty")
self.assertEqual(tagged_item.content_object, self.bacon)
def test_query_content_object(self):
qs = TaggedItem.objects.filter(
animal__isnull=False).order_by('animal__common_name', 'tag')
self.assertQuerysetEqual( self.assertQuerysetEqual(
qs, ["<TaggedItem: hairy>", "<TaggedItem: yellow>", "<TaggedItem: fatty>"] qs, ["<TaggedItem: hairy>", "<TaggedItem: yellow>"]
) )
mpk = ManualPK.objects.create(id=1) mpk = ManualPK.objects.create(id=1)
mpk.tags.create(tag='mpk') mpk.tags.create(tag='mpk')
from django.db.models import Q qs = TaggedItem.objects.filter(
qs = TaggedItem.objects.filter(Q(animal__isnull=False) | Q(manualpk__id=1)).order_by('tag') Q(animal__isnull=False) | Q(manualpk__id=1)).order_by('tag')
self.assertQuerysetEqual( self.assertQuerysetEqual(
qs, ["fatty", "hairy", "mpk", "yellow"], lambda x: x.tag) qs, ["hairy", "mpk", "yellow"], lambda x: x.tag)
mpk.delete()
def test_exclude_generic_relations(self):
"""
Test lookups over an object without GenericRelations.
"""
# Recall that the Mineral class doesn't have an explicit GenericRelation # Recall that the Mineral class doesn't have an explicit GenericRelation
# defined. That's OK, because you can create TaggedItems explicitly. # defined. That's OK, because you can create TaggedItems explicitly.
tag1 = TaggedItem.objects.create(content_object=quartz, tag="shiny")
TaggedItem.objects.create(content_object=quartz, tag="clearish")
# However, excluding GenericRelations means your lookups have to be a # However, excluding GenericRelations means your lookups have to be a
# bit more explicit. # bit more explicit.
ctype = ContentType.objects.get_for_model(quartz) TaggedItem.objects.create(content_object=self.quartz, tag="shiny")
TaggedItem.objects.create(content_object=self.quartz, tag="clearish")
ctype = ContentType.objects.get_for_model(self.quartz)
q = TaggedItem.objects.filter( q = TaggedItem.objects.filter(
content_type__pk=ctype.id, object_id=quartz.id content_type__pk=ctype.id, object_id=self.quartz.id
) )
self.assertQuerysetEqual(q, [ self.assertQuerysetEqual(q, [
"<TaggedItem: clearish>", "<TaggedItem: clearish>",
"<TaggedItem: shiny>" "<TaggedItem: shiny>"
]) ])
# You can set a generic foreign key in the way you'd expect. def test_access_via_content_type(self):
tag1.content_object = platypus """
tag1.save() Test lookups through content type.
self.assertQuerysetEqual(platypus.tags.all(), [ """
"<TaggedItem: fatty>", self.lion.delete()
"<TaggedItem: shiny>" self.platypus.tags.create(tag="fatty")
])
q = TaggedItem.objects.filter(
content_type__pk=ctype.id, object_id=quartz.id
)
self.assertQuerysetEqual(q, ["<TaggedItem: clearish>"])
# Queries across generic relations respect the content types. Even ctype = ContentType.objects.get_for_model(self.platypus)
# though there are two TaggedItems with a tag of "fatty", this query
# only pulls out the one with the content type related to Animals. self.assertQuerysetEqual(
Animal.objects.filter(tags__content_type=ctype),
["<Animal: Platypus>"])
def test_set_foreign_key(self):
"""
You can set a generic foreign key in the way you'd expect.
"""
tag1 = TaggedItem.objects.create(content_object=self.quartz, tag="shiny")
tag1.content_object = self.platypus
tag1.save()
self.assertQuerysetEqual(
self.platypus.tags.all(),
["<TaggedItem: shiny>"])
def test_queries_across_generic_relations(self):
"""
Queries across generic relations respect the content types. Even though
there are two TaggedItems with a tag of "fatty", this query only pulls
out the one with the content type related to Animals.
"""
self.assertQuerysetEqual(Animal.objects.order_by('common_name'), [ self.assertQuerysetEqual(Animal.objects.order_by('common_name'), [
"<Animal: Lion>", "<Animal: Lion>",
"<Animal: Platypus>" "<Animal: Platypus>"
]) ])
# Create another fatty tagged instance with different PK to ensure
# there is a content type restriction in the generated queries below. def test_queries_content_type_restriction(self):
mpk = ManualPK.objects.create(id=lion.pk) """
Create another fatty tagged instance with different PK to ensure there
is a content type restriction in the generated queries below.
"""
mpk = ManualPK.objects.create(id=self.lion.pk)
mpk.tags.create(tag="fatty") mpk.tags.create(tag="fatty")
self.assertQuerysetEqual(Animal.objects.filter(tags__tag='fatty'), [ self.platypus.tags.create(tag="fatty")
"<Animal: Platypus>"
])
self.assertQuerysetEqual(Animal.objects.exclude(tags__tag='fatty'), [
"<Animal: Lion>"
])
mpk.delete()
# If you delete an object with an explicit Generic relation, the related self.assertQuerysetEqual(
# objects are deleted when the source object is deleted. Animal.objects.filter(tags__tag='fatty'), ["<Animal: Platypus>"])
# Original list of tags: self.assertQuerysetEqual(
comp_func = lambda obj: ( Animal.objects.exclude(tags__tag='fatty'), ["<Animal: Lion>"])
obj.tag, obj.content_type.model_class(), obj.object_id
def test_object_deletion_with_generic_relation(self):
"""
If you delete an object with an explicit Generic relation, the related
objects are deleted when the source object is deleted.
"""
self.assertQuerysetEqual(TaggedItem.objects.all(), [
('fatty', Vegetable, self.bacon.pk),
('hairy', Animal, self.lion.pk),
('salty', Vegetable, self.bacon.pk),
('yellow', Animal, self.lion.pk)
],
self.comp_func
) )
self.lion.delete()
self.assertQuerysetEqual(TaggedItem.objects.all(), [ self.assertQuerysetEqual(TaggedItem.objects.all(), [
('clearish', Mineral, quartz.pk), ('fatty', Vegetable, self.bacon.pk),
('fatty', Animal, platypus.pk), ('salty', Vegetable, self.bacon.pk),
('fatty', Vegetable, bacon.pk),
('hairy', Animal, lion.pk),
('salty', Vegetable, bacon.pk),
('shiny', Animal, platypus.pk),
('yellow', Animal, lion.pk)
], ],
comp_func self.comp_func
)
lion.delete()
self.assertQuerysetEqual(TaggedItem.objects.all(), [
('clearish', Mineral, quartz.pk),
('fatty', Animal, platypus.pk),
('fatty', Vegetable, bacon.pk),
('salty', Vegetable, bacon.pk),
('shiny', Animal, platypus.pk)
],
comp_func
) )
# If Generic Relation is not explicitly defined, any related objects def test_object_deletion_without_generic_relation(self):
# remain after deletion of the source object. """
quartz_pk = quartz.pk If Generic Relation is not explicitly defined, any related objects
quartz.delete() remain after deletion of the source object.
"""
TaggedItem.objects.create(content_object=self.quartz, tag="clearish")
quartz_pk = self.quartz.pk
self.quartz.delete()
self.assertQuerysetEqual(TaggedItem.objects.all(), [ self.assertQuerysetEqual(TaggedItem.objects.all(), [
('clearish', Mineral, quartz_pk), ('clearish', Mineral, quartz_pk),
('fatty', Animal, platypus.pk), ('fatty', Vegetable, self.bacon.pk),
('fatty', Vegetable, bacon.pk), ('hairy', Animal, self.lion.pk),
('salty', Vegetable, bacon.pk), ('salty', Vegetable, self.bacon.pk),
('shiny', Animal, platypus.pk) ('yellow', Animal, self.lion.pk),
], ],
comp_func self.comp_func
) )
# If you delete a tag, the objects using the tag are unaffected
# (other than losing a tag) def test_tag_deletion_related_objects_unaffected(self):
tag = TaggedItem.objects.order_by("id")[0] """
If you delete a tag, the objects using the tag are unaffected (other
than losing a tag).
"""
ctype = ContentType.objects.get_for_model(self.lion)
tag = TaggedItem.objects.get(
content_type__pk=ctype.id, object_id=self.lion.id, tag="hairy")
tag.delete() tag.delete()
self.assertQuerysetEqual(bacon.tags.all(), ["<TaggedItem: salty>"])
self.assertQuerysetEqual(self.lion.tags.all(), ["<TaggedItem: yellow>"])
self.assertQuerysetEqual(TaggedItem.objects.all(), [ self.assertQuerysetEqual(TaggedItem.objects.all(), [
('clearish', Mineral, quartz_pk), ('fatty', Vegetable, self.bacon.pk),
('fatty', Animal, platypus.pk), ('salty', Vegetable, self.bacon.pk),
('salty', Vegetable, bacon.pk), ('yellow', Animal, self.lion.pk)
('shiny', Animal, platypus.pk)
], ],
comp_func self.comp_func
) )
TaggedItem.objects.filter(tag='fatty').delete()
ctype = ContentType.objects.get_for_model(lion)
self.assertQuerysetEqual(Animal.objects.filter(tags__content_type=ctype), [
"<Animal: Platypus>"
])
def test_assign_with_queryset(self): def test_assign_with_queryset(self):
# Ensure that querysets used in reverse GFK assignments are pre-evaluated # Ensure that querysets used in reverse GFK assignments are pre-evaluated