Ticket #7270: 121_reverse_r7831.patch
File 121_reverse_r7831.patch, 21.1 KB (added by , 16 years ago) |
---|
-
django/db/models/fields/related.py
diff -r c273e74671a6 django/db/models/fields/related.py
a b 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, get_ul_class, FieldDoesNotExist … … 168 169 # SingleRelatedObjectDescriptor instance. 169 170 def __init__(self, related): 170 171 self.related = related 171 self.cache_name = '_%s_cache' % related.get_accessor_name() 172 cache_name = '_%s_cache' % related.field.related_query_name() 173 # Contribute to the parent model for later lookup. 174 related.parent_model._meta.reverse_field_cache[related.field.related_query_name()] = cache_name 175 self.cache_name = cache_name 172 176 173 177 def __get__(self, instance, instance_type=None): 174 178 if instance is None: … … 274 278 # attribute is a ForeignRelatedObjectsDescriptor instance. 275 279 def __init__(self, related): 276 280 self.related = related # RelatedObject instance 281 if related.field.unique: 282 cache_name = '_%s_cache' % related.field.related_query_name() 283 # Contribute to the parent model for later lookup. 284 related.parent_model._meta.reverse_field_cache[related.field.related_query_name()] = cache_name 285 self.cache_name = cache_name 277 286 278 287 def __get__(self, instance, instance_type=None): 279 288 if instance is None: … … 281 290 282 291 rel_field = self.related.field 283 292 rel_model = self.related.model 293 if rel_field.unique: 294 cache_name = self.cache_name 284 295 285 296 # Dynamically create a class that subclasses the related 286 297 # model's default manager. … … 320 331 setattr(obj, rel_field.name, None) 321 332 obj.save() 322 333 clear.alters_data = True 334 335 if rel_field.unique: 336 def all(self): 337 try: 338 result = getattr(instance, cache_name) 339 if isinstance(result, (types.TupleType, types.ListType)): 340 return result 341 else: 342 return [result] 343 except AttributeError, ae: 344 return superclass.get_query_set(self) 323 345 324 346 manager = RelatedManager() 325 347 attname = rel_field.rel.get_related_field().name … … 339 361 if self.related.field.null: 340 362 manager.clear() 341 363 manager.add(*value) 364 # Cache the value specially if from a unique set. 365 if self.related.field.unique: 366 self.cache_name = value 342 367 343 368 def create_many_related_manager(superclass): 344 369 """Creates a manager that subclasses 'superclass' (which is a Manager) -
django/db/models/options.py
diff -r c273e74671a6 django/db/models/options.py
a b 45 45 self.abstract = False 46 46 self.parents = SortedDict() 47 47 self.duplicate_targets = {} 48 self.reverse_field_cache = {} 48 49 49 50 def contribute_to_class(self, cls, name): 50 51 from django.db import connection -
django/db/models/query.py
diff -r c273e74671a6 django/db/models/query.py
a b 772 772 if cached_row: 773 773 rel_obj, index_end = cached_row 774 774 setattr(obj, f.get_cache_name(), rel_obj) 775 776 # Do the reverse cache if the field is a unique relation. 777 if f.unique: 778 cache_var_name = rel_obj._meta.reverse_field_cache[f.related_query_name()] 779 setattr(rel_obj, cache_var_name, obj) 780 781 if restricted: 782 related_fields = [(x.field, x.model) for x in klass._meta.get_all_related_objects() if x.field.unique] 783 for f, model in related_fields: 784 if f.related_query_name() not in requested: 785 continue 786 787 next = requested.get(f.related_query_name(), {}) 788 cached_row = get_cached_row(model, row, index_end, max_depth, 789 cur_depth+1, next) 790 if cached_row: 791 rel_obj, index_end = cached_row 792 setattr(rel_obj, f.get_cache_name(), obj) 793 794 # Now do the reverse cache. 795 cache_var_name = obj._meta.reverse_field_cache[f.related_query_name()] 796 setattr(obj, cache_var_name, rel_obj) 797 775 798 return obj, index_end 776 799 777 800 def delete_objects(seen_objs): -
django/db/models/sql/query.py
diff -r c273e74671a6 django/db/models/sql/query.py
a b 977 977 next = requested.get(f.name, {}) 978 978 else: 979 979 next = False 980 if f.null is not None: 980 if nullable is not None: 981 new_nullable = nullable 982 else: 981 983 new_nullable = f.null 982 else:983 new_nullable = None984 984 for dupe_opts, dupe_col in dupe_set: 985 985 self.update_dupe_avoidance(dupe_opts, dupe_col, alias) 986 986 self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1, 987 987 used, next, restricted, new_nullable, dupe_set) 988 989 # Do reverse columns, but only the ones in the requested list. 990 if restricted: 991 related_fields = [(x.field, x.model) for x in opts.get_all_related_objects() if x.field.unique] 992 for f, model in related_fields: 993 if f.rel.parent_link or f.related_query_name() not in requested: 994 continue 995 table = model._meta.db_table 996 if nullable or f.null: 997 promote = True 998 else: 999 promote = False 1000 alias = self.join((root_alias, table, f.rel.get_related_field().column, 1001 f.column), exclusions=used, 1002 promote=promote, reuse=used) 1003 used.add(alias) 1004 1005 self.related_select_cols.extend([(alias, f2.column) 1006 for f2 in model._meta.fields]) 1007 self.related_select_fields.extend(model._meta.fields) 1008 1009 next = requested.get(f.related_query_name(), {}) 1010 if nullable is not None: 1011 new_nullable = nullable 1012 else: 1013 new_nullable = f.null 1014 self.fill_related_selections(model._meta, table, cur_depth + 1, 1015 used, next, restricted, new_nullable) 1016 988 1017 989 1018 def add_filter(self, filter_expr, connector=AND, negate=False, trim=False, 990 1019 can_reuse=None): -
new file tests/modeltests/select_related_reverse/models.py
diff -r c273e74671a6 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 -
new file tests/modeltests/select_related_reverse2/models.py
diff -r c273e74671a6 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 -
new file tests/modeltests/select_related_reverse3/models.py
diff -r c273e74671a6 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