Ticket #14270: 14270_r16914.diff

File 14270_r16914.diff, 10.4 KB (added by Luke Plant, 13 years ago)

Updated for r16914, plus more tests

  • django/db/models/fields/related.py

    diff -r aeebc5cc7a4c django/db/models/fields/related.py
    a b  
    99from django.db.models.deletion import CASCADE
    1010from django.utils.encoding import smart_unicode
    1111from django.utils.translation import ugettext_lazy as _, string_concat
    12 from django.utils.functional import curry
     12from django.utils.functional import curry, memoize, cached_property
    1313from django.core import exceptions
    1414from django import forms
    1515
     
    386386        if instance is None:
    387387            return self
    388388
    389         return self.create_manager(instance,
    390                 self.related.model._default_manager.__class__)
     389        manager = self.create_manager(self.related.model._default_manager.__class__)
     390        return manager(instance)
    391391
    392392    def __set__(self, instance, value):
    393393        if instance is None:
     
    406406        than the default manager, as returned by __get__). Used by
    407407        Model.delete().
    408408        """
    409         return self.create_manager(instance,
    410                 self.related.model._base_manager.__class__)
     409        manager = self.create_manager(self.related.model._base_manager.__class__)
     410        return manager(instance)
    411411
    412     def create_manager(self, instance, superclass):
     412    def create_manager(self, superclass):
    413413        """
    414414        Creates the managers used by other methods (__get__() and delete()).
    415415        """
     416
     417        # We use closures for these values so that we only need to memoize this
     418        # function on the one argument of 'superclass', and the two places that
     419        # call create_manager simply need to pass instance to the manager
     420        # __init__
    416421        rel_field = self.related.field
     422        rel_model = self.related.model
     423        attname = rel_field.rel.get_related_field().attname
     424
    417425        class RelatedManager(superclass):
    418             def __init__(self, model=None, core_filters=None, instance=None,
    419                          rel_field=None):
     426            def __init__(self, instance):
    420427                super(RelatedManager, self).__init__()
    421                 self.model = model
    422                 self.core_filters = core_filters
    423428                self.instance = instance
    424                 self.rel_field = rel_field
     429                self.core_filters = {
     430                    '%s__%s' % (rel_field.name, attname): getattr(instance, attname)
     431                }
     432                self.model = rel_model
    425433
    426434            def get_query_set(self):
    427435                db = self._db or router.db_for_read(self.model, instance=self.instance)
     
    431439                for obj in objs:
    432440                    if not isinstance(obj, self.model):
    433441                        raise TypeError("'%s' instance expected" % self.model._meta.object_name)
    434                     setattr(obj, self.rel_field.name, self.instance)
     442                    setattr(obj, rel_field.name, self.instance)
    435443                    obj.save()
    436444            add.alters_data = True
    437445
    438446            def create(self, **kwargs):
    439                 kwargs[self.rel_field.name] = self.instance
     447                kwargs[rel_field.name] = self.instance
    440448                db = router.db_for_write(self.model, instance=self.instance)
    441449                return super(RelatedManager, self.db_manager(db)).create(**kwargs)
    442450            create.alters_data = True
     
    444452            def get_or_create(self, **kwargs):
    445453                # Update kwargs with the related object that this
    446454                # ForeignRelatedObjectsDescriptor knows about.
    447                 kwargs[self.rel_field.name] = self.instance
     455                kwargs[rel_field.name] = self.instance
    448456                db = router.db_for_write(self.model, instance=self.instance)
    449457                return super(RelatedManager, self.db_manager(db)).get_or_create(**kwargs)
    450458            get_or_create.alters_data = True
     
    452460            # remove() and clear() are only provided if the ForeignKey can have a value of null.
    453461            if rel_field.null:
    454462                def remove(self, *objs):
    455                     val = getattr(self.instance, self.rel_field.rel.get_related_field().attname)
     463                    val = getattr(self.instance, attname)
    456464                    for obj in objs:
    457465                        # Is obj actually part of this descriptor set?
    458                         if getattr(obj, self.rel_field.attname) == val:
    459                             setattr(obj, self.rel_field.name, None)
     466                        if getattr(obj, rel_field.attname) == val:
     467                            setattr(obj, rel_field.name, None)
    460468                            obj.save()
    461469                        else:
    462                             raise self.rel_field.rel.to.DoesNotExist("%r is not related to %r." % (obj, self.instance))
     470                            raise rel_field.rel.to.DoesNotExist("%r is not related to %r." % (obj, self.instance))
    463471                remove.alters_data = True
    464472
    465473                def clear(self):
    466                     self.update(**{self.rel_field.name: None})
     474                    self.update(**{rel_field.name: None})
    467475                clear.alters_data = True
    468476
    469         attname = rel_field.rel.get_related_field().name
    470         return RelatedManager(model=self.related.model,
    471                               core_filters = {'%s__%s' % (rel_field.name, attname):
    472                                                   getattr(instance, attname)},
    473                               instance=instance,
    474                               rel_field=rel_field,
    475                               )
     477        return RelatedManager
     478    create_manager = memoize(create_manager, {}, 2)
    476479
    477480
    478481def create_many_related_manager(superclass, rel):
     
    663666    def __init__(self, related):
    664667        self.related = related   # RelatedObject instance
    665668
     669    @cached_property
     670    def related_manager_cls(self):
     671        # Dynamically create a class that subclasses the related
     672        # model's default manager.
     673        return create_many_related_manager(
     674            self.related.model._default_manager.__class__,
     675            self.related.field.rel
     676        )
     677
    666678    def __get__(self, instance, instance_type=None):
    667679        if instance is None:
    668680            return self
    669681
    670         # Dynamically create a class that subclasses the related
    671         # model's default manager.
    672682        rel_model = self.related.model
    673         superclass = rel_model._default_manager.__class__
    674         RelatedManager = create_many_related_manager(superclass, self.related.field.rel)
    675683
    676         manager = RelatedManager(
     684        manager = self.related_manager_cls(
    677685            model=rel_model,
    678686            core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()},
    679687            instance=instance,
     
    716724        # a property to ensure that the fully resolved value is returned.
    717725        return self.field.rel.through
    718726
     727    @cached_property
     728    def related_manager_cls(self):
     729        # Dynamically create a class that subclasses the related model's
     730        # default manager.
     731        return create_many_related_manager(
     732            self.field.rel.to._default_manager.__class__,
     733            self.field.rel
     734        )
     735
    719736    def __get__(self, instance, instance_type=None):
    720737        if instance is None:
    721738            return self
    722739
    723         # Dynamically create a class that subclasses the related
    724         # model's default manager.
    725         rel_model=self.field.rel.to
    726         superclass = rel_model._default_manager.__class__
    727         RelatedManager = create_many_related_manager(superclass, self.field.rel)
    728 
    729         manager = RelatedManager(
    730             model=rel_model,
     740        manager = self.related_manager_cls(
     741            model=self.field.rel.to,
    731742            core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()},
    732743            instance=instance,
    733744            symmetrical=self.field.rel.symmetrical,
  • django/utils/functional.py

    diff -r aeebc5cc7a4c django/utils/functional.py
    a b  
    2828        return result
    2929    return wrapper
    3030
     31class cached_property(object):
     32    """
     33    Decorator that creates converts a method with a single
     34    self argument into a property cached on the instance.
     35    """
     36    def __init__(self, func):
     37        self.func = func
     38
     39    def __get__(self, instance, type):
     40        res = instance.__dict__[self.func.__name__] = self.func(instance)
     41        return res
     42
    3143class Promise(object):
    3244    """
    3345    This is just a base class for the proxy class created in
     
    288300    results = ([], [])
    289301    for item in values:
    290302        results[predicate(item)].append(item)
    291     return results
    292  No newline at end of file
     303    return results
  • tests/modeltests/many_to_one/tests.py

    diff -r aeebc5cc7a4c tests/modeltests/many_to_one/tests.py
    a b  
    399399        self.assertEqual(repr(a3),
    400400                         repr(Article.objects.get(reporter_id=self.r2.id,
    401401                                             pub_date=datetime(2011, 5, 7))))
     402
     403    def test_manager_class_caching(self):
     404        r1 = Reporter.objects.create(first_name='Mike')
     405        r2 = Reporter.objects.create(first_name='John')
     406
     407        # Same twice
     408        self.assertTrue(r1.article_set.__class__ is r1.article_set.__class__)
     409
     410        # Same as each other
     411        self.assertTrue(r1.article_set.__class__ is r2.article_set.__class__)
  • tests/regressiontests/m2m_regress/tests.py

    diff -r aeebc5cc7a4c tests/regressiontests/m2m_regress/tests.py
    a b  
    7373
    7474        self.assertQuerysetEqual(c1.tags.all(), ["<Tag: t1>", "<Tag: t2>"])
    7575        self.assertQuerysetEqual(t1.tag_collections.all(), ["<TagCollection: c1>"])
     76
     77    def test_manager_class_caching(self):
     78        e1 = Entry.objects.create()
     79        e2 = Entry.objects.create()
     80        t1 = Tag.objects.create()
     81        t2 = Tag.objects.create()
     82
     83        # Get same manager twice in a row:
     84        self.assertTrue(t1.entry_set.__class__ is t1.entry_set.__class__)
     85        self.assertTrue(e1.topics.__class__ is e1.topics.__class__)
     86
     87        # Get same manager for different instances
     88        self.assertTrue(e1.topics.__class__ is e2.topics.__class__)
     89        self.assertTrue(t1.entry_set.__class__ is t2.entry_set.__class__)
Back to Top