diff options
| author | Malcolm Tredinnick <malcolm.tredinnick@gmail.com> | 2008-02-17 18:47:57 +0000 |
|---|---|---|
| committer | Malcolm Tredinnick <malcolm.tredinnick@gmail.com> | 2008-02-17 18:47:57 +0000 |
| commit | da6570bf082620205737c82cd7deb7185daaf538 (patch) | |
| tree | 0d19599ca9923f974512bb7453072a553ada28e6 /tests | |
| parent | 2d0588548e52c78e5213d262a5c4e5df1da3450e (diff) | |
queryset-refactor: Model inheritance support.
This adds both types of model inheritance: abstract base classes (ABCs) and
multi-table inheritance. See the documentation and tests / examples for details.
Still a few known bugs here, so don't file tickets (I know about them). Not
quite ready for prime-time usage, but it mostly works as expected.
git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@7126 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/modeltests/model_inheritance/models.py | 157 |
1 files changed, 146 insertions, 11 deletions
diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py index d9956a5452..940b2ee10c 100644 --- a/tests/modeltests/model_inheritance/models.py +++ b/tests/modeltests/model_inheritance/models.py @@ -1,11 +1,35 @@ """ XX. Model inheritance -Model inheritance isn't yet supported. +Model inheritance exists in two varieties: + - abstract base classes which are a way of specifying common + information inherited by the subclasses. They don't exist as a separate + model. + - non-abstract base classes (the default), which are models in their own + right with their own database tables and everything. Their subclasses + have references back to them, created automatically. + +Both styles are demonstrated here. """ from django.db import models +class CommonInfo(models.Model): + name = models.CharField(max_length=50) + age = models.PositiveIntegerField() + + class Meta: + abstract = True + + def __unicode__(self): + return u'%s %s' % (self.__class__.__name__, self.name) + +class Worker(CommonInfo): + job = models.CharField(max_length=50) + +class Student(CommonInfo): + school_class = models.CharField(max_length=10) + class Place(models.Model): name = models.CharField(max_length=50) address = models.CharField(max_length=80) @@ -26,16 +50,44 @@ class ItalianRestaurant(Restaurant): def __unicode__(self): return u"%s the italian restaurant" % self.name -# XFAIL: Recent changes to model saving mean these now fail catastrophically. -# They'll be re-enabled when the porting is a bit further along. -not__test__ = {'API_TESTS':""" -# Make sure Restaurant has the right fields in the right order. ->>> [f.name for f in Restaurant._meta.fields] -['id', 'name', 'address', 'serves_hot_dogs', 'serves_pizza'] +class Supplier(Place): + customers = models.ManyToManyField(Restaurant, related_name='provider') -# Make sure ItalianRestaurant has the right fields in the right order. ->>> [f.name for f in ItalianRestaurant._meta.fields] -['id', 'name', 'address', 'serves_hot_dogs', 'serves_pizza', 'serves_gnocchi'] + def __unicode__(self): + return u"%s the supplier" % self.name + +class ParkingLot(Place): + main_site = models.ForeignKey(Place, related_name='lot') + + def __unicode__(self): + return u"%s the parking lot" % self.name + +__test__ = {'API_TESTS':""" +# The Student and Worker models both have 'name' and 'age' fields on them and +# inherit the __unicode__() method, just as with normal Python subclassing. +# This is useful if you want to factor out common information for programming +# purposes, but still completely independent separate models at the database +# level. + +>>> w = Worker(name='Fred', age=35, job='Quarry worker') +>>> w.save() +>>> s = Student(name='Pebbles', age=5, school_class='1B') +>>> s.save() +>>> unicode(w) +u'Worker Fred' +>>> unicode(s) +u'Student Pebbles' + +# However, the CommonInfo class cannot be used as a normal model (it doesn't +# exist as a model). +>>> CommonInfo.objects.all() +Traceback (most recent call last): + ... +AttributeError: type object 'CommonInfo' has no attribute 'objects' + +# The Place/Restaurant/ItalianRestaurant models, on the other hand, all exist +# as independent models. However, the subclasses also have transparent access +# to the fields of their ancestors. # Create a couple of Places. >>> p1 = Place(name='Master Shakes', address='666 W. Jersey') @@ -43,7 +95,7 @@ not__test__ = {'API_TESTS':""" >>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland') >>> p2.save() -# Test constructor for Restaurant. +Test constructor for Restaurant. >>> r = Restaurant(name='Demon Dogs', address='944 W. Fullerton', serves_hot_dogs=True, serves_pizza=False) >>> r.save() @@ -51,5 +103,88 @@ not__test__ = {'API_TESTS':""" >>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Elm', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True) >>> ir.save() +# Make sure Restaurant and ItalianRestaurant have the right fields in the right +# order. +>>> [f.name for f in Restaurant._meta.fields] +['id', 'name', 'address', 'place_ptr', 'serves_hot_dogs', 'serves_pizza'] +>>> [f.name for f in ItalianRestaurant._meta.fields] +['id', 'name', 'address', 'place_ptr', 'serves_hot_dogs', 'serves_pizza', 'restaurant_ptr', 'serves_gnocchi'] + +# Even though p.supplier for a Place 'p' (a parent of a Supplier), a Restaurant +# object cannot access that reverse relation, since it's not part of the +# Place-Supplier Hierarchy. +>>> Place.objects.filter(supplier__name='foo') +[] +>>> Restaurant.objects.filter(supplier__name='foo') +Traceback (most recent call last): + ... +TypeError: Cannot resolve keyword 'supplier' into field. Choices are: address, id, italianrestaurant, lot, name, place_ptr, provider, serves_hot_dogs, serves_pizza + +# Parent fields can be used directly in filters on the child model. +>>> Restaurant.objects.filter(name='Demon Dogs') +[<Restaurant: Demon Dogs the restaurant>] +>>> ItalianRestaurant.objects.filter(address='1234 W. Elm') +[<ItalianRestaurant: Ristorante Miron the italian restaurant>] + +# Filters against the parent model return objects of the parent's type. +>>> Place.objects.filter(name='Demon Dogs') +[<Place: Demon Dogs the place>] + +# Since the parent and child are linked by an automatically created +# OneToOneField, you can get from the parent to the child by using the child's +# name. +>>> place = Place.objects.get(name='Demon Dogs') +>>> place.restaurant +<Restaurant: Demon Dogs the restaurant> + +>>> Place.objects.get(name='Ristorante Miron').restaurant.italianrestaurant +<ItalianRestaurant: Ristorante Miron the italian restaurant> +>>> Restaurant.objects.get(name='Ristorante Miron').italianrestaurant +<ItalianRestaurant: Ristorante Miron the italian restaurant> + +# This won't work because the Demon Dogs restaurant is not an Italian +# restaurant. +>>> place.restaurant.italianrestaurant +Traceback (most recent call last): + ... +DoesNotExist: ItalianRestaurant matching query does not exist. + +# Related objects work just as they normally do. + +>>> s1 = Supplier(name="Joe's Chickens", address='123 Sesame St') +>>> s1.save() +>>> s1.customers = [r, ir] +>>> s2 = Supplier(name="Luigi's Pasta", address='456 Sesame St') +>>> s2.save() +>>> s2.customers = [ir] + +# This won't work because the Place we select is not a Restaurant (it's a +# Supplier). +>>> p = Place.objects.get(name="Joe's Chickens") +>>> p.restaurant +Traceback (most recent call last): + ... +DoesNotExist: Restaurant matching query does not exist. + +# But we can descend from p to the Supplier child, as expected. +>>> p.supplier +<Supplier: Joe's Chickens the supplier> + +>>> ir.provider.order_by('-name') +[<Supplier: Luigi's Pasta the supplier>, <Supplier: Joe's Chickens the supplier>] + +>>> Restaurant.objects.filter(provider__name__contains="Chickens") +[<Restaurant: Demon Dogs the restaurant>, <Restaurant: Ristorante Miron the restaurant>] +>>> ItalianRestaurant.objects.filter(provider__name__contains="Chickens") +[<ItalianRestaurant: Ristorante Miron the italian restaurant>] + +>>> park1 = ParkingLot(name='Main St', address='111 Main St', main_site=s1) +>>> park1.save() +>>> park2 = ParkingLot(name='Well Lit', address='124 Sesame St', main_site=ir) +>>> park2.save() + +>>> Restaurant.objects.get(lot__name='Well Lit') +<Restaurant: Ristorante Miron the restaurant> + """} |
