~ubuntu-branches/ubuntu/quantal/python-django/quantal

« back to all changes in this revision

Viewing changes to tests/modeltests/generic_relations/models.py

  • Committer: Bazaar Package Importer
  • Author(s): Chris Lamb
  • Date: 2009-07-29 11:26:28 UTC
  • mfrom: (1.1.8 upstream) (4.1.5 sid)
  • Revision ID: james.westby@ubuntu.com-20090729112628-pg09ino8sz0sj21t
Tags: 1.1-1
* New upstream release.
* Merge from experimental:
  - Ship FastCGI initscript and /etc/default file in python-django's examples
    directory (Closes: #538863)
  - Drop "05_10539-sphinx06-compatibility.diff"; it has been applied
    upstream.
  - Bump Standards-Version to 3.8.2.

Show diffs side-by-side

added added

removed removed

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