mirror of https://github.com/django/django.git
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
This commit is contained in:
parent
2b0903b2c4
commit
baaf29895c
|
@ -116,6 +116,8 @@ class ModelBase(type):
|
||||||
new_class._meta.local_many_to_many):
|
new_class._meta.local_many_to_many):
|
||||||
raise FieldError("Proxy model '%s' contains model fields."
|
raise FieldError("Proxy model '%s' contains model fields."
|
||||||
% name)
|
% name)
|
||||||
|
while base._meta.proxy:
|
||||||
|
base = base._meta.proxy_for_model
|
||||||
new_class._meta.setup_proxy(base)
|
new_class._meta.setup_proxy(base)
|
||||||
|
|
||||||
# Do the appropriate setup for any model parents.
|
# Do the appropriate setup for any model parents.
|
||||||
|
@ -123,6 +125,7 @@ class ModelBase(type):
|
||||||
if isinstance(f, OneToOneField)])
|
if isinstance(f, OneToOneField)])
|
||||||
|
|
||||||
for base in parents:
|
for base in parents:
|
||||||
|
original_base = base
|
||||||
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.
|
||||||
|
@ -167,7 +170,7 @@ class ModelBase(type):
|
||||||
# Proxy models inherit the non-abstract managers from their base,
|
# Proxy models inherit the non-abstract managers from their base,
|
||||||
# unless they have redefined any of them.
|
# unless they have redefined any of them.
|
||||||
if is_proxy:
|
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
|
# Inherit virtual fields (like GenericForeignKey) from the parent
|
||||||
# class
|
# class
|
||||||
|
|
|
@ -57,7 +57,7 @@ class Manager(object):
|
||||||
setattr(model, name, ManagerDescriptor(self))
|
setattr(model, name, ManagerDescriptor(self))
|
||||||
if not getattr(model, '_default_manager', None) or self.creation_counter < model._default_manager.creation_counter:
|
if not getattr(model, '_default_manager', None) or self.creation_counter < model._default_manager.creation_counter:
|
||||||
model._default_manager = self
|
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,
|
model._meta.abstract_managers.append((self.creation_counter, name,
|
||||||
self))
|
self))
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -461,8 +461,13 @@ class Options(object):
|
||||||
if ancestor in self.parents:
|
if ancestor in self.parents:
|
||||||
return self.parents[ancestor]
|
return self.parents[ancestor]
|
||||||
for parent in self.parents:
|
for parent in self.parents:
|
||||||
if parent._meta.get_ancestor_link(ancestor):
|
# Tries to get a link field from the immediate parent
|
||||||
return self.parents[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):
|
def get_ordered_objects(self):
|
||||||
"Returns a list of Options objects that are ordered with respect to this object."
|
"Returns a list of Options objects that are ordered with respect to this object."
|
||||||
|
|
|
@ -778,7 +778,9 @@ class BaseQuery(object):
|
||||||
qn2 = self.connection.ops.quote_name
|
qn2 = self.connection.ops.quote_name
|
||||||
aliases = set()
|
aliases = set()
|
||||||
only_load = self.deferred_to_columns()
|
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:
|
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():
|
||||||
|
@ -1301,7 +1303,10 @@ 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
|
|
||||||
|
# Skip all proxy to the root proxied model
|
||||||
|
proxied_model = get_proxied_model(opts)
|
||||||
|
|
||||||
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:
|
||||||
if model is proxied_model:
|
if model is proxied_model:
|
||||||
|
@ -1376,6 +1381,13 @@ class BaseQuery(object):
|
||||||
alias = root_alias
|
alias = root_alias
|
||||||
alias_chain = []
|
alias_chain = []
|
||||||
for int_model in opts.get_base_chain(model):
|
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
|
lhs_col = int_opts.parents[int_model].column
|
||||||
dedupe = lhs_col in opts.duplicate_targets
|
dedupe = lhs_col in opts.duplicate_targets
|
||||||
if dedupe:
|
if dedupe:
|
||||||
|
@ -1720,7 +1732,9 @@ 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
|
# 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):
|
for int_model in opts.get_base_chain(model):
|
||||||
if int_model is proxied_model:
|
if int_model is proxied_model:
|
||||||
opts = int_model._meta
|
opts = int_model._meta
|
||||||
|
@ -2423,3 +2437,11 @@ def add_to_dict(data, key, value):
|
||||||
data[key].add(value)
|
data[key].add(value)
|
||||||
else:
|
else:
|
||||||
data[key] = set([value])
|
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
|
||||||
|
|
|
@ -82,6 +82,87 @@ class MyPersonProxy(MyPerson):
|
||||||
class LowerStatusPerson(MyPersonProxy):
|
class LowerStatusPerson(MyPersonProxy):
|
||||||
status = models.CharField(max_length=80)
|
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' : """
|
__test__ = {'API_TESTS' : """
|
||||||
# The MyPerson model should be generating the same database queries as the
|
# The MyPerson model should be generating the same database queries as the
|
||||||
# Person model (when the same manager is used in each case).
|
# Person model (when the same manager is used in each case).
|
||||||
|
@ -119,6 +200,11 @@ False
|
||||||
>>> LowerStatusPerson.objects.all()
|
>>> LowerStatusPerson.objects.all()
|
||||||
[<LowerStatusPerson: homer>]
|
[<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...
|
# And now for some things that shouldn't work...
|
||||||
#
|
#
|
||||||
# All base classes must be non-abstract
|
# All base classes must be non-abstract
|
||||||
|
@ -178,6 +264,58 @@ FieldError: Proxy model 'NoNewFields' contains model fields.
|
||||||
>>> ctype = ContentType.objects.get_for_model
|
>>> ctype = ContentType.objects.get_for_model
|
||||||
>>> ctype(Person) is ctype(OtherPerson)
|
>>> ctype(Person) is ctype(OtherPerson)
|
||||||
True
|
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>
|
||||||
"""}
|
"""}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue