summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorLoic Bistuer <loic.bistuer@gmail.com>2014-03-27 19:32:48 +0800
committerLoic Bistuer <loic.bistuer@gmail.com>2014-03-30 15:36:45 +0700
commit20399083f49faceef1e48530822137257bca9d6a (patch)
tree6f1c65e9284e8f9a1489c99cc0045bfbe7301dc7 /django
parent2f3e1fe3234f5ebaca7635b0a080c2a751c3c758 (diff)
Fixed #19816 -- Pre-evaluate querysets used in direct relation assignments.
Since assignments on M2M or reverse FK descriptors is composed of a `clear()`, followed by an `add()`, `clear()` could potentially affect the value of the assigned queryset before the `add()` step; pre-evaluating it solves the problem. This patch fixes the issue for ForeignRelatedObjectsDescriptor, ManyRelatedObjectsDescriptor, and ReverseGenericRelatedObjectsDescriptor. It completes 6cb6e1 which addressed ReverseManyRelatedObjectsDescriptor.
Diffstat (limited to 'django')
-rw-r--r--django/contrib/contenttypes/fields.py5
-rw-r--r--django/db/models/fields/related.py17
2 files changed, 15 insertions, 7 deletions
diff --git a/django/contrib/contenttypes/fields.py b/django/contrib/contenttypes/fields.py
index 7e91957546..6ea225191a 100644
--- a/django/contrib/contenttypes/fields.py
+++ b/django/contrib/contenttypes/fields.py
@@ -393,8 +393,11 @@ class ReverseGenericRelatedObjectsDescriptor(object):
return manager
def __set__(self, instance, value):
- manager = self.__get__(instance)
+ # 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.model, instance=manager.instance)
with transaction.atomic(using=db, savepoint=False):
manager.clear()
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 523b64a7e2..a789772dce 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -763,8 +763,11 @@ class ForeignRelatedObjectsDescriptor(object):
return self.related_manager_cls(instance)
def __set__(self, instance, value):
- manager = self.__get__(instance)
+ # 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.model, instance=manager.instance)
with transaction.atomic(using=db, savepoint=False):
# If the foreign key can support nulls, then completely clear the related set.
@@ -1113,8 +1116,11 @@ class ManyRelatedObjectsDescriptor(object):
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))
- manager = self.__get__(instance)
+ # 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()
@@ -1170,12 +1176,11 @@ class ReverseManyRelatedObjectsDescriptor(object):
opts = self.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))
- manager = self.__get__(instance)
-
- # clear() can change expected output of 'value' queryset, we force evaluation
- # of queryset before clear; ticket #19816
+ # 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()