Django

Code

root/django/trunk/tests/modeltests/generic_relations/models.py

Revision 8644, 10.4 kB (checked in by mtredinnick, 2 days ago)

Improvements to [8608] to fix an infinite loop (for exclude(generic_relation)).
Also comes with approximately 67% less stupidity in the table joins for
filtering on generic relations.

Fixed #5937, hopefully for good, this time.

  • Property svn:eol-style set to native
Line 
1 """
2 34. Generic relations
3
4 Generic relations let an object have a foreign key to any object through a
5 content-type/object-id field. A ``GenericForeignKey`` field can point to any
6 object, be it animal, vegetable, or mineral.
7
8 The canonical example is tags (although this example implementation is *far*
9 from complete).
10 """
11
12 from django.db import models
13 from django.contrib.contenttypes.models import ContentType
14 from django.contrib.contenttypes import generic
15
16 class TaggedItem(models.Model):
17     """A tag on an item."""
18     tag = models.SlugField()
19     content_type = models.ForeignKey(ContentType)
20     object_id = models.PositiveIntegerField()
21
22     content_object = generic.GenericForeignKey()
23
24     class Meta:
25         ordering = ["tag"]
26
27     def __unicode__(self):
28         return self.tag
29
30 class Comparison(models.Model):
31     """
32     A model that tests having multiple GenericForeignKeys
33     """
34     comparative = models.CharField(max_length=50)
35
36     content_type1 = models.ForeignKey(ContentType, related_name="comparative1_set")
37     object_id1 = models.PositiveIntegerField()
38
39     content_type2 = models.ForeignKey(ContentType,  related_name="comparative2_set")
40     object_id2 = models.PositiveIntegerField()
41
42     first_obj = generic.GenericForeignKey(ct_field="content_type1", fk_field="object_id1")
43     other_obj = generic.GenericForeignKey(ct_field="content_type2", fk_field="object_id2")
44
45     def __unicode__(self):
46         return u"%s is %s than %s" % (self.first_obj, self.comparative, self.other_obj)
47
48 class Animal(models.Model):
49     common_name = models.CharField(max_length=150)
50     latin_name = models.CharField(max_length=150)
51
52     tags = generic.GenericRelation(TaggedItem)
53     comparisons = generic.GenericRelation(Comparison,
54                                           object_id_field="object_id1",
55                                           content_type_field="content_type1")
56
57     def __unicode__(self):
58         return self.common_name
59
60 class Vegetable(models.Model):
61     name = models.CharField(max_length=150)
62     is_yucky = models.BooleanField(default=True)
63
64     tags = generic.GenericRelation(TaggedItem)
65
66     def __unicode__(self):
67         return self.name
68
69 class Mineral(models.Model):
70     name = models.CharField(max_length=150)
71     hardness = models.PositiveSmallIntegerField()
72
73     # note the lack of an explicit GenericRelation here...
74
75     def __unicode__(self):
76         return self.name
77
78 __test__ = {'API_TESTS':"""
79 # Create the world in 7 lines of code...
80 >>> lion = Animal(common_name="Lion", latin_name="Panthera leo")
81 >>> platypus = Animal(common_name="Platypus", latin_name="Ornithorhynchus anatinus")
82 >>> eggplant = Vegetable(name="Eggplant", is_yucky=True)
83 >>> bacon = Vegetable(name="Bacon", is_yucky=False)
84 >>> quartz = Mineral(name="Quartz", hardness=7)
85 >>> for o in (platypus, lion, eggplant, bacon, quartz):
86 ...     o.save()
87
88 # Objects with declared GenericRelations can be tagged directly -- the API
89 # mimics the many-to-many API.
90 >>> bacon.tags.create(tag="fatty")
91 <TaggedItem: fatty>
92 >>> bacon.tags.create(tag="salty")
93 <TaggedItem: salty>
94 >>> lion.tags.create(tag="yellow")
95 <TaggedItem: yellow>
96 >>> lion.tags.create(tag="hairy")
97 <TaggedItem: hairy>
98 >>> platypus.tags.create(tag="fatty")
99 <TaggedItem: fatty>
100
101 >>> lion.tags.all()
102 [<TaggedItem: hairy>, <TaggedItem: yellow>]
103 >>> bacon.tags.all()
104 [<TaggedItem: fatty>, <TaggedItem: salty>]
105
106 # You can easily access the content object like a foreign key.
107 >>> t = TaggedItem.objects.get(tag="salty")
108 >>> t.content_object
109 <Vegetable: Bacon>
110
111 # Recall that the Mineral class doesn't have an explicit GenericRelation
112 # defined. That's OK, because you can create TaggedItems explicitly.
113 >>> tag1 = TaggedItem(content_object=quartz, tag="shiny")
114 >>> tag2 = TaggedItem(content_object=quartz, tag="clearish")
115 >>> tag1.save()
116 >>> tag2.save()
117
118 # However, excluding GenericRelations means your lookups have to be a bit more
119 # explicit.
120 >>> from django.contrib.contenttypes.models import ContentType
121 >>> ctype = ContentType.objects.get_for_model(quartz)
122 >>> TaggedItem.objects.filter(content_type__pk=ctype.id, object_id=quartz.id)
123 [<TaggedItem: clearish>, <TaggedItem: shiny>]
124
125 # You can set a generic foreign key in the way you'd expect.
126 >>> tag1.content_object = platypus
127 >>> tag1.save()
128 >>> platypus.tags.all()
129 [<TaggedItem: fatty>, <TaggedItem: shiny>]
130 >>> TaggedItem.objects.filter(content_type__pk=ctype.id, object_id=quartz.id)
131 [<TaggedItem: clearish>]
132
133 # 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.
134 >>> Animal.objects.order_by('common_name')
135 [<Animal: Lion>, <Animal: Platypus>]
136 >>> Animal.objects.filter(tags__tag='fatty')
137 [<Animal: Platypus>]
138 >>> Animal.objects.exclude(tags__tag='fatty')
139 [<Animal: Lion>]
140
141 # If you delete an object with an explicit Generic relation, the related
142 # objects are deleted when the source object is deleted.
143 # Original list of tags:
144 >>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
145 [(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'fatty', <ContentType: animal>, 1), (u'hairy', <ContentType: animal>, 2), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 1), (u'yellow', <ContentType: animal>, 2)]
146
147 >>> lion.delete()
148 >>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
149 [(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'fatty', <ContentType: animal>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 1)]
150
151 # If Generic Relation is not explicitly defined, any related objects
152 # remain after deletion of the source object.
153 >>> quartz.delete()
154 >>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
155 [(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'fatty', <ContentType: animal>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 1)]
156
157 # If you delete a tag, the objects using the tag are unaffected
158 # (other than losing a tag)
159 >>> tag = TaggedItem.objects.get(id=1)
160 >>> tag.delete()
161 >>> bacon.tags.all()
162 [<TaggedItem: salty>]
163 >>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
164 [(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: animal>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 1)]
165
166 >>> TaggedItem.objects.filter(tag='fatty').delete()
167
168 >>> ctype = ContentType.objects.get_for_model(lion)
169 >>> Animal.objects.filter(tags__content_type=ctype)
170 [<Animal: Platypus>]
171
172 # Simple tests for multiple GenericForeignKeys
173 # only uses one model, since the above tests should be sufficient.
174 >>> tiger, cheetah, bear = Animal(common_name="tiger"), Animal(common_name="cheetah"), Animal(common_name="bear")
175 >>> for o in [tiger, cheetah, bear]: o.save()
176
177 # Create directly
178 >>> Comparison(first_obj=cheetah, other_obj=tiger, comparative="faster").save()
179 >>> Comparison(first_obj=tiger, other_obj=cheetah, comparative="cooler").save()
180
181 # Create using GenericRelation
182 >>> tiger.comparisons.create(other_obj=bear, comparative="cooler")
183 <Comparison: tiger is cooler than bear>
184 >>> tiger.comparisons.create(other_obj=cheetah, comparative="stronger")
185 <Comparison: tiger is stronger than cheetah>
186
187 >>> cheetah.comparisons.all()
188 [<Comparison: cheetah is faster than tiger>]
189
190 # Filtering works
191 >>> tiger.comparisons.filter(comparative="cooler")
192 [<Comparison: tiger is cooler than cheetah>, <Comparison: tiger is cooler than bear>]
193
194 # Filtering and deleting works
195 >>> subjective = ["cooler"]
196 >>> tiger.comparisons.filter(comparative__in=subjective).delete()
197 >>> Comparison.objects.all()
198 [<Comparison: cheetah is faster than tiger>, <Comparison: tiger is stronger than cheetah>]
199
200 # If we delete cheetah, Comparisons with cheetah as 'first_obj' will be deleted
201 # since Animal has an explicit GenericRelation to Comparison through first_obj.
202 # Comparisons with cheetah as 'other_obj' will not be deleted.
203 >>> cheetah.delete()
204 >>> Comparison.objects.all()
205 [<Comparison: tiger is stronger than None>]
206
207
208 # GenericInlineFormSet tests ##################################################
209
210 >>> from django.contrib.contenttypes.generic import generic_inlineformset_factory
211
212 >>> GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1)
213 >>> formset = GenericFormSet(instance=Animal())
214 >>> for form in formset.forms:
215 ...     print form.as_p()
216 <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>
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" id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p>
218
219 >>> formset = GenericFormSet(instance=platypus)
220 >>> for form in formset.forms:
221 ...     print form.as_p()
222 <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>
223 <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="..." id="id_generic_relations-taggeditem-content_type-object_id-0-id" /></p>
224 <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>
225 <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>
226
227 """}
Note: See TracBrowser for help on using the browser.