Author: jacob
Date: 2009-05-11 05:10:03 -0500 (Mon, 11 May 2009)
New Revision: 10738

Modified:
   django/trunk/django/db/models/base.py
   django/trunk/django/db/models/manager.py
   django/trunk/django/db/models/options.py
   django/trunk/django/db/models/sql/query.py
   django/trunk/tests/modeltests/proxy_models/models.py
Log:
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.

Modified: django/trunk/django/db/models/base.py
===================================================================
--- django/trunk/django/db/models/base.py       2009-05-11 09:57:19 UTC (rev 
10737)
+++ django/trunk/django/db/models/base.py       2009-05-11 10:10:03 UTC (rev 
10738)
@@ -116,6 +116,8 @@
                     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 @@
                 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 @@
             # 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

Modified: django/trunk/django/db/models/manager.py
===================================================================
--- django/trunk/django/db/models/manager.py    2009-05-11 09:57:19 UTC (rev 
10737)
+++ django/trunk/django/db/models/manager.py    2009-05-11 10:10:03 UTC (rev 
10738)
@@ -57,7 +57,7 @@
         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:

Modified: django/trunk/django/db/models/options.py
===================================================================
--- django/trunk/django/db/models/options.py    2009-05-11 09:57:19 UTC (rev 
10737)
+++ django/trunk/django/db/models/options.py    2009-05-11 10:10:03 UTC (rev 
10738)
@@ -461,8 +461,13 @@
         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."

Modified: django/trunk/django/db/models/sql/query.py
===================================================================
--- django/trunk/django/db/models/sql/query.py  2009-05-11 09:57:19 UTC (rev 
10737)
+++ django/trunk/django/db/models/sql/query.py  2009-05-11 10:10:03 UTC (rev 
10738)
@@ -778,7 +778,9 @@
         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 @@
         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 @@
                 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 @@
                 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 @@
         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

Modified: django/trunk/tests/modeltests/proxy_models/models.py
===================================================================
--- django/trunk/tests/modeltests/proxy_models/models.py        2009-05-11 
09:57:19 UTC (rev 10737)
+++ django/trunk/tests/modeltests/proxy_models/models.py        2009-05-11 
10:10:03 UTC (rev 10738)
@@ -82,6 +82,87 @@
 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 @@
 >>> 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 @@
 >>> 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>
+"""}


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to django-updates@googlegroups.com
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to