Merged the queryset-refactor branch into trunk.
[fdr-django.git] / tests / modeltests / model_inheritance / models.py
blobb1a751f5e8472cd20c395f24ebe6f924f1d5a09b
1 """
2 XX. Model inheritance
4 Model inheritance exists in two varieties:
5 - abstract base classes which are a way of specifying common
6 information inherited by the subclasses. They don't exist as a separate
7 model.
8 - non-abstract base classes (the default), which are models in their own
9 right with their own database tables and everything. Their subclasses
10 have references back to them, created automatically.
12 Both styles are demonstrated here.
13 """
15 from django.db import models
18 # Abstract base classes
21 class CommonInfo(models.Model):
22 name = models.CharField(max_length=50)
23 age = models.PositiveIntegerField()
25 class Meta:
26 abstract = True
27 ordering = ['name']
29 def __unicode__(self):
30 return u'%s %s' % (self.__class__.__name__, self.name)
32 class Worker(CommonInfo):
33 job = models.CharField(max_length=50)
35 class Student(CommonInfo):
36 school_class = models.CharField(max_length=10)
38 class Meta:
39 pass
42 # Multi-table inheritance
45 class Chef(models.Model):
46 name = models.CharField(max_length=50)
48 def __unicode__(self):
49 return u"%s the chef" % self.name
51 class Place(models.Model):
52 name = models.CharField(max_length=50)
53 address = models.CharField(max_length=80)
55 def __unicode__(self):
56 return u"%s the place" % self.name
58 class Rating(models.Model):
59 rating = models.IntegerField(null=True, blank=True)
61 class Meta:
62 abstract = True
63 ordering = ['-rating']
65 class Restaurant(Place, Rating):
66 serves_hot_dogs = models.BooleanField()
67 serves_pizza = models.BooleanField()
68 chef = models.ForeignKey(Chef, null=True, blank=True)
70 class Meta(Rating.Meta):
71 db_table = 'my_restaurant'
73 def __unicode__(self):
74 return u"%s the restaurant" % self.name
76 class ItalianRestaurant(Restaurant):
77 serves_gnocchi = models.BooleanField()
79 def __unicode__(self):
80 return u"%s the italian restaurant" % self.name
82 class Supplier(Place):
83 customers = models.ManyToManyField(Restaurant, related_name='provider')
85 def __unicode__(self):
86 return u"%s the supplier" % self.name
88 class ParkingLot(Place):
89 # An explicit link to the parent (we can control the attribute name).
90 parent = models.OneToOneField(Place, primary_key=True, parent_link=True)
91 main_site = models.ForeignKey(Place, related_name='lot')
93 def __unicode__(self):
94 return u"%s the parking lot" % self.name
96 __test__ = {'API_TESTS':"""
97 # The Student and Worker models both have 'name' and 'age' fields on them and
98 # inherit the __unicode__() method, just as with normal Python subclassing.
99 # This is useful if you want to factor out common information for programming
100 # purposes, but still completely independent separate models at the database
101 # level.
103 >>> w = Worker(name='Fred', age=35, job='Quarry worker')
104 >>> w.save()
105 >>> w2 = Worker(name='Barney', age=34, job='Quarry worker')
106 >>> w2.save()
107 >>> s = Student(name='Pebbles', age=5, school_class='1B')
108 >>> s.save()
109 >>> unicode(w)
110 u'Worker Fred'
111 >>> unicode(s)
112 u'Student Pebbles'
114 # The children inherit the Meta class of their parents (if they don't specify
115 # their own).
116 >>> Worker.objects.values('name')
117 [{'name': u'Barney'}, {'name': u'Fred'}]
119 # Since Student does not subclass CommonInfo's Meta, it has the effect of
120 # completely overriding it. So ordering by name doesn't take place for Students.
121 >>> Student._meta.ordering
124 # However, the CommonInfo class cannot be used as a normal model (it doesn't
125 # exist as a model).
126 >>> CommonInfo.objects.all()
127 Traceback (most recent call last):
129 AttributeError: type object 'CommonInfo' has no attribute 'objects'
131 # The Place/Restaurant/ItalianRestaurant models, on the other hand, all exist
132 # as independent models. However, the subclasses also have transparent access
133 # to the fields of their ancestors.
135 # Create a couple of Places.
136 >>> p1 = Place(name='Master Shakes', address='666 W. Jersey')
137 >>> p1.save()
138 >>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
139 >>> p2.save()
141 Test constructor for Restaurant.
142 >>> r = Restaurant(name='Demon Dogs', address='944 W. Fullerton',serves_hot_dogs=True, serves_pizza=False, rating=2)
143 >>> r.save()
145 # Test the constructor for ItalianRestaurant.
146 >>> c = Chef(name="Albert")
147 >>> c.save()
148 >>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Ash', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True, rating=4, chef=c)
149 >>> ir.save()
150 >>> ir.address = '1234 W. Elm'
151 >>> ir.save()
153 # Make sure Restaurant and ItalianRestaurant have the right fields in the right
154 # order.
155 >>> [f.name for f in Restaurant._meta.fields]
156 ['id', 'name', 'address', 'place_ptr', 'rating', 'serves_hot_dogs', 'serves_pizza', 'chef']
157 >>> [f.name for f in ItalianRestaurant._meta.fields]
158 ['id', 'name', 'address', 'place_ptr', 'rating', 'serves_hot_dogs', 'serves_pizza', 'chef', 'restaurant_ptr', 'serves_gnocchi']
159 >>> Restaurant._meta.ordering
160 ['-rating']
162 # Even though p.supplier for a Place 'p' (a parent of a Supplier), a Restaurant
163 # object cannot access that reverse relation, since it's not part of the
164 # Place-Supplier Hierarchy.
165 >>> Place.objects.filter(supplier__name='foo')
167 >>> Restaurant.objects.filter(supplier__name='foo')
168 Traceback (most recent call last):
170 FieldError: Cannot resolve keyword 'supplier' into field. Choices are: address, chef, id, italianrestaurant, lot, name, place_ptr, provider, rating, serves_hot_dogs, serves_pizza
172 # Parent fields can be used directly in filters on the child model.
173 >>> Restaurant.objects.filter(name='Demon Dogs')
174 [<Restaurant: Demon Dogs the restaurant>]
175 >>> ItalianRestaurant.objects.filter(address='1234 W. Elm')
176 [<ItalianRestaurant: Ristorante Miron the italian restaurant>]
178 # Filters against the parent model return objects of the parent's type.
179 >>> Place.objects.filter(name='Demon Dogs')
180 [<Place: Demon Dogs the place>]
182 # Since the parent and child are linked by an automatically created
183 # OneToOneField, you can get from the parent to the child by using the child's
184 # name.
185 >>> place = Place.objects.get(name='Demon Dogs')
186 >>> place.restaurant
187 <Restaurant: Demon Dogs the restaurant>
189 >>> Place.objects.get(name='Ristorante Miron').restaurant.italianrestaurant
190 <ItalianRestaurant: Ristorante Miron the italian restaurant>
191 >>> Restaurant.objects.get(name='Ristorante Miron').italianrestaurant
192 <ItalianRestaurant: Ristorante Miron the italian restaurant>
194 # This won't work because the Demon Dogs restaurant is not an Italian
195 # restaurant.
196 >>> place.restaurant.italianrestaurant
197 Traceback (most recent call last):
199 DoesNotExist: ItalianRestaurant matching query does not exist.
201 # Related objects work just as they normally do.
203 >>> s1 = Supplier(name="Joe's Chickens", address='123 Sesame St')
204 >>> s1.save()
205 >>> s1.customers = [r, ir]
206 >>> s2 = Supplier(name="Luigi's Pasta", address='456 Sesame St')
207 >>> s2.save()
208 >>> s2.customers = [ir]
210 # This won't work because the Place we select is not a Restaurant (it's a
211 # Supplier).
212 >>> p = Place.objects.get(name="Joe's Chickens")
213 >>> p.restaurant
214 Traceback (most recent call last):
216 DoesNotExist: Restaurant matching query does not exist.
218 # But we can descend from p to the Supplier child, as expected.
219 >>> p.supplier
220 <Supplier: Joe's Chickens the supplier>
222 >>> ir.provider.order_by('-name')
223 [<Supplier: Luigi's Pasta the supplier>, <Supplier: Joe's Chickens the supplier>]
225 >>> Restaurant.objects.filter(provider__name__contains="Chickens")
226 [<Restaurant: Ristorante Miron the restaurant>, <Restaurant: Demon Dogs the restaurant>]
227 >>> ItalianRestaurant.objects.filter(provider__name__contains="Chickens")
228 [<ItalianRestaurant: Ristorante Miron the italian restaurant>]
230 >>> park1 = ParkingLot(name='Main St', address='111 Main St', main_site=s1)
231 >>> park1.save()
232 >>> park2 = ParkingLot(name='Well Lit', address='124 Sesame St', main_site=ir)
233 >>> park2.save()
235 >>> Restaurant.objects.get(lot__name='Well Lit')
236 <Restaurant: Ristorante Miron the restaurant>
238 # The update() command can update fields in parent and child classes at once
239 # (although it executed multiple SQL queries to do so).
240 >>> Restaurant.objects.filter(serves_hot_dogs=True, name__contains='D').update(name='Demon Puppies', serves_hot_dogs=False)
241 >>> r1 = Restaurant.objects.get(pk=r.pk)
242 >>> r1.serves_hot_dogs == False
243 True
244 >>> r1.name
245 u'Demon Puppies'
247 # The values() command also works on fields from parent models.
248 >>> d = {'rating': 4, 'name': u'Ristorante Miron'}
249 >>> list(ItalianRestaurant.objects.values('name', 'rating')) == [d]
250 True
252 # select_related works with fields from the parent object as if they were a
253 # normal part of the model.
254 >>> from django import db
255 >>> from django.conf import settings
256 >>> settings.DEBUG = True
257 >>> db.reset_queries()
258 >>> ItalianRestaurant.objects.all()[0].chef
259 <Chef: Albert the chef>
260 >>> len(db.connection.queries)
262 >>> ItalianRestaurant.objects.select_related('chef')[0].chef
263 <Chef: Albert the chef>
264 >>> len(db.connection.queries)
266 >>> settings.DEBUG = False
268 """}