mirror of
https://github.com/django/django.git
synced 2025-06-03 18:49:12 +00:00
Refs #35060 -- Removed passing positional arguments to Model.save()/asave() per deprecation timeline.
This commit is contained in:
parent
8d695bf510
commit
d5fec03dad
@ -58,11 +58,8 @@ class AbstractBaseUser(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.get_username()
|
return self.get_username()
|
||||||
|
|
||||||
# RemovedInDjango60Warning: When the deprecation ends, replace with:
|
def save(self, **kwargs):
|
||||||
# def save(self, **kwargs):
|
super().save(**kwargs)
|
||||||
# super().save(**kwargs)
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
if self._password is not None:
|
if self._password is not None:
|
||||||
password_validation.password_changed(self._password, self)
|
password_validation.password_changed(self._password, self)
|
||||||
self._password = None
|
self._password = None
|
||||||
|
@ -50,7 +50,6 @@ from django.db.models.signals import (
|
|||||||
pre_save,
|
pre_save,
|
||||||
)
|
)
|
||||||
from django.db.models.utils import AltersData, make_model_tuple
|
from django.db.models.utils import AltersData, make_model_tuple
|
||||||
from django.utils.deprecation import RemovedInDjango60Warning
|
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
from django.utils.hashable import make_hashable
|
from django.utils.hashable import make_hashable
|
||||||
from django.utils.text import capfirst, get_text_list
|
from django.utils.text import capfirst, get_text_list
|
||||||
@ -786,50 +785,9 @@ class Model(AltersData, metaclass=ModelBase):
|
|||||||
return getattr(self, field_name)
|
return getattr(self, field_name)
|
||||||
return getattr(self, field.attname)
|
return getattr(self, field.attname)
|
||||||
|
|
||||||
# RemovedInDjango60Warning: When the deprecation ends, remove completely.
|
|
||||||
def _parse_save_params(self, *args, method_name, **kwargs):
|
|
||||||
defaults = {
|
|
||||||
"force_insert": False,
|
|
||||||
"force_update": False,
|
|
||||||
"using": None,
|
|
||||||
"update_fields": None,
|
|
||||||
}
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
f"Passing positional arguments to {method_name}() is deprecated",
|
|
||||||
RemovedInDjango60Warning,
|
|
||||||
stacklevel=3,
|
|
||||||
)
|
|
||||||
total_len_args = len(args) + 1 # include self
|
|
||||||
max_len_args = len(defaults) + 1
|
|
||||||
if total_len_args > max_len_args:
|
|
||||||
# Recreate the proper TypeError message from Python.
|
|
||||||
raise TypeError(
|
|
||||||
f"Model.{method_name}() takes from 1 to {max_len_args} positional "
|
|
||||||
f"arguments but {total_len_args} were given"
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_param(param_name, param_value, arg_index):
|
|
||||||
if arg_index < len(args):
|
|
||||||
if param_value is not defaults[param_name]:
|
|
||||||
# Recreate the proper TypeError message from Python.
|
|
||||||
raise TypeError(
|
|
||||||
f"Model.{method_name}() got multiple values for argument "
|
|
||||||
f"'{param_name}'"
|
|
||||||
)
|
|
||||||
return args[arg_index]
|
|
||||||
|
|
||||||
return param_value
|
|
||||||
|
|
||||||
return [get_param(k, v, i) for i, (k, v) in enumerate(kwargs.items())]
|
|
||||||
|
|
||||||
# RemovedInDjango60Warning: When the deprecation ends, replace with:
|
|
||||||
# def save(
|
|
||||||
# self, *, force_insert=False, force_update=False, using=None, update_fields=None,
|
|
||||||
# ):
|
|
||||||
def save(
|
def save(
|
||||||
self,
|
self,
|
||||||
*args,
|
*,
|
||||||
force_insert=False,
|
force_insert=False,
|
||||||
force_update=False,
|
force_update=False,
|
||||||
using=None,
|
using=None,
|
||||||
@ -843,16 +801,6 @@ class Model(AltersData, metaclass=ModelBase):
|
|||||||
that the "save" must be an SQL insert or update (or equivalent for
|
that the "save" must be an SQL insert or update (or equivalent for
|
||||||
non-SQL backends), respectively. Normally, they should not be set.
|
non-SQL backends), respectively. Normally, they should not be set.
|
||||||
"""
|
"""
|
||||||
# RemovedInDjango60Warning.
|
|
||||||
if args:
|
|
||||||
force_insert, force_update, using, update_fields = self._parse_save_params(
|
|
||||||
*args,
|
|
||||||
method_name="save",
|
|
||||||
force_insert=force_insert,
|
|
||||||
force_update=force_update,
|
|
||||||
using=using,
|
|
||||||
update_fields=update_fields,
|
|
||||||
)
|
|
||||||
|
|
||||||
self._prepare_related_fields_for_save(operation_name="save")
|
self._prepare_related_fields_for_save(operation_name="save")
|
||||||
|
|
||||||
@ -908,28 +856,14 @@ class Model(AltersData, metaclass=ModelBase):
|
|||||||
|
|
||||||
save.alters_data = True
|
save.alters_data = True
|
||||||
|
|
||||||
# RemovedInDjango60Warning: When the deprecation ends, replace with:
|
|
||||||
# async def asave(
|
|
||||||
# self, *, force_insert=False, force_update=False, using=None, update_fields=None,
|
|
||||||
# ):
|
|
||||||
async def asave(
|
async def asave(
|
||||||
self,
|
self,
|
||||||
*args,
|
*,
|
||||||
force_insert=False,
|
force_insert=False,
|
||||||
force_update=False,
|
force_update=False,
|
||||||
using=None,
|
using=None,
|
||||||
update_fields=None,
|
update_fields=None,
|
||||||
):
|
):
|
||||||
# RemovedInDjango60Warning.
|
|
||||||
if args:
|
|
||||||
force_insert, force_update, using, update_fields = self._parse_save_params(
|
|
||||||
*args,
|
|
||||||
method_name="asave",
|
|
||||||
force_insert=force_insert,
|
|
||||||
force_update=force_update,
|
|
||||||
using=using,
|
|
||||||
update_fields=update_fields,
|
|
||||||
)
|
|
||||||
return await sync_to_async(self.save)(
|
return await sync_to_async(self.save)(
|
||||||
force_insert=force_insert,
|
force_insert=force_insert,
|
||||||
force_update=force_update,
|
force_update=force_update,
|
||||||
|
@ -430,10 +430,6 @@ method. See :ref:`overriding-model-methods` for more details.
|
|||||||
|
|
||||||
The model save process also has some subtleties; see the sections below.
|
The model save process also has some subtleties; see the sections below.
|
||||||
|
|
||||||
.. deprecated:: 5.1
|
|
||||||
|
|
||||||
Support for positional arguments is deprecated.
|
|
||||||
|
|
||||||
Auto-incrementing primary keys
|
Auto-incrementing primary keys
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
|
@ -309,3 +309,6 @@ to remove usage of these features.
|
|||||||
* The ``django.contrib.gis.geoip2.GeoIP2.coords()`` method is removed.
|
* The ``django.contrib.gis.geoip2.GeoIP2.coords()`` method is removed.
|
||||||
|
|
||||||
* The ``django.contrib.gis.geoip2.GeoIP2.open()`` method is removed.
|
* The ``django.contrib.gis.geoip2.GeoIP2.open()`` method is removed.
|
||||||
|
|
||||||
|
* Support for passing positional arguments to ``Model.save()`` and
|
||||||
|
``Model.asave()`` is removed.
|
||||||
|
@ -20,9 +20,8 @@ from django.test import (
|
|||||||
TransactionTestCase,
|
TransactionTestCase,
|
||||||
skipUnlessDBFeature,
|
skipUnlessDBFeature,
|
||||||
)
|
)
|
||||||
from django.test.utils import CaptureQueriesContext, ignore_warnings
|
from django.test.utils import CaptureQueriesContext
|
||||||
from django.utils.connection import ConnectionDoesNotExist
|
from django.utils.connection import ConnectionDoesNotExist
|
||||||
from django.utils.deprecation import RemovedInDjango60Warning
|
|
||||||
from django.utils.translation import gettext_lazy
|
from django.utils.translation import gettext_lazy
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
@ -213,144 +212,6 @@ class ModelInstanceCreationTests(TestCase):
|
|||||||
with self.assertNumQueries(1):
|
with self.assertNumQueries(1):
|
||||||
PrimaryKeyWithFalseyDbDefault().save()
|
PrimaryKeyWithFalseyDbDefault().save()
|
||||||
|
|
||||||
def test_save_deprecation(self):
|
|
||||||
a = Article(headline="original", pub_date=datetime(2014, 5, 16))
|
|
||||||
msg = "Passing positional arguments to save() is deprecated"
|
|
||||||
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
|
|
||||||
a.save(False, False, None, None)
|
|
||||||
self.assertEqual(Article.objects.count(), 1)
|
|
||||||
self.assertEqual(ctx.filename, __file__)
|
|
||||||
|
|
||||||
def test_save_deprecation_positional_arguments_used(self):
|
|
||||||
a = Article()
|
|
||||||
fields = ["headline"]
|
|
||||||
with (
|
|
||||||
self.assertWarns(RemovedInDjango60Warning),
|
|
||||||
mock.patch.object(a, "save_base") as mock_save_base,
|
|
||||||
):
|
|
||||||
a.save(None, 1, 2, fields)
|
|
||||||
self.assertEqual(
|
|
||||||
mock_save_base.mock_calls,
|
|
||||||
[
|
|
||||||
mock.call(
|
|
||||||
using=2,
|
|
||||||
force_insert=None,
|
|
||||||
force_update=1,
|
|
||||||
update_fields=frozenset(fields),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_save_too_many_positional_arguments(self):
|
|
||||||
a = Article()
|
|
||||||
msg = "Model.save() takes from 1 to 5 positional arguments but 6 were given"
|
|
||||||
with (
|
|
||||||
self.assertWarns(RemovedInDjango60Warning),
|
|
||||||
self.assertRaisesMessage(TypeError, msg),
|
|
||||||
):
|
|
||||||
a.save(False, False, None, None, None)
|
|
||||||
|
|
||||||
def test_save_conflicting_positional_and_named_arguments(self):
|
|
||||||
a = Article()
|
|
||||||
cases = [
|
|
||||||
("force_insert", True, [42]),
|
|
||||||
("force_update", None, [42, 41]),
|
|
||||||
("using", "some-db", [42, 41, 40]),
|
|
||||||
("update_fields", ["foo"], [42, 41, 40, 39]),
|
|
||||||
]
|
|
||||||
for param_name, param_value, args in cases:
|
|
||||||
with self.subTest(param_name=param_name):
|
|
||||||
msg = f"Model.save() got multiple values for argument '{param_name}'"
|
|
||||||
with (
|
|
||||||
self.assertWarns(RemovedInDjango60Warning),
|
|
||||||
self.assertRaisesMessage(TypeError, msg),
|
|
||||||
):
|
|
||||||
a.save(*args, **{param_name: param_value})
|
|
||||||
|
|
||||||
async def test_asave_deprecation(self):
|
|
||||||
a = Article(headline="original", pub_date=datetime(2014, 5, 16))
|
|
||||||
msg = "Passing positional arguments to asave() is deprecated"
|
|
||||||
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
|
|
||||||
await a.asave(False, False, None, None)
|
|
||||||
self.assertEqual(await Article.objects.acount(), 1)
|
|
||||||
self.assertEqual(ctx.filename, __file__)
|
|
||||||
|
|
||||||
async def test_asave_deprecation_positional_arguments_used(self):
|
|
||||||
a = Article()
|
|
||||||
fields = ["headline"]
|
|
||||||
with (
|
|
||||||
self.assertWarns(RemovedInDjango60Warning),
|
|
||||||
mock.patch.object(a, "save_base") as mock_save_base,
|
|
||||||
):
|
|
||||||
await a.asave(None, 1, 2, fields)
|
|
||||||
self.assertEqual(
|
|
||||||
mock_save_base.mock_calls,
|
|
||||||
[
|
|
||||||
mock.call(
|
|
||||||
using=2,
|
|
||||||
force_insert=None,
|
|
||||||
force_update=1,
|
|
||||||
update_fields=frozenset(fields),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
async def test_asave_too_many_positional_arguments(self):
|
|
||||||
a = Article()
|
|
||||||
msg = "Model.asave() takes from 1 to 5 positional arguments but 6 were given"
|
|
||||||
with (
|
|
||||||
self.assertWarns(RemovedInDjango60Warning),
|
|
||||||
self.assertRaisesMessage(TypeError, msg),
|
|
||||||
):
|
|
||||||
await a.asave(False, False, None, None, None)
|
|
||||||
|
|
||||||
async def test_asave_conflicting_positional_and_named_arguments(self):
|
|
||||||
a = Article()
|
|
||||||
cases = [
|
|
||||||
("force_insert", True, [42]),
|
|
||||||
("force_update", None, [42, 41]),
|
|
||||||
("using", "some-db", [42, 41, 40]),
|
|
||||||
("update_fields", ["foo"], [42, 41, 40, 39]),
|
|
||||||
]
|
|
||||||
for param_name, param_value, args in cases:
|
|
||||||
with self.subTest(param_name=param_name):
|
|
||||||
msg = f"Model.asave() got multiple values for argument '{param_name}'"
|
|
||||||
with (
|
|
||||||
self.assertWarns(RemovedInDjango60Warning),
|
|
||||||
self.assertRaisesMessage(TypeError, msg),
|
|
||||||
):
|
|
||||||
await a.asave(*args, **{param_name: param_value})
|
|
||||||
|
|
||||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
|
||||||
def test_save_positional_arguments(self):
|
|
||||||
a = Article.objects.create(headline="original", pub_date=datetime(2014, 5, 16))
|
|
||||||
a.headline = "changed"
|
|
||||||
|
|
||||||
a.save(False, False, None, ["pub_date"])
|
|
||||||
a.refresh_from_db()
|
|
||||||
self.assertEqual(a.headline, "original")
|
|
||||||
|
|
||||||
a.headline = "changed"
|
|
||||||
a.save(False, False, None, ["pub_date", "headline"])
|
|
||||||
a.refresh_from_db()
|
|
||||||
self.assertEqual(a.headline, "changed")
|
|
||||||
|
|
||||||
@ignore_warnings(category=RemovedInDjango60Warning)
|
|
||||||
async def test_asave_positional_arguments(self):
|
|
||||||
a = await Article.objects.acreate(
|
|
||||||
headline="original", pub_date=datetime(2014, 5, 16)
|
|
||||||
)
|
|
||||||
a.headline = "changed"
|
|
||||||
|
|
||||||
await a.asave(False, False, None, ["pub_date"])
|
|
||||||
await a.arefresh_from_db()
|
|
||||||
self.assertEqual(a.headline, "original")
|
|
||||||
|
|
||||||
a.headline = "changed"
|
|
||||||
await a.asave(False, False, None, ["pub_date", "headline"])
|
|
||||||
await a.arefresh_from_db()
|
|
||||||
self.assertEqual(a.headline, "changed")
|
|
||||||
|
|
||||||
|
|
||||||
class ModelTest(TestCase):
|
class ModelTest(TestCase):
|
||||||
def test_objects_attribute_is_only_available_on_the_class_itself(self):
|
def test_objects_attribute_is_only_available_on_the_class_itself(self):
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from django.db.models.signals import post_save, pre_save
|
from django.db.models.signals import post_save, pre_save
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils.deprecation import RemovedInDjango60Warning
|
|
||||||
|
|
||||||
from .models import Account, Employee, Person, Profile, ProxyEmployee
|
from .models import Account, Employee, Person, Profile, ProxyEmployee
|
||||||
|
|
||||||
@ -257,31 +256,6 @@ class UpdateOnlyFieldsTests(TestCase):
|
|||||||
pre_save.disconnect(pre_save_receiver)
|
pre_save.disconnect(pre_save_receiver)
|
||||||
post_save.disconnect(post_save_receiver)
|
post_save.disconnect(post_save_receiver)
|
||||||
|
|
||||||
def test_empty_update_fields_positional_save(self):
|
|
||||||
s = Person.objects.create(name="Sara", gender="F")
|
|
||||||
|
|
||||||
msg = "Passing positional arguments to save() is deprecated"
|
|
||||||
with (
|
|
||||||
self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx,
|
|
||||||
self.assertNumQueries(0),
|
|
||||||
):
|
|
||||||
s.save(False, False, None, [])
|
|
||||||
self.assertEqual(ctx.filename, __file__)
|
|
||||||
|
|
||||||
async def test_empty_update_fields_positional_asave(self):
|
|
||||||
s = await Person.objects.acreate(name="Sara", gender="F")
|
|
||||||
# Workaround for a lack of async assertNumQueries.
|
|
||||||
s.name = "Other"
|
|
||||||
|
|
||||||
msg = "Passing positional arguments to asave() is deprecated"
|
|
||||||
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
|
|
||||||
await s.asave(False, False, None, [])
|
|
||||||
self.assertEqual(ctx.filename, __file__)
|
|
||||||
|
|
||||||
# No save occurred for an empty update_fields.
|
|
||||||
await s.arefresh_from_db()
|
|
||||||
self.assertEqual(s.name, "Sara")
|
|
||||||
|
|
||||||
def test_num_queries_inheritance(self):
|
def test_num_queries_inheritance(self):
|
||||||
s = Employee.objects.create(name="Sara", gender="F")
|
s = Employee.objects.create(name="Sara", gender="F")
|
||||||
s.employee_num = 1
|
s.employee_num = 1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user