general.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import json
  2. import warnings
  3. from django.contrib.postgres.fields import ArrayField
  4. from django.db.models import Aggregate, BooleanField, JSONField, TextField, Value
  5. from django.utils.deprecation import RemovedInDjango50Warning, RemovedInDjango51Warning
  6. from .mixins import OrderableAggMixin
  7. __all__ = [
  8. "ArrayAgg",
  9. "BitAnd",
  10. "BitOr",
  11. "BitXor",
  12. "BoolAnd",
  13. "BoolOr",
  14. "JSONBAgg",
  15. "StringAgg",
  16. ]
  17. # RemovedInDjango50Warning
  18. NOT_PROVIDED = object()
  19. class DeprecatedConvertValueMixin:
  20. def __init__(self, *expressions, default=NOT_PROVIDED, **extra):
  21. if default is NOT_PROVIDED:
  22. default = None
  23. self._default_provided = False
  24. else:
  25. self._default_provided = True
  26. super().__init__(*expressions, default=default, **extra)
  27. def resolve_expression(self, *args, **kwargs):
  28. resolved = super().resolve_expression(*args, **kwargs)
  29. if not self._default_provided:
  30. resolved.empty_result_set_value = getattr(
  31. self, "deprecation_empty_result_set_value", self.deprecation_value
  32. )
  33. return resolved
  34. def convert_value(self, value, expression, connection):
  35. if value is None and not self._default_provided:
  36. warnings.warn(self.deprecation_msg, category=RemovedInDjango50Warning)
  37. return self.deprecation_value
  38. return value
  39. class ArrayAgg(DeprecatedConvertValueMixin, OrderableAggMixin, Aggregate):
  40. function = "ARRAY_AGG"
  41. template = "%(function)s(%(distinct)s%(expressions)s %(ordering)s)"
  42. allow_distinct = True
  43. # RemovedInDjango50Warning
  44. deprecation_value = property(lambda self: [])
  45. deprecation_msg = (
  46. "In Django 5.0, ArrayAgg() will return None instead of an empty list "
  47. "if there are no rows. Pass default=None to opt into the new behavior "
  48. "and silence this warning or default=[] to keep the previous behavior."
  49. )
  50. @property
  51. def output_field(self):
  52. return ArrayField(self.source_expressions[0].output_field)
  53. class BitAnd(Aggregate):
  54. function = "BIT_AND"
  55. class BitOr(Aggregate):
  56. function = "BIT_OR"
  57. class BitXor(Aggregate):
  58. function = "BIT_XOR"
  59. class BoolAnd(Aggregate):
  60. function = "BOOL_AND"
  61. output_field = BooleanField()
  62. class BoolOr(Aggregate):
  63. function = "BOOL_OR"
  64. output_field = BooleanField()
  65. class JSONBAgg(DeprecatedConvertValueMixin, OrderableAggMixin, Aggregate):
  66. function = "JSONB_AGG"
  67. template = "%(function)s(%(distinct)s%(expressions)s %(ordering)s)"
  68. allow_distinct = True
  69. output_field = JSONField()
  70. # RemovedInDjango50Warning
  71. deprecation_value = "[]"
  72. deprecation_empty_result_set_value = property(lambda self: [])
  73. deprecation_msg = (
  74. "In Django 5.0, JSONBAgg() will return None instead of an empty list "
  75. "if there are no rows. Pass default=None to opt into the new behavior "
  76. "and silence this warning or default=[] to keep the previous "
  77. "behavior."
  78. )
  79. # RemovedInDjango51Warning: When the deprecation ends, remove __init__().
  80. #
  81. # RemovedInDjango50Warning: When the deprecation ends, replace with:
  82. # def __init__(self, *expressions, default=None, **extra):
  83. def __init__(self, *expressions, default=NOT_PROVIDED, **extra):
  84. super().__init__(*expressions, default=default, **extra)
  85. if (
  86. isinstance(default, Value)
  87. and isinstance(default.value, str)
  88. and not isinstance(default.output_field, JSONField)
  89. ):
  90. value = default.value
  91. try:
  92. decoded = json.loads(value)
  93. except json.JSONDecodeError:
  94. warnings.warn(
  95. "Passing a Value() with an output_field that isn't a JSONField as "
  96. "JSONBAgg(default) is deprecated. Pass default="
  97. f"Value({value!r}, output_field=JSONField()) instead.",
  98. stacklevel=2,
  99. category=RemovedInDjango51Warning,
  100. )
  101. self.default.output_field = self.output_field
  102. else:
  103. self.default = Value(decoded, self.output_field)
  104. warnings.warn(
  105. "Passing an encoded JSON string as JSONBAgg(default) is "
  106. f"deprecated. Pass default={decoded!r} instead.",
  107. stacklevel=2,
  108. category=RemovedInDjango51Warning,
  109. )
  110. class StringAgg(DeprecatedConvertValueMixin, OrderableAggMixin, Aggregate):
  111. function = "STRING_AGG"
  112. template = "%(function)s(%(distinct)s%(expressions)s %(ordering)s)"
  113. allow_distinct = True
  114. output_field = TextField()
  115. # RemovedInDjango50Warning
  116. deprecation_value = ""
  117. deprecation_msg = (
  118. "In Django 5.0, StringAgg() will return None instead of an empty "
  119. "string if there are no rows. Pass default=None to opt into the new "
  120. 'behavior and silence this warning or default="" to keep the previous behavior.'
  121. )
  122. def __init__(self, expression, delimiter, **extra):
  123. delimiter_expr = Value(str(delimiter))
  124. super().__init__(expression, delimiter_expr, **extra)