diff options
| author | Loic Bistuer <loic.bistuer@gmail.com> | 2014-03-27 19:32:48 +0800 |
|---|---|---|
| committer | Loic Bistuer <loic.bistuer@gmail.com> | 2014-03-30 15:36:45 +0700 |
| commit | 20399083f49faceef1e48530822137257bca9d6a (patch) | |
| tree | 6f1c65e9284e8f9a1489c99cc0045bfbe7301dc7 /django | |
| parent | 2f3e1fe3234f5ebaca7635b0a080c2a751c3c758 (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.py | 5 | ||||
| -rw-r--r-- | django/db/models/fields/related.py | 17 |
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() |
