1
0
mirror of https://github.com/django/django.git synced 2025-07-05 18:29:11 +00:00

queryset-refactor: Fixed up and documented Meta-class inheritance.

Should be mostly logical (for versions of "logical" that may require you to be
a Vulcan, admittedly, but that's not entirely my fault).


git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@7141 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2008-02-22 01:05:05 +00:00
parent ca123b0760
commit 0c20e88e65
4 changed files with 136 additions and 15 deletions

View File

@ -35,14 +35,29 @@ class ModelBase(type):
# Create the class.
module = attrs.pop('__module__')
meta = attrs.pop('Meta', None)
new_class = type.__new__(cls, name, bases, {'__module__': module})
attr_meta = attrs.pop('Meta', None)
abstract = getattr(attr_meta, 'abstract', False)
if not attr_meta:
meta = getattr(new_class, 'Meta', None)
else:
meta = attr_meta
base_meta = getattr(new_class, '_meta', None)
new_class.add_to_class('_meta', Options(meta))
new_class.add_to_class('DoesNotExist',
subclass_exception('DoesNotExist', ObjectDoesNotExist, module))
new_class.add_to_class('MultipleObjectsReturned',
subclass_exception('MultipleObjectsReturned',
MultipleObjectsReturned, module))
if not abstract:
new_class.add_to_class('DoesNotExist',
subclass_exception('DoesNotExist', ObjectDoesNotExist, module))
new_class.add_to_class('MultipleObjectsReturned',
subclass_exception('MultipleObjectsReturned', MultipleObjectsReturned, module))
if base_meta and not base_meta.abstract:
# Non-abstract child classes inherit some attributes from their
# non-abstract parent (unless an ABC comes before it in the
# method resolution order).
if not hasattr(meta, 'ordering'):
new_class._meta.ordering = base_meta.ordering
if not hasattr(meta, 'get_latest_by'):
new_class._meta.get_latest_by = base_meta.get_latest_by
# Do the appropriate setup for any model parents.
abstract_parents = []
@ -86,10 +101,12 @@ class ModelBase(type):
% (field.name, name, parent.__name__))
new_class.add_to_class(field.name, field)
if new_class._meta.abstract:
if abstract:
# Abstract base models can't be instantiated and don't appear in
# the list of models for an app. We do the final setup for them a
# little differently from normal models.
attr_meta.abstract = False
new_class.Meta = attr_meta
return new_class
new_class._prepare()

View File

@ -55,7 +55,7 @@ class Options(object):
# Next, apply any overridden values from 'class Meta'.
if self.meta:
meta_attrs = self.meta.__dict__
meta_attrs = self.meta.__dict__.copy()
del meta_attrs['__module__']
del meta_attrs['__doc__']
for attr_name in DEFAULT_NAMES:

View File

@ -2031,6 +2031,18 @@ You can also prevent saving::
Model inheritance
=================
**New in Django development version**
Model inheritance in Django works almost identically to the way normal class
inheritance works in Python. The only decision you have to make is whether you
want the parent models to be models in their own right (with their own
database tables), or if the parents are just holders of common information
that will only be visible through the child models.
Often, you will just want to use the parent class to hold information that you
don't want to have to type out for each child model. This class isn't going to
ever be used in isolation, so `abstract base classes`_ are what you're after. However, if you're subclassing an existing model (perhaps something from another application entirely), or want each model to have its own database table, `multi-table inheritance`_ is the way to go.
Abstract base classes
---------------------
@ -2063,6 +2075,38 @@ For many uses, this type of model inheritance will be exactly what you want.
It provides a way to factor out common information at the Python level, whilst
still only creating one database table per child model at the database level.
``Meta`` inheritance
~~~~~~~~~~~~~~~~~~~~
When an abstract base class is created, Django makes any ``Meta`` inner class
you declared on the base class available as an attribute. If a child class
does not declared its own ``Meta`` class, it will inherit the parent's
``Meta``. If the child wants to extend the parent's ``Meta`` class, it can
subclass it. For example::
class CommonInfo(models.Model):
...
class Meta:
abstract = True
ordering = ['name']
class Student(CommonInfo):
...
class Meta(CommonInfo.Meta):
db_table = 'student_info'
Django does make one adjustment to the ``Meta`` class of an abstract base
class: before installing the ``Meta`` attribute, it sets ``abstract=False``.
This means that children of abstract base classes don't automatically become
abstract classes themselves. Of course, you can make an abstract base class
that inherits from another abstract base class. You just need to remember to
explicitly set ``abstract=True`` each time.
Some attributes won't make sense to include in the ``Meta`` class of an
abstract base class. For example, including ``db_table`` would mean that all
the child classes (the ones that don't specify their own ``Meta``) would use
the same database table, which is almost certainly not what you want.
Multi-table inheritance
-----------------------
@ -2100,8 +2144,29 @@ However, if ``p`` in the above example was *not* a ``Restaurant`` (it had been
created directly as a ``Place`` object or was the parent of some other class),
referring to ``p.restaurant`` would give an error.
Normally you won't need to worry too much about how model inheritance works.
It will behave similarly to Python class inheritance.
``Meta`` and multi-table inheritance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In the multi-table inheritance situation, it doesn't make sense for a child
class to inherit from its parent's ``Meta`` class. All the ``Meta`` options
have already been applied to the parent class and applying them again would
normally only lead to contradictory behaviour (this is in contrast with the
abstract base class case, where the base class doesn't exist in its own
right).
So a child model does not have access to its parent's ``Meta`` class. However,
there are a few limited cases where the child inherits behaviour from the
parent: if the child does not specify an ``ordering`` attribute or a
``get_latest_by`` attribute, it will inherit these from its parent.
If the parent has an ordering and you don't want the child to have any natural
ordering, you can explicity set it to be empty::
class ChildModel(ParentModel):
...
class Meta:
# Remove parent's ordering effect
ordering = []
Inheritance and reverse relations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -2127,6 +2192,23 @@ For more information about reverse relations, refer to the `Database API
reference`_ . For now, just remember to run ``manage.py validate`` when
you're writing your models and pay attention to the error messages.
Multiple inheritance
--------------------
Just as with Python's subclassing, it's possible for a Django model to inherit
from multiple parent models. Keep in mind that normal Python name resolution
rules apply. The first base class that a particular name appears in (e.g.
``Meta``) will be the one that is used. We stop searching once we find the
name once. This means that if multiple parents contain a ``Meta`` class, only
the first one is going to be used. All others will be ignored.
Generally, you won't need to inherit from multiple parents. The main use-case
where this is useful is for ''mix-in'' classes: adding a particular extra
field or method to every class that inherits the mix-in. Try to keep your
inheritance hierarchies as simple and straightforward as possible so that you
won't have to struggle to work out where a particular piece of information is
coming from.
Models across files
===================

View File

@ -20,6 +20,7 @@ class CommonInfo(models.Model):
class Meta:
abstract = True
ordering = ['name']
def __unicode__(self):
return u'%s %s' % (self.__class__.__name__, self.name)
@ -30,6 +31,9 @@ class Worker(CommonInfo):
class Student(CommonInfo):
school_class = models.CharField(max_length=10)
class Meta:
pass
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
@ -37,7 +41,13 @@ class Place(models.Model):
def __unicode__(self):
return u"%s the place" % self.name
class Restaurant(Place):
class Rating(models.Model):
rating = models.IntegerField(null=True, blank=True)
class Meta:
abstract = True
class Restaurant(Place, Rating):
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
@ -71,6 +81,8 @@ __test__ = {'API_TESTS':"""
>>> w = Worker(name='Fred', age=35, job='Quarry worker')
>>> w.save()
>>> w2 = Worker(name='Barney', age=34, job='Quarry worker')
>>> w2.save()
>>> s = Student(name='Pebbles', age=5, school_class='1B')
>>> s.save()
>>> unicode(w)
@ -78,6 +90,16 @@ u'Worker Fred'
>>> unicode(s)
u'Student Pebbles'
# The children inherit the Meta class of their parents (if they don't specify
# their own).
>>> Worker.objects.values('name')
[{'name': u'Barney'}, {'name': u'Fred'}]
# Since Student does not subclass CommonInfo's Meta, it has the effect of
# completely overriding it. So ordering by name doesn't take place for Students.
>>> Student._meta.ordering
[]
# However, the CommonInfo class cannot be used as a normal model (it doesn't
# exist as a model).
>>> CommonInfo.objects.all()
@ -96,7 +118,7 @@ AttributeError: type object 'CommonInfo' has no attribute 'objects'
>>> p2.save()
Test constructor for Restaurant.
>>> r = Restaurant(name='Demon Dogs', address='944 W. Fullerton', serves_hot_dogs=True, serves_pizza=False)
>>> r = Restaurant(name='Demon Dogs', address='944 W. Fullerton',serves_hot_dogs=True, serves_pizza=False, rating=2)
>>> r.save()
# Test the constructor for ItalianRestaurant.
@ -106,9 +128,9 @@ Test constructor for Restaurant.
# 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']
['id', 'name', 'address', 'place_ptr', 'rating', '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']
['id', 'name', 'address', 'place_ptr', 'rating', '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
@ -118,7 +140,7 @@ Test constructor for Restaurant.
>>> 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
TypeError: Cannot resolve keyword 'supplier' into field. Choices are: address, id, italianrestaurant, lot, name, place_ptr, provider, rating, serves_hot_dogs, serves_pizza
# Parent fields can be used directly in filters on the child model.
>>> Restaurant.objects.filter(name='Demon Dogs')