From 354c84d277e3a9516cd2af1567eb02cb4da1a3e4 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 22 Feb 2012 05:26:50 +0000 Subject: [PATCH] =?UTF-8?q?Fixed=20#17678=20--=20Corrected=20setup=20of=20?= =?UTF-8?q?=5Fmeta.proxy=5Ffor=5Fmodel=20and=20added=20=5Fmeta.concrete=5F?= =?UTF-8?q?model.=20Thanks=20Anssi=20K=C3=A4=C3=A4ri=C3=A4inen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: http://code.djangoproject.com/svn/django/trunk@17573 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/contenttypes/models.py | 6 +----- django/contrib/gis/db/models/sql/compiler.py | 3 +-- django/db/models/base.py | 9 ++++----- django/db/models/options.py | 9 +++++++++ django/db/models/sql/compiler.py | 4 ++-- django/db/models/sql/query.py | 19 ++++--------------- tests/modeltests/proxy_models/tests.py | 6 ++++++ 7 files changed, 27 insertions(+), 29 deletions(-) diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py index 6d919b4237..d588ff4d23 100644 --- a/django/contrib/contenttypes/models.py +++ b/django/contrib/contenttypes/models.py @@ -17,11 +17,7 @@ class ContentTypeManager(models.Manager): return ct def _get_opts(self, model): - opts = model._meta - while opts.proxy: - model = opts.proxy_for_model - opts = model._meta - return opts + return model._meta.concrete_model._meta def _get_from_cache(self, opts): key = (opts.app_label, opts.object_name.lower()) diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py index a7c3fbc406..07eea32b69 100644 --- a/django/contrib/gis/db/models/sql/compiler.py +++ b/django/contrib/gis/db/models/sql/compiler.py @@ -2,7 +2,6 @@ from itertools import izip from django.db.backends.util import truncate_name, typecast_timestamp from django.db.models.sql import compiler from django.db.models.sql.constants import TABLE_NAME, MULTI -from django.db.models.sql.query import get_proxied_model SQLCompiler = compiler.SQLCompiler @@ -116,7 +115,7 @@ class GeoSQLCompiler(compiler.SQLCompiler): aliases = set() only_load = self.deferred_to_columns() # Skip all proxy to the root proxied model - proxied_model = get_proxied_model(opts) + proxied_model = opts.concrete_model if start_alias: seen = {None: start_alias} diff --git a/django/db/models/base.py b/django/db/models/base.py index ebd67bef05..fc38224345 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -122,9 +122,10 @@ class ModelBase(type): if (new_class._meta.local_fields or new_class._meta.local_many_to_many): raise FieldError("Proxy model '%s' contains model fields." % name) - while base._meta.proxy: - base = base._meta.proxy_for_model new_class._meta.setup_proxy(base) + new_class._meta.concrete_model = base._meta.concrete_model + else: + new_class._meta.concrete_model = new_class # Do the appropriate setup for any model parents. o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields @@ -149,9 +150,7 @@ 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 + base = base._meta.concrete_model if base in o2o_map: field = o2o_map[base] elif not is_proxy: diff --git a/django/db/models/options.py b/django/db/models/options.py index 0cd52a3122..f68473de94 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -40,7 +40,16 @@ class Options(object): self.abstract = False self.managed = True self.proxy = False + # For any class which is a proxy (including automatically created + # classes for deferred object loading) the proxy_for_model tells + # which class this model is proxying. Note that proxy_for_model + # can create a chain of proxy models. For non-proxy models the + # variable is always None. self.proxy_for_model = None + # For any non-abstract class the concrete class is the model + # in the end of the proxy_for_model chain. In particular, for + # concrete models the concrete_model is always the class itself. + self.concrete_model = None self.parents = SortedDict() self.duplicate_targets = {} self.auto_created = False diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 72948f9cd7..6c516e2b21 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -7,7 +7,7 @@ from django.db.models.query_utils import select_related_descend from django.db.models.sql.constants import * from django.db.models.sql.datastructures import EmptyResultSet from django.db.models.sql.expressions import SQLEvaluator -from django.db.models.sql.query import get_proxied_model, get_order_dir, Query +from django.db.models.sql.query import get_order_dir, Query from django.db.utils import DatabaseError @@ -266,7 +266,7 @@ class SQLCompiler(object): aliases = set() only_load = self.deferred_to_columns() # Skip all proxy to the root proxied model - proxied_model = get_proxied_model(opts) + proxied_model = opts.concrete_model if start_alias: seen = {None: start_alias} diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index a78df343a7..693dde3b40 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -575,10 +575,7 @@ class Query(object): return orig_opts = self.model._meta seen = {} - if orig_opts.proxy: - must_include = {orig_opts.proxy_for_model: set([orig_opts.pk])} - else: - must_include = {self.model: set([orig_opts.pk])} + must_include = {orig_opts.concrete_model: set([orig_opts.pk])} for field_name in field_names: parts = field_name.split(LOOKUP_SEP) cur_model = self.model @@ -586,7 +583,7 @@ class Query(object): for name in parts[:-1]: old_model = cur_model source = opts.get_field_by_name(name)[0] - cur_model = opts.get_field_by_name(name)[0].rel.to + cur_model = source.rel.to opts = cur_model._meta # Even if we're "just passing through" this model, we must add # both the current model's pk and the related reference field @@ -946,7 +943,7 @@ class Query(object): seen = {None: root_alias} # Skip all proxy to the root proxied model - proxied_model = get_proxied_model(opts) + proxied_model = opts.concrete_model for field, model in opts.get_fields_with_model(): if model not in seen: @@ -1325,7 +1322,7 @@ class Query(object): if model: # The field lives on a base class of the current model. # Skip the chain of proxy to the concrete proxied model - proxied_model = get_proxied_model(opts) + proxied_model = opts.concrete_model for int_model in opts.get_base_chain(model): if int_model is proxied_model: @@ -1990,11 +1987,3 @@ def add_to_dict(data, key, value): data[key].add(value) else: data[key] = set([value]) - -def get_proxied_model(opts): - int_opts = opts - proxied_model = None - while int_opts.proxy: - proxied_model = int_opts.proxy_for_model - int_opts = proxied_model._meta - return proxied_model diff --git a/tests/modeltests/proxy_models/tests.py b/tests/modeltests/proxy_models/tests.py index 3ec8465689..b3321038db 100644 --- a/tests/modeltests/proxy_models/tests.py +++ b/tests/modeltests/proxy_models/tests.py @@ -232,6 +232,12 @@ class ProxyModelTests(TestCase): resp = [u.name for u in UserProxyProxy.objects.all()] self.assertEqual(resp, ['Bruce']) + def test_proxy_for_model(self): + self.assertEqual(UserProxy, UserProxyProxy._meta.proxy_for_model) + + def test_concrete_model(self): + self.assertEqual(User, UserProxyProxy._meta.concrete_model) + def test_proxy_delete(self): """ Proxy objects can be deleted