1 from __future__
import absolute_import
3 from django
import forms
4 from django
.contrib
.contenttypes
.generic
import generic_inlineformset_factory
5 from django
.contrib
.contenttypes
.models
import ContentType
6 from django
.test
import TestCase
8 from .models
import (TaggedItem
, ValuableTaggedItem
, Comparison
, Animal
,
9 Vegetable
, Mineral
, Gecko
)
12 class GenericRelationsTests(TestCase
):
13 def test_generic_relations(self
):
14 # Create the world in 7 lines of code...
15 lion
= Animal
.objects
.create(common_name
="Lion", latin_name
="Panthera leo")
16 platypus
= Animal
.objects
.create(
17 common_name
="Platypus", latin_name
="Ornithorhynchus anatinus"
19 eggplant
= Vegetable
.objects
.create(name
="Eggplant", is_yucky
=True)
20 bacon
= Vegetable
.objects
.create(name
="Bacon", is_yucky
=False)
21 quartz
= Mineral
.objects
.create(name
="Quartz", hardness
=7)
23 # Objects with declared GenericRelations can be tagged directly -- the
24 # API mimics the many-to-many API.
25 bacon
.tags
.create(tag
="fatty")
26 bacon
.tags
.create(tag
="salty")
27 lion
.tags
.create(tag
="yellow")
28 lion
.tags
.create(tag
="hairy")
29 platypus
.tags
.create(tag
="fatty")
30 self
.assertQuerysetEqual(lion
.tags
.all(), [
31 "<TaggedItem: hairy>",
32 "<TaggedItem: yellow>"
34 self
.assertQuerysetEqual(bacon
.tags
.all(), [
35 "<TaggedItem: fatty>",
39 # You can easily access the content object like a foreign key.
40 t
= TaggedItem
.objects
.get(tag
="salty")
41 self
.assertEqual(t
.content_object
, bacon
)
43 # Recall that the Mineral class doesn't have an explicit GenericRelation
44 # defined. That's OK, because you can create TaggedItems explicitly.
45 tag1
= TaggedItem
.objects
.create(content_object
=quartz
, tag
="shiny")
46 tag2
= TaggedItem
.objects
.create(content_object
=quartz
, tag
="clearish")
48 # However, excluding GenericRelations means your lookups have to be a
50 ctype
= ContentType
.objects
.get_for_model(quartz
)
51 q
= TaggedItem
.objects
.filter(
52 content_type__pk
=ctype
.id, object_id
=quartz
.id
54 self
.assertQuerysetEqual(q
, [
55 "<TaggedItem: clearish>",
59 # You can set a generic foreign key in the way you'd expect.
60 tag1
.content_object
= platypus
62 self
.assertQuerysetEqual(platypus
.tags
.all(), [
63 "<TaggedItem: fatty>",
66 q
= TaggedItem
.objects
.filter(
67 content_type__pk
=ctype
.id, object_id
=quartz
.id
69 self
.assertQuerysetEqual(q
, ["<TaggedItem: clearish>"])
71 # Queries across generic relations respect the content types. Even
72 # though there are two TaggedItems with a tag of "fatty", this query
73 # only pulls out the one with the content type related to Animals.
74 self
.assertQuerysetEqual(Animal
.objects
.order_by('common_name'), [
78 self
.assertQuerysetEqual(Animal
.objects
.filter(tags__tag
='fatty'), [
81 self
.assertQuerysetEqual(Animal
.objects
.exclude(tags__tag
='fatty'), [
85 # If you delete an object with an explicit Generic relation, the related
86 # objects are deleted when the source object is deleted.
87 # Original list of tags:
88 comp_func
= lambda obj
: (
89 obj
.tag
, obj
.content_type
.model_class(), obj
.object_id
92 self
.assertQuerysetEqual(TaggedItem
.objects
.all(), [
93 (u
'clearish', Mineral
, quartz
.pk
),
94 (u
'fatty', Animal
, platypus
.pk
),
95 (u
'fatty', Vegetable
, bacon
.pk
),
96 (u
'hairy', Animal
, lion
.pk
),
97 (u
'salty', Vegetable
, bacon
.pk
),
98 (u
'shiny', Animal
, platypus
.pk
),
99 (u
'yellow', Animal
, lion
.pk
)
104 self
.assertQuerysetEqual(TaggedItem
.objects
.all(), [
105 (u
'clearish', Mineral
, quartz
.pk
),
106 (u
'fatty', Animal
, platypus
.pk
),
107 (u
'fatty', Vegetable
, bacon
.pk
),
108 (u
'salty', Vegetable
, bacon
.pk
),
109 (u
'shiny', Animal
, platypus
.pk
)
114 # If Generic Relation is not explicitly defined, any related objects
115 # remain after deletion of the source object.
116 quartz_pk
= quartz
.pk
118 self
.assertQuerysetEqual(TaggedItem
.objects
.all(), [
119 (u
'clearish', Mineral
, quartz_pk
),
120 (u
'fatty', Animal
, platypus
.pk
),
121 (u
'fatty', Vegetable
, bacon
.pk
),
122 (u
'salty', Vegetable
, bacon
.pk
),
123 (u
'shiny', Animal
, platypus
.pk
)
127 # If you delete a tag, the objects using the tag are unaffected
128 # (other than losing a tag)
129 tag
= TaggedItem
.objects
.order_by("id")[0]
131 self
.assertQuerysetEqual(bacon
.tags
.all(), ["<TaggedItem: salty>"])
132 self
.assertQuerysetEqual(TaggedItem
.objects
.all(), [
133 (u
'clearish', Mineral
, quartz_pk
),
134 (u
'fatty', Animal
, platypus
.pk
),
135 (u
'salty', Vegetable
, bacon
.pk
),
136 (u
'shiny', Animal
, platypus
.pk
)
140 TaggedItem
.objects
.filter(tag
='fatty').delete()
141 ctype
= ContentType
.objects
.get_for_model(lion
)
142 self
.assertQuerysetEqual(Animal
.objects
.filter(tags__content_type
=ctype
), [
147 def test_multiple_gfk(self
):
148 # Simple tests for multiple GenericForeignKeys
149 # only uses one model, since the above tests should be sufficient.
150 tiger
= Animal
.objects
.create(common_name
="tiger")
151 cheetah
= Animal
.objects
.create(common_name
="cheetah")
152 bear
= Animal
.objects
.create(common_name
="bear")
155 Comparison
.objects
.create(
156 first_obj
=cheetah
, other_obj
=tiger
, comparative
="faster"
158 Comparison
.objects
.create(
159 first_obj
=tiger
, other_obj
=cheetah
, comparative
="cooler"
162 # Create using GenericRelation
163 tiger
.comparisons
.create(other_obj
=bear
, comparative
="cooler")
164 tiger
.comparisons
.create(other_obj
=cheetah
, comparative
="stronger")
165 self
.assertQuerysetEqual(cheetah
.comparisons
.all(), [
166 "<Comparison: cheetah is faster than tiger>"
170 self
.assertQuerysetEqual(tiger
.comparisons
.filter(comparative
="cooler"), [
171 "<Comparison: tiger is cooler than cheetah>",
172 "<Comparison: tiger is cooler than bear>"
175 # Filtering and deleting works
176 subjective
= ["cooler"]
177 tiger
.comparisons
.filter(comparative__in
=subjective
).delete()
178 self
.assertQuerysetEqual(Comparison
.objects
.all(), [
179 "<Comparison: cheetah is faster than tiger>",
180 "<Comparison: tiger is stronger than cheetah>"
183 # If we delete cheetah, Comparisons with cheetah as 'first_obj' will be
184 # deleted since Animal has an explicit GenericRelation to Comparison
185 # through first_obj. Comparisons with cheetah as 'other_obj' will not
188 self
.assertQuerysetEqual(Comparison
.objects
.all(), [
189 "<Comparison: tiger is stronger than None>"
192 def test_gfk_subclasses(self
):
193 # GenericForeignKey should work with subclasses (see #8309)
194 quartz
= Mineral
.objects
.create(name
="Quartz", hardness
=7)
195 valuedtag
= ValuableTaggedItem
.objects
.create(
196 content_object
=quartz
, tag
="shiny", value
=10
198 self
.assertEqual(valuedtag
.content_object
, quartz
)
200 def test_generic_inline_formsets(self
):
201 GenericFormSet
= generic_inlineformset_factory(TaggedItem
, extra
=1)
202 formset
= GenericFormSet()
203 self
.assertHTMLEqual(u
''.join(form
.as_p() for form
in formset
.forms
), u
"""<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" maxlength="50" /></p>
204 <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p>""")
206 formset
= GenericFormSet(instance
=Animal())
207 self
.assertHTMLEqual(u
''.join(form
.as_p() for form
in formset
.forms
), u
"""<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" maxlength="50" /></p>
208 <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p>""")
210 platypus
= Animal
.objects
.create(
211 common_name
="Platypus", latin_name
="Ornithorhynchus anatinus"
213 platypus
.tags
.create(tag
="shiny")
214 GenericFormSet
= generic_inlineformset_factory(TaggedItem
, extra
=1)
215 formset
= GenericFormSet(instance
=platypus
)
216 tagged_item_id
= TaggedItem
.objects
.get(
217 tag
='shiny', object_id
=platypus
.id
219 self
.assertHTMLEqual(u
''.join(form
.as_p() for form
in formset
.forms
), u
"""<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" value="shiny" maxlength="50" /></p>
220 <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" value="%s" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p><p><label for="id_generic_relations-taggeditem-content_type-object_id-1-tag">Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-1-tag" type="text" name="generic_relations-taggeditem-content_type-object_id-1-tag" maxlength="50" /></p>
221 <p><label for="id_generic_relations-taggeditem-content_type-object_id-1-DELETE">Delete:</label> <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-1-DELETE" id="id_generic_relations-taggeditem-content_type-object_id-1-DELETE" /><input type="hidden" name="generic_relations-taggeditem-content_type-object_id-1-id" id="id_generic_relations-taggeditem-content_type-object_id-1-id" /></p>""" % tagged_item_id
)
223 lion
= Animal
.objects
.create(common_name
="Lion", latin_name
="Panthera leo")
224 formset
= GenericFormSet(instance
=lion
, prefix
='x')
225 self
.assertHTMLEqual(u
''.join(form
.as_p() for form
in formset
.forms
), u
"""<p><label for="id_x-0-tag">Tag:</label> <input id="id_x-0-tag" type="text" name="x-0-tag" maxlength="50" /></p>
226 <p><label for="id_x-0-DELETE">Delete:</label> <input type="checkbox" name="x-0-DELETE" id="id_x-0-DELETE" /><input type="hidden" name="x-0-id" id="id_x-0-id" /></p>""")
228 def test_gfk_manager(self
):
229 # GenericForeignKey should not use the default manager (which may filter objects) #16048
230 tailless
= Gecko
.objects
.create(has_tail
=False)
231 tag
= TaggedItem
.objects
.create(content_object
=tailless
, tag
="lizard")
232 self
.assertEqual(tag
.content_object
, tailless
)
234 class CustomWidget(forms
.CharField
):
237 class TaggedItemForm(forms
.ModelForm
):
240 widgets
= {'tag': CustomWidget
}
242 class GenericInlineFormsetTest(TestCase
):
244 Regression for #14572: Using base forms with widgets
245 defined in Meta should not raise errors.
248 def test_generic_inlineformset_factory(self
):
249 Formset
= generic_inlineformset_factory(TaggedItem
, TaggedItemForm
)
250 form
= Formset().forms
[0]
251 self
.assertTrue(isinstance(form
['tag'].field
.widget
, CustomWidget
))