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
|
8 | 8 | from django.db.models.query import QuerySet |
9 | 9 | from django.db.models.query_utils import QueryWrapper |
10 | 10 | from 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 |
| 11 | from django.utils.translation import (ugettext_lazy as _, string_concat, |
| 12 | ungettext, ugettext) |
| 13 | from django.utils.functional import curry, cached_property, memoize |
13 | 14 | from django.core import exceptions |
14 | 15 | from django import forms |
15 | 16 | |
… |
… |
class ForeignRelatedObjectsDescriptor(object):
|
373 | 374 | def __get__(self, instance, instance_type=None): |
374 | 375 | if instance is None: |
375 | 376 | 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) |
379 | 381 | |
380 | 382 | def __set__(self, instance, value): |
381 | 383 | if instance is None: |
… |
… |
class ForeignRelatedObjectsDescriptor(object):
|
394 | 396 | than the default manager, as returned by __get__). Used by |
395 | 397 | Model.delete(). |
396 | 398 | """ |
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) |
399 | 401 | |
400 | | def create_manager(self, instance, superclass): |
| 402 | def create_manager(self, superclass): |
401 | 403 | """ |
402 | 404 | Creates the managers used by other methods (__get__() and delete()). |
403 | 405 | """ |
… |
… |
class ForeignRelatedObjectsDescriptor(object):
|
405 | 407 | rel_model = self.related.model |
406 | 408 | |
407 | 409 | 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 | |
408 | 419 | 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) |
411 | 424 | |
412 | 425 | def add(self, *objs): |
413 | 426 | for obj in objs: |
414 | 427 | if not isinstance(obj, self.model): |
415 | 428 | 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) |
417 | 430 | obj.save() |
418 | 431 | add.alters_data = True |
419 | 432 | |
420 | 433 | 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) |
423 | 436 | return super(RelatedManager, self).using(db).create(**kwargs) |
424 | 437 | create.alters_data = True |
425 | 438 | |
426 | 439 | def get_or_create(self, **kwargs): |
427 | 440 | # Update kwargs with the related object that this |
428 | 441 | # 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) |
431 | 444 | return super(RelatedManager, self).using(db).get_or_create(**kwargs) |
432 | 445 | get_or_create.alters_data = True |
433 | 446 | |
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. |
435 | 449 | if rel_field.null: |
436 | 450 | 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) |
438 | 452 | for obj in objs: |
439 | 453 | # Is obj actually part of this descriptor set? |
440 | 454 | if getattr(obj, rel_field.attname) == val: |
441 | 455 | setattr(obj, rel_field.name, None) |
442 | 456 | obj.save() |
443 | 457 | 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)) |
445 | 459 | remove.alters_data = True |
446 | 460 | |
447 | 461 | def clear(self): |
… |
… |
class ForeignRelatedObjectsDescriptor(object):
|
450 | 464 | obj.save() |
451 | 465 | clear.alters_data = True |
452 | 466 | |
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) |
460 | 469 | |
461 | 470 | def create_many_related_manager(superclass, rel=False): |
462 | 471 | """Creates a manager that subclasses 'superclass' (which is a Manager) |
… |
… |
class ManyRelatedObjectsDescriptor(object):
|
645 | 654 | # ManyRelatedObjectsDescriptor instance. |
646 | 655 | def __init__(self, related): |
647 | 656 | 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 | ) |
648 | 666 | |
649 | 667 | def __get__(self, instance, instance_type=None): |
650 | 668 | if instance is None: |
651 | 669 | return self |
652 | 670 | |
653 | | # Dynamically create a class that subclasses the related |
654 | | # model's default manager. |
655 | 671 | 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( |
660 | 673 | model=rel_model, |
661 | 674 | core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()}, |
662 | 675 | instance=instance, |
… |
… |
class ReverseManyRelatedObjectsDescriptor(object):
|
690 | 703 | # ReverseManyRelatedObjectsDescriptor instance. |
691 | 704 | def __init__(self, m2m_field): |
692 | 705 | 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 | ) |
693 | 715 | |
694 | 716 | def _through(self): |
695 | 717 | # through is provided so that you have easy access to the through |
… |
… |
class ReverseManyRelatedObjectsDescriptor(object):
|
701 | 723 | def __get__(self, instance, instance_type=None): |
702 | 724 | if instance is None: |
703 | 725 | 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, |
713 | 729 | core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()}, |
714 | 730 | instance=instance, |
715 | 731 | symmetrical=self.field.rel.symmetrical, |
… |
… |
class ManyToManyField(RelatedField, Field):
|
1022 | 1038 | if hasattr(self, cache_attr): |
1023 | 1039 | return getattr(self, cache_attr) |
1024 | 1040 | 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: |
1026 | 1042 | setattr(self, cache_attr, getattr(f, attr)) |
1027 | 1043 | return getattr(self, cache_attr) |
1028 | 1044 | |
… |
… |
class ManyToManyField(RelatedField, Field):
|
1049 | 1065 | break |
1050 | 1066 | return getattr(self, cache_attr) |
1051 | 1067 | |
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 | | |
1071 | 1068 | def value_to_string(self, obj): |
1072 | 1069 | data = '' |
1073 | 1070 | if obj: |
diff --git a/django/utils/functional.py b/django/utils/functional.py
index ccfbcb0..ed6fc57 100644
a
|
b
|
def memoize(func, cache, num_args):
|
126 | 126 | return result |
127 | 127 | return wraps(func)(wrapper) |
128 | 128 | |
| 129 | class 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 | |
129 | 137 | class Promise(object): |
130 | 138 | """ |
131 | 139 | This is just a base class for the proxy class created in |
diff --git a/tests/regressiontests/m2m_regress/tests.py b/tests/regressiontests/m2m_regress/tests.py
new file mode 100644
index 0000000..cbd54af
-
|
+
|
|
| 1 | from django.test import TestCase |
| 2 | |
| 3 | from models import Entry, Tag |
| 4 | |
| 5 | |
| 6 | class 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__) |