|
1 from django.contrib.contenttypes.generic import generic_inlineformset_factory |
|
2 from django.contrib.contenttypes.models import ContentType |
|
3 from django.test import TestCase |
|
4 |
|
5 from models import (TaggedItem, ValuableTaggedItem, Comparison, Animal, |
|
6 Vegetable, Mineral) |
|
7 |
|
8 |
|
9 class GenericRelationsTests(TestCase): |
|
10 def test_generic_relations(self): |
|
11 # Create the world in 7 lines of code... |
|
12 lion = Animal.objects.create(common_name="Lion", latin_name="Panthera leo") |
|
13 platypus = Animal.objects.create( |
|
14 common_name="Platypus", latin_name="Ornithorhynchus anatinus" |
|
15 ) |
|
16 eggplant = Vegetable.objects.create(name="Eggplant", is_yucky=True) |
|
17 bacon = Vegetable.objects.create(name="Bacon", is_yucky=False) |
|
18 quartz = Mineral.objects.create(name="Quartz", hardness=7) |
|
19 |
|
20 # Objects with declared GenericRelations can be tagged directly -- the |
|
21 # API mimics the many-to-many API. |
|
22 bacon.tags.create(tag="fatty") |
|
23 bacon.tags.create(tag="salty") |
|
24 lion.tags.create(tag="yellow") |
|
25 lion.tags.create(tag="hairy") |
|
26 platypus.tags.create(tag="fatty") |
|
27 self.assertQuerysetEqual(lion.tags.all(), [ |
|
28 "<TaggedItem: hairy>", |
|
29 "<TaggedItem: yellow>" |
|
30 ]) |
|
31 self.assertQuerysetEqual(bacon.tags.all(), [ |
|
32 "<TaggedItem: fatty>", |
|
33 "<TaggedItem: salty>" |
|
34 ]) |
|
35 |
|
36 # You can easily access the content object like a foreign key. |
|
37 t = TaggedItem.objects.get(tag="salty") |
|
38 self.assertEqual(t.content_object, bacon) |
|
39 |
|
40 # Recall that the Mineral class doesn't have an explicit GenericRelation |
|
41 # defined. That's OK, because you can create TaggedItems explicitly. |
|
42 tag1 = TaggedItem.objects.create(content_object=quartz, tag="shiny") |
|
43 tag2 = TaggedItem.objects.create(content_object=quartz, tag="clearish") |
|
44 |
|
45 # However, excluding GenericRelations means your lookups have to be a |
|
46 # bit more explicit. |
|
47 ctype = ContentType.objects.get_for_model(quartz) |
|
48 q = TaggedItem.objects.filter( |
|
49 content_type__pk=ctype.id, object_id=quartz.id |
|
50 ) |
|
51 self.assertQuerysetEqual(q, [ |
|
52 "<TaggedItem: clearish>", |
|
53 "<TaggedItem: shiny>" |
|
54 ]) |
|
55 |
|
56 # You can set a generic foreign key in the way you'd expect. |
|
57 tag1.content_object = platypus |
|
58 tag1.save() |
|
59 self.assertQuerysetEqual(platypus.tags.all(), [ |
|
60 "<TaggedItem: fatty>", |
|
61 "<TaggedItem: shiny>" |
|
62 ]) |
|
63 q = TaggedItem.objects.filter( |
|
64 content_type__pk=ctype.id, object_id=quartz.id |
|
65 ) |
|
66 self.assertQuerysetEqual(q, ["<TaggedItem: clearish>"]) |
|
67 |
|
68 # Queries across generic relations respect the content types. Even |
|
69 # though there are two TaggedItems with a tag of "fatty", this query |
|
70 # only pulls out the one with the content type related to Animals. |
|
71 self.assertQuerysetEqual(Animal.objects.order_by('common_name'), [ |
|
72 "<Animal: Lion>", |
|
73 "<Animal: Platypus>" |
|
74 ]) |
|
75 self.assertQuerysetEqual(Animal.objects.filter(tags__tag='fatty'), [ |
|
76 "<Animal: Platypus>" |
|
77 ]) |
|
78 self.assertQuerysetEqual(Animal.objects.exclude(tags__tag='fatty'), [ |
|
79 "<Animal: Lion>" |
|
80 ]) |
|
81 |
|
82 # If you delete an object with an explicit Generic relation, the related |
|
83 # objects are deleted when the source object is deleted. |
|
84 # Original list of tags: |
|
85 comp_func = lambda obj: ( |
|
86 obj.tag, obj.content_type.model_class(), obj.object_id |
|
87 ) |
|
88 |
|
89 self.assertQuerysetEqual(TaggedItem.objects.all(), [ |
|
90 (u'clearish', Mineral, quartz.pk), |
|
91 (u'fatty', Animal, platypus.pk), |
|
92 (u'fatty', Vegetable, bacon.pk), |
|
93 (u'hairy', Animal, lion.pk), |
|
94 (u'salty', Vegetable, bacon.pk), |
|
95 (u'shiny', Animal, platypus.pk), |
|
96 (u'yellow', Animal, lion.pk) |
|
97 ], |
|
98 comp_func |
|
99 ) |
|
100 lion.delete() |
|
101 self.assertQuerysetEqual(TaggedItem.objects.all(), [ |
|
102 (u'clearish', Mineral, quartz.pk), |
|
103 (u'fatty', Animal, platypus.pk), |
|
104 (u'fatty', Vegetable, bacon.pk), |
|
105 (u'salty', Vegetable, bacon.pk), |
|
106 (u'shiny', Animal, platypus.pk) |
|
107 ], |
|
108 comp_func |
|
109 ) |
|
110 |
|
111 # If Generic Relation is not explicitly defined, any related objects |
|
112 # remain after deletion of the source object. |
|
113 quartz_pk = quartz.pk |
|
114 quartz.delete() |
|
115 self.assertQuerysetEqual(TaggedItem.objects.all(), [ |
|
116 (u'clearish', Mineral, quartz_pk), |
|
117 (u'fatty', Animal, platypus.pk), |
|
118 (u'fatty', Vegetable, bacon.pk), |
|
119 (u'salty', Vegetable, bacon.pk), |
|
120 (u'shiny', Animal, platypus.pk) |
|
121 ], |
|
122 comp_func |
|
123 ) |
|
124 # If you delete a tag, the objects using the tag are unaffected |
|
125 # (other than losing a tag) |
|
126 tag = TaggedItem.objects.order_by("id")[0] |
|
127 tag.delete() |
|
128 self.assertQuerysetEqual(bacon.tags.all(), ["<TaggedItem: salty>"]) |
|
129 self.assertQuerysetEqual(TaggedItem.objects.all(), [ |
|
130 (u'clearish', Mineral, quartz_pk), |
|
131 (u'fatty', Animal, platypus.pk), |
|
132 (u'salty', Vegetable, bacon.pk), |
|
133 (u'shiny', Animal, platypus.pk) |
|
134 ], |
|
135 comp_func |
|
136 ) |
|
137 TaggedItem.objects.filter(tag='fatty').delete() |
|
138 ctype = ContentType.objects.get_for_model(lion) |
|
139 self.assertQuerysetEqual(Animal.objects.filter(tags__content_type=ctype), [ |
|
140 "<Animal: Platypus>" |
|
141 ]) |
|
142 |
|
143 |
|
144 def test_multiple_gfk(self): |
|
145 # Simple tests for multiple GenericForeignKeys |
|
146 # only uses one model, since the above tests should be sufficient. |
|
147 tiger = Animal.objects.create(common_name="tiger") |
|
148 cheetah = Animal.objects.create(common_name="cheetah") |
|
149 bear = Animal.objects.create(common_name="bear") |
|
150 |
|
151 # Create directly |
|
152 Comparison.objects.create( |
|
153 first_obj=cheetah, other_obj=tiger, comparative="faster" |
|
154 ) |
|
155 Comparison.objects.create( |
|
156 first_obj=tiger, other_obj=cheetah, comparative="cooler" |
|
157 ) |
|
158 |
|
159 # Create using GenericRelation |
|
160 tiger.comparisons.create(other_obj=bear, comparative="cooler") |
|
161 tiger.comparisons.create(other_obj=cheetah, comparative="stronger") |
|
162 self.assertQuerysetEqual(cheetah.comparisons.all(), [ |
|
163 "<Comparison: cheetah is faster than tiger>" |
|
164 ]) |
|
165 |
|
166 # Filtering works |
|
167 self.assertQuerysetEqual(tiger.comparisons.filter(comparative="cooler"), [ |
|
168 "<Comparison: tiger is cooler than cheetah>", |
|
169 "<Comparison: tiger is cooler than bear>" |
|
170 ]) |
|
171 |
|
172 # Filtering and deleting works |
|
173 subjective = ["cooler"] |
|
174 tiger.comparisons.filter(comparative__in=subjective).delete() |
|
175 self.assertQuerysetEqual(Comparison.objects.all(), [ |
|
176 "<Comparison: cheetah is faster than tiger>", |
|
177 "<Comparison: tiger is stronger than cheetah>" |
|
178 ]) |
|
179 |
|
180 # If we delete cheetah, Comparisons with cheetah as 'first_obj' will be |
|
181 # deleted since Animal has an explicit GenericRelation to Comparison |
|
182 # through first_obj. Comparisons with cheetah as 'other_obj' will not |
|
183 # be deleted. |
|
184 cheetah.delete() |
|
185 self.assertQuerysetEqual(Comparison.objects.all(), [ |
|
186 "<Comparison: tiger is stronger than None>" |
|
187 ]) |
|
188 |
|
189 def test_gfk_subclasses(self): |
|
190 # GenericForeignKey should work with subclasses (see #8309) |
|
191 quartz = Mineral.objects.create(name="Quartz", hardness=7) |
|
192 valuedtag = ValuableTaggedItem.objects.create( |
|
193 content_object=quartz, tag="shiny", value=10 |
|
194 ) |
|
195 self.assertEqual(valuedtag.content_object, quartz) |
|
196 |
|
197 def test_generic_inline_formsets(self): |
|
198 GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1) |
|
199 formset = GenericFormSet() |
|
200 self.assertEqual(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> |
|
201 <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>""") |
|
202 |
|
203 formset = GenericFormSet(instance=Animal()) |
|
204 self.assertEqual(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> |
|
205 <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 |
|
207 platypus = Animal.objects.create( |
|
208 common_name="Platypus", latin_name="Ornithorhynchus anatinus" |
|
209 ) |
|
210 platypus.tags.create(tag="shiny") |
|
211 GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1) |
|
212 formset = GenericFormSet(instance=platypus) |
|
213 tagged_item_id = TaggedItem.objects.get( |
|
214 tag='shiny', object_id=platypus.id |
|
215 ).id |
|
216 self.assertEqual(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> |
|
217 <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> |
|
218 <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) |
|
219 |
|
220 lion = Animal.objects.create(common_name="Lion", latin_name="Panthera leo") |
|
221 formset = GenericFormSet(instance=lion, prefix='x') |
|
222 self.assertEqual(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> |
|
223 <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>""") |