mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	Fixed #10356 -- Added pure-Python inheritance for models (a.k.a proxy models).
Large portions of this are needed for #5420, so I implemented it fully. Thanks to Ryan Kelly for an initial patch to get this started. Refs #5420. git-svn-id: http://code.djangoproject.com/svn/django/trunk@10083 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
		
							
								
								
									
										1
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								AUTHORS
									
									
									
									
									
								
							| @@ -220,6 +220,7 @@ answer newbie questions, and generally made Django that much better: | ||||
|     Erik Karulf <erik@karulf.com> | ||||
|     Ben Dean Kawamura <ben.dean.kawamura@gmail.com> | ||||
|     Ian G. Kelly <ian.g.kelly@gmail.com> | ||||
|     Ryan Kelly <ryan@rfk.id.au> | ||||
|     Thomas Kerpe <thomas@kerpe.net> | ||||
|     Ossama M. Khayat <okhayat@yahoo.com> | ||||
|     Ben Khoo <khoobks@westnet.com.au> | ||||
|   | ||||
| @@ -67,9 +67,19 @@ class ModelBase(type): | ||||
|                 if not hasattr(meta, 'get_latest_by'): | ||||
|                     new_class._meta.get_latest_by = base_meta.get_latest_by | ||||
|  | ||||
|         is_proxy = new_class._meta.proxy | ||||
|  | ||||
|         if getattr(new_class, '_default_manager', None): | ||||
|             if not is_proxy: | ||||
|                 # Multi-table inheritance doesn't inherit default manager from | ||||
|                 # parents. | ||||
|                 new_class._default_manager = None | ||||
|                 new_class._base_manager = None | ||||
|             else: | ||||
|                 # Proxy classes do inherit parent's default manager, if none is | ||||
|                 # set explicitly. | ||||
|                 new_class._default_manager = new_class._default_manager._copy_to_model(new_class) | ||||
|                 new_class._base_manager = new_class._base_manager._copy_to_model(new_class) | ||||
|  | ||||
|         # Bail out early if we have already created this class. | ||||
|         m = get_model(new_class._meta.app_label, name, False) | ||||
| @@ -80,21 +90,43 @@ class ModelBase(type): | ||||
|         for obj_name, obj in attrs.items(): | ||||
|             new_class.add_to_class(obj_name, obj) | ||||
|  | ||||
|         # Do the appropriate setup for any model parents. | ||||
|         o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields | ||||
|                 if isinstance(f, OneToOneField)]) | ||||
|         for base in parents: | ||||
|             if not hasattr(base, '_meta'): | ||||
|                 # Things without _meta aren't functional models, so they're | ||||
|                 # uninteresting parents. | ||||
|                 continue | ||||
|  | ||||
|         # All the fields of any type declared on this model | ||||
|         new_fields = new_class._meta.local_fields + \ | ||||
|                      new_class._meta.local_many_to_many + \ | ||||
|                      new_class._meta.virtual_fields | ||||
|         field_names = set([f.name for f in new_fields]) | ||||
|  | ||||
|         # Basic setup for proxy models. | ||||
|         if is_proxy: | ||||
|             base = None | ||||
|             for parent in [cls for cls in parents if hasattr(cls, '_meta')]: | ||||
|                 if parent._meta.abstract: | ||||
|                     if parent._meta.fields: | ||||
|                         raise TypeError("Abstract base class containing model fields not permitted for proxy model '%s'." % name) | ||||
|                     else: | ||||
|                         continue | ||||
|                 if base is not None: | ||||
|                     raise TypeError("Proxy model '%s' has more than one non-abstract model base class." % name) | ||||
|                 else: | ||||
|                     base = parent | ||||
|             if base is None: | ||||
|                     raise TypeError("Proxy model '%s' has no non-abstract model base class." % name) | ||||
|             if (new_class._meta.local_fields or | ||||
|                     new_class._meta.local_many_to_many): | ||||
|                 raise FieldError("Proxy model '%s' contains model fields." | ||||
|                         % name) | ||||
|             new_class._meta.setup_proxy(base) | ||||
|  | ||||
|         # Do the appropriate setup for any model parents. | ||||
|         o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields | ||||
|                 if isinstance(f, OneToOneField)]) | ||||
|  | ||||
|         for base in parents: | ||||
|             if not hasattr(base, '_meta'): | ||||
|                 # Things without _meta aren't functional models, so they're | ||||
|                 # uninteresting parents. | ||||
|                 continue | ||||
|  | ||||
|             parent_fields = base._meta.local_fields + base._meta.local_many_to_many | ||||
|             # Check for clashes between locally declared fields and those | ||||
|             # on the base classes (we cannot handle shadowed fields at the | ||||
| @@ -107,15 +139,19 @@ class ModelBase(type): | ||||
|                                         (field.name, name, base.__name__)) | ||||
|             if not base._meta.abstract: | ||||
|                 # Concrete classes... | ||||
|                 while base._meta.proxy: | ||||
|                     # Skip over a proxy class to the "real" base it proxies. | ||||
|                     base = base._meta.proxy_for_model | ||||
|                 if base in o2o_map: | ||||
|                     field = o2o_map[base] | ||||
|                 else: | ||||
|                 elif not is_proxy: | ||||
|                     attr_name = '%s_ptr' % base._meta.module_name | ||||
|                     field = OneToOneField(base, name=attr_name, | ||||
|                             auto_created=True, parent_link=True) | ||||
|                     new_class.add_to_class(attr_name, field) | ||||
|                 else: | ||||
|                     field = None | ||||
|                 new_class._meta.parents[base] = field | ||||
|  | ||||
|             else: | ||||
|                 # .. and abstract ones. | ||||
|                 for field in parent_fields: | ||||
| @@ -125,13 +161,12 @@ class ModelBase(type): | ||||
|                 new_class._meta.parents.update(base._meta.parents) | ||||
|  | ||||
|             # Inherit managers from the abstract base classes. | ||||
|             base_managers = base._meta.abstract_managers | ||||
|             base_managers.sort() | ||||
|             for _, mgr_name, manager in base_managers: | ||||
|                 val = getattr(new_class, mgr_name, None) | ||||
|                 if not val or val is manager: | ||||
|                     new_manager = manager._copy_to_model(new_class) | ||||
|                     new_class.add_to_class(mgr_name, new_manager) | ||||
|             new_class.copy_managers(base._meta.abstract_managers) | ||||
|  | ||||
|             # Proxy models inherit the non-abstract managers from their base, | ||||
|             # unless they have redefined any of them. | ||||
|             if is_proxy: | ||||
|                 new_class.copy_managers(base._meta.concrete_managers) | ||||
|  | ||||
|             # Inherit virtual fields (like GenericForeignKey) from the parent | ||||
|             # class | ||||
| @@ -160,6 +195,15 @@ class ModelBase(type): | ||||
|         # registered version. | ||||
|         return get_model(new_class._meta.app_label, name, False) | ||||
|  | ||||
|     def copy_managers(cls, base_managers): | ||||
|         # This is in-place sorting of an Options attribute, but that's fine. | ||||
|         base_managers.sort() | ||||
|         for _, mgr_name, manager in base_managers: | ||||
|             val = getattr(cls, mgr_name, None) | ||||
|             if not val or val is manager: | ||||
|                 new_manager = manager._copy_to_model(cls) | ||||
|                 cls.add_to_class(mgr_name, new_manager) | ||||
|  | ||||
|     def add_to_class(cls, name, value): | ||||
|         if hasattr(value, 'contribute_to_class'): | ||||
|             value.contribute_to_class(cls, name) | ||||
| @@ -358,12 +402,16 @@ class Model(object): | ||||
|                 # At this point, parent's primary key field may be unknown | ||||
|                 # (for example, from administration form which doesn't fill | ||||
|                 # this field). If so, fill it. | ||||
|                 if getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None: | ||||
|                 if field and getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None: | ||||
|                     setattr(self, parent._meta.pk.attname, getattr(self, field.attname)) | ||||
|  | ||||
|                 self.save_base(raw, parent) | ||||
|                 self.save_base(cls=parent) | ||||
|                 if field: | ||||
|                     setattr(self, field.attname, self._get_pk_val(parent._meta)) | ||||
|             if meta.proxy: | ||||
|                 return | ||||
|  | ||||
|         if not meta.proxy: | ||||
|             non_pks = [f for f in meta.local_fields if not f.primary_key] | ||||
|  | ||||
|             # First, try an UPDATE. If that doesn't update anything, do an INSERT. | ||||
|   | ||||
| @@ -60,6 +60,9 @@ class Manager(object): | ||||
|         if model._meta.abstract or self._inherited: | ||||
|             model._meta.abstract_managers.append((self.creation_counter, name, | ||||
|                     self)) | ||||
|         else: | ||||
|             model._meta.concrete_managers.append((self.creation_counter, name, | ||||
|                 self)) | ||||
|  | ||||
|     def _set_creation_counter(self): | ||||
|         """ | ||||
|   | ||||
| @@ -21,7 +21,7 @@ get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]| | ||||
| DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', | ||||
|                  'unique_together', 'permissions', 'get_latest_by', | ||||
|                  'order_with_respect_to', 'app_label', 'db_tablespace', | ||||
|                  'abstract', 'managed') | ||||
|                  'abstract', 'managed', 'proxy') | ||||
|  | ||||
| class Options(object): | ||||
|     def __init__(self, meta, app_label=None): | ||||
| @@ -43,11 +43,15 @@ class Options(object): | ||||
|         self.has_auto_field, self.auto_field = False, None | ||||
|         self.abstract = False | ||||
|         self.managed = True | ||||
|         self.proxy = False | ||||
|         self.proxy_for_model = None | ||||
|         self.parents = SortedDict() | ||||
|         self.duplicate_targets = {} | ||||
|         # Managers that have been inherited from abstract base classes. These | ||||
|         # are passed onto any children. | ||||
|  | ||||
|         # To handle various inheritance situations, we need to track where | ||||
|         # managers came from (concrete or abstract base classes). | ||||
|         self.abstract_managers = [] | ||||
|         self.concrete_managers = [] | ||||
|  | ||||
|     def contribute_to_class(self, cls, name): | ||||
|         from django.db import connection | ||||
| @@ -164,6 +168,15 @@ class Options(object): | ||||
|             self.pk = field | ||||
|             field.serialize = False | ||||
|  | ||||
|     def setup_proxy(self, target): | ||||
|         """ | ||||
|         Does the internal setup so that the current model is a proxy for | ||||
|         "target". | ||||
|         """ | ||||
|         self.pk = target._meta.pk | ||||
|         self.proxy_for_model = target | ||||
|         self.db_table = target._meta.db_table | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return '<Options for %s>' % self.object_name | ||||
|  | ||||
|   | ||||
| @@ -641,6 +641,7 @@ class BaseQuery(object): | ||||
|         qn = self.quote_name_unless_alias | ||||
|         qn2 = self.connection.ops.quote_name | ||||
|         aliases = set() | ||||
|         proxied_model = opts.proxy and opts.proxy_for_model or 0 | ||||
|         if start_alias: | ||||
|             seen = {None: start_alias} | ||||
|         for field, model in opts.get_fields_with_model(): | ||||
| @@ -648,6 +649,9 @@ class BaseQuery(object): | ||||
|                 try: | ||||
|                     alias = seen[model] | ||||
|                 except KeyError: | ||||
|                     if model is proxied_model: | ||||
|                         alias = start_alias | ||||
|                     else: | ||||
|                         link_field = opts.get_ancestor_link(model) | ||||
|                         alias = self.join((start_alias, model._meta.db_table, | ||||
|                                 link_field.column, model._meta.pk.column)) | ||||
| @@ -1158,8 +1162,12 @@ class BaseQuery(object): | ||||
|         opts = self.model._meta | ||||
|         root_alias = self.tables[0] | ||||
|         seen = {None: root_alias} | ||||
|         proxied_model = opts.proxy and opts.proxy_for_model or 0 | ||||
|         for field, model in opts.get_fields_with_model(): | ||||
|             if model not in seen: | ||||
|                 if model is proxied_model: | ||||
|                     seen[model] = root_alias | ||||
|                 else: | ||||
|                     link_field = opts.get_ancestor_link(model) | ||||
|                     seen[model] = self.join((root_alias, model._meta.db_table, | ||||
|                             link_field.column, model._meta.pk.column)) | ||||
| @@ -1559,7 +1567,11 @@ class BaseQuery(object): | ||||
|                 raise MultiJoin(pos + 1) | ||||
|             if model: | ||||
|                 # The field lives on a base class of the current model. | ||||
|                 proxied_model = opts.proxy and opts.proxy_for_model or 0 | ||||
|                 for int_model in opts.get_base_chain(model): | ||||
|                     if int_model is proxied_model: | ||||
|                         opts = int_model._meta | ||||
|                     else: | ||||
|                         lhs_col = opts.parents[int_model].column | ||||
|                         dedupe = lhs_col in opts.duplicate_targets | ||||
|                         if dedupe: | ||||
| @@ -1572,7 +1584,8 @@ class BaseQuery(object): | ||||
|                         joins.append(alias) | ||||
|                         exclusions.add(alias) | ||||
|                         for (dupe_opts, dupe_col) in dupe_set: | ||||
|                         self.update_dupe_avoidance(dupe_opts, dupe_col, alias) | ||||
|                             self.update_dupe_avoidance(dupe_opts, dupe_col, | ||||
|                                     alias) | ||||
|             cached_data = opts._join_cache.get(name) | ||||
|             orig_opts = opts | ||||
|             dupe_col = direct and field.column or field.field.column | ||||
|   | ||||
| @@ -162,6 +162,16 @@ that has ``admin`` set. This example specifies an extra permission, | ||||
| This is a list or tuple of 2-tuples in the format ``(permission_code, | ||||
| human_readable_permission_name)``. | ||||
|  | ||||
| ``proxy`` | ||||
| --------- | ||||
|  | ||||
| .. attribute:: Options.proxy | ||||
|  | ||||
| .. versionadded: 1.1 | ||||
|  | ||||
| If set to ``True``, a model which subclasses another model will be treated as | ||||
| a :ref:`proxy model <proxy-models>`. | ||||
|  | ||||
| ``unique_together`` | ||||
| ------------------- | ||||
|  | ||||
|   | ||||
| @@ -195,6 +195,8 @@ attribute on the manager class. This is documented fully below_. | ||||
|  | ||||
| .. _below: manager-types_ | ||||
|  | ||||
| .. _custom-managers-and-inheritance: | ||||
|  | ||||
| Custom managers and model inheritance | ||||
| ------------------------------------- | ||||
|  | ||||
|   | ||||
| @@ -773,13 +773,18 @@ is whether you want the parent models to be models in their own right | ||||
| 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 | ||||
| :ref:`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, :ref:`multi-table-inheritance` is the way to go. | ||||
| There are three styles of inheritance that are possible in Django. | ||||
|  | ||||
|  1. 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 :ref:`abstract-base-classes` are | ||||
|     what you're after. | ||||
|  2. If you're subclassing an existing model (perhaps something from another | ||||
|     application entirely) and want each model to have its own database table, | ||||
|     :ref:`multi-table-inheritance` is the way to go. | ||||
|  3. Finally, if you only want to modify the Python-level behaviour of a model, | ||||
|     without changing the models fields in any way, you can use | ||||
|     :ref:`proxy-models`. | ||||
|  | ||||
| .. _abstract-base-classes: | ||||
|  | ||||
| @@ -937,14 +942,16 @@ referring to ``p.restaurant`` would raise a Restaurant.DoesNotExist exception. | ||||
| In the multi-table inheritance situation, it doesn't make sense for a child | ||||
| class to inherit from its parent's :ref:`Meta <meta-options>` class. All the :ref:`Meta <meta-options>` 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 | ||||
| normally only lead to contradictory behavior (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 :ref:`Meta <meta-options>` class. However, | ||||
| there are a few limited cases where the child inherits behaviour from the | ||||
| parent: if the child does not specify an :attr:`django.db.models.Options.ordering` attribute or a | ||||
| :attr:`django.db.models.Options.get_latest_by` attribute, it will inherit these from its parent. | ||||
| So a child model does not have access to its parent's :ref:`Meta | ||||
| <meta-options>` class. However, there are a few limited cases where the child | ||||
| inherits behavior from the parent: if the child does not specify an | ||||
| :attr:`django.db.models.Options.ordering` attribute or a | ||||
| :attr:`django.db.models.Options.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 explicitly disable it:: | ||||
| @@ -990,6 +997,126 @@ own :class:`~django.db.models.fields.OneToOneField` and set | ||||
| :attr:`parent_link=True <django.db.models.fields.OneToOneField.parent_link>` | ||||
| to indicate that your field is the link back to the parent class. | ||||
|  | ||||
| .. _proxy-models: | ||||
|  | ||||
| Proxy models | ||||
| ------------ | ||||
|  | ||||
| .. versionadded:: 1.1 | ||||
|  | ||||
| When using :ref:`multi-table inheritance <multi-table-inheritance>`, a new | ||||
| database table is created for each subclass of a model. This is usually the | ||||
| desired behavior, since the subclass needs a place to store any additional | ||||
| data fields that are not present on the base class. Sometimes, however, you | ||||
| only want to change the Python behavior of a model -- perhaps to change the | ||||
| default manager, or add a new method. | ||||
|  | ||||
| This is what proxy model inheritance is for: creating a *proxy* for the | ||||
| original model. You can create, delete and update instances of the proxy model | ||||
| and all the data will be saved as if you were using the original (non-proxied) | ||||
| model. The difference is that you can change things like the default model | ||||
| ordering or the default manager in the proxy, without having to alter the | ||||
| original. | ||||
|  | ||||
| Proxy models are declared like normal models. You tell Django that it's a | ||||
| proxy model by setting the :attr:`~django.db.models.Options.proxy` attribute to of the ``Meta`` class to ``True``. | ||||
|  | ||||
| For example, suppose you want to add a method to the standard ``User`` model | ||||
| that will make be used in your templates. You can do it like this:: | ||||
|  | ||||
|     from django.contrib.auth.models import User | ||||
|  | ||||
|     class MyUser(User): | ||||
|         class Meta: | ||||
|             proxy = True | ||||
|  | ||||
|         def do_something(self): | ||||
|             ... | ||||
|  | ||||
| The ``MyUser`` class operates on the same database table as its parent | ||||
| ``User`` class. In particular, any new instances of ``User`` will also be | ||||
| accessible through ``MyUser``, and vice-versa:: | ||||
|  | ||||
|     >>> u = User.objects.create(username="foobar") | ||||
|     >>> MyUser.objects.get(username="foobar") | ||||
|     <MyUser: foobar> | ||||
|  | ||||
| You could also use a proxy model to define a different default ordering on a | ||||
| model. The standard ``User`` model has no ordering defined on it | ||||
| (intentionally; sorting is expensive and we don't want to do it all the time | ||||
| when we fetch users). You might want to regularly order by the ``username`` | ||||
| attribute when you use the proxy. This is easy:: | ||||
|  | ||||
|     class OrderedUser(User): | ||||
|         class Meta: | ||||
|             ordering = ["username"] | ||||
|             proxy = True | ||||
|  | ||||
| Now normal ``User`` queries will be unorderd and ``OrderedUser`` queries will | ||||
| be ordered by ``username``. | ||||
|  | ||||
| Querysets still return the model that was requested | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| There is no way to have Django return, say, a ``MyUser`` object whenever you | ||||
| query for ``User`` objects. A queryset for ``User`` objects will return those | ||||
| types of objects. The whole point of proxy objects is that code relying on the | ||||
| original ``User`` will use those and your own code can use the extensions you | ||||
| included (that no other code is relying on anyway). It is not a way to replace | ||||
| the ``User`` (or any other) model everywhere with something of your own | ||||
| creation. | ||||
|  | ||||
| Base class restrictions | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| A proxy model must inherit from exactly one non-abstract model class. You | ||||
| can't inherit from multiple non-abstract models as the proxy model doesn't | ||||
| provide any connection between the rows in the different database tables. A | ||||
| proxy model can inherit from any number of abstract model classes, providing | ||||
| they do *not* define any model fields. | ||||
|  | ||||
| Proxy models inherit any ``Meta`` options that they don't define from their | ||||
| non-abstract model parent (the model they are proxying for). | ||||
|  | ||||
| Proxy model managers | ||||
| ~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| If you don't specify any model managers on a proxy model, it inherits the | ||||
| managers from its model parents. If you define a manager on the proxy model, | ||||
| it will become the default, although any managers defined on the parent | ||||
| classes will still be available. | ||||
|  | ||||
| Continuing our example from above, you could change the default manager used | ||||
| when you query the ``User`` model like this:: | ||||
|  | ||||
|     class NewManager(models.Manager): | ||||
|         ... | ||||
|  | ||||
|     class MyUser(User): | ||||
|         objects = NewManager() | ||||
|  | ||||
|         class Meta: | ||||
|             proxy = True | ||||
|  | ||||
| If you wanted to add a new manager to the Proxy, without replacing the | ||||
| existing default, you can use the techniques described in the :ref:`custom | ||||
| manager <custom-managers-and-inheritance>` documentation: create a base class | ||||
| containing the new managers and inherit that after the primary base class:: | ||||
|  | ||||
|     # Create an abstract class for the new manager. | ||||
|     class ExtraManagers: | ||||
|         secondary = NewManager() | ||||
|  | ||||
|         class Meta: | ||||
|             abstract = True | ||||
|  | ||||
|     class MyUser(User, ExtraManagers): | ||||
|         class Meta: | ||||
|             proxy = True | ||||
|  | ||||
| You probably won't need to do this very often, but, when you do, it's | ||||
| possible. | ||||
|  | ||||
| Multiple inheritance | ||||
| -------------------- | ||||
|  | ||||
|   | ||||
							
								
								
									
										0
									
								
								tests/modeltests/proxy_models/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/modeltests/proxy_models/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										176
									
								
								tests/modeltests/proxy_models/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								tests/modeltests/proxy_models/models.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,176 @@ | ||||
| """ | ||||
| By specifying the 'proxy' Meta attribute, model subclasses can specify that | ||||
| they will take data directly from the table of their base class table rather | ||||
| than using a new table of their own. This allows them to act as simple proxies, | ||||
| providing a modified interface to the data from the base class. | ||||
| """ | ||||
|  | ||||
| from django.db import models | ||||
|  | ||||
|  | ||||
| # A couple of managers for testing managing overriding in proxy model cases. | ||||
|  | ||||
| class PersonManager(models.Manager): | ||||
|     def get_query_set(self): | ||||
|         return super(PersonManager, self).get_query_set().exclude(name="fred") | ||||
|  | ||||
| class SubManager(models.Manager): | ||||
|     def get_query_set(self): | ||||
|         return super(SubManager, self).get_query_set().exclude(name="wilma") | ||||
|  | ||||
| class Person(models.Model): | ||||
|     """ | ||||
|     A simple concrete base class. | ||||
|     """ | ||||
|     name = models.CharField(max_length=50) | ||||
|  | ||||
|     objects = PersonManager() | ||||
|  | ||||
|     def __unicode__(self): | ||||
|         return self.name | ||||
|  | ||||
| class Abstract(models.Model): | ||||
|     """ | ||||
|     A simple abstract base class, to be used for error checking. | ||||
|     """ | ||||
|     data = models.CharField(max_length=10) | ||||
|  | ||||
|     class Meta: | ||||
|         abstract = True | ||||
|  | ||||
| class MyPerson(Person): | ||||
|     """ | ||||
|     A proxy subclass, this should not get a new table. Overrides the default | ||||
|     manager. | ||||
|     """ | ||||
|     class Meta: | ||||
|         proxy = True | ||||
|         ordering = ["name"] | ||||
|  | ||||
|     objects = SubManager() | ||||
|     other = PersonManager() | ||||
|  | ||||
|     def has_special_name(self): | ||||
|         return self.name.lower() == "special" | ||||
|  | ||||
| class ManagerMixin(models.Model): | ||||
|     excluder = SubManager() | ||||
|  | ||||
|     class Meta: | ||||
|         abstract = True | ||||
|  | ||||
| class OtherPerson(Person, ManagerMixin): | ||||
|     """ | ||||
|     A class with the default manager from Person, plus an secondary manager. | ||||
|     """ | ||||
|     class Meta: | ||||
|         proxy = True | ||||
|         ordering = ["name"] | ||||
|  | ||||
| class StatusPerson(MyPerson): | ||||
|     """ | ||||
|     A non-proxy subclass of a proxy, it should get a new table. | ||||
|     """ | ||||
|     status = models.CharField(max_length=80) | ||||
|  | ||||
| # We can even have proxies of proxies (and subclass of those). | ||||
| class MyPersonProxy(MyPerson): | ||||
|     class Meta: | ||||
|         proxy = True | ||||
|  | ||||
| class LowerStatusPerson(MyPersonProxy): | ||||
|     status = models.CharField(max_length=80) | ||||
|  | ||||
| __test__ = {'API_TESTS' : """ | ||||
| # The MyPerson model should be generating the same database queries as the | ||||
| # Person model (when the same manager is used in each case). | ||||
| >>> MyPerson.other.all().query.as_sql() == Person.objects.order_by("name").query.as_sql() | ||||
| True | ||||
|  | ||||
| # The StatusPerson models should have its own table (it's using ORM-level | ||||
| # inheritance). | ||||
| >>> StatusPerson.objects.all().query.as_sql() == Person.objects.all().query.as_sql() | ||||
| False | ||||
|  | ||||
| # Creating a Person makes them accessible through the MyPerson proxy. | ||||
| >>> _ = Person.objects.create(name="Foo McBar") | ||||
| >>> len(Person.objects.all()) | ||||
| 1 | ||||
| >>> len(MyPerson.objects.all()) | ||||
| 1 | ||||
| >>> MyPerson.objects.get(name="Foo McBar").id | ||||
| 1 | ||||
| >>> MyPerson.objects.get(id=1).has_special_name() | ||||
| False | ||||
|  | ||||
| # Person is not proxied by StatusPerson subclass, however. | ||||
| >>> StatusPerson.objects.all() | ||||
| [] | ||||
|  | ||||
| # A new MyPerson also shows up as a standard Person | ||||
| >>> _ = MyPerson.objects.create(name="Bazza del Frob") | ||||
| >>> len(MyPerson.objects.all()) | ||||
| 2 | ||||
| >>> len(Person.objects.all()) | ||||
| 2 | ||||
|  | ||||
| >>> _ = LowerStatusPerson.objects.create(status="low", name="homer") | ||||
| >>> LowerStatusPerson.objects.all() | ||||
| [<LowerStatusPerson: homer>] | ||||
|  | ||||
| # And now for some things that shouldn't work... | ||||
| # | ||||
| # All base classes must be non-abstract | ||||
| >>> class NoAbstract(Abstract): | ||||
| ...     class Meta: | ||||
| ...         proxy = True | ||||
| Traceback (most recent call last): | ||||
|     .... | ||||
| TypeError: Abstract base class containing model fields not permitted for proxy model 'NoAbstract'. | ||||
|  | ||||
| # The proxy must actually have one concrete base class | ||||
| >>> class TooManyBases(Person, Abstract): | ||||
| ...     class Meta: | ||||
| ...         proxy = True | ||||
| Traceback (most recent call last): | ||||
|     .... | ||||
| TypeError: Abstract base class containing model fields not permitted for proxy model 'TooManyBases'. | ||||
|  | ||||
| >>> class NoBaseClasses(models.Model): | ||||
| ...     class Meta: | ||||
| ...         proxy = True | ||||
| Traceback (most recent call last): | ||||
|     .... | ||||
| TypeError: Proxy model 'NoBaseClasses' has no non-abstract model base class. | ||||
|  | ||||
|  | ||||
| # A proxy cannot introduce any new fields | ||||
| >>> class NoNewFields(Person): | ||||
| ...     newfield = models.BooleanField() | ||||
| ...     class Meta: | ||||
| ...         proxy = True | ||||
| Traceback (most recent call last): | ||||
|     .... | ||||
| FieldError: Proxy model 'NoNewFields' contains model fields. | ||||
|  | ||||
| # Manager tests. | ||||
|  | ||||
| >>> Person.objects.all().delete() | ||||
| >>> _ = Person.objects.create(name="fred") | ||||
| >>> _ = Person.objects.create(name="wilma") | ||||
| >>> _ = Person.objects.create(name="barney") | ||||
|  | ||||
| >>> MyPerson.objects.all() | ||||
| [<MyPerson: barney>, <MyPerson: fred>] | ||||
| >>> MyPerson._default_manager.all() | ||||
| [<MyPerson: barney>, <MyPerson: fred>] | ||||
|  | ||||
| >>> OtherPerson.objects.all() | ||||
| [<OtherPerson: barney>, <OtherPerson: wilma>] | ||||
| >>> OtherPerson.excluder.all() | ||||
| [<OtherPerson: barney>, <OtherPerson: fred>] | ||||
| >>> OtherPerson._default_manager.all() | ||||
| [<OtherPerson: barney>, <OtherPerson: wilma>] | ||||
| """} | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user