Skip to content

Commit c9cbdf0

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents fecaaec + d6925f0 commit c9cbdf0

File tree

27 files changed

+485
-250
lines changed

27 files changed

+485
-250
lines changed

django/contrib/admin/templates/admin/change_list.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
</div>
7575
{% block filters %}
7676
{% if cl.has_filters %}
77-
<nav id="changelist-filter" aria-labelledby="changelist-filter-header">
77+
<search id="changelist-filter" aria-labelledby="changelist-filter-header">
7878
<h2 id="changelist-filter-header">{% translate 'Filter' %}</h2>
7979
{% if cl.is_facets_optional or cl.has_active_filters %}<div id="changelist-filter-extra-actions">
8080
{% if cl.is_facets_optional %}<h3>
@@ -86,7 +86,7 @@ <h2 id="changelist-filter-header">{% translate 'Filter' %}</h2>
8686
</h3>{% endif %}
8787
</div>{% endif %}
8888
{% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
89-
</nav>
89+
</search>
9090
{% endif %}
9191
{% endblock %}
9292
</div>

django/contrib/admin/templates/admin/search_form.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{% load i18n static %}
22
{% if cl.search_fields %}
3-
<div id="toolbar"><form id="changelist-search" method="get" role="search">
3+
<div id="toolbar">
4+
<h2 id="changelist-search-form" class="visually-hidden">{% blocktranslate with name=cl.opts.verbose_name_plural %}Search {{ name }}{% endblocktranslate %}</h2>
5+
<form id="changelist-search" method="get" role="search" aria-labelledby="changelist-search-form">
46
<div><!-- DIV needed for valid HTML -->
57
<label for="searchbar"><img src="{% static "admin/img/search.svg" %}" alt="Search"></label>
68
<input type="text" size="40" name="{{ search_var }}" value="{{ cl.query }}" id="searchbar"{% if cl.search_help_text %} aria-describedby="searchbar_helptext"{% endif %}>

django/contrib/humanize/templatetags/humanize.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import re
22
from datetime import UTC, date, datetime
3-
from decimal import Decimal
3+
from decimal import Decimal, InvalidOperation
44

55
from django import template
66
from django.template import defaultfilters
@@ -66,14 +66,15 @@ def ordinal(value):
6666
@register.filter(is_safe=True)
6767
def intcomma(value, use_l10n=True):
6868
"""
69-
Convert an integer to a string containing commas every three digits.
70-
For example, 3000 becomes '3,000' and 45000 becomes '45,000'.
69+
Convert an integer or float (or a string representation of either) to a
70+
string containing commas every three digits. Format localization is
71+
respected. For example, 3000 becomes '3,000' and 45000 becomes '45,000'.
7172
"""
7273
if use_l10n:
7374
try:
7475
if not isinstance(value, (float, Decimal)):
75-
value = int(value)
76-
except (TypeError, ValueError):
76+
value = Decimal(value)
77+
except (TypeError, ValueError, InvalidOperation):
7778
return intcomma(value, False)
7879
else:
7980
return number_format(value, use_l10n=True, force_grouping=True)

django/db/backends/base/features.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,8 @@ class BaseDatabaseFeatures:
347347
json_key_contains_list_matching_requires_list = False
348348
# Does the backend support JSONObject() database function?
349349
has_json_object_function = True
350+
# Does the backend support negative JSON array indexing?
351+
supports_json_negative_indexing = True
350352

351353
# Does the backend support column collations?
352354
supports_collation_on_charfield = True

django/db/backends/base/operations.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,3 +792,32 @@ def prepare_join_on_clause(self, lhs_table, lhs_field, rhs_table, rhs_field):
792792
def format_debug_sql(self, sql):
793793
# Hook for backends (e.g. NoSQL) to customize formatting.
794794
return sqlparse.format(sql, reindent=True, keyword_case="upper")
795+
796+
def format_json_path_numeric_index(self, num):
797+
"""
798+
Hook for backends to customize array indexing in JSON paths.
799+
"""
800+
return "[%s]" % num
801+
802+
def compile_json_path(self, key_transforms, include_root=True):
803+
"""
804+
Hook for backends to customize all aspects of JSON path construction.
805+
"""
806+
path = ["$"] if include_root else []
807+
for key_transform in key_transforms:
808+
try:
809+
num = int(key_transform)
810+
except ValueError: # Non-integer.
811+
path.append(".")
812+
path.append(json.dumps(key_transform))
813+
else:
814+
if (
815+
num < 0
816+
and not self.connection.features.supports_json_negative_indexing
817+
):
818+
raise NotSupportedError(
819+
"Using negative JSON array indices is not supported on this "
820+
"database backend."
821+
)
822+
path.append(self.format_json_path_numeric_index(num))
823+
return "".join(path)

django/db/backends/mysql/features.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
5858
supports_stored_generated_columns = True
5959
supports_virtual_generated_columns = True
6060

61+
supports_json_negative_indexing = False
62+
6163
@cached_property
6264
def minimum_database_version(self):
6365
if self.connection.mysql_is_mariadb:

django/db/backends/oracle/features.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
7373
requires_compound_order_by_subquery = True
7474
allows_multiple_constraints_on_same_fields = False
7575
supports_json_field_contains = False
76+
supports_json_negative_indexing = False
7677
supports_collation_on_textfield = False
7778
test_now_utc_template = "CURRENT_TIMESTAMP AT TIME ZONE 'UTC'"
7879
django_test_expected_failures = {

django/db/backends/sqlite3/operations.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,3 +441,6 @@ def on_conflict_suffix_sql(self, fields, on_conflict, update_fields, unique_fiel
441441

442442
def force_group_by(self):
443443
return ["GROUP BY TRUE"] if Database.sqlite_version_info < (3, 39) else []
444+
445+
def format_json_path_numeric_index(self, num):
446+
return "[#%s]" % num if num < 0 else super().format_json_path_numeric_index(num)

django/db/migrations/serializer.py

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,29 @@ def serialize(self):
9393
return repr(self.value), {"from decimal import Decimal"}
9494

9595

96-
class DeconstructableSerializer(BaseSerializer):
96+
class DeconstructibleSerializer(BaseSerializer):
9797
@staticmethod
9898
def serialize_deconstructed(path, args, kwargs):
99-
name, imports = DeconstructableSerializer._serialize_path(path)
99+
name, imports = DeconstructibleSerializer._serialize_path(path)
100100
strings = []
101101
for arg in args:
102102
arg_string, arg_imports = serializer_factory(arg).serialize()
103103
strings.append(arg_string)
104104
imports.update(arg_imports)
105+
non_ident_kwargs = {}
105106
for kw, arg in sorted(kwargs.items()):
106-
arg_string, arg_imports = serializer_factory(arg).serialize()
107-
imports.update(arg_imports)
108-
strings.append("%s=%s" % (kw, arg_string))
107+
if kw.isidentifier():
108+
arg_string, arg_imports = serializer_factory(arg).serialize()
109+
imports.update(arg_imports)
110+
strings.append("%s=%s" % (kw, arg_string))
111+
else:
112+
non_ident_kwargs[kw] = arg
113+
if non_ident_kwargs:
114+
# Serialize non-identifier keyword arguments as a dict.
115+
kw_string, kw_imports = serializer_factory(non_ident_kwargs).serialize()
116+
strings.append(f"**{kw_string}")
117+
imports.update(kw_imports)
118+
109119
return "%s(%s)" % (name, ", ".join(strings)), imports
110120

111121
@staticmethod
@@ -197,23 +207,11 @@ def serialize(self):
197207

198208
class FunctoolsPartialSerializer(BaseSerializer):
199209
def serialize(self):
200-
# Serialize functools.partial() arguments
201-
func_string, func_imports = serializer_factory(self.value.func).serialize()
202-
args_string, args_imports = serializer_factory(self.value.args).serialize()
203-
keywords_string, keywords_imports = serializer_factory(
204-
self.value.keywords
205-
).serialize()
206-
# Add any imports needed by arguments
207-
imports = {"import functools", *func_imports, *args_imports, *keywords_imports}
208-
return (
209-
"functools.%s(%s, *%s, **%s)"
210-
% (
211-
self.value.__class__.__name__,
212-
func_string,
213-
args_string,
214-
keywords_string,
215-
),
216-
imports,
210+
partial_name = self.value.__class__.__name__
211+
return DeconstructibleSerializer.serialize_deconstructed(
212+
f"functools.{partial_name}",
213+
(self.value.func, *self.value.args),
214+
self.value.keywords,
217215
)
218216

219217

@@ -231,13 +229,13 @@ def serialize(self):
231229
return value % (", ".join(strings)), imports
232230

233231

234-
class ModelFieldSerializer(DeconstructableSerializer):
232+
class ModelFieldSerializer(DeconstructibleSerializer):
235233
def serialize(self):
236234
attr_name, path, args, kwargs = self.value.deconstruct()
237235
return self.serialize_deconstructed(path, args, kwargs)
238236

239237

240-
class ModelManagerSerializer(DeconstructableSerializer):
238+
class ModelManagerSerializer(DeconstructibleSerializer):
241239
def serialize(self):
242240
as_manager, manager_path, qs_path, args, kwargs = self.value.deconstruct()
243241
if as_manager:
@@ -397,7 +395,7 @@ def serializer_factory(value):
397395
return TypeSerializer(value)
398396
# Anything that knows how to deconstruct itself.
399397
if hasattr(value, "deconstruct"):
400-
return DeconstructableSerializer(value)
398+
return DeconstructibleSerializer(value)
401399
for type_, serializer_cls in Serializer._registry.items():
402400
if isinstance(value, type_):
403401
return serializer_cls(value)

django/db/models/base.py

Lines changed: 2 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2115,93 +2115,13 @@ def _check_unique_together(cls):
21152115

21162116
@classmethod
21172117
def _check_indexes(cls, databases):
2118-
"""Check fields, names, and conditions of indexes."""
21192118
errors = []
2120-
references = set()
2121-
for index in cls._meta.indexes:
2122-
# Index name can't start with an underscore or a number, restricted
2123-
# for cross-database compatibility with Oracle.
2124-
if index.name[0] == "_" or index.name[0].isdigit():
2125-
errors.append(
2126-
checks.Error(
2127-
"The index name '%s' cannot start with an underscore "
2128-
"or a number." % index.name,
2129-
obj=cls,
2130-
id="models.E033",
2131-
),
2132-
)
2133-
if len(index.name) > index.max_name_length:
2134-
errors.append(
2135-
checks.Error(
2136-
"The index name '%s' cannot be longer than %d "
2137-
"characters." % (index.name, index.max_name_length),
2138-
obj=cls,
2139-
id="models.E034",
2140-
),
2141-
)
2142-
if index.contains_expressions:
2143-
for expression in index.expressions:
2144-
references.update(
2145-
ref[0] for ref in cls._get_expr_references(expression)
2146-
)
21472119
for db in databases:
21482120
if not router.allow_migrate_model(db, cls):
21492121
continue
21502122
connection = connections[db]
2151-
if not (
2152-
connection.features.supports_partial_indexes
2153-
or "supports_partial_indexes" in cls._meta.required_db_features
2154-
) and any(index.condition is not None for index in cls._meta.indexes):
2155-
errors.append(
2156-
checks.Warning(
2157-
"%s does not support indexes with conditions."
2158-
% connection.display_name,
2159-
hint=(
2160-
"Conditions will be ignored. Silence this warning "
2161-
"if you don't care about it."
2162-
),
2163-
obj=cls,
2164-
id="models.W037",
2165-
)
2166-
)
2167-
if not (
2168-
connection.features.supports_covering_indexes
2169-
or "supports_covering_indexes" in cls._meta.required_db_features
2170-
) and any(index.include for index in cls._meta.indexes):
2171-
errors.append(
2172-
checks.Warning(
2173-
"%s does not support indexes with non-key columns."
2174-
% connection.display_name,
2175-
hint=(
2176-
"Non-key columns will be ignored. Silence this "
2177-
"warning if you don't care about it."
2178-
),
2179-
obj=cls,
2180-
id="models.W040",
2181-
)
2182-
)
2183-
if not (
2184-
connection.features.supports_expression_indexes
2185-
or "supports_expression_indexes" in cls._meta.required_db_features
2186-
) and any(index.contains_expressions for index in cls._meta.indexes):
2187-
errors.append(
2188-
checks.Warning(
2189-
"%s does not support indexes on expressions."
2190-
% connection.display_name,
2191-
hint=(
2192-
"An index won't be created. Silence this warning "
2193-
"if you don't care about it."
2194-
),
2195-
obj=cls,
2196-
id="models.W043",
2197-
)
2198-
)
2199-
fields = [
2200-
field for index in cls._meta.indexes for field, _ in index.fields_orders
2201-
]
2202-
fields += [include for index in cls._meta.indexes for include in index.include]
2203-
fields += references
2204-
errors.extend(cls._check_local_fields(fields, "indexes"))
2123+
for index in cls._meta.indexes:
2124+
errors.extend(index.check(cls, connection))
22052125
return errors
22062126

22072127
@classmethod

0 commit comments

Comments
 (0)