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__) |