mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	[5.1.x] Fixed #35561 -- Made *args and **kwargs parsing more strict in Model.save()/asave().
Backport of e56a32b89b from main.
			
			
This commit is contained in:
		| @@ -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)) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user