Commit baaf2989 authored by Jacob Kaplan-Moss's avatar Jacob Kaplan-Moss
Browse files

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

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
parent 2b0903b2
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -116,6 +116,8 @@ class ModelBase(type):
                    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 @@ class ModelBase(type):
                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 @@ class ModelBase(type):
            # 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
+1 −1
Original line number Diff line number Diff line
@@ -57,7 +57,7 @@ class Manager(object):
        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:
+7 −2
Original line number Diff line number Diff line
@@ -461,8 +461,13 @@ class Options(object):
        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."
+25 −3
Original line number Diff line number Diff line
@@ -778,7 +778,9 @@ class BaseQuery(object):
        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 @@ class BaseQuery(object):
        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 @@ class BaseQuery(object):
                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 @@ class BaseQuery(object):
                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
+140 −2
Original line number Diff line number Diff line
@@ -82,6 +82,87 @@ class MyPersonProxy(MyPerson):
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 @@ False
>>> 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 @@ FieldError: Proxy model 'NoNewFields' contains model fields.
>>> 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>
"""}