diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py index af6fdc3e25..19b465b8ad 100644 --- a/django/db/models/fields/related_descriptors.py +++ b/django/db/models/fields/related_descriptors.py @@ -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, ) diff --git a/docs/ref/models/relations.txt b/docs/ref/models/relations.txt index c34d3104b2..9cb61d14ed 100644 --- a/docs/ref/models/relations.txt +++ b/docs/ref/models/relations.txt @@ -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. diff --git a/docs/releases/2.0.txt b/docs/releases/2.0.txt index 4c9347d554..1d46067405 100644 --- a/docs/releases/2.0.txt +++ b/docs/releases/2.0.txt @@ -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. diff --git a/tests/many_to_many/tests.py b/tests/many_to_many/tests.py index 2917214471..e5bc51be47 100644 --- a/tests/many_to_many/tests.py +++ b/tests/many_to_many/tests.py @@ -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(), - [ - '', - '', - ] - ) - self.assertQuerysetEqual(self.a4.publications.all(), ['']) - self.a4.publications = [self.p3.id] - self.assertQuerysetEqual(self.p2.article_set.all(), ['']) - self.assertQuerysetEqual(self.a4.publications.all(), ['']) - - # 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]) diff --git a/tests/many_to_one/tests.py b/tests/many_to_one/tests.py index e29c485baa..c0dd316f79 100644 --- a/tests/many_to_one/tests.py +++ b/tests/many_to_one/tests.py @@ -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):