Ticket #16128: 16128.diff

File 16128.diff, 13.8 KB (added by akaariai, 3 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): 
    1717        return ct
    1818
    1919    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
    2521
    2622    def _get_from_cache(self, opts):
    2723        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): 
    122122            if (new_class._meta.local_fields or
    123123                    new_class._meta.local_many_to_many):
    124124                raise FieldError("Proxy model '%s' contains model fields." % name)
    125             while base._meta.proxy:
    126                 base = base._meta.proxy_for_model
    127125            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
    128129
    129130        # Do the appropriate setup for any model parents.
    130131        o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields
    class ModelBase(type): 
    149150                                        (field.name, name, base.__name__))
    150151            if not base._meta.abstract:
    151152                # 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
    155154                if base in o2o_map:
    156155                    field = o2o_map[base]
    157156                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): 
    144144                            reverse_dependency=reverse_dependency)
    145145        if not new_objs:
    146146            return
     147       
    147148        model = new_objs[0].__class__
    148149
    149150        # Recursively collect parent models, but not their related objects.
    class Collector(object): 
    157158                             reverse_dependency=True)
    158159
    159160        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):
    161163                field = related.field
    162164                if related.model._meta.auto_created:
    163165                    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): 
    4040        self.abstract = False
    4141        self.managed = True
    4242        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.
    4348        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
    4453        self.parents = SortedDict()
    4554        self.duplicate_targets = {}
    4655        self.auto_created = False
    class Options(object): 
    350359    def get_delete_permission(self):
    351360        return 'delete_%s' % self.object_name.lower()
    352361
    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):
    354364        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)]
    356367
    357368    def get_all_related_objects_with_model(self, local_only=False,
    358                                            include_hidden=False):
     369                                           include_hidden=False,
     370                                           include_proxy_eq=False):
    359371        """
    360372        Returns a list of (related-object, model) pairs. Similar to
    361373        get_fields_with_model().
    class Options(object): 
    369381            predicates.append(lambda k, v: not v)
    370382        if not include_hidden:
    371383            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())
    374387
    375388    def _fill_related_objects_cache(self):
    376389        cache = SortedDict()
    class Options(object): 
    383396                    cache[obj] = parent
    384397                else:
    385398                    cache[obj] = model
     399        # Collect also objects which are in relation to some proxy child/parent of self.
     400        proxy_cache = cache.copy()
    386401        for klass in get_models(include_auto_created=True, only_installed=False):
    387402            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
    390409        self._related_objects_cache = cache
     410        self._related_objects_proxy_cache = proxy_cache
    391411
    392412    def get_all_related_many_to_many_objects(self, local_only=False):
    393413        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): 
    575575            return
    576576        orig_opts = self.model._meta
    577577        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])}
    582581        for field_name in field_names:
    583582            parts = field_name.split(LOOKUP_SEP)
    584583            cur_model = self.model
    class Query(object): 
    586585            for name in parts[:-1]:
    587586                old_model = cur_model
    588587                source = opts.get_field_by_name(name)[0]
    589                 cur_model = opts.get_field_by_name(name)[0].rel.to
     588                cur_model = source.rel.to
    590589                opts = cur_model._meta
    591590                # Even if we're "just passing through" this model, we must add
    592591                # both the current model's pk and the related reference field
    def add_to_dict(data, key, value): 
    19921991        data[key] = set([value])
    19931992
    19941993def 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): 
    232232        resp = [u.name for u in UserProxyProxy.objects.all()]
    233233        self.assertEqual(resp, ['Bruce'])
    234234
     235    def test_proxy_for_model(self):
     236        self.assertEquals(UserProxy, UserProxyProxy._meta.proxy_for_model)
     237
    235238    def test_proxy_delete(self):
    236239        """
    237240        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): 
    6767class Item(models.Model):
    6868    version = models.ForeignKey(Version)
    6969    location = models.ForeignKey(Location, blank=True, null=True)
     70
     71# Models for #16128
     72
     73class File(models.Model):
     74    pass
     75
     76class Image(File):
     77    class Meta:
     78        proxy = True
     79
     80class Photo(Image):
     81    class Meta:
     82        proxy = True
     83
     84class FooImage(models.Model):
     85    my_image = models.ForeignKey(Image)
     86   
     87class FooFile(models.Model):
     88    my_file = models.ForeignKey(File)
     89
     90class 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 
    88
    99from .models import (Book, Award, AwardNote, Person, Child, Toy, PlayedWith,
    1010    PlayedWithNote, Email, Researcher, Food, Eaten, Policy, Version, Location,
    11     Item)
     11    Item, Image, File, Photo, FooFile, FooImage, FooPhoto)
    1212
    1313
    1414# Can't run this test under SQLite, because you can't
    class LargeDeleteTests(TestCase): 
    147147            track = Book.objects.create(pagecount=x+100)
    148148        Book.objects.all().delete()
    149149        self.assertEqual(Book.objects.count(), 0)
     150
     151
     152class 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
     185class 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
     220class 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)
Back to Top