Created
January 27, 2015 20:10
-
-
Save loic/49118661dea42fbf8be5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
commit bc5eac933cdbe129625c6fc5054859b33f77ab88 | |
Author: Loic Bistuer <[email protected]> | |
Date: Wed Jan 28 02:30:33 2015 +0700 | |
WIP | |
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py | |
index d917b01..d5bc763 100644 | |
--- a/django/db/models/fields/related.py | |
+++ b/django/db/models/fields/related.py | |
@@ -806,7 +806,7 @@ class ForeignRelatedObjectsDescriptor(object): | |
# some other model. In the example "poll.choice_set", the choice_set | |
# attribute is a ForeignRelatedObjectsDescriptor instance. | |
def __init__(self, related): | |
- self.related = related # RelatedObject instance | |
+ self.related = related | |
def __get__(self, instance, instance_type=None): | |
if instance is None: | |
@@ -839,38 +839,51 @@ class ForeignRelatedObjectsDescriptor(object): | |
) | |
-def create_many_related_manager(superclass, rel): | |
- """Creates a manager that subclasses 'superclass' (which is a Manager) | |
- and adds behavior for many-to-many related objects.""" | |
+def create_many_related_manager(superclass, rel, reverse): | |
+ """ | |
+ Creates a manager that subclasses 'superclass' (which is a Manager) | |
+ and adds behavior for many-to-many related objects. | |
+ """ | |
+ | |
class ManyRelatedManager(superclass): | |
- def __init__(self, model=None, query_field_name=None, instance=None, symmetrical=None, | |
- source_field_name=None, target_field_name=None, reverse=False, | |
- through=None, prefetch_cache_name=None): | |
+ | |
+ def __init__(self, instance=None): | |
super(ManyRelatedManager, self).__init__() | |
- self.model = model | |
- self.query_field_name = query_field_name | |
- source_field = through._meta.get_field(source_field_name) | |
- source_related_fields = source_field.related_fields | |
+ self.instance = instance | |
- self.core_filters = {} | |
- for lh_field, rh_field in source_related_fields: | |
- self.core_filters['%s__%s' % (query_field_name, rh_field.name)] = getattr(instance, rh_field.attname) | |
+ if not reverse: | |
+ self.model = rel.to | |
+ self.query_field_name = rel.field.related_query_name() | |
+ self.prefetch_cache_name = rel.field.name | |
+ self.source_field_name = rel.field.m2m_field_name() | |
+ self.target_field_name = rel.field.m2m_reverse_field_name() | |
+ self.symmetrical = rel.symmetrical | |
- self.instance = instance | |
- self.symmetrical = symmetrical | |
- self.source_field = source_field | |
- self.target_field = through._meta.get_field(target_field_name) | |
- self.source_field_name = source_field_name | |
- self.target_field_name = target_field_name | |
+ else: | |
+ self.model = rel.related_model | |
+ self.query_field_name = rel.field.name | |
+ self.prefetch_cache_name = rel.field.related_query_name() | |
+ self.source_field_name = rel.field.m2m_reverse_field_name() | |
+ self.target_field_name = rel.field.m2m_field_name() | |
+ self.symmetrical=False | |
+ | |
+ self.through = rel.through | |
self.reverse = reverse | |
- self.through = through | |
- self.prefetch_cache_name = prefetch_cache_name | |
- self.related_val = source_field.get_foreign_related_value(instance) | |
+ | |
+ self.source_field = self.through._meta.get_field(self.source_field_name) | |
+ self.target_field = self.through._meta.get_field(self.target_field_name) | |
+ | |
+ self.core_filters = {} | |
+ for lh_field, rh_field in self.source_field.related_fields: | |
+ self.core_filters['%s__%s' % (self.query_field_name, rh_field.name)] = getattr(instance, rh_field.attname) | |
+ | |
+ self.related_val = self.source_field.get_foreign_related_value(instance) | |
if None in self.related_val: | |
raise ValueError('"%r" needs to have a value for field "%s" before ' | |
'this many-to-many relationship can be used.' % | |
- (instance, source_field_name)) | |
+ (instance, self.source_field_name)) | |
+ | |
# Even if this relation is not to pk, we require still pk value. | |
# The wish is that the instance has been already saved to DB, | |
# although having a pk value isn't a guarantee of that. | |
@@ -883,18 +896,8 @@ def create_many_related_manager(superclass, rel): | |
# We use **kwargs rather than a kwarg argument to enforce the | |
# `manager='manager_name'` syntax. | |
manager = getattr(self.model, kwargs.pop('manager')) | |
- manager_class = create_many_related_manager(manager.__class__, rel) | |
- return manager_class( | |
- model=self.model, | |
- query_field_name=self.query_field_name, | |
- instance=self.instance, | |
- symmetrical=self.symmetrical, | |
- source_field_name=self.source_field_name, | |
- target_field_name=self.target_field_name, | |
- reverse=self.reverse, | |
- through=self.through, | |
- prefetch_cache_name=self.prefetch_cache_name, | |
- ) | |
+ manager_class = create_many_related_manager(manager.__class__, rel, self.reverse) | |
+ return manager_class(instance=self.instance) | |
do_not_call_in_templates = True | |
def _build_remove_filters(self, removed_vals): | |
@@ -959,7 +962,7 @@ def create_many_related_manager(superclass, rel): | |
) | |
def add(self, *objs): | |
- if not rel.through._meta.auto_created: | |
+ if not self.through._meta.auto_created: | |
opts = self.through._meta | |
raise AttributeError( | |
"Cannot use add() on a ManyToManyField which specifies an " | |
@@ -977,7 +980,7 @@ def create_many_related_manager(superclass, rel): | |
add.alters_data = True | |
def remove(self, *objs): | |
- if not rel.through._meta.auto_created: | |
+ if not self.through._meta.auto_created: | |
opts = self.through._meta | |
raise AttributeError( | |
"Cannot use remove() on a ManyToManyField which specifies " | |
@@ -1144,111 +1147,36 @@ def create_many_related_manager(superclass, rel): | |
class ManyRelatedObjectsDescriptor(object): | |
# This class provides the functionality that makes the related-object | |
# managers available as attributes on a model class, for fields that have | |
- # multiple "remote" values and have a ManyToManyField pointed at them by | |
- # some other model (rather than having a ManyToManyField themselves). | |
- # In the example "publication.article_set", the article_set attribute is a | |
- # ManyRelatedObjectsDescriptor instance. | |
- def __init__(self, related): | |
- self.related = related # RelatedObject instance | |
- | |
- @cached_property | |
- def related_manager_cls(self): | |
- # Dynamically create a class that subclasses the related | |
- # model's default manager. | |
- return create_many_related_manager( | |
- self.related.related_model._default_manager.__class__, | |
- self.related.field.rel | |
- ) | |
- | |
- def __get__(self, instance, instance_type=None): | |
- if instance is None: | |
- return self | |
- | |
- rel_model = self.related.related_model | |
- | |
- manager = self.related_manager_cls( | |
- model=rel_model, | |
- query_field_name=self.related.field.name, | |
- prefetch_cache_name=self.related.field.related_query_name(), | |
- instance=instance, | |
- symmetrical=False, | |
- source_field_name=self.related.field.m2m_reverse_field_name(), | |
- target_field_name=self.related.field.m2m_field_name(), | |
- reverse=True, | |
- through=self.related.field.rel.through, | |
- ) | |
- | |
- return manager | |
- | |
- def __set__(self, instance, value): | |
- if not self.related.field.rel.through._meta.auto_created: | |
- opts = self.related.field.rel.through._meta | |
- raise AttributeError( | |
- "Cannot set values on a ManyToManyField which specifies an " | |
- "intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name) | |
- ) | |
- | |
- # Force evaluation of `value` in case it's a queryset whose | |
- # value could be affected by `manager.clear()`. Refs #19816. | |
- value = tuple(value) | |
- | |
- manager = self.__get__(instance) | |
- db = router.db_for_write(manager.through, instance=manager.instance) | |
- with transaction.atomic(using=db, savepoint=False): | |
- manager.clear() | |
- manager.add(*value) | |
- | |
- | |
-class ReverseManyRelatedObjectsDescriptor(object): | |
- # This class provides the functionality that makes the related-object | |
- # managers available as attributes on a model class, for fields that have | |
- # multiple "remote" values and have a ManyToManyField defined in their | |
- # model (rather than having another model pointed *at* them). | |
- # In the example "article.publications", the publications attribute is a | |
- # ReverseManyRelatedObjectsDescriptor instance. | |
- def __init__(self, m2m_field): | |
- self.field = m2m_field | |
+ # multiple "remote" values | |
+ def __init__(self, rel, reverse=False): | |
+ self.rel = rel | |
+ self.reverse = reverse | |
@property | |
def through(self): | |
- # through is provided so that you have easy access to the through | |
+ # This is provided so that you have easy access to the through | |
# model (Book.authors.through) for inlines, etc. This is done as | |
# a property to ensure that the fully resolved value is returned. | |
- return self.field.rel.through | |
+ return self.rel.through | |
@cached_property | |
def related_manager_cls(self): | |
- # Dynamically create a class that subclasses the related model's | |
- # default manager. | |
- return create_many_related_manager( | |
- self.field.rel.to._default_manager.__class__, | |
- self.field.rel | |
- ) | |
+ # Dynamically create a class that subclasses the related | |
+ # model's default manager. | |
+ model = self.rel.related_model if self.reverse else self.rel.to | |
+ return create_many_related_manager(model._default_manager.__class__, self.rel, self.reverse) | |
def __get__(self, instance, instance_type=None): | |
if instance is None: | |
return self | |
- | |
- manager = self.related_manager_cls( | |
- model=self.field.rel.to, | |
- query_field_name=self.field.related_query_name(), | |
- prefetch_cache_name=self.field.name, | |
- instance=instance, | |
- symmetrical=self.field.rel.symmetrical, | |
- source_field_name=self.field.m2m_field_name(), | |
- target_field_name=self.field.m2m_reverse_field_name(), | |
- reverse=False, | |
- through=self.field.rel.through, | |
- ) | |
- | |
- return manager | |
+ return self.related_manager_cls(instance=instance) | |
def __set__(self, instance, value): | |
- if not self.field.rel.through._meta.auto_created: | |
- opts = self.field.rel.through._meta | |
+ if not self.rel.through._meta.auto_created: | |
+ opts = self.rel.through._meta | |
raise AttributeError( | |
"Cannot set values on a ManyToManyField which specifies an " | |
- "intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name) | |
+ "intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name) | |
) | |
# Force evaluation of `value` in case it's a queryset whose | |
@@ -1474,10 +1402,6 @@ class ManyToManyRel(ForeignObjectRel): | |
self.symmetrical = symmetrical | |
self.db_constraint = db_constraint | |
- def is_hidden(self): | |
- "Should the related object be hidden?" | |
- return self.related_name is not None and self.related_name[-1] == '+' | |
- | |
def get_related_field(self): | |
""" | |
Returns the field in the 'to' object to which this relationship is tied. | |
@@ -2567,7 +2491,7 @@ class ManyToManyField(RelatedField): | |
self.rel.through = create_many_to_many_intermediary_model(self, cls) | |
# Add the descriptor for the m2m relation | |
- setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self)) | |
+ setattr(cls, self.name, ManyRelatedObjectsDescriptor(self.rel)) | |
# Set up the accessor for the m2m table name for the relation | |
self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) | |
@@ -2583,7 +2507,7 @@ class ManyToManyField(RelatedField): | |
# Internal M2Ms (i.e., those with a related name ending with '+') | |
# and swapped models don't get a related descriptor. | |
if not self.rel.is_hidden() and not related.related_model._meta.swapped: | |
- setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related)) | |
+ setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related, reverse=True)) | |
# Set up the accessors for the column names on the m2m table | |
self.m2m_column_name = curry(self._get_m2m_attr, related, 'column') | |
diff --git a/tests/many_to_many/tests.py b/tests/many_to_many/tests.py | |
index adb8978..8626a44 100644 | |
--- a/tests/many_to_many/tests.py | |
+++ b/tests/many_to_many/tests.py | |
@@ -385,7 +385,7 @@ class ManyToManyTests(TestCase): | |
def test_reverse_assign_with_queryset(self): | |
# Ensure that querysets used in M2M assignments are pre-evaluated | |
# so their value isn't affected by the clearing operation in | |
- # ReverseManyRelatedObjectsDescriptor.__set__. Refs #19816. | |
+ # ManyRelatedObjectsDescriptor.__set__. Refs #19816. | |
self.p1.article_set = [self.a1, self.a2] | |
qs = self.p1.article_set.filter(headline='Django lets you build Web apps easily') | |
diff --git a/tests/schema/fields.py b/tests/schema/fields.py | |
index 4f70c96..e536b52 100644 | |
--- a/tests/schema/fields.py | |
+++ b/tests/schema/fields.py | |
@@ -1,8 +1,8 @@ | |
from django.db.models.fields.related import ( | |
create_many_to_many_intermediary_model, | |
ManyToManyField, ManyToManyRel, RelatedField, | |
- RECURSIVE_RELATIONSHIP_CONSTANT, ReverseManyRelatedObjectsDescriptor, | |
-) | |
+ RECURSIVE_RELATIONSHIP_CONSTANT, | |
+ ManyRelatedObjectsDescriptor) | |
from django.utils.functional import curry | |
@@ -41,7 +41,7 @@ class CustomManyToManyField(RelatedField): | |
super(CustomManyToManyField, self).contribute_to_class(cls, name, **kwargs) | |
if not self.rel.through and not cls._meta.abstract and not cls._meta.swapped: | |
self.rel.through = create_many_to_many_intermediary_model(self, cls) | |
- setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self)) | |
+ setattr(cls, self.name, ManyRelatedObjectsDescriptor(self)) | |
self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) | |
def get_internal_type(self): |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment