Ticket #7270: 121_reverse_r8985.patch
File 121_reverse_r8985.patch, 21.5 KB (added by , 16 years ago) |
---|
-
django/db/models/sql/query.py
1038 1038 self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1, 1039 1039 used, next, restricted, new_nullable, dupe_set, avoid) 1040 1040 1041 1042 # Do reverse columns, but only the ones in the requested list. 1043 if restricted: 1044 related_fields = [(x.field, x.model) for x in opts.get_all_related_objects() if x.field.unique] 1045 for f, model in related_fields: 1046 if f.rel.parent_link or f.related_query_name() not in requested: 1047 continue 1048 table = model._meta.db_table 1049 if nullable or f.null: 1050 promote = True 1051 else: 1052 promote = False 1053 alias = self.join((root_alias, table, f.rel.get_related_field().column, 1054 f.column), exclusions=used, 1055 promote=promote, reuse=used) 1056 used.add(alias) 1057 1058 self.related_select_cols.extend([(alias, f2.column) 1059 for f2 in model._meta.fields]) 1060 self.related_select_fields.extend(model._meta.fields) 1061 1062 next = requested.get(f.related_query_name(), {}) 1063 #if nullable is not None: 1064 # new_nullable = nullable 1065 #else: 1066 # new_nullable = f.null 1067 if f.null is not None: 1068 new_nullable = f.null 1069 else: 1070 new_nullable = None 1071 self.fill_related_selections(model._meta, table, cur_depth + 1, 1072 used, next, restricted, new_nullable) 1073 1041 1074 def add_filter(self, filter_expr, connector=AND, negate=False, trim=False, 1042 1075 can_reuse=None, process_extras=True): 1043 1076 """ -
django/db/models/options.py
45 45 self.abstract = False 46 46 self.parents = SortedDict() 47 47 self.duplicate_targets = {} 48 self.reverse_field_cache = {} 48 49 # Managers that have been inherited from abstract base classes. These 49 50 # are passed onto any children. 50 51 self.abstract_managers = [] -
django/db/models/fields/related.py
1 import types 1 2 from django.db import connection, transaction 2 3 from django.db.models import signals, get_model 3 4 from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist … … 170 171 # SingleRelatedObjectDescriptor instance. 171 172 def __init__(self, related): 172 173 self.related = related 173 self.cache_name = '_%s_cache' % related.get_accessor_name() 174 #self.cache_name = '_%s_cache' % related.get_accessor_name() 175 cache_name = '_%s_cache' % related.field.related_query_name() 176 # Contribute to the parent model for later lookup. 177 related.parent_model._meta.reverse_field_cache[related.field.related_query_name()] = cache_name 178 self.cache_name = cache_name 174 179 175 180 def __get__(self, instance, instance_type=None): 176 181 if instance is None: … … 283 288 # attribute is a ForeignRelatedObjectsDescriptor instance. 284 289 def __init__(self, related): 285 290 self.related = related # RelatedObject instance 291 if related.field.unique: 292 cache_name = '_%s_cache' % related.field.related_query_name() 293 # Contribute to the parent model for later lookup. 294 related.parent_model._meta.reverse_field_cache[related.field.related_query_name()] = cache_name 295 self.cache_name = cache_name 286 296 287 297 def __get__(self, instance, instance_type=None): 288 298 if instance is None: … … 290 300 291 301 rel_field = self.related.field 292 302 rel_model = self.related.model 303 if rel_field.unique: 304 cache_name = self.cache_name 293 305 294 306 # Dynamically create a class that subclasses the related 295 307 # model's default manager. … … 336 348 obj.save() 337 349 clear.alters_data = True 338 350 351 if rel_field.unique: 352 def all(self): 353 try: 354 result = getattr(instance, cache_name) 355 if isinstance(result, (types.TupleType, types.ListType)): 356 return result 357 else: 358 return [result] 359 except AttributeError, ae: 360 return superclass.get_query_set(self) 361 339 362 manager = RelatedManager() 340 363 attname = rel_field.rel.get_related_field().name 341 364 manager.core_filters = {'%s__%s' % (rel_field.name, attname): … … 354 377 if self.related.field.null: 355 378 manager.clear() 356 379 manager.add(*value) 380 # Cache the value specially if from a unique set. 381 if self.related.field.unique: 382 self.cache_name = value 357 383 358 384 def create_many_related_manager(superclass, through=False): 359 385 """Creates a manager that subclasses 'superclass' (which is a Manager) -
django/db/models/query.py
812 812 rel_obj, index_end = cached_row 813 813 if obj is not None: 814 814 setattr(obj, f.get_cache_name(), rel_obj) 815 816 # Do the reverse cache if the field is a unique relation. 817 if f.unique: 818 cache_var_name = rel_obj._meta.reverse_field_cache[f.related_query_name()] 819 setattr(rel_obj, cache_var_name, obj) 820 821 if restricted: 822 related_fields = [(x.field, x.model) for x in klass._meta.get_all_related_objects() if x.field.unique] 823 for f, model in related_fields: 824 if f.related_query_name() not in requested: 825 continue 826 827 next = requested.get(f.related_query_name(), {}) 828 cached_row = get_cached_row(model, row, index_end, max_depth, 829 cur_depth+1, next) 830 if cached_row: 831 rel_obj, index_end = cached_row 832 setattr(rel_obj, f.get_cache_name(), obj) 833 834 # Now do the reverse cache. 835 cache_var_name = obj._meta.reverse_field_cache[f.related_query_name()] 836 setattr(obj, cache_var_name, rel_obj) 837 815 838 return obj, index_end 816 839 817 840 -
tests/modeltests/select_related_reverse/models.py
1 """ 2 ``select_related()`` follows all forward relationships, but should also follow 3 reverse relationships where it is appropriate (currently for OneToOneFields 4 only). 5 """ 6 7 from django.db import models 8 9 # OneToOneField tests only. 10 11 class User(models.Model): 12 alias = models.CharField(max_length=20) 13 14 def __unicode__(self): 15 return 'User, alias = %s' % (self.alias,) 16 17 18 class 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 26 class UserStatResults(models.Model): 27 results = models.CharField(max_length=50) 28 29 def __unicode__(self): 30 return 'UserStatResults, results = %s' % (self.results,) 31 32 33 class 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 42 class 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) 75 1 76 >>> u.userstat.statdetails 77 <StatDetails: StatDetails, comments = 259> 78 >>> len(db.connection.queries) 79 2 80 >>> u.userinfo 81 <UserInfo: UserInfo, name = Tom Jones> 82 >>> len(db.connection.queries) 83 2 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) 89 1 90 >>> u.userstat.statdetails 91 <StatDetails: StatDetails, comments = 259> 92 >>> len(db.connection.queries) 93 1 94 >>> u.userstat.results 95 <UserStatResults: UserStatResults, results = first results> 96 >>> len(db.connection.queries) 97 1 98 >>> u.userinfo 99 <UserInfo: UserInfo, name = Tom Jones> 100 >>> len(db.connection.queries) 101 2 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) 108 1 109 >>> us.user 110 <User: User, alias = john> 111 >>> len(db.connection.queries) 112 1 113 >>> us.user.userinfo 114 <UserInfo: UserInfo, name = John Smith> 115 >>> len(db.connection.queries) 116 1 117 >>> us.user.userstat 118 <UserStat: UserStat, posts = 33> 119 >>> len(db.connection.queries) 120 1 121 122 """} 123 124 No newline at end of file -
tests/modeltests/select_related_reverse2/models.py
1 """ 2 ``select_related()`` follows all forward relationships, but should also follow 3 reverse relationships where it is appropriate (currently for OneToOneFields 4 only). 5 """ 6 7 from django.db import models 8 9 # OneToOneField tests only. 10 11 class User(models.Model): 12 alias = models.CharField(max_length=20) 13 14 def __unicode__(self): 15 return 'User, alias = %s' % (self.alias,) 16 17 18 class 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 26 class UserStatResults(models.Model): 27 results = models.CharField(max_length=50) 28 29 def __unicode__(self): 30 return 'UserStatResults, results = %s' % (self.results,) 31 32 33 class 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 42 class 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) 75 1 76 >>> u.userstat.all()[0].statdetails 77 <StatDetails: StatDetails, comments = 259> 78 >>> len(db.connection.queries) 79 2 80 >>> u.userinfo 81 <UserInfo: UserInfo, name = Tom Jones> 82 >>> len(db.connection.queries) 83 2 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) 89 1 90 >>> u.userstat.all()[0].statdetails 91 <StatDetails: StatDetails, comments = 259> 92 >>> len(db.connection.queries) 93 1 94 >>> u.userstat.all()[0].results 95 <UserStatResults: UserStatResults, results = first results> 96 >>> len(db.connection.queries) 97 1 98 >>> u.userinfo 99 <UserInfo: UserInfo, name = Tom Jones> 100 >>> len(db.connection.queries) 101 2 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) 108 1 109 >>> us.user 110 <User: User, alias = john> 111 >>> len(db.connection.queries) 112 1 113 >>> us.user.userinfo 114 <UserInfo: UserInfo, name = John Smith> 115 >>> len(db.connection.queries) 116 1 117 >>> us.user.userstat.all() 118 [<UserStat: UserStat, posts = 33>] 119 >>> len(db.connection.queries) 120 1 121 122 """} 123 124 No newline at end of file -
tests/modeltests/select_related_reverse3/models.py
1 """ 2 ``select_related()`` follows all forward relationships, but should also follow 3 reverse relationships where it is appropriate (currently for OneToOneFields 4 only). 5 """ 6 7 from django.db import models 8 9 # OneToOneField tests only. 10 11 class User(models.Model): 12 alias = models.CharField(max_length=20) 13 14 def __unicode__(self): 15 return 'User, alias = %s' % (self.alias,) 16 17 18 class 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 26 class UserStatResults(models.Model): 27 results = models.CharField(max_length=50) 28 29 def __unicode__(self): 30 return 'UserStatResults, results = %s' % (self.results,) 31 32 33 class 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 43 class 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) 78 1 79 >>> u.userstat.statdetails 80 <StatDetails: StatDetails, comments = 259> 81 >>> len(db.connection.queries) 82 2 83 >>> u.userinfo 84 <UserInfo: UserInfo, name = Tom Jones> 85 >>> len(db.connection.queries) 86 2 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) 92 1 93 >>> u.userstat.statdetails 94 <StatDetails: StatDetails, comments = 259> 95 >>> len(db.connection.queries) 96 1 97 >>> u.userstat.results 98 <UserStatResults: UserStatResults, results = first results> 99 >>> len(db.connection.queries) 100 1 101 >>> u.userinfo 102 <UserInfo: UserInfo, name = Tom Jones> 103 >>> len(db.connection.queries) 104 2 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) 111 1 112 >>> us.user 113 <User: User, alias = john> 114 >>> len(db.connection.queries) 115 1 116 >>> us.user.userinfo 117 <UserInfo: UserInfo, name = John Smith> 118 >>> len(db.connection.queries) 119 1 120 >>> us.user.userstat 121 <UserStat: UserStat, posts = 33> 122 >>> len(db.connection.queries) 123 1 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) 131 2 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) 138 1 139 >>> us.user 140 <User: User, alias = john> 141 >>> len(db.connection.queries) 142 1 143 >>> us.user.userinfo 144 <UserInfo: UserInfo, name = John Smith> 145 >>> len(db.connection.queries) 146 1 147 >>> us.user.userstat 148 <UserStat: UserStat, posts = 33> 149 >>> len(db.connection.queries) 150 1 151 152 >>> us.user.userstat.watcher 153 <User: User, alias = john> 154 >>> len(db.connection.queries) 155 1 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) 163 1 164 >>> u.userstat.statdetails 165 <StatDetails: StatDetails, comments = 259> 166 >>> len(db.connection.queries) 167 2 168 >>> u.userstat.watcher 169 <User: User, alias = bob> 170 >>> len(db.connection.queries) 171 3 172 >>> u.userinfo 173 <UserInfo: UserInfo, name = Tom Jones> 174 >>> len(db.connection.queries) 175 3 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) 183 1 184 >>> u.userstat.watcher 185 <User: User, alias = john> 186 >>> len(db.connection.queries) 187 2 188 >>> u.stat_watcher 189 <UserStat: UserStat, posts = 33> 190 >>> len(db.connection.queries) 191 3 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) 199 1 200 >>> u.userstat.watcher 201 <User: User, alias = john> 202 >>> len(db.connection.queries) 203 2 204 >>> u.stat_watcher 205 <UserStat: UserStat, posts = 33> 206 >>> len(db.connection.queries) 207 2 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) 215 1 216 >>> u.userstat.watcher 217 <User: User, alias = bob> 218 >>> len(db.connection.queries) 219 1 220 221 """} 222 223 No newline at end of file