Refs #25550 -- Removed support for direct assignment to the reverse side of a related set.

This commit is contained in:
Tim Graham 2016-12-31 09:04:09 -05:00
parent e0910dcc92
commit ed251246cc
5 changed files with 17 additions and 74 deletions

View File

@ -510,28 +510,16 @@ class ReverseManyToOneDescriptor(object):
return self.related_manager_cls(instance)
def _get_set_deprecation_msg_params(self):
return ( # RemovedInDjango20Warning
return (
'reverse side of a related set',
self.rel.get_accessor_name(),
)
def __set__(self, instance, value):
"""
Set the related objects through the reverse relation.
With the example above, when setting ``parent.children = children``:
- ``self`` is the descriptor managing the ``children`` attribute
- ``instance`` is the ``parent`` instance
- ``value`` is the ``children`` sequence on the right of the equal sign
"""
warnings.warn(
'Direct assignment to the %s is deprecated due to the implicit '
'save() that happens. Use %s.set() instead.' % self._get_set_deprecation_msg_params(),
RemovedInDjango20Warning, stacklevel=2,
raise TypeError(
'Direct assignment to the %s is prohibited. Use %s.set() instead.'
% self._get_set_deprecation_msg_params(),
)
manager = self.__get__(instance)
manager.set(value)
def create_reverse_many_to_one_manager(superclass, rel):
@ -772,7 +760,7 @@ class ManyToManyDescriptor(ReverseManyToOneDescriptor):
)
def _get_set_deprecation_msg_params(self):
return ( # RemovedInDjango20Warning
return (
'%s side of a many-to-many set' % ('reverse' if self.reverse else 'forward'),
self.rel.get_accessor_name() if self.reverse else self.field.name,
)

View File

@ -177,26 +177,3 @@ Related objects reference
.. versionchanged:: 1.11
The clearing of the prefetched cache described above was added.
Direct Assignment
=================
A related object set can be replaced in bulk with one operation by assigning a
new iterable of objects to it::
>>> new_list = [obj1, obj2, obj3]
>>> e.related_set = new_list
If the foreign key relationship has ``null=True``, then the related manager
will first disassociate any existing objects in the related set before adding
the contents of ``new_list``. Otherwise the objects in ``new_list`` will be
added to the existing related object set.
.. deprecated:: 1.10
Direct assignment is deprecated in favor of the
:meth:`~django.db.models.fields.related.RelatedManager.set` method::
>>> e.related_set.set([obj1, obj2, obj3])
This prevents confusion about an assignment resulting in an implicit save.

View File

@ -321,3 +321,6 @@ these features.
``django.template.base.Origin`` are removed.
* The ``makemigrations --exit`` option is removed.
* Support for direct assignment to a reverse foreign key or many-to-many
relation is removed.

View File

@ -1,8 +1,7 @@
from __future__ import unicode_literals
from django.db import transaction
from django.test import TestCase, ignore_warnings
from django.utils.deprecation import RemovedInDjango20Warning
from django.test import TestCase
from .models import Article, InheritedArticleA, InheritedArticleB, Publication
@ -400,45 +399,22 @@ class ManyToManyTests(TestCase):
self.a4.publications.set([], clear=True)
self.assertQuerysetEqual(self.a4.publications.all(), [])
def test_assign_forward_deprecation(self):
def test_assign_forward(self):
msg = (
"Direct assignment to the reverse side of a many-to-many set is "
"deprecated due to the implicit save() that happens. Use "
"article_set.set() instead."
"prohibited. Use article_set.set() instead."
)
with self.assertRaisesMessage(RemovedInDjango20Warning, msg):
with self.assertRaisesMessage(TypeError, msg):
self.p2.article_set = [self.a4, self.a3]
def test_assign_reverse_deprecation(self):
def test_assign_reverse(self):
msg = (
"Direct assignment to the forward side of a many-to-many "
"set is deprecated due to the implicit save() that happens. Use "
"publications.set() instead."
"set is prohibited. Use publications.set() instead."
)
with self.assertRaisesMessage(RemovedInDjango20Warning, msg):
with self.assertRaisesMessage(TypeError, msg):
self.a1.publications = [self.p1, self.p2]
@ignore_warnings(category=RemovedInDjango20Warning)
def test_assign_deprecated(self):
self.p2.article_set = [self.a4, self.a3]
self.assertQuerysetEqual(
self.p2.article_set.all(),
[
'<Article: NASA finds intelligent life on Earth>',
'<Article: Oxygen-free diet works wonders>',
]
)
self.assertQuerysetEqual(self.a4.publications.all(), ['<Publication: Science News>'])
self.a4.publications = [self.p3.id]
self.assertQuerysetEqual(self.p2.article_set.all(), ['<Article: NASA finds intelligent life on Earth>'])
self.assertQuerysetEqual(self.a4.publications.all(), ['<Publication: Science Weekly>'])
# An alternate to calling clear() is to assign the empty set
self.p2.article_set = []
self.assertQuerysetEqual(self.p2.article_set.all(), [])
self.a4.publications = []
self.assertQuerysetEqual(self.a4.publications.all(), [])
def test_assign(self):
# Relation sets can be assigned using set().
self.p2.article_set.set([self.a4, self.a3])

View File

@ -115,10 +115,9 @@ class ManyToOneTests(TestCase):
def test_reverse_assignment_deprecation(self):
msg = (
"Direct assignment to the reverse side of a related set is "
"deprecated due to the implicit save() that happens. Use "
"article_set.set() instead."
"prohibited. Use article_set.set() instead."
)
with self.assertRaisesMessage(RemovedInDjango20Warning, msg):
with self.assertRaisesMessage(TypeError, msg):
self.r2.article_set = []
def test_assign(self):