mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Refs #33308 -- Deprecated support for passing encoded JSON string literals to JSONField & co.
JSON should be provided as literal Python objects an not in their encoded string literal forms.
This commit is contained in:
		
				
					committed by
					
						 Mariusz Felisiak
						Mariusz Felisiak
					
				
			
			
				
	
			
			
			
						parent
						
							d3e746ace5
						
					
				
				
					commit
					0ff46591ac
				
			| @@ -1,8 +1,9 @@ | ||||
| import json | ||||
| import warnings | ||||
|  | ||||
| from django.contrib.postgres.fields import ArrayField | ||||
| from django.db.models import Aggregate, BooleanField, JSONField, TextField, Value | ||||
| from django.utils.deprecation import RemovedInDjango50Warning | ||||
| from django.utils.deprecation import RemovedInDjango50Warning, RemovedInDjango51Warning | ||||
|  | ||||
| from .mixins import OrderableAggMixin | ||||
|  | ||||
| @@ -31,6 +32,14 @@ class DeprecatedConvertValueMixin: | ||||
|             self._default_provided = True | ||||
|         super().__init__(*expressions, default=default, **extra) | ||||
|  | ||||
|     def resolve_expression(self, *args, **kwargs): | ||||
|         resolved = super().resolve_expression(*args, **kwargs) | ||||
|         if not self._default_provided: | ||||
|             resolved.empty_result_set_value = getattr( | ||||
|                 self, "deprecation_empty_result_set_value", self.deprecation_value | ||||
|             ) | ||||
|         return resolved | ||||
|  | ||||
|     def convert_value(self, value, expression, connection): | ||||
|         if value is None and not self._default_provided: | ||||
|             warnings.warn(self.deprecation_msg, category=RemovedInDjango50Warning) | ||||
| @@ -48,8 +57,7 @@ class ArrayAgg(DeprecatedConvertValueMixin, OrderableAggMixin, Aggregate): | ||||
|     deprecation_msg = ( | ||||
|         "In Django 5.0, ArrayAgg() will return None instead of an empty list " | ||||
|         "if there are no rows. Pass default=None to opt into the new behavior " | ||||
|         "and silence this warning or default=Value([]) to keep the previous " | ||||
|         "behavior." | ||||
|         "and silence this warning or default=[] to keep the previous behavior." | ||||
|     ) | ||||
|  | ||||
|     @property | ||||
| @@ -87,13 +95,46 @@ class JSONBAgg(DeprecatedConvertValueMixin, OrderableAggMixin, Aggregate): | ||||
|  | ||||
|     # RemovedInDjango50Warning | ||||
|     deprecation_value = "[]" | ||||
|     deprecation_empty_result_set_value = property(lambda self: []) | ||||
|     deprecation_msg = ( | ||||
|         "In Django 5.0, JSONBAgg() will return None instead of an empty list " | ||||
|         "if there are no rows. Pass default=None to opt into the new behavior " | ||||
|         "and silence this warning or default=Value('[]') to keep the previous " | ||||
|         "and silence this warning or default=[] to keep the previous " | ||||
|         "behavior." | ||||
|     ) | ||||
|  | ||||
|     # RemovedInDjango51Warning: When the deprecation ends, remove __init__(). | ||||
|     # | ||||
|     # RemovedInDjango50Warning: When the deprecation ends, replace with: | ||||
|     # def __init__(self, *expressions, default=None, **extra): | ||||
|     def __init__(self, *expressions, default=NOT_PROVIDED, **extra): | ||||
|         super().__init__(*expressions, default=default, **extra) | ||||
|         if ( | ||||
|             isinstance(default, Value) | ||||
|             and isinstance(default.value, str) | ||||
|             and not isinstance(default.output_field, JSONField) | ||||
|         ): | ||||
|             value = default.value | ||||
|             try: | ||||
|                 decoded = json.loads(value) | ||||
|             except json.JSONDecodeError: | ||||
|                 warnings.warn( | ||||
|                     "Passing a Value() with an output_field that isn't a JSONField as " | ||||
|                     "JSONBAgg(default) is deprecated. Pass default=" | ||||
|                     f"Value({value!r}, output_field=JSONField()) instead.", | ||||
|                     stacklevel=2, | ||||
|                     category=RemovedInDjango51Warning, | ||||
|                 ) | ||||
|                 self.default.output_field = self.output_field | ||||
|             else: | ||||
|                 self.default = Value(decoded, self.output_field) | ||||
|                 warnings.warn( | ||||
|                     "Passing an encoded JSON string as JSONBAgg(default) is " | ||||
|                     f"deprecated. Pass default={decoded!r} instead.", | ||||
|                     stacklevel=2, | ||||
|                     category=RemovedInDjango51Warning, | ||||
|                 ) | ||||
|  | ||||
|  | ||||
| class StringAgg(DeprecatedConvertValueMixin, OrderableAggMixin, Aggregate): | ||||
|     function = "STRING_AGG" | ||||
| @@ -106,8 +147,7 @@ class StringAgg(DeprecatedConvertValueMixin, OrderableAggMixin, Aggregate): | ||||
|     deprecation_msg = ( | ||||
|         "In Django 5.0, StringAgg() will return None instead of an empty " | ||||
|         "string if there are no rows. Pass default=None to opt into the new " | ||||
|         "behavior and silence this warning or default=Value('') to keep the " | ||||
|         "previous behavior." | ||||
|         'behavior and silence this warning or default="" to keep the previous behavior.' | ||||
|     ) | ||||
|  | ||||
|     def __init__(self, expression, delimiter, **extra): | ||||
|   | ||||
| @@ -85,6 +85,8 @@ class Aggregate(Func): | ||||
|             return c | ||||
|         if hasattr(default, "resolve_expression"): | ||||
|             default = default.resolve_expression(query, allow_joins, reuse, summarize) | ||||
|             if default._output_field_or_none is None: | ||||
|                 default.output_field = c._output_field_or_none | ||||
|         else: | ||||
|             default = Value(default, c._output_field_or_none) | ||||
|         c.default = None  # Reset the default argument before wrapping. | ||||
|   | ||||
| @@ -921,6 +921,8 @@ class Field(RegisterLookupMixin): | ||||
|  | ||||
|     def get_db_prep_save(self, value, connection): | ||||
|         """Return field's value prepared for saving into a database.""" | ||||
|         if hasattr(value, "as_sql"): | ||||
|             return value | ||||
|         return self.get_db_prep_value(value, connection=connection, prepared=False) | ||||
|  | ||||
|     def has_default(self): | ||||
| @@ -1715,6 +1717,8 @@ class DecimalField(Field): | ||||
|     def get_db_prep_value(self, value, connection, prepared=False): | ||||
|         if not prepared: | ||||
|             value = self.get_prep_value(value) | ||||
|         if hasattr(value, "as_sql"): | ||||
|             return value | ||||
|         return connection.ops.adapt_decimalfield_value( | ||||
|             value, self.max_digits, self.decimal_places | ||||
|         ) | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| import json | ||||
| import warnings | ||||
|  | ||||
| from django import forms | ||||
| from django.core import checks, exceptions | ||||
| from django.db import NotSupportedError, connections, router | ||||
| from django.db.models import lookups | ||||
| from django.db.models import expressions, lookups | ||||
| from django.db.models.constants import LOOKUP_SEP | ||||
| from django.db.models.fields import TextField | ||||
| from django.db.models.lookups import ( | ||||
| @@ -11,6 +12,7 @@ from django.db.models.lookups import ( | ||||
|     PostgresOperatorLookup, | ||||
|     Transform, | ||||
| ) | ||||
| from django.utils.deprecation import RemovedInDjango51Warning | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
| from . import Field | ||||
| @@ -97,7 +99,32 @@ class JSONField(CheckFieldDefaultMixin, Field): | ||||
|         return "JSONField" | ||||
|  | ||||
|     def get_db_prep_value(self, value, connection, prepared=False): | ||||
|         if hasattr(value, "as_sql"): | ||||
|         # RemovedInDjango51Warning: When the deprecation ends, replace with: | ||||
|         # if ( | ||||
|         #     isinstance(value, expressions.Value) | ||||
|         #     and isinstance(value.output_field, JSONField) | ||||
|         # ): | ||||
|         #     value = value.value | ||||
|         # elif hasattr(value, "as_sql"): ... | ||||
|         if isinstance(value, expressions.Value): | ||||
|             if isinstance(value.value, str) and not isinstance( | ||||
|                 value.output_field, JSONField | ||||
|             ): | ||||
|                 try: | ||||
|                     value = json.loads(value.value, cls=self.decoder) | ||||
|                 except json.JSONDecodeError: | ||||
|                     value = value.value | ||||
|                 else: | ||||
|                     warnings.warn( | ||||
|                         "Providing an encoded JSON string via Value() is deprecated. " | ||||
|                         f"Use Value({value!r}, output_field=JSONField()) instead.", | ||||
|                         category=RemovedInDjango51Warning, | ||||
|                     ) | ||||
|             elif isinstance(value.output_field, JSONField): | ||||
|                 value = value.value | ||||
|             else: | ||||
|                 return value | ||||
|         elif hasattr(value, "as_sql"): | ||||
|             return value | ||||
|         return connection.ops.adapt_json_value(value, self.encoder) | ||||
|  | ||||
|   | ||||
| @@ -1637,9 +1637,7 @@ class SQLInsertCompiler(SQLCompiler): | ||||
|                     "Window expressions are not allowed in this query (%s=%r)." | ||||
|                     % (field.name, value) | ||||
|                 ) | ||||
|         else: | ||||
|             value = field.get_db_prep_save(value, connection=self.connection) | ||||
|         return value | ||||
|         return field.get_db_prep_save(value, connection=self.connection) | ||||
|  | ||||
|     def pre_save_val(self, field, obj): | ||||
|         """ | ||||
| @@ -1893,18 +1891,14 @@ class SQLUpdateCompiler(SQLCompiler): | ||||
|                     ) | ||||
|             elif hasattr(val, "prepare_database_save"): | ||||
|                 if field.remote_field: | ||||
|                     val = field.get_db_prep_save( | ||||
|                         val.prepare_database_save(field), | ||||
|                         connection=self.connection, | ||||
|                     ) | ||||
|                     val = val.prepare_database_save(field) | ||||
|                 else: | ||||
|                     raise TypeError( | ||||
|                         "Tried to update field %s with a model instance, %r. " | ||||
|                         "Use a value compatible with %s." | ||||
|                         % (field, val, field.__class__.__name__) | ||||
|                     ) | ||||
|             else: | ||||
|                 val = field.get_db_prep_save(val, connection=self.connection) | ||||
|             val = field.get_db_prep_save(val, connection=self.connection) | ||||
|  | ||||
|             # Getting the placeholder for the field. | ||||
|             if hasattr(field, "get_placeholder"): | ||||
|   | ||||
| @@ -522,9 +522,9 @@ class Query(BaseExpression): | ||||
|         result = compiler.execute_sql(SINGLE) | ||||
|         if result is None: | ||||
|             result = empty_set_result | ||||
|  | ||||
|         converters = compiler.get_converters(outer_query.annotation_select.values()) | ||||
|         result = next(compiler.apply_converters((result,), converters)) | ||||
|         else: | ||||
|             converters = compiler.get_converters(outer_query.annotation_select.values()) | ||||
|             result = next(compiler.apply_converters((result,), converters)) | ||||
|  | ||||
|         return dict(zip(outer_query.annotation_select, result)) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user