mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +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> |     Erik Karulf <erik@karulf.com> | ||||||
|     Ben Dean Kawamura <ben.dean.kawamura@gmail.com> |     Ben Dean Kawamura <ben.dean.kawamura@gmail.com> | ||||||
|     Ian G. Kelly <ian.g.kelly@gmail.com> |     Ian G. Kelly <ian.g.kelly@gmail.com> | ||||||
|  |     Ryan Kelly <ryan@rfk.id.au> | ||||||
|     Thomas Kerpe <thomas@kerpe.net> |     Thomas Kerpe <thomas@kerpe.net> | ||||||
|     Ossama M. Khayat <okhayat@yahoo.com> |     Ossama M. Khayat <okhayat@yahoo.com> | ||||||
|     Ben Khoo <khoobks@westnet.com.au> |     Ben Khoo <khoobks@westnet.com.au> | ||||||
|   | |||||||
| @@ -67,9 +67,19 @@ class ModelBase(type): | |||||||
|                 if not hasattr(meta, 'get_latest_by'): |                 if not hasattr(meta, 'get_latest_by'): | ||||||
|                     new_class._meta.get_latest_by = base_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 getattr(new_class, '_default_manager', None): | ||||||
|             new_class._default_manager = None |             if not is_proxy: | ||||||
|             new_class._base_manager = None |                 # 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. |         # Bail out early if we have already created this class. | ||||||
|         m = get_model(new_class._meta.app_label, name, False) |         m = get_model(new_class._meta.app_label, name, False) | ||||||
| @@ -80,21 +90,43 @@ class ModelBase(type): | |||||||
|         for obj_name, obj in attrs.items(): |         for obj_name, obj in attrs.items(): | ||||||
|             new_class.add_to_class(obj_name, obj) |             new_class.add_to_class(obj_name, obj) | ||||||
|  |  | ||||||
|  |         # 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. |         # Do the appropriate setup for any model parents. | ||||||
|         o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields |         o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields | ||||||
|                 if isinstance(f, OneToOneField)]) |                 if isinstance(f, OneToOneField)]) | ||||||
|  |  | ||||||
|         for base in parents: |         for base in parents: | ||||||
|             if not hasattr(base, '_meta'): |             if not hasattr(base, '_meta'): | ||||||
|                 # Things without _meta aren't functional models, so they're |                 # Things without _meta aren't functional models, so they're | ||||||
|                 # uninteresting parents. |                 # uninteresting parents. | ||||||
|                 continue |                 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]) |  | ||||||
|  |  | ||||||
|             parent_fields = base._meta.local_fields + base._meta.local_many_to_many |             parent_fields = base._meta.local_fields + base._meta.local_many_to_many | ||||||
|             # Check for clashes between locally declared fields and those |             # Check for clashes between locally declared fields and those | ||||||
|             # on the base classes (we cannot handle shadowed fields at the |             # on the base classes (we cannot handle shadowed fields at the | ||||||
| @@ -107,15 +139,19 @@ class ModelBase(type): | |||||||
|                                         (field.name, name, base.__name__)) |                                         (field.name, name, base.__name__)) | ||||||
|             if not base._meta.abstract: |             if not base._meta.abstract: | ||||||
|                 # Concrete classes... |                 # 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: |                 if base in o2o_map: | ||||||
|                     field = o2o_map[base] |                     field = o2o_map[base] | ||||||
|                 else: |                 elif not is_proxy: | ||||||
|                     attr_name = '%s_ptr' % base._meta.module_name |                     attr_name = '%s_ptr' % base._meta.module_name | ||||||
|                     field = OneToOneField(base, name=attr_name, |                     field = OneToOneField(base, name=attr_name, | ||||||
|                             auto_created=True, parent_link=True) |                             auto_created=True, parent_link=True) | ||||||
|                     new_class.add_to_class(attr_name, field) |                     new_class.add_to_class(attr_name, field) | ||||||
|  |                 else: | ||||||
|  |                     field = None | ||||||
|                 new_class._meta.parents[base] = field |                 new_class._meta.parents[base] = field | ||||||
|  |  | ||||||
|             else: |             else: | ||||||
|                 # .. and abstract ones. |                 # .. and abstract ones. | ||||||
|                 for field in parent_fields: |                 for field in parent_fields: | ||||||
| @@ -125,13 +161,12 @@ class ModelBase(type): | |||||||
|                 new_class._meta.parents.update(base._meta.parents) |                 new_class._meta.parents.update(base._meta.parents) | ||||||
|  |  | ||||||
|             # Inherit managers from the abstract base classes. |             # Inherit managers from the abstract base classes. | ||||||
|             base_managers = base._meta.abstract_managers |             new_class.copy_managers(base._meta.abstract_managers) | ||||||
|             base_managers.sort() |  | ||||||
|             for _, mgr_name, manager in base_managers: |             # Proxy models inherit the non-abstract managers from their base, | ||||||
|                 val = getattr(new_class, mgr_name, None) |             # unless they have redefined any of them. | ||||||
|                 if not val or val is manager: |             if is_proxy: | ||||||
|                     new_manager = manager._copy_to_model(new_class) |                 new_class.copy_managers(base._meta.concrete_managers) | ||||||
|                     new_class.add_to_class(mgr_name, new_manager) |  | ||||||
|  |  | ||||||
|             # Inherit virtual fields (like GenericForeignKey) from the parent |             # Inherit virtual fields (like GenericForeignKey) from the parent | ||||||
|             # class |             # class | ||||||
| @@ -160,6 +195,15 @@ class ModelBase(type): | |||||||
|         # registered version. |         # registered version. | ||||||
|         return get_model(new_class._meta.app_label, name, False) |         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): |     def add_to_class(cls, name, value): | ||||||
|         if hasattr(value, 'contribute_to_class'): |         if hasattr(value, 'contribute_to_class'): | ||||||
|             value.contribute_to_class(cls, name) |             value.contribute_to_class(cls, name) | ||||||
| @@ -358,55 +402,59 @@ class Model(object): | |||||||
|                 # At this point, parent's primary key field may be unknown |                 # At this point, parent's primary key field may be unknown | ||||||
|                 # (for example, from administration form which doesn't fill |                 # (for example, from administration form which doesn't fill | ||||||
|                 # this field). If so, fill it. |                 # 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)) |                     setattr(self, parent._meta.pk.attname, getattr(self, field.attname)) | ||||||
|  |  | ||||||
|                 self.save_base(raw, parent) |                 self.save_base(cls=parent) | ||||||
|                 setattr(self, field.attname, self._get_pk_val(parent._meta)) |                 if field: | ||||||
|  |                     setattr(self, field.attname, self._get_pk_val(parent._meta)) | ||||||
|  |             if meta.proxy: | ||||||
|  |                 return | ||||||
|  |  | ||||||
|         non_pks = [f for f in meta.local_fields if not f.primary_key] |         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. |             # First, try an UPDATE. If that doesn't update anything, do an INSERT. | ||||||
|         pk_val = self._get_pk_val(meta) |             pk_val = self._get_pk_val(meta) | ||||||
|         pk_set = pk_val is not None |             pk_set = pk_val is not None | ||||||
|         record_exists = True |             record_exists = True | ||||||
|         manager = cls._base_manager |             manager = cls._base_manager | ||||||
|         if pk_set: |             if pk_set: | ||||||
|             # Determine whether a record with the primary key already exists. |                 # Determine whether a record with the primary key already exists. | ||||||
|             if (force_update or (not force_insert and |                 if (force_update or (not force_insert and | ||||||
|                     manager.filter(pk=pk_val).extra(select={'a': 1}).values('a').order_by())): |                         manager.filter(pk=pk_val).extra(select={'a': 1}).values('a').order_by())): | ||||||
|                 # It does already exist, so do an UPDATE. |                     # It does already exist, so do an UPDATE. | ||||||
|                 if force_update or non_pks: |                     if force_update or non_pks: | ||||||
|                     values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks] |                         values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks] | ||||||
|                     rows = manager.filter(pk=pk_val)._update(values) |                         rows = manager.filter(pk=pk_val)._update(values) | ||||||
|                     if force_update and not rows: |                         if force_update and not rows: | ||||||
|                         raise DatabaseError("Forced update did not affect any rows.") |                             raise DatabaseError("Forced update did not affect any rows.") | ||||||
|             else: |                 else: | ||||||
|  |                     record_exists = False | ||||||
|  |             if not pk_set or not record_exists: | ||||||
|  |                 if not pk_set: | ||||||
|  |                     if force_update: | ||||||
|  |                         raise ValueError("Cannot force an update in save() with no primary key.") | ||||||
|  |                     values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, AutoField)] | ||||||
|  |                 else: | ||||||
|  |                     values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields] | ||||||
|  |  | ||||||
|  |                 if meta.order_with_respect_to: | ||||||
|  |                     field = meta.order_with_respect_to | ||||||
|  |                     values.append((meta.get_field_by_name('_order')[0], manager.filter(**{field.name: getattr(self, field.attname)}).count())) | ||||||
|                 record_exists = False |                 record_exists = False | ||||||
|         if not pk_set or not record_exists: |  | ||||||
|             if not pk_set: |  | ||||||
|                 if force_update: |  | ||||||
|                     raise ValueError("Cannot force an update in save() with no primary key.") |  | ||||||
|                 values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, AutoField)] |  | ||||||
|             else: |  | ||||||
|                 values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields] |  | ||||||
|  |  | ||||||
|             if meta.order_with_respect_to: |                 update_pk = bool(meta.has_auto_field and not pk_set) | ||||||
|                 field = meta.order_with_respect_to |                 if values: | ||||||
|                 values.append((meta.get_field_by_name('_order')[0], manager.filter(**{field.name: getattr(self, field.attname)}).count())) |                     # Create a new record. | ||||||
|             record_exists = False |                     result = manager._insert(values, return_id=update_pk) | ||||||
|  |                 else: | ||||||
|  |                     # Create a new record with defaults for everything. | ||||||
|  |                     result = manager._insert([(meta.pk, connection.ops.pk_default_value())], return_id=update_pk, raw_values=True) | ||||||
|  |  | ||||||
|             update_pk = bool(meta.has_auto_field and not pk_set) |                 if update_pk: | ||||||
|             if values: |                     setattr(self, meta.pk.attname, result) | ||||||
|                 # Create a new record. |             transaction.commit_unless_managed() | ||||||
|                 result = manager._insert(values, return_id=update_pk) |  | ||||||
|             else: |  | ||||||
|                 # Create a new record with defaults for everything. |  | ||||||
|                 result = manager._insert([(meta.pk, connection.ops.pk_default_value())], return_id=update_pk, raw_values=True) |  | ||||||
|  |  | ||||||
|             if update_pk: |  | ||||||
|                 setattr(self, meta.pk.attname, result) |  | ||||||
|         transaction.commit_unless_managed() |  | ||||||
|  |  | ||||||
|         if signal: |         if signal: | ||||||
|             signals.post_save.send(sender=self.__class__, instance=self, |             signals.post_save.send(sender=self.__class__, instance=self, | ||||||
|   | |||||||
| @@ -60,6 +60,9 @@ class Manager(object): | |||||||
|         if model._meta.abstract or self._inherited: |         if model._meta.abstract or self._inherited: | ||||||
|             model._meta.abstract_managers.append((self.creation_counter, name, |             model._meta.abstract_managers.append((self.creation_counter, name, | ||||||
|                     self)) |                     self)) | ||||||
|  |         else: | ||||||
|  |             model._meta.concrete_managers.append((self.creation_counter, name, | ||||||
|  |                 self)) | ||||||
|  |  | ||||||
|     def _set_creation_counter(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', | DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', | ||||||
|                  'unique_together', 'permissions', 'get_latest_by', |                  'unique_together', 'permissions', 'get_latest_by', | ||||||
|                  'order_with_respect_to', 'app_label', 'db_tablespace', |                  'order_with_respect_to', 'app_label', 'db_tablespace', | ||||||
|                  'abstract', 'managed') |                  'abstract', 'managed', 'proxy') | ||||||
|  |  | ||||||
| class Options(object): | class Options(object): | ||||||
|     def __init__(self, meta, app_label=None): |     def __init__(self, meta, app_label=None): | ||||||
| @@ -43,11 +43,15 @@ class Options(object): | |||||||
|         self.has_auto_field, self.auto_field = False, None |         self.has_auto_field, self.auto_field = False, None | ||||||
|         self.abstract = False |         self.abstract = False | ||||||
|         self.managed = True |         self.managed = True | ||||||
|  |         self.proxy = False | ||||||
|  |         self.proxy_for_model = None | ||||||
|         self.parents = SortedDict() |         self.parents = SortedDict() | ||||||
|         self.duplicate_targets = {} |         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.abstract_managers = [] | ||||||
|  |         self.concrete_managers = [] | ||||||
|  |  | ||||||
|     def contribute_to_class(self, cls, name): |     def contribute_to_class(self, cls, name): | ||||||
|         from django.db import connection |         from django.db import connection | ||||||
| @@ -164,6 +168,15 @@ class Options(object): | |||||||
|             self.pk = field |             self.pk = field | ||||||
|             field.serialize = False |             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): |     def __repr__(self): | ||||||
|         return '<Options for %s>' % self.object_name |         return '<Options for %s>' % self.object_name | ||||||
|  |  | ||||||
|   | |||||||
| @@ -641,6 +641,7 @@ class BaseQuery(object): | |||||||
|         qn = self.quote_name_unless_alias |         qn = self.quote_name_unless_alias | ||||||
|         qn2 = self.connection.ops.quote_name |         qn2 = self.connection.ops.quote_name | ||||||
|         aliases = set() |         aliases = set() | ||||||
|  |         proxied_model = opts.proxy and opts.proxy_for_model or 0 | ||||||
|         if start_alias: |         if start_alias: | ||||||
|             seen = {None: start_alias} |             seen = {None: start_alias} | ||||||
|         for field, model in opts.get_fields_with_model(): |         for field, model in opts.get_fields_with_model(): | ||||||
| @@ -648,9 +649,12 @@ class BaseQuery(object): | |||||||
|                 try: |                 try: | ||||||
|                     alias = seen[model] |                     alias = seen[model] | ||||||
|                 except KeyError: |                 except KeyError: | ||||||
|                     link_field = opts.get_ancestor_link(model) |                     if model is proxied_model: | ||||||
|                     alias = self.join((start_alias, model._meta.db_table, |                         alias = start_alias | ||||||
|                             link_field.column, model._meta.pk.column)) |                     else: | ||||||
|  |                         link_field = opts.get_ancestor_link(model) | ||||||
|  |                         alias = self.join((start_alias, model._meta.db_table, | ||||||
|  |                                 link_field.column, model._meta.pk.column)) | ||||||
|                     seen[model] = alias |                     seen[model] = alias | ||||||
|             else: |             else: | ||||||
|                 # If we're starting from the base model of the queryset, the |                 # If we're starting from the base model of the queryset, the | ||||||
| @@ -1158,11 +1162,15 @@ class BaseQuery(object): | |||||||
|         opts = self.model._meta |         opts = self.model._meta | ||||||
|         root_alias = self.tables[0] |         root_alias = self.tables[0] | ||||||
|         seen = {None: root_alias} |         seen = {None: root_alias} | ||||||
|  |         proxied_model = opts.proxy and opts.proxy_for_model or 0 | ||||||
|         for field, model in opts.get_fields_with_model(): |         for field, model in opts.get_fields_with_model(): | ||||||
|             if model not in seen: |             if model not in seen: | ||||||
|                 link_field = opts.get_ancestor_link(model) |                 if model is proxied_model: | ||||||
|                 seen[model] = self.join((root_alias, model._meta.db_table, |                     seen[model] = root_alias | ||||||
|                         link_field.column, model._meta.pk.column)) |                 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)) | ||||||
|         self.included_inherited_models = seen |         self.included_inherited_models = seen | ||||||
|  |  | ||||||
|     def remove_inherited_models(self): |     def remove_inherited_models(self): | ||||||
| @@ -1559,20 +1567,25 @@ class BaseQuery(object): | |||||||
|                 raise MultiJoin(pos + 1) |                 raise MultiJoin(pos + 1) | ||||||
|             if model: |             if model: | ||||||
|                 # The field lives on a base class of the current 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): |                 for int_model in opts.get_base_chain(model): | ||||||
|                     lhs_col = opts.parents[int_model].column |                     if int_model is proxied_model: | ||||||
|                     dedupe = lhs_col in opts.duplicate_targets |                         opts = int_model._meta | ||||||
|                     if dedupe: |                     else: | ||||||
|                         exclusions.update(self.dupe_avoidance.get( |                         lhs_col = opts.parents[int_model].column | ||||||
|                                 (id(opts), lhs_col), ())) |                         dedupe = lhs_col in opts.duplicate_targets | ||||||
|                         dupe_set.add((opts, lhs_col)) |                         if dedupe: | ||||||
|                     opts = int_model._meta |                             exclusions.update(self.dupe_avoidance.get( | ||||||
|                     alias = self.join((alias, opts.db_table, lhs_col, |                                     (id(opts), lhs_col), ())) | ||||||
|                             opts.pk.column), exclusions=exclusions) |                             dupe_set.add((opts, lhs_col)) | ||||||
|                     joins.append(alias) |                         opts = int_model._meta | ||||||
|                     exclusions.add(alias) |                         alias = self.join((alias, opts.db_table, lhs_col, | ||||||
|                     for (dupe_opts, dupe_col) in dupe_set: |                                 opts.pk.column), exclusions=exclusions) | ||||||
|                         self.update_dupe_avoidance(dupe_opts, dupe_col, alias) |                         joins.append(alias) | ||||||
|  |                         exclusions.add(alias) | ||||||
|  |                         for (dupe_opts, dupe_col) in dupe_set: | ||||||
|  |                             self.update_dupe_avoidance(dupe_opts, dupe_col, | ||||||
|  |                                     alias) | ||||||
|             cached_data = opts._join_cache.get(name) |             cached_data = opts._join_cache.get(name) | ||||||
|             orig_opts = opts |             orig_opts = opts | ||||||
|             dupe_col = direct and field.column or field.field.column |             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, | This is a list or tuple of 2-tuples in the format ``(permission_code, | ||||||
| human_readable_permission_name)``. | 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`` | ``unique_together`` | ||||||
| ------------------- | ------------------- | ||||||
|  |  | ||||||
|   | |||||||
| @@ -195,6 +195,8 @@ attribute on the manager class. This is documented fully below_. | |||||||
|  |  | ||||||
| .. _below: manager-types_ | .. _below: manager-types_ | ||||||
|  |  | ||||||
|  | .. _custom-managers-and-inheritance: | ||||||
|  |  | ||||||
| Custom managers and model 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 | of common information that will only be visible through the child | ||||||
| models. | models. | ||||||
|  |  | ||||||
| Often, you will just want to use the parent class to hold information | There are three styles of inheritance that are possible in Django. | ||||||
| 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 |  1. Often, you will just want to use the parent class to hold information that | ||||||
| :ref:`abstract-base-classes` are what you're after. However, if you're |     you don't want to have to type out for each child model. This class isn't | ||||||
| subclassing an existing model (perhaps something from another |     going to ever be used in isolation, so :ref:`abstract-base-classes` are | ||||||
| application entirely), or want each model to have its own database |     what you're after. | ||||||
| table, :ref:`multi-table-inheritance` is the way to go. |  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: | .. _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 | 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 | 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 | 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 | abstract base class case, where the base class doesn't exist in its own | ||||||
| right). | right). | ||||||
|  |  | ||||||
| So a child model does not have access to its parent's :ref:`Meta <meta-options>` class. However, | So a child model does not have access to its parent's :ref:`Meta | ||||||
| there are a few limited cases where the child inherits behaviour from the | <meta-options>` class. However, there are a few limited cases where the child | ||||||
| parent: if the child does not specify an :attr:`django.db.models.Options.ordering` attribute or a | inherits behavior from the parent: if the child does not specify an | ||||||
| :attr:`django.db.models.Options.get_latest_by` attribute, it will inherit these from its parent. | :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 | If the parent has an ordering and you don't want the child to have any natural | ||||||
| ordering, you can explicitly disable it:: | 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>` | :attr:`parent_link=True <django.db.models.fields.OneToOneField.parent_link>` | ||||||
| to indicate that your field is the link back to the parent class. | 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 | 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