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 generic foreign key can point to any object, |
|
6 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 |
|
15 class TaggedItem(models.Model): |
|
16 """A tag on an item.""" |
|
17 tag = models.SlugField() |
|
18 content_type = models.ForeignKey(ContentType) |
|
19 object_id = models.PositiveIntegerField() |
|
20 |
|
21 content_object = models.GenericForeignKey() |
|
22 |
|
23 class Meta: |
|
24 ordering = ["tag"] |
|
25 |
|
26 def __str__(self): |
|
27 return self.tag |
|
28 |
|
29 class Animal(models.Model): |
|
30 common_name = models.CharField(maxlength=150) |
|
31 latin_name = models.CharField(maxlength=150) |
|
32 |
|
33 tags = models.GenericRelation(TaggedItem) |
|
34 |
|
35 def __str__(self): |
|
36 return self.common_name |
|
37 |
|
38 class Vegetable(models.Model): |
|
39 name = models.CharField(maxlength=150) |
|
40 is_yucky = models.BooleanField(default=True) |
|
41 |
|
42 tags = models.GenericRelation(TaggedItem) |
|
43 |
|
44 def __str__(self): |
|
45 return self.name |
|
46 |
|
47 class Mineral(models.Model): |
|
48 name = models.CharField(maxlength=150) |
|
49 hardness = models.PositiveSmallIntegerField() |
|
50 |
|
51 # note the lack of an explicit GenericRelation here... |
|
52 |
|
53 def __str__(self): |
|
54 return self.name |
|
55 |
|
56 __test__ = {'API_TESTS':""" |
|
57 # Create the world in 7 lines of code... |
|
58 >>> lion = Animal(common_name="Lion", latin_name="Panthera leo") |
|
59 >>> platypus = Animal(common_name="Platypus", latin_name="Ornithorhynchus anatinus") |
|
60 >>> eggplant = Vegetable(name="Eggplant", is_yucky=True) |
|
61 >>> bacon = Vegetable(name="Bacon", is_yucky=False) |
|
62 >>> quartz = Mineral(name="Quartz", hardness=7) |
|
63 >>> for o in (lion, platypus, eggplant, bacon, quartz): |
|
64 ... o.save() |
|
65 |
|
66 # Objects with declared GenericRelations can be tagged directly -- the API |
|
67 # mimics the many-to-many API. |
|
68 >>> bacon.tags.create(tag="fatty") |
|
69 <TaggedItem: fatty> |
|
70 >>> bacon.tags.create(tag="salty") |
|
71 <TaggedItem: salty> |
|
72 >>> lion.tags.create(tag="yellow") |
|
73 <TaggedItem: yellow> |
|
74 >>> lion.tags.create(tag="hairy") |
|
75 <TaggedItem: hairy> |
|
76 |
|
77 >>> lion.tags.all() |
|
78 [<TaggedItem: hairy>, <TaggedItem: yellow>] |
|
79 >>> bacon.tags.all() |
|
80 [<TaggedItem: fatty>, <TaggedItem: salty>] |
|
81 |
|
82 # You can easily access the content object like a foreign key. |
|
83 >>> t = TaggedItem.objects.get(tag="salty") |
|
84 >>> t.content_object |
|
85 <Vegetable: Bacon> |
|
86 |
|
87 # Recall that the Mineral class doesn't have an explicit GenericRelation |
|
88 # defined. That's OK, because you can create TaggedItems explicitly. |
|
89 >>> tag1 = TaggedItem(content_object=quartz, tag="shiny") |
|
90 >>> tag2 = TaggedItem(content_object=quartz, tag="clearish") |
|
91 >>> tag1.save() |
|
92 >>> tag2.save() |
|
93 |
|
94 # However, excluding GenericRelations means your lookups have to be a bit more |
|
95 # explicit. |
|
96 >>> from django.contrib.contenttypes.models import ContentType |
|
97 >>> ctype = ContentType.objects.get_for_model(quartz) |
|
98 >>> TaggedItem.objects.filter(content_type__pk=ctype.id, object_id=quartz.id) |
|
99 [<TaggedItem: clearish>, <TaggedItem: shiny>] |
|
100 |
|
101 # You can set a generic foreign key in the way you'd expect. |
|
102 >>> tag1.content_object = platypus |
|
103 >>> tag1.save() |
|
104 >>> platypus.tags.all() |
|
105 [<TaggedItem: shiny>] |
|
106 >>> TaggedItem.objects.filter(content_type__pk=ctype.id, object_id=quartz.id) |
|
107 [<TaggedItem: clearish>] |
|
108 |
|
109 # If you delete an object with an explicit Generic relation, the related |
|
110 # objects are deleted when the source object is deleted. |
|
111 # Original list of tags: |
|
112 >>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] |
|
113 [('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('hairy', <ContentType: animal>, 1), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2), ('yellow', <ContentType: animal>, 1)] |
|
114 |
|
115 >>> lion.delete() |
|
116 >>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] |
|
117 [('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)] |
|
118 |
|
119 # If Generic Relation is not explicitly defined, any related objects |
|
120 # remain after deletion of the source object. |
|
121 >>> quartz.delete() |
|
122 >>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] |
|
123 [('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)] |
|
124 |
|
125 # If you delete a tag, the objects using the tag are unaffected |
|
126 # (other than losing a tag) |
|
127 >>> tag = TaggedItem.objects.get(id=1) |
|
128 >>> tag.delete() |
|
129 >>> bacon.tags.all() |
|
130 [<TaggedItem: salty>] |
|
131 >>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] |
|
132 [('clearish', <ContentType: mineral>, 1), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)] |
|
133 |
|
134 """} |
|