Ticket #10955: 10955_proxy_select_related_r10697.diff

File 10955_proxy_select_related_r10697.diff, 9.6 KB (added by Clément Nodet, 15 years ago)

initial patch, fixes select_related + filter on proxy, and type issue on subproxy

  • django/db/models/sql/query.py

     
    778778        qn2 = self.connection.ops.quote_name
    779779        aliases = set()
    780780        only_load = self.deferred_to_columns()
    781         proxied_model = opts.proxy and opts.proxy_for_model or 0
     781        # Skip all proxy to the root proxied model
     782        int_opts = opts
     783        proxied_model = None
     784        while int_opts.proxy:
     785            proxied_model = int_opts.proxy_for_model
     786            int_opts = proxied_model._meta
     787
    782788        if start_alias:
    783789            seen = {None: start_alias}
    784790        for field, model in opts.get_fields_with_model():
     
    13011307        opts = self.model._meta
    13021308        root_alias = self.tables[0]
    13031309        seen = {None: root_alias}
    1304         proxied_model = opts.proxy and opts.proxy_for_model or 0
     1310       
     1311        # Skip all proxy to the root proxied model
     1312        int_opts = opts
     1313        proxied_model = None
     1314        while int_opts.proxy:
     1315            proxied_model = int_opts.proxy_for_model
     1316            int_opts = proxied_model._meta
     1317
    13051318        for field, model in opts.get_fields_with_model():
    13061319            if model not in seen:
    13071320                if model is proxied_model:
     
    13761389                alias = root_alias
    13771390                alias_chain = []
    13781391                for int_model in opts.get_base_chain(model):
     1392                    # Proxy model have elements in base chain
     1393                    # with no parents, assign the new options
     1394                    # object and skip to the next base in that
     1395                    # case
     1396                    if not int_opts.parents[int_model]:
     1397                        int_opts = int_model._meta
     1398                        continue
    13791399                    lhs_col = int_opts.parents[int_model].column
    13801400                    dedupe = lhs_col in opts.duplicate_targets
    13811401                    if dedupe:
     
    17201740                raise MultiJoin(pos + 1)
    17211741            if model:
    17221742                # The field lives on a base class of the current model.
    1723                 proxied_model = opts.proxy and opts.proxy_for_model or 0
     1743                # Skip the chain of proxy to the concrete proxied model               
     1744                int_opts = opts
     1745                proxied_model = None
     1746                while int_opts.proxy:
     1747                    proxied_model = int_opts.proxy_for_model
     1748                    int_opts = proxied_model._meta
     1749
    17241750                for int_model in opts.get_base_chain(model):
    17251751                    if int_model is proxied_model:
    17261752                        opts = int_model._meta
  • django/db/models/base.py

     
    138138                                     'with field of similar name from '
    139139                                     'base class %r' %
    140140                                        (field.name, name, base.__name__))
     141
     142            # Stores real base (before skipping proxy classes)
     143            # for managers inheritance. Managers should be inherited
     144            # from concrete and abstract managers of immediate bases
     145            base_orig = base
    141146            if not base._meta.abstract:
    142147                # Concrete classes...
    143148                while base._meta.proxy:
     
    162167                new_class._meta.parents.update(base._meta.parents)
    163168
    164169            # Inherit managers from the abstract base classes.
    165             new_class.copy_managers(base._meta.abstract_managers)
     170            new_class.copy_managers(base_orig._meta.abstract_managers)
    166171
    167172            # Proxy models inherit the non-abstract managers from their base,
    168173            # unless they have redefined any of them.
    169174            if is_proxy:
    170                 new_class.copy_managers(base._meta.concrete_managers)
     175                new_class.copy_managers(base_orig._meta.concrete_managers)
    171176
    172177            # Inherit virtual fields (like GenericForeignKey) from the parent
    173178            # class
  • django/db/models/options.py

     
    461461        if ancestor in self.parents:
    462462            return self.parents[ancestor]
    463463        for parent in self.parents:
    464             if parent._meta.get_ancestor_link(ancestor):
    465                 return self.parents[parent]
     464            # Tries to get a link field from the immediate parent
     465            parent_link = parent._meta.get_ancestor_link(ancestor)
     466            if parent_link:
     467                # In case of a proxied model, the first link
     468                # of the chain to the ancestor is that parent
     469                # links
     470                return self.parents[parent] or parent_link
    466471
    467472    def get_ordered_objects(self):
    468473        "Returns a list of Options objects that are ordered with respect to this object."
  • tests/modeltests/proxy_models/models.py

     
    8282class LowerStatusPerson(MyPersonProxy):
    8383    status = models.CharField(max_length=80)
    8484
     85# We can still use `select_related()` to include related models in our querysets.
     86class Country(models.Model):
     87        name = models.CharField(max_length=50)
     88
     89class State(models.Model):
     90        name = models.CharField(max_length=50)
     91        country = models.ForeignKey(Country)
     92
     93        def __unicode__(self):
     94                return self.name
     95
     96class StateProxy(State):
     97        class Meta:
     98                proxy = True
     99
     100# Proxy models still works with filters (on related fields)
     101# and select_related, even when mixed with model inheritance
     102class BaseUser(models.Model):
     103    name = models.CharField(max_length=255)
     104
     105class TrackerUser(BaseUser):
     106    status = models.CharField(max_length=50)
     107
     108class ProxyTrackerUser(TrackerUser):
     109    class Meta:
     110        proxy = True
     111
     112
     113class Issue(models.Model):
     114    summary = models.CharField(max_length=255)
     115    assignee = models.ForeignKey(TrackerUser)
     116
     117    def __unicode__(self):
     118        return ':'.join((self.__class__.__name__,self.summary,))
     119
     120class Bug(Issue):
     121    version = models.CharField(max_length=50)
     122    reporter = models.ForeignKey(BaseUser)
     123
     124class ProxyBug(Bug):
     125    """
     126    Proxy of an inherited class
     127    """
     128    class Meta:
     129        proxy = True
     130
     131
     132class ProxyProxyBug(ProxyBug):
     133    """
     134    A proxy of proxy model with related field
     135    """
     136    class Meta:
     137        proxy = True
     138
     139class Improvement(Issue):
     140    """
     141    A model that has relation to a proxy model
     142    or to a proxy of proxy model
     143    """
     144    version = models.CharField(max_length=50)
     145    reporter = models.ForeignKey(ProxyTrackerUser)
     146    associated_bug = models.ForeignKey(ProxyProxyBug)
     147   
     148class ProxyImprovement(Improvement):
     149    class Meta:
     150        proxy = True
     151
    85152__test__ = {'API_TESTS' : """
    86153# The MyPerson model should be generating the same database queries as the
    87154# Person model (when the same manager is used in each case).
     
    119186>>> LowerStatusPerson.objects.all()
    120187[<LowerStatusPerson: homer>]
    121188
     189# Correct type when querying a proxy of proxy
     190
     191>>> MyPersonProxy.objects.all()
     192[<MyPersonProxy: Bazza del Frob>, <MyPersonProxy: Foo McBar>, <MyPersonProxy: homer>]
     193
    122194# And now for some things that shouldn't work...
    123195#
    124196# All base classes must be non-abstract
     
    178250>>> ctype = ContentType.objects.get_for_model
    179251>>> ctype(Person) is ctype(OtherPerson)
    180252True
    181 """}
    182253
     254# We can still use `select_related()` to include related models in our querysets.
     255>>> country = Country.objects.create(name='Australia')
     256>>> state = State.objects.create(name='New South Wales', country=country)
    183257
     258>>> State.objects.select_related()
     259[<State: New South Wales>]
     260>>> StateProxy.objects.select_related()
     261[<StateProxy: New South Wales>]
     262>>> StateProxy.objects.get(name='New South Wales')
     263<StateProxy: New South Wales>
     264>>> StateProxy.objects.select_related().get(name='New South Wales')
     265<StateProxy: New South Wales>
     266
     267>>> contributor = TrackerUser.objects.create(name='Contributor',status='contrib')
     268>>> someone = BaseUser.objects.create(name='Someone')
     269>>> _ = Bug.objects.create(summary='fix this', version='1.1beta',
     270...                        assignee=contributor, reporter=someone)
     271>>> pcontributor = ProxyTrackerUser.objects.create(name='OtherContributor',
     272...                                                status='proxy')
     273>>> _ = Improvement.objects.create(summary='improve that', version='1.1beta',
     274...                                assignee=contributor, reporter=pcontributor,
     275...                                associated_bug=ProxyProxyBug.objects.all()[0])
     276
     277# Related field filter on proxy
     278>>> ProxyBug.objects.get(version__icontains='beta')
     279<ProxyBug: ProxyBug:fix this>
     280
     281# Select related + filter on proxy
     282>>> ProxyBug.objects.select_related().get(version__icontains='beta')
     283<ProxyBug: ProxyBug:fix this>
     284
     285# Proxy of proxy, select_related + filter
     286>>> ProxyProxyBug.objects.select_related().get(version__icontains='beta')
     287<ProxyProxyBug: ProxyProxyBug:fix this>
     288
     289# Select related + filter on a related proxy field
     290>>> ProxyImprovement.objects.select_related().get(reporter__name__icontains='butor')
     291<ProxyImprovement: ProxyImprovement:improve that>
     292
     293# Select related + filter on a related proxy of proxy field
     294>>> ProxyImprovement.objects.select_related().get(associated_bug__summary__icontains='fix')
     295<ProxyImprovement: ProxyImprovement:improve that>
     296"""}
Back to Top