mirror of https://github.com/django/django.git
Fixed 35561 -- Made *args and **kwargs parsing more strict in Model.save()/asave().
This commit is contained in:
parent
88966bc2fe
commit
e56a32b89b
|
@ -776,6 +776,43 @@ class Model(AltersData, metaclass=ModelBase):
|
|||
return getattr(self, field_name)
|
||||
return getattr(self, field.attname)
|
||||
|
||||
# RemovedInDjango60Warning: When the deprecation ends, remove completely.
|
||||
def _parse_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=2,
|
||||
)
|
||||
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,
|
||||
|
@ -798,25 +835,14 @@ class Model(AltersData, metaclass=ModelBase):
|
|||
"""
|
||||
# RemovedInDjango60Warning.
|
||||
if args:
|
||||
warnings.warn(
|
||||
"Passing positional arguments to save() is deprecated",
|
||||
RemovedInDjango60Warning,
|
||||
stacklevel=2,
|
||||
force_insert, force_update, using, update_fields = self._parse_params(
|
||||
*args,
|
||||
method_name="save",
|
||||
force_insert=force_insert,
|
||||
force_update=force_update,
|
||||
using=using,
|
||||
update_fields=update_fields,
|
||||
)
|
||||
total_len_args = len(args) + 1 # include self
|
||||
if total_len_args > 5:
|
||||
# Recreate the proper TypeError message from Python.
|
||||
raise TypeError(
|
||||
"Model.save() takes from 1 to 5 positional arguments but "
|
||||
f"{total_len_args} were given"
|
||||
)
|
||||
force_insert = args[0]
|
||||
try:
|
||||
force_update = args[1]
|
||||
using = args[2]
|
||||
update_fields = args[3]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
self._prepare_related_fields_for_save(operation_name="save")
|
||||
|
||||
|
@ -885,26 +911,14 @@ class Model(AltersData, metaclass=ModelBase):
|
|||
):
|
||||
# RemovedInDjango60Warning.
|
||||
if args:
|
||||
warnings.warn(
|
||||
"Passing positional arguments to asave() is deprecated",
|
||||
RemovedInDjango60Warning,
|
||||
stacklevel=2,
|
||||
force_insert, force_update, using, update_fields = self._parse_params(
|
||||
*args,
|
||||
method_name="asave",
|
||||
force_insert=force_insert,
|
||||
force_update=force_update,
|
||||
using=using,
|
||||
update_fields=update_fields,
|
||||
)
|
||||
total_len_args = len(args) + 1 # include self
|
||||
if total_len_args > 5:
|
||||
# Recreate the proper TypeError message from Python.
|
||||
raise TypeError(
|
||||
"Model.asave() takes from 1 to 5 positional arguments but "
|
||||
f"{total_len_args} were given"
|
||||
)
|
||||
force_insert = args[0]
|
||||
try:
|
||||
force_update = args[1]
|
||||
using = args[2]
|
||||
update_fields = args[3]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
return await sync_to_async(self.save)(
|
||||
force_insert=force_insert,
|
||||
force_update=force_update,
|
||||
|
|
|
@ -239,6 +239,23 @@ class ModelInstanceCreationTests(TestCase):
|
|||
):
|
||||
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"
|
||||
|
@ -275,6 +292,23 @@ class ModelInstanceCreationTests(TestCase):
|
|||
):
|
||||
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))
|
||||
|
|
Loading…
Reference in New Issue