Ticket #16128: 16128.diff
File 16128.diff, 13.8 KB (added by , 13 years ago) |
---|
-
django/contrib/contenttypes/models.py
diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py index 6d919b4..6e99632 100644
a b class ContentTypeManager(models.Manager): 17 17 return ct 18 18 19 19 def _get_opts(self, model): 20 opts = model._meta 21 while opts.proxy: 22 model = opts.proxy_for_model 23 opts = model._meta 24 return opts 20 return model._meta.concrete_class._meta 25 21 26 22 def _get_from_cache(self, opts): 27 23 key = (opts.app_label, opts.object_name.lower()) -
django/db/models/base.py
diff --git a/django/db/models/base.py b/django/db/models/base.py index ebd67be..1810abd 100644
a b class ModelBase(type): 122 122 if (new_class._meta.local_fields or 123 123 new_class._meta.local_many_to_many): 124 124 raise FieldError("Proxy model '%s' contains model fields." % name) 125 while base._meta.proxy:126 base = base._meta.proxy_for_model127 125 new_class._meta.setup_proxy(base) 126 new_class._meta.concrete_class = base._meta.concrete_class 127 else: 128 new_class._meta.concrete_class = new_class 128 129 129 130 # Do the appropriate setup for any model parents. 130 131 o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields … … class ModelBase(type): 149 150 (field.name, name, base.__name__)) 150 151 if not base._meta.abstract: 151 152 # Concrete classes... 152 while base._meta.proxy: 153 # Skip over a proxy class to the "real" base it proxies. 154 base = base._meta.proxy_for_model 153 base = base._meta.concrete_class 155 154 if base in o2o_map: 156 155 field = o2o_map[base] 157 156 elif not is_proxy: -
django/db/models/deletion.py
diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py index 9a6d499..69d14d3 100644
a b class Collector(object): 144 144 reverse_dependency=reverse_dependency) 145 145 if not new_objs: 146 146 return 147 147 148 model = new_objs[0].__class__ 148 149 149 150 # Recursively collect parent models, but not their related objects. … … class Collector(object): 157 158 reverse_dependency=True) 158 159 159 160 if collect_related: 160 for related in model._meta.get_all_related_objects(include_hidden=True): 161 for related in model._meta.get_all_related_objects(include_hidden=True, 162 include_proxy_eq=True): 161 163 field = related.field 162 164 if related.model._meta.auto_created: 163 165 self.add_batch(related.model, field, new_objs) -
django/db/models/options.py
diff --git a/django/db/models/options.py b/django/db/models/options.py index 0cd52a3..c81c44f 100644
a b class Options(object): 40 40 self.abstract = False 41 41 self.managed = True 42 42 self.proxy = False 43 # For any class which is a proxy (including automatically created 44 # classes for deferred object loading) the proxy_for_model tells 45 # which class this model is proxying. Note that proxy_for_model 46 # can create a chain of proxy models. For non-proxy models the 47 # variable is always None. 43 48 self.proxy_for_model = None 49 # For any non-abstract class the concrete class is the model 50 # in the end of the proxy_for_model chain. In particular, for 51 # concrete models the concrete_class is always the class itself. 52 self.concrete_class = None 44 53 self.parents = SortedDict() 45 54 self.duplicate_targets = {} 46 55 self.auto_created = False … … class Options(object): 350 359 def get_delete_permission(self): 351 360 return 'delete_%s' % self.object_name.lower() 352 361 353 def get_all_related_objects(self, local_only=False, include_hidden=False): 362 def get_all_related_objects(self, local_only=False, include_hidden=False, 363 include_proxy_eq=False): 354 364 return [k for k, v in self.get_all_related_objects_with_model( 355 local_only=local_only, include_hidden=include_hidden)] 365 local_only=local_only, include_hidden=include_hidden, 366 include_proxy_eq=include_proxy_eq)] 356 367 357 368 def get_all_related_objects_with_model(self, local_only=False, 358 include_hidden=False): 369 include_hidden=False, 370 include_proxy_eq=False): 359 371 """ 360 372 Returns a list of (related-object, model) pairs. Similar to 361 373 get_fields_with_model(). … … class Options(object): 369 381 predicates.append(lambda k, v: not v) 370 382 if not include_hidden: 371 383 predicates.append(lambda k, v: not k.field.rel.is_hidden()) 372 return filter(lambda t: all([p(*t) for p in predicates]), 373 self._related_objects_cache.items()) 384 cache = (self._related_objects_proxy_cache if include_proxy_eq 385 else self._related_objects_cache) 386 return filter(lambda t: all([p(*t) for p in predicates]), cache.items()) 374 387 375 388 def _fill_related_objects_cache(self): 376 389 cache = SortedDict() … … class Options(object): 383 396 cache[obj] = parent 384 397 else: 385 398 cache[obj] = model 399 # Collect also objects which are in relation to some proxy child/parent of self. 400 proxy_cache = cache.copy() 386 401 for klass in get_models(include_auto_created=True, only_installed=False): 387 402 for f in klass._meta.local_fields: 388 if f.rel and not isinstance(f.rel.to, basestring) and self == f.rel.to._meta: 389 cache[RelatedObject(f.rel.to, klass, f)] = None 403 if f.rel and not isinstance(f.rel.to, basestring): 404 if self == f.rel.to._meta: 405 cache[RelatedObject(f.rel.to, klass, f)] = None 406 proxy_cache[RelatedObject(f.rel.to, klass, f)] = None 407 elif self.concrete_class == f.rel.to._meta.concrete_class: 408 proxy_cache[RelatedObject(f.rel.to, klass, f)] = None 390 409 self._related_objects_cache = cache 410 self._related_objects_proxy_cache = proxy_cache 391 411 392 412 def get_all_related_many_to_many_objects(self, local_only=False): 393 413 try: -
django/db/models/sql/query.py
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index a78df34..f7cac1e 100644
a b class Query(object): 575 575 return 576 576 orig_opts = self.model._meta 577 577 seen = {} 578 if orig_opts.proxy: 579 must_include = {orig_opts.proxy_for_model: set([orig_opts.pk])} 580 else: 581 must_include = {self.model: set([orig_opts.pk])} 578 # NOTE: This does what the previous code did. Is this correct? The 579 # code below is a bit hard to grasp... 580 must_include = {orig_opts.concrete_class: set([orig_opts.pk])} 582 581 for field_name in field_names: 583 582 parts = field_name.split(LOOKUP_SEP) 584 583 cur_model = self.model … … class Query(object): 586 585 for name in parts[:-1]: 587 586 old_model = cur_model 588 587 source = opts.get_field_by_name(name)[0] 589 cur_model = opts.get_field_by_name(name)[0].rel.to588 cur_model = source.rel.to 590 589 opts = cur_model._meta 591 590 # Even if we're "just passing through" this model, we must add 592 591 # both the current model's pk and the related reference field … … def add_to_dict(data, key, value): 1992 1991 data[key] = set([value]) 1993 1992 1994 1993 def get_proxied_model(opts): 1995 int_opts = opts 1996 proxied_model = None 1997 while int_opts.proxy: 1998 proxied_model = int_opts.proxy_for_model 1999 int_opts = proxied_model._meta 2000 return proxied_model 1994 return opts.concrete_class -
tests/modeltests/proxy_models/tests.py
diff --git a/tests/modeltests/proxy_models/tests.py b/tests/modeltests/proxy_models/tests.py index 3ec8465..15c5dc0 100644
a b class ProxyModelTests(TestCase): 232 232 resp = [u.name for u in UserProxyProxy.objects.all()] 233 233 self.assertEqual(resp, ['Bruce']) 234 234 235 def test_proxy_for_model(self): 236 self.assertEquals(UserProxy, UserProxyProxy._meta.proxy_for_model) 237 235 238 def test_proxy_delete(self): 236 239 """ 237 240 Proxy objects can be deleted -
tests/regressiontests/delete_regress/models.py
diff --git a/tests/regressiontests/delete_regress/models.py b/tests/regressiontests/delete_regress/models.py index 622fbc8..7ce37c7 100644
a b class Location(models.Model): 67 67 class Item(models.Model): 68 68 version = models.ForeignKey(Version) 69 69 location = models.ForeignKey(Location, blank=True, null=True) 70 71 # Models for #16128 72 73 class File(models.Model): 74 pass 75 76 class Image(File): 77 class Meta: 78 proxy = True 79 80 class Photo(Image): 81 class Meta: 82 proxy = True 83 84 class FooImage(models.Model): 85 my_image = models.ForeignKey(Image) 86 87 class FooFile(models.Model): 88 my_file = models.ForeignKey(File) 89 90 class FooPhoto(models.Model): 91 my_photo = models.ForeignKey(Photo) 92 93 -
tests/regressiontests/delete_regress/tests.py
diff --git a/tests/regressiontests/delete_regress/tests.py b/tests/regressiontests/delete_regress/tests.py index c2a2171..e8a77d8 100644
a b from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature 8 8 9 9 from .models import (Book, Award, AwardNote, Person, Child, Toy, PlayedWith, 10 10 PlayedWithNote, Email, Researcher, Food, Eaten, Policy, Version, Location, 11 Item )11 Item, Image, File, Photo, FooFile, FooImage, FooPhoto) 12 12 13 13 14 14 # Can't run this test under SQLite, because you can't … … class LargeDeleteTests(TestCase): 147 147 track = Book.objects.create(pagecount=x+100) 148 148 Book.objects.all().delete() 149 149 self.assertEqual(Book.objects.count(), 0) 150 151 152 class ProxyDeleteImageTest(TestCase): 153 ''' 154 Tests on_delete behaviour for proxy models. Deleting the *proxy* 155 instance bubbles through to its non-proxy and *all* referring objects 156 are deleted. 157 ''' 158 159 def setUp(self): 160 # Create an Image 161 self.test_image = Image() 162 self.test_image.save() 163 foo_image = FooImage(my_image=self.test_image) 164 foo_image.save() 165 166 # Get the Image instance as a File 167 test_file = File.objects.get(pk=self.test_image.pk) 168 foo_file = FooFile(my_file=test_file) 169 foo_file.save() 170 171 #@override_settings(DEBUG=True) 172 def test_delete(self): 173 self.assertTrue(Image.objects.all().delete() is None) 174 # An Image deletion == File deletion 175 self.assertEqual(len(Image.objects.all()), 0) 176 self.assertEqual(len(File.objects.all()), 0) 177 # The Image deletion cascaded and *all* references to it are deleted. 178 self.assertEqual(len(FooImage.objects.all()), 0) 179 self.assertEqual(len(FooFile.objects.all()), 0) 180 #from django.db import connection 181 #for q in connection.queries: 182 # print q 183 184 185 class ProxyDeletePhotoTest(ProxyDeleteImageTest): 186 ''' 187 Tests on_delete behaviour for proxy-of-proxy models. Deleting the *proxy* 188 instance should bubble through to its proxy and non-proxy variants. 189 Deleting *all* referring objects. For some reason it seems that the 190 intermediate proxy model isn't cleaned up. 191 ''' 192 193 def setUp(self): 194 # Create the Image, FooImage and FooFile instances 195 super(ProxyDeletePhotoTest, self).setUp() 196 # Get the Image as a Photo 197 test_photo = Photo.objects.get(pk=self.test_image.pk) 198 foo_photo = FooPhoto(my_photo=test_photo) 199 foo_photo.save() 200 201 202 #@override_settings(DEBUG=True) 203 def test_delete(self): 204 self.assertTrue(Photo.objects.all().delete() is None) 205 # A Photo deletion == Image deletion == File deletion 206 self.assertEqual(len(Photo.objects.all()), 0) 207 self.assertEqual(len(Image.objects.all()), 0) 208 self.assertEqual(len(File.objects.all()), 0) 209 # The Photo deletion should have cascaded and deleted *all* 210 # references to it. 211 self.assertEqual(len(FooPhoto.objects.all()), 0) 212 self.assertEqual(len(FooFile.objects.all()), 0) 213 self.assertEqual(len(FooImage.objects.all()), 0) 214 #from django.db import connection 215 #for q in connection.queries: 216 # print q 217 #print len(connection.queries) 218 219 220 class ProxyDeleteFileTest(ProxyDeleteImageTest): 221 ''' 222 Tests on_delete cascade behaviour for proxy models. Deleting the 223 *non-proxy* instance of a model should somehow notify it's proxy. 224 Currently it doesn't, making this test fail. 225 ''' 226 227 def test_delete(self): 228 # This should *not* raise an IntegrityError on databases that support 229 # FK constraints. 230 self.assertTrue(File.objects.all().delete() is None) 231 # A File deletion == Image deletion 232 self.assertEqual(len(File.objects.all()), 0) 233 self.assertEqual(len(Image.objects.all()), 0) 234 # The File deletion should have cascaded and deleted *all* references 235 # to it. 236 self.assertEqual(len(FooFile.objects.all()), 0) 237 self.assertEqual(len(FooImage.objects.all()), 0)