Ticket #7270: 121_reverse_r7601.patch

File 121_reverse_r7601.patch, 9.6 KB (added by gav, 7 years ago)
  • django/db/models/sql/query.py

     
    909909                next = requested.get(f.name, {})
    910910            else:
    911911                next = False
    912             if f.null is not None:
     912            if nullable is not None:
     913                new_nullable = nullable
     914            else:
    913915                new_nullable = f.null
    914             else:
    915                 new_nullable = None
    916916            self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1,
    917917                    used, next, restricted, new_nullable)
     918           
     919        # Do reverse columns, but only the ones in the requested list.
     920        if restricted:
     921            related_fields = [(x.field, x.model) for x in opts.get_all_related_objects() if x.field.unique]
     922            for f, model in related_fields:
     923                if f.rel.parent_link or f.related_query_name() not in requested:
     924                    continue
     925                table = model._meta.db_table
     926                if nullable or f.null:
     927                    promote = True
     928                else:
     929                    promote = False
     930                alias = root_alias
     931                alias = self.join((alias, table, f.rel.get_related_field().column,
     932                        f.column), exclusions=used,
     933                        promote=promote, reuse=used)
     934                used.add(alias)
     935   
     936                self.related_select_cols.extend([(alias, f2.column)
     937                        for f2 in model._meta.fields])
     938                self.related_select_fields.extend(model._meta.fields)
     939   
     940                next = requested.get(f.related_query_name(), {})
     941                if nullable is not None:
     942                    new_nullable = nullable
     943                else:
     944                    new_nullable = f.null
     945                self.fill_related_selections(model._meta, table, cur_depth + 1,
     946                        used, next, restricted, new_nullable)
    918947
    919948    def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
    920949            can_reuse=None):
  • django/db/models/query.py

     
    673673        if cached_row:
    674674            rel_obj, index_end = cached_row
    675675            setattr(obj, f.get_cache_name(), rel_obj)
     676
     677            # Do the reverse cache if the field is a unique relation.
     678            if f.unique and f.related_query_name() in dir(f.rel.to):
     679                cache_var_name = rel_obj.__class__.__dict__[f.related_query_name()].cache_name
     680                setattr(rel_obj, cache_var_name, obj)
     681           
     682    if restricted:
     683        related_fields = [(x.field, x.model) for x in klass._meta.get_all_related_objects() if x.field.unique]
     684        for f, model in related_fields:
     685            if f.related_query_name() not in requested:
     686                continue
     687
     688            next = requested.get(f.related_query_name(), {})
     689            cached_row = get_cached_row(model, row, index_end, max_depth,
     690                    cur_depth+1, next)
     691            if cached_row:
     692                rel_obj, index_end = cached_row
     693                setattr(rel_obj, f.get_cache_name(), obj)
     694               
     695                # Now do the reverse cache.
     696                cache_var_name = obj.__class__.__dict__[f.related_query_name()].cache_name
     697                setattr(obj, cache_var_name, rel_obj)
     698   
    676699    return obj, index_end
    677700
    678701def delete_objects(seen_objs):
  • tests/modeltests/select_related_reverse/models.py

     
     1"""
     2``select_related()`` follows all forward relationships, but should also follow
     3reverse relationships where it is appropriate (currently for OneToOneFields
     4only).
     5"""
     6
     7from django.db import models
     8
     9# OneToOneField tests only.
     10
     11class User(models.Model):
     12    alias = models.CharField(max_length=20)
     13   
     14    def __unicode__(self):
     15        return 'User, alias = %s' % (self.alias,)
     16
     17
     18class UserInfo(models.Model):
     19    user = models.OneToOneField(User, primary_key=True)
     20    name = models.CharField(max_length=32)
     21
     22    def __unicode__(self):
     23        return 'UserInfo, name = %s' % (self.name,)
     24   
     25
     26class UserStatResults(models.Model):
     27    results = models.CharField(max_length=50)
     28
     29    def __unicode__(self):
     30        return 'UserStatResults, results = %s' % (self.results,)
     31   
     32
     33class UserStat(models.Model):
     34    user = models.OneToOneField(User, primary_key=True)
     35    posts = models.IntegerField()
     36    results = models.ForeignKey(UserStatResults)
     37
     38    def __unicode__(self):
     39        return 'UserStat, posts = %s' % (self.posts,)
     40
     41
     42class StatDetails(models.Model):
     43    base_stats = models.OneToOneField(UserStat, primary_key=True)
     44    comments = models.IntegerField()
     45
     46    def __unicode__(self):
     47        return 'StatDetails, comments = %s' % (self.comments,)
     48
     49
     50__test__ = {'API_TESTS':"""
     51
     52# Set up.
     53# The test runner sets settings.DEBUG to False, but we want to gather queries
     54# so we'll set it to True here and reset it at the end of the test suite.
     55>>> from django.conf import settings
     56>>> settings.DEBUG = True
     57>>> from django import db
     58
     59>>> usr = UserStatResults.objects.create(results='first results')
     60>>> u = User.objects.create(alias='tom')
     61>>> ui = UserInfo.objects.create(user=u, name='Tom Jones')
     62>>> stat = UserStat.objects.create(user=u, posts=150, results=usr)
     63>>> sd = StatDetails.objects.create(base_stats=stat, comments=259)
     64>>> u = User.objects.create(alias='john')
     65>>> ui = UserInfo.objects.create(user=u, name='John Smith')
     66>>> stat = UserStat.objects.create(user=u, posts=33, results=usr)
     67>>> sd = StatDetails = StatDetails.objects.create(base_stats=stat, comments=104)
     68
     69# Specifying models in the select_related(...) works as expected.
     70>>> db.reset_queries()
     71>>> u = User.objects.select_related('userinfo', 'userstat').get(alias='tom')
     72>>> u.userstat
     73<UserStat: UserStat, posts = 150>
     74>>> len(db.connection.queries)
     751
     76>>> u.userstat.statdetails
     77<StatDetails: StatDetails, comments = 259>
     78>>> len(db.connection.queries)
     792
     80>>> u.userinfo
     81<UserInfo: UserInfo, name = Tom Jones>
     82>>> len(db.connection.queries)
     832
     84>>> db.reset_queries()
     85>>> u = User.objects.select_related('userstat', 'userstat__statdetails', 'userstat__results').get(alias='tom')
     86>>> u.userstat
     87<UserStat: UserStat, posts = 150>
     88>>> len(db.connection.queries)
     891
     90>>> u.userstat.statdetails
     91<StatDetails: StatDetails, comments = 259>
     92>>> len(db.connection.queries)
     931
     94>>> u.userstat.results
     95<UserStatResults: UserStatResults, results = first results>
     96>>> len(db.connection.queries)
     971
     98>>> u.userinfo
     99<UserInfo: UserInfo, name = Tom Jones>
     100>>> len(db.connection.queries)
     1012
     102
     103>>> db.reset_queries()
     104>>> us = UserStat.objects.select_related('user__userinfo').get(user__alias='john')
     105>>> us
     106<UserStat: UserStat, posts = 33>
     107>>> len(db.connection.queries)
     1081
     109>>> us.user
     110<User: User, alias = john>
     111>>> len(db.connection.queries)
     1121
     113>>> us.user.userinfo
     114<UserInfo: UserInfo, name = John Smith>
     115>>> len(db.connection.queries)
     1161
     117>>> us.user.userstat
     118<UserStat: UserStat, posts = 33>
     119>>> len(db.connection.queries)
     1201
     121
     122"""}
     123   
     124 No newline at end of file
  • docs/db-api.txt

     
    889889list of fields and the ``depth`` parameter in the same ``select_related()``
    890890call, since they are conflicting options.
    891891
     892**New in Django development version:** If you are using OneToOneFields, you
     893can refer to them from either side of the relationship via their related
     894field names in ``select_related()``, and they will be followed and cached
     895appropriately.  Also, any ``ForeignKey`` relations that are appropriate to be
     896followed from either side of the OneToOneField relation can be, in the same
     897way that is described above.  For example, if we have the following::
     898
     899class User(models.Model):
     900    alias = models.CharField(...)
     901   
     902class UserInfo(models.Model):
     903    user = models.OneToOneField(User)
     904    name = models.CharField(...)
     905
     906class UserStatResults(models.Model):
     907    results = models.CharField(...)
     908
     909class UserStat(models.Model):
     910    user = models.OneToOneField(User)
     911    posts = models.IntegerField(...)
     912    results = models.ForeignKey(UserStatResults)
     913
     914This is valid::
     915
     916    User.objects.select_related('userstat__results', 'userinfo')
     917   
     918This would cache not only the UserStat and UserInfo instances that are
     919appropriate to be cached, but also any UserStatResults instances,
     920since the ForeignKey goes in the proper direction from UserStat.
     921
     922This is also valid::
     923
     924    ui = UserInfo.objects.select_related('user__userstat').get(id=1)
     925    print ui.user.userstat
     926    print ui.user.userinfo.user.userstat
     927   
     928This would cache all the UserInfo, User and UserStat objects that are selected
     929by the criteria, and would generate no extra queries no matter how the related
     930OneToOneFields are chained together.
     931
    892932``extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)``
    893933~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    894934
Back to Top