Code

Ticket #7270: 121_reverse_r10396.patch

File 121_reverse_r10396.patch, 21.2 KB (added by bendavis78, 5 years ago)

121_reverse_patch updated to 10396

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

     
    13911391            self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1, 
    13921392                    used, next, restricted, new_nullable, dupe_set, avoid) 
    13931393 
     1394        # Do reverse columns, but only the ones in the requested list. 
     1395        if restricted: 
     1396            related_fields = [(x.field, x.model) for x in opts.get_all_related_objects() if x.field.unique] 
     1397            for f, model in related_fields: 
     1398                if f.rel.parent_link or f.related_query_name() not in requested: 
     1399                    continue 
     1400                table = model._meta.db_table 
     1401                alias = self.join((root_alias, table, f.rel.get_related_field().column, 
     1402                        f.column), exclusions=used, 
     1403                        promote=True, reuse=used) 
     1404                used.add(alias) 
     1405 
     1406                self.related_select_cols.extend([(alias, f2.column) 
     1407                        for f2 in model._meta.fields]) 
     1408                self.related_select_fields.extend(model._meta.fields) 
     1409 
     1410                next = requested.get(f.related_query_name(), {}) 
     1411                #if nullable is not None: 
     1412                #    new_nullable = nullable 
     1413                #else: 
     1414                #    new_nullable = f.null 
     1415                if f.null is not None: 
     1416                    new_nullable = f.null 
     1417                else: 
     1418                    new_nullable = None 
     1419                self.fill_related_selections(model._meta, table, cur_depth + 1, 
     1420                        used, next, restricted, new_nullable) 
     1421 
     1422 
     1423 
    13941424    def add_aggregate(self, aggregate, model, alias, is_summary): 
    13951425        """ 
    13961426        Adds a single aggregate expression to the Query 
  • django/db/models/options.py

     
    4747        self.proxy_for_model = None 
    4848        self.parents = SortedDict() 
    4949        self.duplicate_targets = {} 
     50        self.reverse_field_cache = {} 
    5051 
    5152        # To handle various inheritance situations, we need to track where 
    5253        # managers came from (concrete or abstract base classes). 
  • django/db/models/fields/related.py

     
     1import types 
    12from django.db import connection, transaction 
    23from django.db.backends import util 
    34from django.db.models import signals, get_model 
     
    178179    # SingleRelatedObjectDescriptor instance. 
    179180    def __init__(self, related): 
    180181        self.related = related 
    181         self.cache_name = '_%s_cache' % related.get_accessor_name() 
     182        #self.cache_name = '_%s_cache' % related.get_accessor_name() 
     183        cache_name = '_%s_cache' % related.field.related_query_name() 
     184        # Contribute to the parent model for later lookup. 
     185        related.parent_model._meta.reverse_field_cache[related.field.related_query_name()] = cache_name 
     186        self.cache_name = cache_name 
    182187 
    183188    def __get__(self, instance, instance_type=None): 
    184189        if instance is None: 
     
    291296    # attribute is a ForeignRelatedObjectsDescriptor instance. 
    292297    def __init__(self, related): 
    293298        self.related = related   # RelatedObject instance 
     299        if related.field.unique: 
     300            cache_name = '_%s_cache' % related.field.related_query_name() 
     301            # Contribute to the parent model for later lookup. 
     302            related.parent_model._meta.reverse_field_cache[related.field.related_query_name()] = cache_name 
     303            self.cache_name = cache_name 
    294304 
     305 
    295306    def __get__(self, instance, instance_type=None): 
    296307        if instance is None: 
    297308            return self 
     
    309320        if self.related.field.null: 
    310321            manager.clear() 
    311322        manager.add(*value) 
     323        # Cache the value specially if from a unique set. 
     324        if self.related.field.unique: 
     325            self.cache_name = value 
    312326 
     327 
    313328    def delete_manager(self, instance): 
    314329        """ 
    315330        Returns a queryset based on the related model's base manager (rather 
     
    325340        """ 
    326341        rel_field = self.related.field 
    327342        rel_model = self.related.model 
     343        if rel_field.unique: 
     344            cache_name = self.cache_name 
    328345 
    329346        class RelatedManager(superclass): 
    330347            def get_query_set(self): 
     
    369386                        obj.save() 
    370387                clear.alters_data = True 
    371388 
     389            if rel_field.unique: 
     390                def all(self): 
     391                    try: 
     392                        result = getattr(instance, cache_name) 
     393                        if isinstance(result, (types.TupleType, types.ListType)): 
     394                            return result 
     395                        else: 
     396                            return [result] 
     397                    except AttributeError, ae: 
     398                        return superclass.get_query_set(self) 
     399 
     400 
    372401        manager = RelatedManager() 
    373402        attname = rel_field.rel.get_related_field().name 
    374403        manager.core_filters = {'%s__%s' % (rel_field.name, attname): 
  • django/db/models/query.py

     
    950950            rel_obj, index_end = cached_row 
    951951            if obj is not None: 
    952952                setattr(obj, f.get_cache_name(), rel_obj) 
     953 
     954            # Do the reverse cache if the field is a unique relation. 
     955            if f.unique: 
     956                cache_var_name = rel_obj._meta.reverse_field_cache[f.related_query_name()] 
     957                setattr(rel_obj, cache_var_name, obj) 
     958 
     959    if restricted: 
     960        related_fields = [(x.field, x.model) for x in klass._meta.get_all_related_objects() if x.field.unique] 
     961        for f, model in related_fields: 
     962            if f.related_query_name() not in requested: 
     963                continue 
     964 
     965            next = requested.get(f.related_query_name(), {}) 
     966            cached_row = get_cached_row(model, row, index_end, max_depth, 
     967                    cur_depth+1, next) 
     968            if cached_row: 
     969                rel_obj, index_end = cached_row 
     970                setattr(rel_obj, f.get_cache_name(), obj) 
     971 
     972                # Now do the reverse cache. 
     973                cache_var_name = obj._meta.reverse_field_cache[f.related_query_name()] 
     974                setattr(obj, cache_var_name, rel_obj) 
    953975    return obj, index_end 
    954976 
    955977def 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 
  • tests/modeltests/select_related_reverse2/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.ForeignKey(User, primary_key=True, unique=True, related_name='userstat') 
     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.all() 
     73[<UserStat: UserStat, posts = 150>] 
     74>>> len(db.connection.queries) 
     751 
     76>>> u.userstat.all()[0].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.all() 
     87[<UserStat: UserStat, posts = 150>] 
     88>>> len(db.connection.queries) 
     891 
     90>>> u.userstat.all()[0].statdetails 
     91<StatDetails: StatDetails, comments = 259> 
     92>>> len(db.connection.queries) 
     931 
     94>>> u.userstat.all()[0].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.all() 
     118[<UserStat: UserStat, posts = 33>] 
     119>>> len(db.connection.queries) 
     1201 
     121 
     122"""} 
     123 
  • tests/modeltests/select_related_reverse3/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    watcher = models.OneToOneField(User, related_name='stat_watcher') 
     38 
     39    def __unicode__(self): 
     40        return 'UserStat, posts = %s' % (self.posts,) 
     41 
     42 
     43class StatDetails(models.Model): 
     44    base_stats = models.OneToOneField(UserStat, primary_key=True) 
     45    comments = models.IntegerField() 
     46 
     47    def __unicode__(self): 
     48        return 'StatDetails, comments = %s' % (self.comments,) 
     49 
     50 
     51__test__ = {'API_TESTS':""" 
     52 
     53# Set up. 
     54# The test runner sets settings.DEBUG to False, but we want to gather queries 
     55# so we'll set it to True here and reset it at the end of the test suite. 
     56>>> from django.conf import settings 
     57>>> settings.DEBUG = True 
     58>>> from django import db 
     59 
     60>>> usr = UserStatResults.objects.create(results='first results') 
     61>>> u1 = User.objects.create(alias='tom') 
     62>>> ui = UserInfo.objects.create(user=u1, name='Tom Jones') 
     63>>> u2 = User.objects.create(alias='john') 
     64>>> ui = UserInfo.objects.create(user=u2, name='John Smith') 
     65>>> u3 = User.objects.create(alias='bob') 
     66>>> ui = UserInfo.objects.create(user=u3, name='Bob Rogers') 
     67>>> stat = UserStat.objects.create(user=u1, posts=150, results=usr, watcher=u3) 
     68>>> sd = StatDetails.objects.create(base_stats=stat, comments=259) 
     69>>> stat = UserStat.objects.create(user=u2, posts=33, results=usr, watcher=u2) 
     70>>> sd = StatDetails = StatDetails.objects.create(base_stats=stat, comments=104) 
     71 
     72# Specifying models in the select_related(...) works as expected. 
     73>>> db.reset_queries() 
     74>>> u = User.objects.select_related('userinfo', 'userstat').get(alias='tom') 
     75>>> u.userstat 
     76<UserStat: UserStat, posts = 150> 
     77>>> len(db.connection.queries) 
     781 
     79>>> u.userstat.statdetails 
     80<StatDetails: StatDetails, comments = 259> 
     81>>> len(db.connection.queries) 
     822 
     83>>> u.userinfo 
     84<UserInfo: UserInfo, name = Tom Jones> 
     85>>> len(db.connection.queries) 
     862 
     87>>> db.reset_queries() 
     88>>> u = User.objects.select_related('userstat', 'userstat__statdetails', 'userstat__results').get(alias='tom') 
     89>>> u.userstat 
     90<UserStat: UserStat, posts = 150> 
     91>>> len(db.connection.queries) 
     921 
     93>>> u.userstat.statdetails 
     94<StatDetails: StatDetails, comments = 259> 
     95>>> len(db.connection.queries) 
     961 
     97>>> u.userstat.results 
     98<UserStatResults: UserStatResults, results = first results> 
     99>>> len(db.connection.queries) 
     1001 
     101>>> u.userinfo 
     102<UserInfo: UserInfo, name = Tom Jones> 
     103>>> len(db.connection.queries) 
     1042 
     105 
     106>>> db.reset_queries() 
     107>>> us = UserStat.objects.select_related('user__userinfo').get(user__alias='john') 
     108>>> us 
     109<UserStat: UserStat, posts = 33> 
     110>>> len(db.connection.queries) 
     1111 
     112>>> us.user 
     113<User: User, alias = john> 
     114>>> len(db.connection.queries) 
     1151 
     116>>> us.user.userinfo 
     117<UserInfo: UserInfo, name = John Smith> 
     118>>> len(db.connection.queries) 
     1191 
     120>>> us.user.userstat 
     121<UserStat: UserStat, posts = 33> 
     122>>> len(db.connection.queries) 
     1231 
     124 
     125# !!!!!!!!!!!!!! 
     126# Everything past this point is testing multiple fields pointing to the same model. 
     127# !!!!!!!!!!!!!! 
     128>>> us.user.userstat.watcher 
     129<User: User, alias = john> 
     130>>> len(db.connection.queries) 
     1312 
     132 
     133>>> db.reset_queries() 
     134>>> us = UserStat.objects.select_related('user__userinfo', 'watcher').get(user__alias='john') 
     135>>> us 
     136<UserStat: UserStat, posts = 33> 
     137>>> len(db.connection.queries) 
     1381 
     139>>> us.user 
     140<User: User, alias = john> 
     141>>> len(db.connection.queries) 
     1421 
     143>>> us.user.userinfo 
     144<UserInfo: UserInfo, name = John Smith> 
     145>>> len(db.connection.queries) 
     1461 
     147>>> us.user.userstat 
     148<UserStat: UserStat, posts = 33> 
     149>>> len(db.connection.queries) 
     1501 
     151 
     152>>> us.user.userstat.watcher 
     153<User: User, alias = john> 
     154>>> len(db.connection.queries) 
     1551 
     156 
     157# Show that the # of queries increases as expected when used reverse. 
     158>>> db.reset_queries() 
     159>>> u = User.objects.select_related('userinfo', 'userstat').get(alias='tom') 
     160>>> u.userstat 
     161<UserStat: UserStat, posts = 150> 
     162>>> len(db.connection.queries) 
     1631 
     164>>> u.userstat.statdetails 
     165<StatDetails: StatDetails, comments = 259> 
     166>>> len(db.connection.queries) 
     1672 
     168>>> u.userstat.watcher 
     169<User: User, alias = bob> 
     170>>> len(db.connection.queries) 
     1713 
     172>>> u.userinfo 
     173<UserInfo: UserInfo, name = Tom Jones> 
     174>>> len(db.connection.queries) 
     1753 
     176 
     177# Show that we can follow the watcher too. 
     178>>> db.reset_queries() 
     179>>> u = User.objects.select_related('userinfo', 'userstat').get(alias='john') 
     180>>> u.userstat 
     181<UserStat: UserStat, posts = 33> 
     182>>> len(db.connection.queries) 
     1831 
     184>>> u.userstat.watcher 
     185<User: User, alias = john> 
     186>>> len(db.connection.queries) 
     1872 
     188>>> u.stat_watcher 
     189<UserStat: UserStat, posts = 33> 
     190>>> len(db.connection.queries) 
     1913 
     192 
     193# Show that we can follow the watcher via the select_related(...) bits. 
     194>>> db.reset_queries() 
     195>>> u = User.objects.select_related('userstat', 'stat_watcher').get(alias='john') 
     196>>> u.userstat 
     197<UserStat: UserStat, posts = 33> 
     198>>> len(db.connection.queries) 
     1991 
     200>>> u.userstat.watcher 
     201<User: User, alias = john> 
     202>>> len(db.connection.queries) 
     2032 
     204>>> u.stat_watcher 
     205<UserStat: UserStat, posts = 33> 
     206>>> len(db.connection.queries) 
     2072 
     208 
     209# Show that we can follow the watcher via the select_related(...) bits that are nested. 
     210>>> db.reset_queries() 
     211>>> u = User.objects.select_related('userstat', 'userstat__watcher').get(alias='tom') 
     212>>> u.userstat 
     213<UserStat: UserStat, posts = 150> 
     214>>> len(db.connection.queries) 
     2151 
     216>>> u.userstat.watcher 
     217<User: User, alias = bob> 
     218>>> len(db.connection.queries) 
     2191 
     220 
     221"""} 
     222