Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #10953, #10955: proxies of proxies now work correctly, though I…

… still don't quite understand why you'd want to do such a thing. Thanks, Armin Ronacher.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@10738 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit baaf29895c41cfafcd130d5a8f5ab548e5e9c308 1 parent 2b0903b
@jacobian jacobian authored
View
5 django/db/models/base.py
@@ -116,6 +116,8 @@ def __new__(cls, name, bases, attrs):
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)
# Do the appropriate setup for any model parents.
@@ -123,6 +125,7 @@ def __new__(cls, name, bases, attrs):
if isinstance(f, OneToOneField)])
for base in parents:
+ original_base = base
if not hasattr(base, '_meta'):
# Things without _meta aren't functional models, so they're
# uninteresting parents.
@@ -167,7 +170,7 @@ def __new__(cls, name, bases, attrs):
# 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)
+ new_class.copy_managers(original_base._meta.concrete_managers)
# Inherit virtual fields (like GenericForeignKey) from the parent
# class
View
2  django/db/models/manager.py
@@ -57,7 +57,7 @@ def contribute_to_class(self, model, name):
setattr(model, name, ManagerDescriptor(self))
if not getattr(model, '_default_manager', None) or self.creation_counter < model._default_manager.creation_counter:
model._default_manager = self
- if model._meta.abstract or self._inherited:
+ if model._meta.abstract or (self._inherited and not self.model._meta.proxy):
model._meta.abstract_managers.append((self.creation_counter, name,
self))
else:
View
9 django/db/models/options.py
@@ -461,8 +461,13 @@ def get_ancestor_link(self, ancestor):
if ancestor in self.parents:
return self.parents[ancestor]
for parent in self.parents:
- if parent._meta.get_ancestor_link(ancestor):
- return self.parents[parent]
+ # Tries to get a link field from the immediate parent
+ parent_link = parent._meta.get_ancestor_link(ancestor)
+ if parent_link:
+ # In case of a proxied model, the first link
+ # of the chain to the ancestor is that parent
+ # links
+ return self.parents[parent] or parent_link
def get_ordered_objects(self):
"Returns a list of Options objects that are ordered with respect to this object."
View
28 django/db/models/sql/query.py
@@ -778,7 +778,9 @@ def get_default_columns(self, with_aliases=False, col_aliases=None,
qn2 = self.connection.ops.quote_name
aliases = set()
only_load = self.deferred_to_columns()
- proxied_model = opts.proxy and opts.proxy_for_model or 0
+ # Skip all proxy to the root proxied model
+ proxied_model = get_proxied_model(opts)
+
if start_alias:
seen = {None: start_alias}
for field, model in opts.get_fields_with_model():
@@ -1301,7 +1303,10 @@ def setup_inherited_models(self):
opts = self.model._meta
root_alias = self.tables[0]
seen = {None: root_alias}
- proxied_model = opts.proxy and opts.proxy_for_model or 0
+
+ # Skip all proxy to the root proxied model
+ proxied_model = get_proxied_model(opts)
+
for field, model in opts.get_fields_with_model():
if model not in seen:
if model is proxied_model:
@@ -1376,6 +1381,13 @@ def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1,
alias = root_alias
alias_chain = []
for int_model in opts.get_base_chain(model):
+ # Proxy model have elements in base chain
+ # with no parents, assign the new options
+ # object and skip to the next base in that
+ # case
+ if not int_opts.parents[int_model]:
+ int_opts = int_model._meta
+ continue
lhs_col = int_opts.parents[int_model].column
dedupe = lhs_col in opts.duplicate_targets
if dedupe:
@@ -1720,7 +1732,9 @@ def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True,
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
+ # Skip the chain of proxy to the concrete proxied model
+ proxied_model = get_proxied_model(opts)
+
for int_model in opts.get_base_chain(model):
if int_model is proxied_model:
opts = int_model._meta
@@ -2423,3 +2437,11 @@ 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
View
142 tests/modeltests/proxy_models/models.py
@@ -82,6 +82,87 @@ class Meta:
class LowerStatusPerson(MyPersonProxy):
status = models.CharField(max_length=80)
+class User(models.Model):
+ name = models.CharField(max_length=100)
+
+ def __unicode__(self):
+ return self.name
+
+class UserProxy(User):
+ class Meta:
+ proxy = True
+
+class UserProxyProxy(UserProxy):
+ class Meta:
+ proxy = True
+
+# We can still use `select_related()` to include related models in our querysets.
+class Country(models.Model):
+ name = models.CharField(max_length=50)
+
+class State(models.Model):
+ name = models.CharField(max_length=50)
+ country = models.ForeignKey(Country)
+
+ def __unicode__(self):
+ return self.name
+
+class StateProxy(State):
+ class Meta:
+ proxy = True
+
+# Proxy models still works with filters (on related fields)
+# and select_related, even when mixed with model inheritance
+class BaseUser(models.Model):
+ name = models.CharField(max_length=255)
+
+class TrackerUser(BaseUser):
+ status = models.CharField(max_length=50)
+
+class ProxyTrackerUser(TrackerUser):
+ class Meta:
+ proxy = True
+
+
+class Issue(models.Model):
+ summary = models.CharField(max_length=255)
+ assignee = models.ForeignKey(TrackerUser)
+
+ def __unicode__(self):
+ return ':'.join((self.__class__.__name__,self.summary,))
+
+class Bug(Issue):
+ version = models.CharField(max_length=50)
+ reporter = models.ForeignKey(BaseUser)
+
+class ProxyBug(Bug):
+ """
+ Proxy of an inherited class
+ """
+ class Meta:
+ proxy = True
+
+
+class ProxyProxyBug(ProxyBug):
+ """
+ A proxy of proxy model with related field
+ """
+ class Meta:
+ proxy = True
+
+class Improvement(Issue):
+ """
+ A model that has relation to a proxy model
+ or to a proxy of proxy model
+ """
+ version = models.CharField(max_length=50)
+ reporter = models.ForeignKey(ProxyTrackerUser)
+ associated_bug = models.ForeignKey(ProxyProxyBug)
+
+class ProxyImprovement(Improvement):
+ class Meta:
+ proxy = True
+
__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).
@@ -119,6 +200,11 @@ class LowerStatusPerson(MyPersonProxy):
>>> LowerStatusPerson.objects.all()
[<LowerStatusPerson: homer>]
+# Correct type when querying a proxy of proxy
+
+>>> MyPersonProxy.objects.all()
+[<MyPersonProxy: Bazza del Frob>, <MyPersonProxy: Foo McBar>, <MyPersonProxy: homer>]
+
# And now for some things that shouldn't work...
#
# All base classes must be non-abstract
@@ -178,6 +264,58 @@ class LowerStatusPerson(MyPersonProxy):
>>> ctype = ContentType.objects.get_for_model
>>> ctype(Person) is ctype(OtherPerson)
True
-"""}
-
+>>> MyPersonProxy.objects.all()
+[<MyPersonProxy: barney>, <MyPersonProxy: fred>]
+
+>>> u = User.objects.create(name='Bruce')
+>>> User.objects.all()
+[<User: Bruce>]
+>>> UserProxy.objects.all()
+[<UserProxy: Bruce>]
+>>> UserProxyProxy.objects.all()
+[<UserProxyProxy: Bruce>]
+
+# We can still use `select_related()` to include related models in our querysets.
+>>> country = Country.objects.create(name='Australia')
+>>> state = State.objects.create(name='New South Wales', country=country)
+
+>>> State.objects.select_related()
+[<State: New South Wales>]
+>>> StateProxy.objects.select_related()
+[<StateProxy: New South Wales>]
+>>> StateProxy.objects.get(name='New South Wales')
+<StateProxy: New South Wales>
+>>> StateProxy.objects.select_related().get(name='New South Wales')
+<StateProxy: New South Wales>
+
+>>> contributor = TrackerUser.objects.create(name='Contributor',status='contrib')
+>>> someone = BaseUser.objects.create(name='Someone')
+>>> _ = Bug.objects.create(summary='fix this', version='1.1beta',
+... assignee=contributor, reporter=someone)
+>>> pcontributor = ProxyTrackerUser.objects.create(name='OtherContributor',
+... status='proxy')
+>>> _ = Improvement.objects.create(summary='improve that', version='1.1beta',
+... assignee=contributor, reporter=pcontributor,
+... associated_bug=ProxyProxyBug.objects.all()[0])
+
+# Related field filter on proxy
+>>> ProxyBug.objects.get(version__icontains='beta')
+<ProxyBug: ProxyBug:fix this>
+
+# Select related + filter on proxy
+>>> ProxyBug.objects.select_related().get(version__icontains='beta')
+<ProxyBug: ProxyBug:fix this>
+
+# Proxy of proxy, select_related + filter
+>>> ProxyProxyBug.objects.select_related().get(version__icontains='beta')
+<ProxyProxyBug: ProxyProxyBug:fix this>
+
+# Select related + filter on a related proxy field
+>>> ProxyImprovement.objects.select_related().get(reporter__name__icontains='butor')
+<ProxyImprovement: ProxyImprovement:improve that>
+
+# Select related + filter on a related proxy of proxy field
+>>> ProxyImprovement.objects.select_related().get(associated_bug__summary__icontains='fix')
+<ProxyImprovement: ProxyImprovement:improve that>
+"""}
Please sign in to comment.
Something went wrong with that request. Please try again.