Ticket #14270: django-m2m-cleanup.diff

File django-m2m-cleanup.diff, 11.3 KB (added by Carl Meyer, 14 years ago)

patch from Alex Gaynor

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

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    index 1634d7d..8a86742 100644
    a b from django.db.models.related import RelatedObject  
    88from django.db.models.query import QuerySet
    99from django.db.models.query_utils import QueryWrapper
    1010from django.utils.encoding import smart_unicode
    11 from django.utils.translation import ugettext_lazy as _, string_concat, ungettext, ugettext
    12 from django.utils.functional import curry
     11from django.utils.translation import (ugettext_lazy as _, string_concat,
     12    ungettext, ugettext)
     13from django.utils.functional import curry, cached_property, memoize
    1314from django.core import exceptions
    1415from django import forms
    1516
    class ForeignRelatedObjectsDescriptor(object):  
    373374    def __get__(self, instance, instance_type=None):
    374375        if instance is None:
    375376            return self
    376 
    377         return self.create_manager(instance,
    378                 self.related.model._default_manager.__class__)
     377        manager = self.create_manager(
     378            self.related.model._default_manager.__class__
     379        )
     380        return manager(instance)
    379381
    380382    def __set__(self, instance, value):
    381383        if instance is None:
    class ForeignRelatedObjectsDescriptor(object):  
    394396        than the default manager, as returned by __get__). Used by
    395397        Model.delete().
    396398        """
    397         return self.create_manager(instance,
    398                 self.related.model._base_manager.__class__)
     399        manager = self.create_manager(self.related.model._base_manager.__class__)
     400        return manager(instance)
    399401
    400     def create_manager(self, instance, superclass):
     402    def create_manager(self, superclass):
    401403        """
    402404        Creates the managers used by other methods (__get__() and delete()).
    403405        """
    class ForeignRelatedObjectsDescriptor(object):  
    405407        rel_model = self.related.model
    406408
    407409        class RelatedManager(superclass):
     410            def __init__(self, instance):
     411                super(RelatedManager, self).__init__()
     412                attname = rel_field.rel.get_related_field().name
     413                self.instance = instance
     414                self.core_filters = {
     415                    '%s__%s' % (rel_field.name, attname): getattr(instance, attname)
     416                }
     417                self.model = rel_model
     418           
    408419            def get_query_set(self):
    409                 db = self._db or router.db_for_read(rel_model, instance=instance)
    410                 return superclass.get_query_set(self).using(db).filter(**(self.core_filters))
     420                db = self._db or router.db_for_read(
     421                    rel_model, instance=self.instance
     422                )
     423                return superclass.get_query_set(self).using(db).filter(**self.core_filters)
    411424
    412425            def add(self, *objs):
    413426                for obj in objs:
    414427                    if not isinstance(obj, self.model):
    415428                        raise TypeError("'%s' instance expected" % self.model._meta.object_name)
    416                     setattr(obj, rel_field.name, instance)
     429                    setattr(obj, rel_field.name, self.instance)
    417430                    obj.save()
    418431            add.alters_data = True
    419432
    420433            def create(self, **kwargs):
    421                 kwargs.update({rel_field.name: instance})
    422                 db = router.db_for_write(rel_model, instance=instance)
     434                kwargs.update({rel_field.name: self.instance})
     435                db = router.db_for_write(rel_model, instance=self.instance)
    423436                return super(RelatedManager, self).using(db).create(**kwargs)
    424437            create.alters_data = True
    425438
    426439            def get_or_create(self, **kwargs):
    427440                # Update kwargs with the related object that this
    428441                # ForeignRelatedObjectsDescriptor knows about.
    429                 kwargs.update({rel_field.name: instance})
    430                 db = router.db_for_write(rel_model, instance=instance)
     442                kwargs.update({rel_field.name: self.instance})
     443                db = router.db_for_write(rel_model, instance=self.instance)
    431444                return super(RelatedManager, self).using(db).get_or_create(**kwargs)
    432445            get_or_create.alters_data = True
    433446
    434             # remove() and clear() are only provided if the ForeignKey can have a value of null.
     447            # remove() and clear() are only provided if the ForeignKey can have
     448            # a value of null.
    435449            if rel_field.null:
    436450                def remove(self, *objs):
    437                     val = getattr(instance, rel_field.rel.get_related_field().attname)
     451                    val = getattr(self.instance, rel_field.rel.get_related_field().attname)
    438452                    for obj in objs:
    439453                        # Is obj actually part of this descriptor set?
    440454                        if getattr(obj, rel_field.attname) == val:
    441455                            setattr(obj, rel_field.name, None)
    442456                            obj.save()
    443457                        else:
    444                             raise rel_field.rel.to.DoesNotExist("%r is not related to %r." % (obj, instance))
     458                            raise rel_field.rel.to.DoesNotExist("%r is not related to %r." % (obj, self.instance))
    445459                remove.alters_data = True
    446460
    447461                def clear(self):
    class ForeignRelatedObjectsDescriptor(object):  
    450464                        obj.save()
    451465                clear.alters_data = True
    452466
    453         manager = RelatedManager()
    454         attname = rel_field.rel.get_related_field().name
    455         manager.core_filters = {'%s__%s' % (rel_field.name, attname):
    456                 getattr(instance, attname)}
    457         manager.model = self.related.model
    458 
    459         return manager
     467        return RelatedManager
     468    create_manager = memoize(create_manager, {}, 2)
    460469
    461470def create_many_related_manager(superclass, rel=False):
    462471    """Creates a manager that subclasses 'superclass' (which is a Manager)
    class ManyRelatedObjectsDescriptor(object):  
    645654    # ManyRelatedObjectsDescriptor instance.
    646655    def __init__(self, related):
    647656        self.related = related   # RelatedObject instance
     657   
     658    @cached_property
     659    def related_manager_cls(self):
     660        # Dynamically create a class that subclasses the related
     661        # model's default manager.
     662        return create_many_related_manager(
     663            self.related.model._default_manager.__class__,
     664            self.related.field.rel
     665        )
    648666
    649667    def __get__(self, instance, instance_type=None):
    650668        if instance is None:
    651669            return self
    652670
    653         # Dynamically create a class that subclasses the related
    654         # model's default manager.
    655671        rel_model = self.related.model
    656         superclass = rel_model._default_manager.__class__
    657         RelatedManager = create_many_related_manager(superclass, self.related.field.rel)
    658 
    659         manager = RelatedManager(
     672        manager = self.related_manager_cls(
    660673            model=rel_model,
    661674            core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()},
    662675            instance=instance,
    class ReverseManyRelatedObjectsDescriptor(object):  
    690703    # ReverseManyRelatedObjectsDescriptor instance.
    691704    def __init__(self, m2m_field):
    692705        self.field = m2m_field
     706   
     707    @cached_property
     708    def related_manager_cls(self):
     709        # Dynamically create a class that subclasses the related model's
     710        # default manager.
     711        return create_many_related_manager(
     712            self.field.rel.to._default_manager.__class__,
     713            self.field.rel
     714        )
    693715
    694716    def _through(self):
    695717        # through is provided so that you have easy access to the through
    class ReverseManyRelatedObjectsDescriptor(object):  
    701723    def __get__(self, instance, instance_type=None):
    702724        if instance is None:
    703725            return self
    704 
    705         # Dynamically create a class that subclasses the related
    706         # model's default manager.
    707         rel_model=self.field.rel.to
    708         superclass = rel_model._default_manager.__class__
    709         RelatedManager = create_many_related_manager(superclass, self.field.rel)
    710 
    711         manager = RelatedManager(
    712             model=rel_model,
     726       
     727        manager = self.related_manager_cls(
     728            model=self.field.rel.to,
    713729            core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()},
    714730            instance=instance,
    715731            symmetrical=self.field.rel.symmetrical,
    class ManyToManyField(RelatedField, Field):  
    10221038        if hasattr(self, cache_attr):
    10231039            return getattr(self, cache_attr)
    10241040        for f in self.rel.through._meta.fields:
    1025             if hasattr(f,'rel') and f.rel and f.rel.to == related.model:
     1041            if hasattr(f, 'rel') and f.rel and f.rel.to == related.model:
    10261042                setattr(self, cache_attr, getattr(f, attr))
    10271043                return getattr(self, cache_attr)
    10281044
    class ManyToManyField(RelatedField, Field):  
    10491065                    break
    10501066        return getattr(self, cache_attr)
    10511067
    1052     def isValidIDList(self, field_data, all_data):
    1053         "Validates that the value is a valid list of foreign keys"
    1054         mod = self.rel.to
    1055         try:
    1056             pks = map(int, field_data.split(','))
    1057         except ValueError:
    1058             # the CommaSeparatedIntegerField validator will catch this error
    1059             return
    1060         objects = mod._default_manager.in_bulk(pks)
    1061         if len(objects) != len(pks):
    1062             badkeys = [k for k in pks if k not in objects]
    1063             raise exceptions.ValidationError(
    1064                 ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
    1065                           "Please enter valid %(self)s IDs. The values %(value)r are invalid.",
    1066                           len(badkeys)) % {
    1067                 'self': self.verbose_name,
    1068                 'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
    1069             })
    1070 
    10711068    def value_to_string(self, obj):
    10721069        data = ''
    10731070        if obj:
  • django/utils/functional.py

    diff --git a/django/utils/functional.py b/django/utils/functional.py
    index ccfbcb0..ed6fc57 100644
    a b def memoize(func, cache, num_args):  
    126126        return result
    127127    return wraps(func)(wrapper)
    128128
     129class cached_property(object):
     130    def __init__(self, func):
     131        self.func = func
     132   
     133    def __get__(self, instance, type):
     134        res = instance.__dict__[self.func.__name__] = self.func(instance)
     135        return res
     136
    129137class Promise(object):
    130138    """
    131139    This is just a base class for the proxy class created in
  • new file tests/regressiontests/m2m_regress/tests.py

    diff --git a/tests/regressiontests/m2m_regress/tests.py b/tests/regressiontests/m2m_regress/tests.py
    new file mode 100644
    index 0000000..cbd54af
    - +  
     1from django.test import TestCase
     2
     3from models import Entry, Tag
     4
     5
     6class M2MTests(TestCase):
     7    def test_manager_class(self):
     8        e1 = Entry.objects.create()
     9        e2 = Entry.objects.create()
     10        t1 = Tag.objects.create()
     11        t2 = Tag.objects.create()
     12        self.assertTrue(e1.topics.__class__ is e1.topics.__class__)
     13        self.assertTrue(e2.topics.__class__ is e2.topics.__class__)
     14       
     15        self.assertTrue(t1.entry_set.__class__ is t1.entry_set.__class__)
     16        self.assertTrue(t1.entry_set.__class__ is t2.entry_set.__class__)
Back to Top