Fixed 35561 -- Made *args and **kwargs parsing more strict in Model.save()/asave().

This commit is contained in:
nessita 2024-06-26 12:13:17 -03:00 committed by GitHub
parent 88966bc2fe
commit e56a32b89b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 85 additions and 37 deletions

View File

@ -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,

View File

@ -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))