Skip to content

Commit

Permalink
Fixed #17678 -- Corrected setup of _meta.proxy_for_model and added _m…
Browse files Browse the repository at this point in the history
…eta.concrete_model. Thanks Anssi Kääriäinen.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17573 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
carljm committed Feb 22, 2012
1 parent 3ac0961 commit 354c84d
Show file tree
Hide file tree
Showing 7 changed files with 27 additions and 29 deletions.
6 changes: 1 addition & 5 deletions django/contrib/contenttypes/models.py
Expand Up @@ -17,11 +17,7 @@ def get_by_natural_key(self, app_label, model):
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())
Expand Down
3 changes: 1 addition & 2 deletions django/contrib/gis/db/models/sql/compiler.py
Expand Up @@ -2,7 +2,6 @@
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

Expand Down Expand Up @@ -116,7 +115,7 @@ def get_default_columns(self, with_aliases=False, col_aliases=None,
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}
Expand Down
9 changes: 4 additions & 5 deletions django/db/models/base.py
Expand Up @@ -122,9 +122,10 @@ def __new__(cls, name, bases, attrs):
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
Expand All @@ -149,9 +150,7 @@ def __new__(cls, name, bases, attrs):
(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:
Expand Down
9 changes: 9 additions & 0 deletions django/db/models/options.py
Expand Up @@ -40,7 +40,16 @@ def __init__(self, meta, app_label=None):
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
Expand Down
4 changes: 2 additions & 2 deletions django/db/models/sql/compiler.py
Expand Up @@ -7,7 +7,7 @@
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


Expand Down Expand Up @@ -266,7 +266,7 @@ def get_default_columns(self, with_aliases=False, col_aliases=None,
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}
Expand Down
19 changes: 4 additions & 15 deletions django/db/models/sql/query.py
Expand Up @@ -575,18 +575,15 @@ def deferred_to_data(self, target, callback):
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
opts = orig_opts
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
Expand Down Expand Up @@ -946,7 +943,7 @@ def setup_inherited_models(self):
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:
Expand Down Expand Up @@ -1325,7 +1322,7 @@ def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True,
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:
Expand Down Expand Up @@ -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
6 changes: 6 additions & 0 deletions tests/modeltests/proxy_models/tests.py
Expand Up @@ -232,6 +232,12 @@ def test_user_userproxy_userproxyproxy(self):
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
Expand Down

0 comments on commit 354c84d

Please sign in to comment.