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:
parent
ca123b0760
commit
0c20e88e65
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
===================
|
||||
|
||||
|
@ -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')
|
||||
|
Loading…
x
Reference in New Issue
Block a user