Ticket #16128: 16128.6.diff
File 16128.6.diff, 13.4 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 1bb07e0..bc9de56 100644
a b class ContentTypeManager(models.Manager): 16 16 return ct 17 17 18 18 def _get_opts(self, model): 19 opts = model._meta 20 while opts.proxy: 21 model = opts.proxy_for_model 22 opts = model._meta 23 return opts 19 """ 20 Returns the options for the concrete parent of the model. 21 """ 22 return model._meta.concrete_class._meta 24 23 25 24 def _get_from_cache(self, opts): 26 25 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 b5ce39e..033bf37 100644
a b class ModelBase(type): 75 75 new_class._meta.get_latest_by = base_meta.get_latest_by 76 76 77 77 is_proxy = new_class._meta.proxy 78 if not is_proxy and not abstract: 79 new_class._meta.concrete_class = new_class 78 80 79 81 if getattr(new_class, '_default_manager', None): 80 82 if not is_proxy: … … class ModelBase(type): 122 124 if (new_class._meta.local_fields or 123 125 new_class._meta.local_many_to_many): 124 126 raise FieldError("Proxy model '%s' contains model fields." % name) 125 while base._meta.proxy:126 base = base._meta.proxy_for_model127 127 new_class._meta.setup_proxy(base) 128 # Add this class to 'proxying_models' for every proxied parent of 129 # this class. 130 while base: 131 base._meta.proxying_models.append(new_class) 132 base = base._meta.proxy_for_model 128 133 129 134 # Do the appropriate setup for any model parents. 130 135 o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields … … class ModelBase(type): 149 154 (field.name, name, base.__name__)) 150 155 if not base._meta.abstract: 151 156 # 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 157 base = base._meta.concrete_class 155 158 if base in o2o_map: 156 159 field = o2o_map[base] 157 160 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..d119534 100644
a b class Collector(object): 145 145 if not new_objs: 146 146 return 147 147 model = new_objs[0].__class__ 148 # Always use the concrete model class for deletion. We will handle 149 # all the proxy-child classes later on. We do this because when 150 # we are deleting a model, we are in fact deleting the whole proxy 151 # equivalence class. 152 model = model._meta.concrete_class 148 153 149 154 # Recursively collect parent models, but not their related objects. 150 155 # These will be found by meta.get_all_related_objects() … … class Collector(object): 157 162 reverse_dependency=True) 158 163 159 164 if collect_related: 160 for related in model._meta.get_all_related_objects(include_hidden=True): 161 field = related.field 162 if related.model._meta.auto_created: 163 self.add_batch(related.model, field, new_objs) 164 else: 165 sub_objs = self.related_objects(related, new_objs) 166 if not sub_objs: 165 # Collect all related objects to this model and for models proxying 166 # this model. Note that at this point the model is always the concrete 167 # parent in the proxy chain. 168 # When deleting a model we are also deleting every model that 169 # is in proxy-relation to this model. Would this be better handled by 170 # get_all_related_objects kwarg include_proxy? Are we continuously 171 # finding the same relations (proxy has all the same relations out as 172 # the parent). 173 proxy_equivalance = [model] + model._meta.proxying_models 174 175 # keep track of duplicate related models. These can be seen because of 176 # proxy models. 177 seen_related = set() 178 for base in proxy_equivalance: 179 for related in base._meta.get_all_related_objects(include_hidden=True): 180 if related in seen_related: 167 181 continue 168 field.rel.on_delete(self, field, sub_objs, self.using) 169 170 # TODO This entire block is only needed as a special case to 171 # support cascade-deletes for GenericRelation. It should be 172 # removed/fixed when the ORM gains a proper abstraction for virtual 173 # or composite fields, and GFKs are reworked to fit into that. 174 for relation in model._meta.many_to_many: 175 if not relation.rel.through: 176 sub_objs = relation.bulk_related_objects(new_objs, self.using) 177 self.collect(sub_objs, 178 source=model, 179 source_attr=relation.rel.related_name, 180 nullable=True) 182 seen_related.add(related) 183 field = related.field 184 if related.model._meta.auto_created: 185 self.add_batch(related.model, field, new_objs) 186 else: 187 sub_objs = self.related_objects(related, new_objs) 188 if not sub_objs: 189 continue 190 field.rel.on_delete(self, field, sub_objs, self.using) 191 192 # TODO This entire block is only needed as a special case to 193 # support cascade-deletes for GenericRelation. It should be 194 # removed/fixed when the ORM gains a proper abstraction for virtual 195 # or composite fields, and GFKs are reworked to fit into that. 196 for relation in base._meta.many_to_many: 197 if not relation.rel.through: 198 if relation in seen_related: 199 continue 200 seen_related.add(relation) 201 sub_objs = relation.bulk_related_objects(new_objs, self.using) 202 self.collect(sub_objs, 203 source=model, 204 source_attr=relation.rel.related_name, 205 nullable=True) 181 206 182 207 def related_objects(self, related, objs): 183 208 """ -
django/db/models/options.py
diff --git a/django/db/models/options.py b/django/db/models/options.py index 0cd52a3..853a616 100644
a b class Options(object): 40 40 self.abstract = False 41 41 self.managed = True 42 42 self.proxy = False 43 # None for abstract classes, own class for concrete models 44 # and the first concrete parent for proxy models. This does 45 # not track multitable parents. 46 self.concrete_class = None 43 47 self.proxy_for_model = None 48 # We need to keep track of which models are proxying this model 49 # so that we can fetch all references to this model when deleting. 50 self.proxying_models = [] 44 51 self.parents = SortedDict() 45 52 self.duplicate_targets = {} 46 53 self.auto_created = False … … class Options(object): 183 190 """ 184 191 self.pk = target._meta.pk 185 192 self.proxy_for_model = target 193 self.concrete_class = target._meta.concrete_class 186 194 self.db_table = target._meta.db_table 187 195 188 196 def __repr__(self): -
django/db/models/sql/query.py
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 61fd2be..b40c4c8 100644
a b class Query(object): 569 569 return 570 570 orig_opts = self.model._meta 571 571 seen = {} 572 if orig_opts.proxy: 573 must_include = {orig_opts.proxy_for_model: set([orig_opts.pk])} 574 else: 575 must_include = {self.model: set([orig_opts.pk])} 572 must_include = {orig_opts.concrete_class: set([orig_opts.pk])} 576 573 for field_name in field_names: 577 574 parts = field_name.split(LOOKUP_SEP) 578 575 cur_model = self.model … … def add_to_dict(data, key, value): 1949 1946 data[key] = set([value]) 1950 1947 1951 1948 def get_proxied_model(opts): 1952 int_opts = opts 1953 proxied_model = None 1954 while int_opts.proxy: 1955 proxied_model = int_opts.proxy_for_model 1956 int_opts = proxied_model._meta 1957 return proxied_model 1949 return opts.concrete_class -
new file tests/regressiontests/proxy_bug/models.py
diff --git a/tests/regressiontests/proxy_bug/__init__.py b/tests/regressiontests/proxy_bug/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/regressiontests/proxy_bug/models.py b/tests/regressiontests/proxy_bug/models.py new file mode 100644 index 0000000..7389ffe
- + 1 from django.db import models 2 3 class File(models.Model): 4 pass 5 6 class Image(File): 7 class Meta: 8 proxy = True 9 10 class Photo(Image): 11 class Meta: 12 proxy = True 13 14 class FooImage(models.Model): 15 my_image = models.ForeignKey(Image) 16 17 class FooFile(models.Model): 18 my_file = models.ForeignKey(File) 19 20 class FooPhoto(models.Model): 21 my_photo = models.ForeignKey(Photo) 22 -
new file tests/regressiontests/proxy_bug/tests.py
diff --git a/tests/regressiontests/proxy_bug/tests.py b/tests/regressiontests/proxy_bug/tests.py new file mode 100644 index 0000000..5704d35
- + 1 from django.test import TestCase 2 #from django.test.utils import override_settings 3 from .models import Image, File, Photo, FooFile, FooImage, FooPhoto 4 5 6 class ProxyDeleteImageTest(TestCase): 7 ''' 8 Tests on_delete behaviour for proxy models. Deleting the *proxy* 9 instance bubbles through to its non-proxy and *all* referring objects 10 are deleted. 11 ''' 12 13 def setUp(self): 14 # Create an Image 15 self.test_image = Image() 16 self.test_image.save() 17 foo_image = FooImage(my_image=self.test_image) 18 foo_image.save() 19 20 # Get the Image instance as a File 21 test_file = File.objects.get(pk=self.test_image.pk) 22 foo_file = FooFile(my_file=test_file) 23 foo_file.save() 24 25 #@override_settings(DEBUG=True) 26 def test_delete(self): 27 self.assertTrue(Image.objects.all().delete() is None) 28 # An Image deletion == File deletion 29 self.assertEqual(len(Image.objects.all()), 0) 30 self.assertEqual(len(File.objects.all()), 0) 31 # The Image deletion cascaded and *all* references to it are deleted. 32 self.assertEqual(len(FooImage.objects.all()), 0) 33 self.assertEqual(len(FooFile.objects.all()), 0) 34 #from django.db import connection 35 #for q in connection.queries: 36 # print q 37 38 39 class ProxyDeletePhotoTest(ProxyDeleteImageTest): 40 ''' 41 Tests on_delete behaviour for proxy-of-proxy models. Deleting the *proxy* 42 instance should bubble through to its proxy and non-proxy variants. 43 Deleting *all* referring objects. For some reason it seems that the 44 intermediate proxy model isn't cleaned up. 45 ''' 46 47 def setUp(self): 48 # Create the Image, FooImage and FooFile instances 49 super(ProxyDeletePhotoTest, self).setUp() 50 # Get the Image as a Photo 51 test_photo = Photo.objects.get(pk=self.test_image.pk) 52 foo_photo = FooPhoto(my_photo=test_photo) 53 foo_photo.save() 54 55 56 #@override_settings(DEBUG=True) 57 def test_delete(self): 58 self.assertTrue(Photo.objects.all().delete() is None) 59 # A Photo deletion == Image deletion == File deletion 60 self.assertEqual(len(Photo.objects.all()), 0) 61 self.assertEqual(len(Image.objects.all()), 0) 62 self.assertEqual(len(File.objects.all()), 0) 63 # The Photo deletion should have cascaded and deleted *all* 64 # references to it. 65 self.assertEqual(len(FooPhoto.objects.all()), 0) 66 self.assertEqual(len(FooFile.objects.all()), 0) 67 self.assertEqual(len(FooImage.objects.all()), 0) 68 #from django.db import connection 69 #for q in connection.queries: 70 # print q 71 #print len(connection.queries) 72 73 74 class ProxyDeleteFileTest(ProxyDeleteImageTest): 75 ''' 76 Tests on_delete cascade behaviour for proxy models. Deleting the 77 *non-proxy* instance of a model should somehow notify it's proxy. 78 Currently it doesn't, making this test fail. 79 ''' 80 81 def test_delete(self): 82 # This should *not* raise an IntegrityError on databases that support 83 # FK constraints. 84 self.assertTrue(File.objects.all().delete() is None) 85 # A File deletion == Image deletion 86 self.assertEqual(len(File.objects.all()), 0) 87 self.assertEqual(len(Image.objects.all()), 0) 88 # The File deletion should have cascaded and deleted *all* references 89 # to it. 90 self.assertEqual(len(FooFile.objects.all()), 0) 91 self.assertEqual(len(FooImage.objects.all()), 0)