Code

Ticket #7270: 121_reverse_r7601.patch

File 121_reverse_r7601.patch, 9.6 KB (added by gav, 6 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