Fixed #25550 -- Deprecated direct assignment to the reverse side of a related set.

This commit is contained in:
Tim Graham 2015-10-08 17:17:10 -04:00
parent 0b5d32faca
commit 9c5e272860
37 changed files with 194 additions and 130 deletions

View File

@ -201,7 +201,7 @@ class DeserializedObject(object):
models.Model.save_base(self.object, using=using, raw=True, **kwargs) models.Model.save_base(self.object, using=using, raw=True, **kwargs)
if self.m2m_data and save_m2m: if self.m2m_data and save_m2m:
for accessor_name, object_list in self.m2m_data.items(): for accessor_name, object_list in self.m2m_data.items():
setattr(self.object, accessor_name, object_list) getattr(self.object, accessor_name).set(object_list)
# prevent a second (possibly accidental) call to save() from saving # prevent a second (possibly accidental) call to save() from saving
# the m2m data twice. # the m2m data twice.

View File

@ -1600,7 +1600,7 @@ class ManyToManyField(RelatedField):
return getattr(obj, self.attname).all() return getattr(obj, self.attname).all()
def save_form_data(self, instance, data): def save_form_data(self, instance, data):
setattr(instance, self.attname, data) getattr(instance, self.attname).set(data)
def formfield(self, **kwargs): def formfield(self, **kwargs):
db = kwargs.pop('using', None) db = kwargs.pop('using', None)

View File

@ -62,11 +62,13 @@ and two directions (forward and reverse) for a total of six combinations.
from __future__ import unicode_literals from __future__ import unicode_literals
import warnings
from operator import attrgetter from operator import attrgetter
from django.db import connections, router, transaction from django.db import connections, router, transaction
from django.db.models import Q, signals from django.db.models import Q, signals
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.functional import cached_property from django.utils.functional import cached_property
@ -477,6 +479,11 @@ class ReverseManyToOneDescriptor(object):
- ``instance`` is the ``parent`` instance - ``instance`` is the ``parent`` instance
- ``value`` in the ``children`` sequence on the right of the equal sign - ``value`` in the ``children`` sequence on the right of the equal sign
""" """
warnings.warn(
'Direct assignment to the reverse side of a related set is '
'deprecated due to the implicit save() that happens. Use %s.set() '
'instead.' % self.rel.get_accessor_name(), RemovedInDjango20Warning, stacklevel=2,
)
manager = self.__get__(instance) manager = self.__get__(instance)
manager.set(value) manager.set(value)

View File

@ -109,6 +109,9 @@ details on these changes.
* The ``makemigrations --exit`` option will be removed. * The ``makemigrations --exit`` option will be removed.
* Support for direct assignment to a reverse foreign key or many-to-many
relation will be removed.
.. _deprecation-removed-in-1.10: .. _deprecation-removed-in-1.10:
1.10 1.10

View File

@ -337,7 +337,7 @@ Fields
Many-to-many field to :class:`~django.contrib.auth.models.Permission`:: Many-to-many field to :class:`~django.contrib.auth.models.Permission`::
group.permissions = [permission_list] group.permissions.set([permission_list])
group.permissions.add(permission, permission, ...) group.permissions.add(permission, permission, ...)
group.permissions.remove(permission, permission, ...) group.permissions.remove(permission, permission, ...)
group.permissions.clear() group.permissions.clear()

View File

@ -179,8 +179,6 @@ Related objects reference
<intermediary-manytomany>` for a many-to-many relationship, some of the <intermediary-manytomany>` for a many-to-many relationship, some of the
related manager's methods are disabled. related manager's methods are disabled.
.. _direct-assignment:
Direct Assignment Direct Assignment
----------------- -----------------
@ -200,3 +198,12 @@ added to the existing related object set.
In earlier versions, direct assignment used to perform ``clear()`` followed In earlier versions, direct assignment used to perform ``clear()`` followed
by ``add()``. It now performs a ``set()`` with the keyword argument by ``add()``. It now performs a ``set()`` with the keyword argument
``clear=False``. ``clear=False``.
.. 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

@ -269,6 +269,21 @@ Miscellaneous
Features deprecated in 1.10 Features deprecated in 1.10
=========================== ===========================
Direct assignment to a reverse foreign key or many-to-many relation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Instead of assigning related objects using direct assignment::
>>> new_list = [obj1, obj2, obj3]
>>> e.related_set = new_list
Use the :meth:`~django.db.models.fields.related.RelatedManager.set` method
added in Django 1.9::
>>> e.related_set.set([obj1, obj2, obj3])
This prevents confusion about an assignment resulting in an implicit save.
Miscellaneous Miscellaneous
~~~~~~~~~~~~~ ~~~~~~~~~~~~~

View File

@ -673,13 +673,12 @@ Related object operations are run in a transaction
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Some operations on related objects such as Some operations on related objects such as
:meth:`~django.db.models.fields.related.RelatedManager.add()` or :meth:`~django.db.models.fields.related.RelatedManager.add()` or direct
:ref:`direct assignment<direct-assignment>` ran multiple data modifying assignment ran multiple data modifying queries without wrapping them in
queries without wrapping them in transactions. To reduce the risk of data transactions. To reduce the risk of data corruption, all data modifying methods
corruption, all data modifying methods that affect multiple related objects that affect multiple related objects (i.e. ``add()``, ``remove()``,
(i.e. ``add()``, ``remove()``, ``clear()``, and :ref:`direct assignment ``clear()``, and direct assignment) now perform their data modifying queries
<direct-assignment>`) now perform their data modifying queries from within a from within a transaction, provided your database supports transactions.
transaction, provided your database supports transactions.
This has one backwards incompatible side effect, signal handlers triggered from This has one backwards incompatible side effect, signal handlers triggered from
these methods are now executed within the method's transaction and any these methods are now executed within the method's transaction and any

View File

@ -747,11 +747,10 @@ setuptools is not installed.
Related set direct assignment Related set direct assignment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:ref:`Direct assignment <direct-assignment>` of related objects in the ORM used Direct assignment of related objects in the ORM used to perform a ``clear()``
to perform a ``clear()`` followed by a call to ``add()``. This caused followed by a call to ``add()``. This caused needlessly large data changes and
needlessly large data changes and prevented using the prevented using the :data:`~django.db.models.signals.m2m_changed` signal to
:data:`~django.db.models.signals.m2m_changed` signal to track individual track individual changes in many-to-many relations.
changes in many-to-many relations.
Direct assignment now relies on the the new Direct assignment now relies on the the new
:meth:`~django.db.models.fields.related.RelatedManager.set` method on related :meth:`~django.db.models.fields.related.RelatedManager.set` method on related

View File

@ -181,11 +181,11 @@ fields: ``groups`` and ``user_permissions``.
objects in the same way as any other :doc:`Django model objects in the same way as any other :doc:`Django model
</topics/db/models>`:: </topics/db/models>`::
myuser.groups = [group_list] myuser.groups.set([group_list])
myuser.groups.add(group, group, ...) myuser.groups.add(group, group, ...)
myuser.groups.remove(group, group, ...) myuser.groups.remove(group, group, ...)
myuser.groups.clear() myuser.groups.clear()
myuser.user_permissions = [permission_list] myuser.user_permissions.set([permission_list])
myuser.user_permissions.add(permission, permission, ...) myuser.user_permissions.add(permission, permission, ...)
myuser.user_permissions.remove(permission, permission, ...) myuser.user_permissions.remove(permission, permission, ...)
myuser.user_permissions.clear() myuser.user_permissions.clear()

View File

@ -221,11 +221,11 @@ And from the other end::
>>> a5.publications.all() >>> a5.publications.all()
<QuerySet []> <QuerySet []>
Relation sets can be assigned. Assignment clears any existing set members:: Relation sets can be set::
>>> a4.publications.all() >>> a4.publications.all()
<QuerySet [<Publication: Science News>]> <QuerySet [<Publication: Science News>]>
>>> a4.publications = [p3] >>> a4.publications.set([p3])
>>> a4.publications.all() >>> a4.publications.all()
<QuerySet [<Publication: Science Weekly>]> <QuerySet [<Publication: Science Weekly>]>
@ -282,18 +282,3 @@ referenced objects should be gone::
<QuerySet []> <QuerySet []>
>>> p1.article_set.all() >>> p1.article_set.all()
<QuerySet [<Article: NASA uses Python>]> <QuerySet [<Article: NASA uses Python>]>
An alternate to calling
:meth:`~django.db.models.fields.related.RelatedManager.clear` is to assign the
empty set::
>>> p1.article_set = []
>>> p1.article_set.all()
<QuerySet []>
>>> a2.publications = [p1, new_publication]
>>> a2.publications.all()
<QuerySet [<Publication: Highlights for Children>, <Publication: The Python Journal>]>
>>> a2.publications = []
>>> a2.publications.all()
<QuerySet []>

View File

@ -510,15 +510,15 @@ the intermediate model::
>>> beatles.members.all() >>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]> <QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>
Unlike normal many-to-many fields, you *can't* use ``add``, ``create``, Unlike normal many-to-many fields, you *can't* use ``add()``, ``create()``,
or assignment (i.e., ``beatles.members = [...]``) to create relationships:: or ``set()`` to create relationships::
# THIS WILL NOT WORK # THIS WILL NOT WORK
>>> beatles.members.add(john) >>> beatles.members.add(john)
# NEITHER WILL THIS # NEITHER WILL THIS
>>> beatles.members.create(name="George Harrison") >>> beatles.members.create(name="George Harrison")
# AND NEITHER WILL THIS # AND NEITHER WILL THIS
>>> beatles.members = [john, paul, ringo, george] >>> beatles.members.set([john, paul, ringo, george])
Why? You can't just create a relationship between a ``Person`` and a ``Group`` Why? You can't just create a relationship between a ``Person`` and a ``Group``
- you need to specify all the detail for the relationship required by the - you need to specify all the detail for the relationship required by the
@ -575,7 +575,6 @@ Another way to access the same information is by querying the
>>> ringos_membership.invite_reason >>> ringos_membership.invite_reason
'Needed a new drummer.' 'Needed a new drummer.'
One-to-one relationships One-to-one relationships
~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1223,12 +1223,11 @@ be found in the :doc:`related objects reference </ref/models/relations>`.
``set(objs)`` ``set(objs)``
Replace the set of related objects. Replace the set of related objects.
To assign the members of a related set in one fell swoop, just assign to it To assign the members of a related set, use the ``set()`` method with an
from any iterable object. The iterable can contain object instances, or just iterable of object instances or a list of primary key values. For example::
a list of primary key values. For example::
b = Blog.objects.get(id=1) b = Blog.objects.get(id=1)
b.entry_set = [e1, e2] b.entry_set.set([e1, e2]
In this example, ``e1`` and ``e2`` can be full Entry instances, or integer In this example, ``e1`` and ``e2`` can be full Entry instances, or integer
primary key values. primary key values.

View File

@ -263,8 +263,7 @@ class ListFiltersTests(TestCase):
title='Gipsy guitar for dummies', year=2002, is_best_seller=True, title='Gipsy guitar for dummies', year=2002, is_best_seller=True,
date_registered=self.one_week_ago, date_registered=self.one_week_ago,
) )
self.gipsy_book.contributors = [self.bob, self.lisa] self.gipsy_book.contributors.set([self.bob, self.lisa])
self.gipsy_book.save()
# Departments # Departments
self.dev = Department.objects.create(code='DEV', description='Development') self.dev = Department.objects.create(code='DEV', description='Development')

View File

@ -15,7 +15,7 @@ class CustomColumnsTests(TestCase):
self.authors = [self.a1, self.a2] self.authors = [self.a1, self.a2]
self.article = Article.objects.create(headline="Django lets you build Web apps easily", primary_author=self.a1) self.article = Article.objects.create(headline="Django lets you build Web apps easily", primary_author=self.a1)
self.article.authors = self.authors self.article.authors.set(self.authors)
def test_query_all_available_authors(self): def test_query_all_available_authors(self):
self.assertQuerysetEqual( self.assertQuerysetEqual(

View File

@ -284,7 +284,8 @@ class ManyToManyExclusionTestCase(TestCase):
'multi_choice_int': [opt1.pk], 'multi_choice_int': [opt1.pk],
} }
instance = ChoiceFieldModel.objects.create(**initial) instance = ChoiceFieldModel.objects.create(**initial)
instance.multi_choice = instance.multi_choice_int = [opt2, opt3] instance.multi_choice.set([opt2, opt3])
instance.multi_choice_int.set([opt2, opt3])
form = ChoiceFieldExclusionForm(data=data, instance=instance) form = ChoiceFieldExclusionForm(data=data, instance=instance)
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data['multi_choice'], data['multi_choice']) self.assertEqual(form.cleaned_data['multi_choice'], data['multi_choice'])

View File

@ -313,31 +313,31 @@ class GenericRelationsTests(TestCase):
fatty = bacon.tags.create(tag="fatty") fatty = bacon.tags.create(tag="fatty")
salty = bacon.tags.create(tag="salty") salty = bacon.tags.create(tag="salty")
bacon.tags = [fatty, salty] bacon.tags.set([fatty, salty])
self.assertQuerysetEqual(bacon.tags.all(), [ self.assertQuerysetEqual(bacon.tags.all(), [
"<TaggedItem: fatty>", "<TaggedItem: fatty>",
"<TaggedItem: salty>", "<TaggedItem: salty>",
]) ])
bacon.tags = [fatty] bacon.tags.set([fatty])
self.assertQuerysetEqual(bacon.tags.all(), [ self.assertQuerysetEqual(bacon.tags.all(), [
"<TaggedItem: fatty>", "<TaggedItem: fatty>",
]) ])
bacon.tags = [] bacon.tags.set([])
self.assertQuerysetEqual(bacon.tags.all(), []) self.assertQuerysetEqual(bacon.tags.all(), [])
def test_assign_with_queryset(self): def test_assign_with_queryset(self):
# Ensure that querysets used in reverse GFK assignments are pre-evaluated # Ensure that querysets used in reverse GFK assignments are pre-evaluated
# so their value isn't affected by the clearing operation in # so their value isn't affected by the clearing operation in
# ManyToManyDescriptor.__set__. Refs #19816. # ManyRelatedManager.set() (#19816).
bacon = Vegetable.objects.create(name="Bacon", is_yucky=False) bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
bacon.tags.create(tag="fatty") bacon.tags.create(tag="fatty")
bacon.tags.create(tag="salty") bacon.tags.create(tag="salty")
self.assertEqual(2, bacon.tags.count()) self.assertEqual(2, bacon.tags.count())
qs = bacon.tags.filter(tag="fatty") qs = bacon.tags.filter(tag="fatty")
bacon.tags = qs bacon.tags.set(qs)
self.assertEqual(1, bacon.tags.count()) self.assertEqual(1, bacon.tags.count())
self.assertEqual(1, qs.count()) self.assertEqual(1, qs.count())
@ -705,7 +705,7 @@ class ProxyRelatedModelTest(TestCase):
base.save() base.save()
newrel = ConcreteRelatedModel.objects.create() newrel = ConcreteRelatedModel.objects.create()
newrel.bases = [base] newrel.bases.set([base])
newrel = ConcreteRelatedModel.objects.get(pk=newrel.pk) newrel = ConcreteRelatedModel.objects.get(pk=newrel.pk)
self.assertEqual(base, newrel.bases.get()) self.assertEqual(base, newrel.bases.get())

View File

@ -16,7 +16,7 @@ class GetObjectOr404Tests(TestCase):
self.assertRaises(Http404, get_object_or_404, Article, title="Foo") self.assertRaises(Http404, get_object_or_404, Article, title="Foo")
article = Article.objects.create(title="Run away!") article = Article.objects.create(title="Run away!")
article.authors = [a1, a2] article.authors.set([a1, a2])
# get_object_or_404 can be passed a Model to query. # get_object_or_404 can be passed a Model to query.
self.assertEqual( self.assertEqual(
get_object_or_404(Article, title__contains="Run"), get_object_or_404(Article, title__contains="Run"),

View File

@ -677,13 +677,13 @@ class LookupTests(TestCase):
season_2011.games.create(home="Houston Astros", away="St. Louis Cardinals") season_2011.games.create(home="Houston Astros", away="St. Louis Cardinals")
season_2011.games.create(home="Houston Astros", away="Milwaukee Brewers") season_2011.games.create(home="Houston Astros", away="Milwaukee Brewers")
hunter_pence = Player.objects.create(name="Hunter Pence") hunter_pence = Player.objects.create(name="Hunter Pence")
hunter_pence.games = Game.objects.filter(season__year__in=[2009, 2010]) hunter_pence.games.set(Game.objects.filter(season__year__in=[2009, 2010]))
pudge = Player.objects.create(name="Ivan Rodriquez") pudge = Player.objects.create(name="Ivan Rodriquez")
pudge.games = Game.objects.filter(season__year=2009) pudge.games.set(Game.objects.filter(season__year=2009))
pedro_feliz = Player.objects.create(name="Pedro Feliz") pedro_feliz = Player.objects.create(name="Pedro Feliz")
pedro_feliz.games = Game.objects.filter(season__year__in=[2011]) pedro_feliz.games.set(Game.objects.filter(season__year__in=[2011]))
johnson = Player.objects.create(name="Johnson") johnson = Player.objects.create(name="Johnson")
johnson.games = Game.objects.filter(season__year__in=[2011]) johnson.games.set(Game.objects.filter(season__year__in=[2011]))
# Games in 2010 # Games in 2010
self.assertEqual(Game.objects.filter(season__year=2010).count(), 3) self.assertEqual(Game.objects.filter(season__year=2010).count(), 3)

View File

@ -74,7 +74,7 @@ class M2MRegressionTests(TestCase):
t2 = Tag.objects.create(name='t2') t2 = Tag.objects.create(name='t2')
c1 = TagCollection.objects.create(name='c1') c1 = TagCollection.objects.create(name='c1')
c1.tags = [t1, t2] c1.tags.set([t1, t2])
c1 = TagCollection.objects.get(name='c1') c1 = TagCollection.objects.get(name='c1')
self.assertQuerysetEqual(c1.tags.all(), ["<Tag: t1>", "<Tag: t2>"], ordered=False) self.assertQuerysetEqual(c1.tags.all(), ["<Tag: t1>", "<Tag: t2>"], ordered=False)
@ -104,10 +104,10 @@ class M2MRegressionTests(TestCase):
t1 = Tag.objects.create(name='t1') t1 = Tag.objects.create(name='t1')
t2 = Tag.objects.create(name='t2') t2 = Tag.objects.create(name='t2')
c1 = TagCollection.objects.create(name='c1') c1 = TagCollection.objects.create(name='c1')
c1.tags = [t1, t2] c1.tags.set([t1, t2])
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
c1.tags = 7 c1.tags.set(7)
c1.refresh_from_db() c1.refresh_from_db()
self.assertQuerysetEqual(c1.tags.order_by('name'), ["<Tag: t1>", "<Tag: t2>"]) self.assertQuerysetEqual(c1.tags.order_by('name'), ["<Tag: t1>", "<Tag: t2>"])

View File

@ -270,7 +270,7 @@ class ManyToManySignalsTest(TestCase):
self.assertEqual(self.m2m_changed_messages, expected_messages) self.assertEqual(self.m2m_changed_messages, expected_messages)
# direct assignment clears the set first, then adds # direct assignment clears the set first, then adds
self.vw.default_parts = [self.wheelset, self.doors, self.engine] self.vw.default_parts.set([self.wheelset, self.doors, self.engine])
expected_messages.append({ expected_messages.append({
'instance': self.vw, 'instance': self.vw,
'action': 'pre_remove', 'action': 'pre_remove',
@ -362,7 +362,7 @@ class ManyToManySignalsTest(TestCase):
# Check that signals still work when model inheritance is involved # Check that signals still work when model inheritance is involved
c4 = SportsCar.objects.create(name='Bugatti', price='1000000') c4 = SportsCar.objects.create(name='Bugatti', price='1000000')
c4b = Car.objects.get(name='Bugatti') c4b = Car.objects.get(name='Bugatti')
c4.default_parts = [self.doors] c4.default_parts.set([self.doors])
expected_messages.append({ expected_messages.append({
'instance': c4, 'instance': c4,
'action': 'pre_add', 'action': 'pre_add',
@ -407,7 +407,7 @@ class ManyToManySignalsTest(TestCase):
def test_m2m_relations_with_self_add_friends(self): def test_m2m_relations_with_self_add_friends(self):
self._initialize_signal_person() self._initialize_signal_person()
self.alice.friends = [self.bob, self.chuck] self.alice.friends.set([self.bob, self.chuck])
self.assertEqual(self.m2m_changed_messages, [ self.assertEqual(self.m2m_changed_messages, [
{ {
'instance': self.alice, 'instance': self.alice,
@ -426,7 +426,7 @@ class ManyToManySignalsTest(TestCase):
def test_m2m_relations_with_self_add_fan(self): def test_m2m_relations_with_self_add_fan(self):
self._initialize_signal_person() self._initialize_signal_person()
self.alice.fans = [self.daisy] self.alice.fans.set([self.daisy])
self.assertEqual(self.m2m_changed_messages, [ self.assertEqual(self.m2m_changed_messages, [
{ {
'instance': self.alice, 'instance': self.alice,
@ -445,7 +445,7 @@ class ManyToManySignalsTest(TestCase):
def test_m2m_relations_with_self_add_idols(self): def test_m2m_relations_with_self_add_idols(self):
self._initialize_signal_person() self._initialize_signal_person()
self.chuck.idols = [self.alice, self.bob] self.chuck.idols.set([self.alice, self.bob])
self.assertEqual(self.m2m_changed_messages, [ self.assertEqual(self.m2m_changed_messages, [
{ {
'instance': self.chuck, 'instance': self.chuck,

View File

@ -97,7 +97,7 @@ class M2mThroughTests(TestCase):
members = list(Person.objects.filter(name__in=['Bob', 'Jim'])) members = list(Person.objects.filter(name__in=['Bob', 'Jim']))
with self.assertRaisesMessage(AttributeError, msg): with self.assertRaisesMessage(AttributeError, msg):
setattr(self.rock, 'members', members) self.rock.members.set(members)
self.assertQuerysetEqual( self.assertQuerysetEqual(
self.rock.members.all(), self.rock.members.all(),
@ -166,7 +166,7 @@ class M2mThroughTests(TestCase):
members = list(Group.objects.filter(name__in=['Rock', 'Roll'])) members = list(Group.objects.filter(name__in=['Rock', 'Roll']))
with self.assertRaisesMessage(AttributeError, msg): with self.assertRaisesMessage(AttributeError, msg):
setattr(self.bob, 'group_set', members) self.bob.group_set.set(members)
self.assertQuerysetEqual( self.assertQuerysetEqual(
self.bob.group_set.all(), self.bob.group_set.all(),

View File

@ -49,10 +49,22 @@ class M2MThroughTestCase(TestCase):
) )
def test_cannot_use_setattr_on_reverse_m2m_with_intermediary_model(self): def test_cannot_use_setattr_on_reverse_m2m_with_intermediary_model(self):
self.assertRaises(AttributeError, setattr, self.bob, "group_set", []) msg = (
"Cannot set values on a ManyToManyField which specifies an "
"intermediary model. Use m2m_through_regress.Membership's Manager "
"instead."
)
with self.assertRaisesMessage(AttributeError, msg):
self.bob.group_set.set([])
def test_cannot_use_setattr_on_forward_m2m_with_intermediary_model(self): def test_cannot_use_setattr_on_forward_m2m_with_intermediary_model(self):
self.assertRaises(AttributeError, setattr, self.roll, "members", []) msg = (
"Cannot set values on a ManyToManyField which specifies an "
"intermediary model. Use m2m_through_regress.Membership's Manager "
"instead."
)
with self.assertRaisesMessage(AttributeError, msg):
self.roll.members.set([])
def test_cannot_use_create_on_m2m_with_intermediary_model(self): def test_cannot_use_create_on_m2m_with_intermediary_model(self):
self.assertRaises(AttributeError, self.rock.members.create, name="Anne") self.assertRaises(AttributeError, self.rock.members.create, name="Anne")

View File

@ -1,8 +1,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import transaction from django.db import transaction
from django.test import TestCase from django.test import TestCase, ignore_warnings
from django.utils import six from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning
from .models import Article, InheritedArticleA, InheritedArticleB, Publication from .models import Article, InheritedArticleA, InheritedArticleB, Publication
@ -371,8 +372,17 @@ class ManyToManyTests(TestCase):
self.a4.publications.set([], clear=True) self.a4.publications.set([], clear=True)
self.assertQuerysetEqual(self.a4.publications.all(), []) self.assertQuerysetEqual(self.a4.publications.all(), [])
def test_assign(self): def test_assign_deprecation(self):
# Relation sets can be assigned. Assignment clears any existing set members 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."
)
with self.assertRaisesMessage(RemovedInDjango20Warning, msg):
self.p2.article_set = [self.a4, self.a3]
@ignore_warnings(category=RemovedInDjango20Warning)
def test_assign_deprecated(self):
self.p2.article_set = [self.a4, self.a3] self.p2.article_set = [self.a4, self.a3]
self.assertQuerysetEqual(self.p2.article_set.all(), self.assertQuerysetEqual(self.p2.article_set.all(),
[ [
@ -393,9 +403,29 @@ class ManyToManyTests(TestCase):
self.a4.publications = [] self.a4.publications = []
self.assertQuerysetEqual(self.a4.publications.all(), []) 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])
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.set([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 set an empty set.
self.p2.article_set.set([])
self.assertQuerysetEqual(self.p2.article_set.all(), [])
self.a4.publications.set([])
self.assertQuerysetEqual(self.a4.publications.all(), [])
def test_assign_ids(self): def test_assign_ids(self):
# Relation sets can also be set using primary key values # Relation sets can also be set using primary key values
self.p2.article_set = [self.a4.id, self.a3.id] self.p2.article_set.set([self.a4.id, self.a3.id])
self.assertQuerysetEqual(self.p2.article_set.all(), self.assertQuerysetEqual(self.p2.article_set.all(),
[ [
'<Article: NASA finds intelligent life on Earth>', '<Article: NASA finds intelligent life on Earth>',
@ -403,7 +433,7 @@ class ManyToManyTests(TestCase):
]) ])
self.assertQuerysetEqual(self.a4.publications.all(), self.assertQuerysetEqual(self.a4.publications.all(),
['<Publication: Science News>']) ['<Publication: Science News>'])
self.a4.publications = [self.p3.id] self.a4.publications.set([self.p3.id])
self.assertQuerysetEqual(self.p2.article_set.all(), self.assertQuerysetEqual(self.p2.article_set.all(),
['<Article: NASA finds intelligent life on Earth>']) ['<Article: NASA finds intelligent life on Earth>'])
self.assertQuerysetEqual(self.a4.publications.all(), self.assertQuerysetEqual(self.a4.publications.all(),
@ -412,11 +442,11 @@ class ManyToManyTests(TestCase):
def test_forward_assign_with_queryset(self): def test_forward_assign_with_queryset(self):
# Ensure that querysets used in m2m assignments are pre-evaluated # Ensure that querysets used in m2m assignments are pre-evaluated
# so their value isn't affected by the clearing operation in # so their value isn't affected by the clearing operation in
# ManyToManyDescriptor.__set__. Refs #19816. # ManyRelatedManager.set() (#19816).
self.a1.publications = [self.p1, self.p2] self.a1.publications.set([self.p1, self.p2])
qs = self.a1.publications.filter(title='The Python Journal') qs = self.a1.publications.filter(title='The Python Journal')
self.a1.publications = qs self.a1.publications.set(qs)
self.assertEqual(1, self.a1.publications.count()) self.assertEqual(1, self.a1.publications.count())
self.assertEqual(1, qs.count()) self.assertEqual(1, qs.count())
@ -424,11 +454,11 @@ class ManyToManyTests(TestCase):
def test_reverse_assign_with_queryset(self): def test_reverse_assign_with_queryset(self):
# Ensure that querysets used in M2M assignments are pre-evaluated # Ensure that querysets used in M2M assignments are pre-evaluated
# so their value isn't affected by the clearing operation in # so their value isn't affected by the clearing operation in
# ManyToManyDescriptor.__set__. Refs #19816. # ManyRelatedManager.set() (#19816).
self.p1.article_set = [self.a1, self.a2] self.p1.article_set.set([self.a1, self.a2])
qs = self.p1.article_set.filter(headline='Django lets you build Web apps easily') qs = self.p1.article_set.filter(headline='Django lets you build Web apps easily')
self.p1.article_set = qs self.p1.article_set.set(qs)
self.assertEqual(1, self.p1.article_set.count()) self.assertEqual(1, self.p1.article_set.count())
self.assertEqual(1, qs.count()) self.assertEqual(1, qs.count())

View File

@ -5,6 +5,7 @@ from django.core.exceptions import FieldError, MultipleObjectsReturned
from django.db import models, transaction from django.db import models, transaction
from django.test import TestCase from django.test import TestCase
from django.utils import six from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.translation import ugettext_lazy from django.utils.translation import ugettext_lazy
from .models import ( from .models import (
@ -123,6 +124,15 @@ class ManyToOneTests(TestCase):
]) ])
self.assertQuerysetEqual(self.r2.article_set.all(), ["<Article: Paul's story>"]) self.assertQuerysetEqual(self.r2.article_set.all(), ["<Article: Paul's story>"])
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."
)
with self.assertRaisesMessage(RemovedInDjango20Warning, msg):
self.r2.article_set = []
def test_assign(self): def test_assign(self):
new_article = self.r.article_set.create(headline="John's second story", new_article = self.r.article_set.create(headline="John's second story",
pub_date=datetime.date(2005, 7, 29)) pub_date=datetime.date(2005, 7, 29))
@ -141,8 +151,8 @@ class ManyToOneTests(TestCase):
]) ])
self.assertQuerysetEqual(self.r2.article_set.all(), []) self.assertQuerysetEqual(self.r2.article_set.all(), [])
# Set the article back again using set descriptor. # Set the article back again using set() method.
self.r2.article_set = [new_article, new_article2] self.r2.article_set.set([new_article, new_article2])
self.assertQuerysetEqual(self.r.article_set.all(), ["<Article: This is a test>"]) self.assertQuerysetEqual(self.r.article_set.all(), ["<Article: This is a test>"])
self.assertQuerysetEqual(self.r2.article_set.all(), self.assertQuerysetEqual(self.r2.article_set.all(),
[ [
@ -150,9 +160,9 @@ class ManyToOneTests(TestCase):
"<Article: Paul's story>", "<Article: Paul's story>",
]) ])
# Funny case - assignment notation can only go so far; because the # Because the ForeignKey cannot be null, existing members of the set
# ForeignKey cannot be null, existing members of the set must remain. # must remain.
self.r.article_set = [new_article] self.r.article_set.set([new_article])
self.assertQuerysetEqual(self.r.article_set.all(), self.assertQuerysetEqual(self.r.article_set.all(),
[ [
"<Article: John's second story>", "<Article: John's second story>",

View File

@ -94,7 +94,7 @@ class ManyToOneNullTests(TestCase):
# Use descriptor assignment to allocate ForeignKey. Null is legal, so # Use descriptor assignment to allocate ForeignKey. Null is legal, so
# existing members of the set that are not in the assignment set are # existing members of the set that are not in the assignment set are
# set to null. # set to null.
self.r2.article_set = [self.a2, self.a3] self.r2.article_set.set([self.a2, self.a3])
self.assertQuerysetEqual(self.r2.article_set.all(), self.assertQuerysetEqual(self.r2.article_set.all(),
['<Article: Second>', '<Article: Third>']) ['<Article: Second>', '<Article: Third>'])
# Clear the rest of the set # Clear the rest of the set
@ -106,11 +106,11 @@ class ManyToOneNullTests(TestCase):
def test_assign_with_queryset(self): def test_assign_with_queryset(self):
# Ensure that querysets used in reverse FK assignments are pre-evaluated # Ensure that querysets used in reverse FK assignments are pre-evaluated
# so their value isn't affected by the clearing operation in # so their value isn't affected by the clearing operation in
# ReverseManyToOneDescriptor.__set__. Refs #19816. # RelatedManager.set() (#19816).
self.r2.article_set = [self.a2, self.a3] self.r2.article_set.set([self.a2, self.a3])
qs = self.r2.article_set.filter(headline="Second") qs = self.r2.article_set.filter(headline="Second")
self.r2.article_set = qs self.r2.article_set.set(qs)
self.assertEqual(1, self.r2.article_set.count()) self.assertEqual(1, self.r2.article_set.count())
self.assertEqual(1, qs.count()) self.assertEqual(1, qs.count())

View File

@ -344,7 +344,7 @@ class ModelFormsetTest(TestCase):
author3 = Author.objects.create(name='Walt Whitman') author3 = Author.objects.create(name='Walt Whitman')
meeting = AuthorMeeting.objects.create(created=date.today()) meeting = AuthorMeeting.objects.create(created=date.today())
meeting.authors = Author.objects.all() meeting.authors.set(Author.objects.all())
# create an Author instance to add to the meeting. # create an Author instance to add to the meeting.

View File

@ -234,9 +234,9 @@ class ModelInheritanceDataTests(TestCase):
def test_related_objects_for_inherited_models(self): def test_related_objects_for_inherited_models(self):
# Related objects work just as they normally do. # Related objects work just as they normally do.
s1 = Supplier.objects.create(name="Joe's Chickens", address="123 Sesame St") s1 = Supplier.objects.create(name="Joe's Chickens", address="123 Sesame St")
s1.customers = [self.restaurant, self.italian_restaurant] s1.customers .set([self.restaurant, self.italian_restaurant])
s2 = Supplier.objects.create(name="Luigi's Pasta", address="456 Sesame St") s2 = Supplier.objects.create(name="Luigi's Pasta", address="456 Sesame St")
s2.customers = [self.italian_restaurant] s2.customers.set([self.italian_restaurant])
# This won't work because the Place we select is not a Restaurant (it's # This won't work because the Place we select is not a Restaurant (it's
# a Supplier). # a Supplier).

View File

@ -350,10 +350,10 @@ class ModelInheritanceTest(TestCase):
birthday = BirthdayParty.objects.create( birthday = BirthdayParty.objects.create(
name='Birthday party for Alice') name='Birthday party for Alice')
birthday.attendees = [p1, p3] birthday.attendees.set([p1, p3])
bachelor = BachelorParty.objects.create(name='Bachelor party for Bob') bachelor = BachelorParty.objects.create(name='Bachelor party for Bob')
bachelor.attendees = [p2, p4] bachelor.attendees.set([p2, p4])
parties = list(p1.birthdayparty_set.all()) parties = list(p1.birthdayparty_set.all())
self.assertEqual(parties, [birthday]) self.assertEqual(parties, [birthday])
@ -371,7 +371,7 @@ class ModelInheritanceTest(TestCase):
# ... but it does inherit the m2m from its parent # ... but it does inherit the m2m from its parent
messy = MessyBachelorParty.objects.create( messy = MessyBachelorParty.objects.create(
name='Bachelor party for Dave') name='Bachelor party for Dave')
messy.attendees = [p4] messy.attendees.set([p4])
messy_parent = messy.bachelorparty_ptr messy_parent = messy.bachelorparty_ptr
parties = list(p4.bachelorparty_set.all()) parties = list(p4.bachelorparty_set.all())

View File

@ -177,8 +177,8 @@ class QueryTestCase(TestCase):
mark = Person.objects.using('other').create(name="Mark Pilgrim") mark = Person.objects.using('other').create(name="Mark Pilgrim")
# Save the author relations # Save the author relations
pro.authors = [marty] pro.authors.set([marty])
dive.authors = [mark] dive.authors.set([mark])
# Inspect the m2m tables directly. # Inspect the m2m tables directly.
# There should be 1 entry in each database # There should be 1 entry in each database
@ -224,7 +224,7 @@ class QueryTestCase(TestCase):
mark = Person.objects.using('other').create(name="Mark Pilgrim") mark = Person.objects.using('other').create(name="Mark Pilgrim")
# Save the author relations # Save the author relations
dive.authors = [mark] dive.authors.set([mark])
# Add a second author # Add a second author
john = Person.objects.using('other').create(name="John Smith") john = Person.objects.using('other').create(name="John Smith")
@ -285,7 +285,7 @@ class QueryTestCase(TestCase):
mark = Person.objects.using('other').create(name="Mark Pilgrim") mark = Person.objects.using('other').create(name="Mark Pilgrim")
# Save the author relations # Save the author relations
dive.authors = [mark] dive.authors.set([mark])
# Create a second book on the other database # Create a second book on the other database
grease = Book.objects.using('other').create(title="Greasemonkey Hacks", grease = Book.objects.using('other').create(title="Greasemonkey Hacks",
@ -357,7 +357,7 @@ class QueryTestCase(TestCase):
# Set a foreign key set with an object from a different database # Set a foreign key set with an object from a different database
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
with transaction.atomic(using='default'): with transaction.atomic(using='default'):
marty.edited = [pro, dive] marty.edited.set([pro, dive])
# Add to an m2m with an object from a different database # Add to an m2m with an object from a different database
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
@ -367,7 +367,7 @@ class QueryTestCase(TestCase):
# Set a m2m with an object from a different database # Set a m2m with an object from a different database
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
with transaction.atomic(using='default'): with transaction.atomic(using='default'):
marty.book_set = [pro, dive] marty.book_set.set([pro, dive])
# Add to a reverse m2m with an object from a different database # Add to a reverse m2m with an object from a different database
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
@ -377,7 +377,7 @@ class QueryTestCase(TestCase):
# Set a reverse m2m with an object from a different database # Set a reverse m2m with an object from a different database
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
with transaction.atomic(using='other'): with transaction.atomic(using='other'):
dive.authors = [mark, marty] dive.authors.set([mark, marty])
def test_m2m_deletion(self): def test_m2m_deletion(self):
"Cascaded deletions of m2m relations issue queries on the right database" "Cascaded deletions of m2m relations issue queries on the right database"
@ -386,7 +386,7 @@ class QueryTestCase(TestCase):
published=datetime.date(2009, 5, 4)) published=datetime.date(2009, 5, 4))
mark = Person.objects.using('other').create(name="Mark Pilgrim") mark = Person.objects.using('other').create(name="Mark Pilgrim")
dive.authors = [mark] dive.authors.set([mark])
# Check the initial state # Check the initial state
self.assertEqual(Person.objects.using('default').count(), 0) self.assertEqual(Person.objects.using('default').count(), 0)
@ -414,7 +414,7 @@ class QueryTestCase(TestCase):
# Now try deletion in the reverse direction. Set up the relation again # Now try deletion in the reverse direction. Set up the relation again
dive = Book.objects.using('other').create(title="Dive into Python", dive = Book.objects.using('other').create(title="Dive into Python",
published=datetime.date(2009, 5, 4)) published=datetime.date(2009, 5, 4))
dive.authors = [mark] dive.authors.set([mark])
# Check the initial state # Check the initial state
self.assertEqual(Person.objects.using('default').count(), 0) self.assertEqual(Person.objects.using('default').count(), 0)
@ -589,7 +589,7 @@ class QueryTestCase(TestCase):
# Set a foreign key set with an object from a different database # Set a foreign key set with an object from a different database
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
with transaction.atomic(using='default'): with transaction.atomic(using='default'):
marty.edited = [pro, dive] marty.edited.set([pro, dive])
# Add to a foreign key set with an object from a different database # Add to a foreign key set with an object from a different database
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
@ -1095,7 +1095,7 @@ class RouterTestCase(TestCase):
pro = Book.objects.using('default').create(title="Pro Django", pro = Book.objects.using('default').create(title="Pro Django",
published=datetime.date(2008, 12, 16), published=datetime.date(2008, 12, 16),
editor=marty) editor=marty)
pro.authors = [marty] pro.authors.set([marty])
# Create a book and author on the other database # Create a book and author on the other database
Book.objects.using('other').create(title="Dive into Python", Book.objects.using('other').create(title="Dive into Python",
@ -1320,7 +1320,7 @@ class RouterTestCase(TestCase):
# Set a m2m set with an object from a different database # Set a m2m set with an object from a different database
try: try:
marty.book_set = [pro, dive] marty.book_set.set([pro, dive])
except ValueError: except ValueError:
self.fail("Assignment across primary/replica databases with a common source should be ok") self.fail("Assignment across primary/replica databases with a common source should be ok")
@ -1358,7 +1358,7 @@ class RouterTestCase(TestCase):
# Set a reverse m2m with an object from a different database # Set a reverse m2m with an object from a different database
try: try:
dive.authors = [mark, marty] dive.authors.set([mark, marty])
except ValueError: except ValueError:
self.fail("Assignment across primary/replica databases with a common source should be ok") self.fail("Assignment across primary/replica databases with a common source should be ok")
@ -1861,7 +1861,7 @@ class RouterAttributeErrorTestCase(TestCase):
b = Book.objects.create(title="Pro Django", b = Book.objects.create(title="Pro Django",
published=datetime.date(2008, 12, 16)) published=datetime.date(2008, 12, 16))
p = Person.objects.create(name="Marty Alchin") p = Person.objects.create(name="Marty Alchin")
b.authors = [p] b.authors.set([p])
b.editor = p b.editor = p
with self.override_router(): with self.override_router():
self.assertRaises(AttributeError, b.delete) self.assertRaises(AttributeError, b.delete)
@ -1872,7 +1872,8 @@ class RouterAttributeErrorTestCase(TestCase):
published=datetime.date(2008, 12, 16)) published=datetime.date(2008, 12, 16))
p = Person.objects.create(name="Marty Alchin") p = Person.objects.create(name="Marty Alchin")
with self.override_router(): with self.override_router():
self.assertRaises(AttributeError, setattr, b, 'authors', [p]) with self.assertRaises(AttributeError):
b.authors.set([p])
class ModelMetaRouter(object): class ModelMetaRouter(object):
@ -1898,7 +1899,7 @@ class RouterModelArgumentTestCase(TestCase):
# test clear # test clear
b.authors.clear() b.authors.clear()
# test setattr # test setattr
b.authors = [p] b.authors.set([p])
# test M2M collection # test M2M collection
b.delete() b.delete()

View File

@ -171,7 +171,7 @@ class OneToOneTests(TestCase):
""" """
f = Favorites(name='Fred') f = Favorites(name='Fred')
f.save() f.save()
f.restaurants = [self.r1] f.restaurants.set([self.r1])
self.assertQuerysetEqual( self.assertQuerysetEqual(
f.restaurants.all(), f.restaurants.all(),
['<Restaurant: Demon Dogs the restaurant>'] ['<Restaurant: Demon Dogs the restaurant>']

View File

@ -73,12 +73,12 @@ class Queries1Tests(BaseQuerysetTest):
time3 = datetime.datetime(2007, 12, 20, 22, 25, 0) time3 = datetime.datetime(2007, 12, 20, 22, 25, 0)
time4 = datetime.datetime(2007, 12, 20, 21, 0, 0) time4 = datetime.datetime(2007, 12, 20, 21, 0, 0)
cls.i1 = Item.objects.create(name='one', created=cls.time1, modified=cls.time1, creator=cls.a1, note=cls.n3) cls.i1 = Item.objects.create(name='one', created=cls.time1, modified=cls.time1, creator=cls.a1, note=cls.n3)
cls.i1.tags = [cls.t1, cls.t2] cls.i1.tags.set([cls.t1, cls.t2])
cls.i2 = Item.objects.create(name='two', created=cls.time2, creator=cls.a2, note=n2) cls.i2 = Item.objects.create(name='two', created=cls.time2, creator=cls.a2, note=n2)
cls.i2.tags = [cls.t1, cls.t3] cls.i2.tags.set([cls.t1, cls.t3])
cls.i3 = Item.objects.create(name='three', created=time3, creator=cls.a2, note=cls.n3) cls.i3 = Item.objects.create(name='three', created=time3, creator=cls.a2, note=cls.n3)
i4 = Item.objects.create(name='four', created=time4, creator=cls.a4, note=cls.n3) i4 = Item.objects.create(name='four', created=time4, creator=cls.a4, note=cls.n3)
i4.tags = [t4] i4.tags.set([t4])
cls.r1 = Report.objects.create(name='r1', creator=cls.a1) cls.r1 = Report.objects.create(name='r1', creator=cls.a1)
Report.objects.create(name='r2', creator=a3) Report.objects.create(name='r2', creator=a3)
@ -1373,8 +1373,8 @@ class Queries4Tests(BaseQuerysetTest):
math101 = tag.note_set.create(note='MATH', misc='101') math101 = tag.note_set.create(note='MATH', misc='101')
s1 = tag.annotation_set.create(name='1') s1 = tag.annotation_set.create(name='1')
s2 = tag.annotation_set.create(name='2') s2 = tag.annotation_set.create(name='2')
s1.notes = [math101, anth100] s1.notes.set([math101, anth100])
s2.notes = [math101] s2.notes.set([math101])
result = math101.annotation_set.all() & tag.annotation_set.exclude(notes__in=[anth100]) result = math101.annotation_set.all() & tag.annotation_set.exclude(notes__in=[anth100])
self.assertEqual(list(result), [s2]) self.assertEqual(list(result), [s2])
@ -3348,9 +3348,9 @@ class ManyToManyExcludeTest(TestCase):
pg2 = Page.objects.create(text='pg2') pg2 = Page.objects.create(text='pg2')
pg1 = Page.objects.create(text='pg1') pg1 = Page.objects.create(text='pg1')
pa1 = Paragraph.objects.create(text='pa1') pa1 = Paragraph.objects.create(text='pa1')
pa1.page = [pg1, pg2] pa1.page.set([pg1, pg2])
pa2 = Paragraph.objects.create(text='pa2') pa2 = Paragraph.objects.create(text='pa2')
pa2.page = [pg2, pg3] pa2.page.set([pg2, pg3])
pa3 = Paragraph.objects.create(text='pa3') pa3 = Paragraph.objects.create(text='pa3')
ch1 = Chapter.objects.create(title='ch1', paragraph=pa1) ch1 = Chapter.objects.create(title='ch1', paragraph=pa1)
ch2 = Chapter.objects.create(title='ch2', paragraph=pa2) ch2 = Chapter.objects.create(title='ch2', paragraph=pa2)

View File

@ -66,7 +66,7 @@ def fk_create(pk, klass, data):
def m2m_create(pk, klass, data): def m2m_create(pk, klass, data):
instance = klass(id=pk) instance = klass(id=pk)
models.Model.save_base(instance, raw=True) models.Model.save_base(instance, raw=True)
instance.data = data instance.data.set(data)
return [instance] return [instance]

View File

@ -112,7 +112,7 @@ class SerializersTestBase(object):
pub_date=datetime(2006, 6, 16, 11, 00) pub_date=datetime(2006, 6, 16, 11, 00)
) )
self.a1.save() self.a1.save()
self.a1.categories = [sports, op_ed] self.a1.categories.set([sports, op_ed])
self.a2 = Article( self.a2 = Article(
author=self.joe, author=self.joe,
@ -120,7 +120,7 @@ class SerializersTestBase(object):
pub_date=datetime(2006, 6, 16, 13, 00, 11, 345) pub_date=datetime(2006, 6, 16, 13, 00, 11, 345)
) )
self.a2.save() self.a2.save()
self.a2.categories = [music, op_ed] self.a2.categories.set([music, op_ed])
def test_serialize(self): def test_serialize(self):
"""Tests that basic serialization works.""" """Tests that basic serialization works."""

View File

@ -222,9 +222,9 @@ class SignalTests(BaseSignalTest):
data[:] = [] data[:] = []
# Assigning and removing to/from m2m shouldn't generate an m2m signal. # Assigning and removing to/from m2m shouldn't generate an m2m signal.
b1.authors = [a1] b1.authors.set([a1])
self.assertEqual(data, []) self.assertEqual(data, [])
b1.authors = [] b1.authors.set([])
self.assertEqual(data, []) self.assertEqual(data, [])
finally: finally:
signals.pre_save.disconnect(pre_save_handler) signals.pre_save.disconnect(pre_save_handler)

View File

@ -19,7 +19,7 @@ class SimpleTests(TestCase):
a = A01.objects.create(f_a="foo", f_b=42) a = A01.objects.create(f_a="foo", f_b=42)
B01.objects.create(fk_a=a, f_a="fred", f_b=1729) B01.objects.create(fk_a=a, f_a="fred", f_b=1729)
c = C01.objects.create(f_a="barney", f_b=1) c = C01.objects.create(f_a="barney", f_b=1)
c.mm_a = [a] c.mm_a.set([a])
# ... and pull it out via the other set. # ... and pull it out via the other set.
a2 = A02.objects.all()[0] a2 = A02.objects.all()[0]

View File

@ -124,11 +124,9 @@ class UpdateOnlyFieldsTests(TestCase):
profile_boss = Profile.objects.create(name='Boss', salary=3000) profile_boss = Profile.objects.create(name='Boss', salary=3000)
e1 = Employee.objects.create(name='Sara', gender='F', e1 = Employee.objects.create(name='Sara', gender='F',
employee_num=1, profile=profile_boss) employee_num=1, profile=profile_boss)
a1 = Account.objects.create(num=1) a1 = Account.objects.create(num=1)
a2 = Account.objects.create(num=2) a2 = Account.objects.create(num=2)
e1.accounts.set([a1, a2])
e1.accounts = [a1, a2]
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
e1.save(update_fields=['accounts']) e1.save(update_fields=['accounts'])