Skip to content

Commit fd0bc79

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents 89396af + 7fc9db1 commit fd0bc79

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+580
-198
lines changed

django/contrib/gis/db/models/lookups.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class GISLookup(Lookup):
1919
band_lhs = None
2020

2121
def __init__(self, lhs, rhs):
22-
rhs, *self.rhs_params = rhs if isinstance(rhs, (list, tuple)) else [rhs]
22+
rhs, *self.rhs_params = rhs if isinstance(rhs, (list, tuple)) else (rhs,)
2323
super().__init__(lhs, rhs)
2424
self.template_params = {}
2525
self.process_rhs_params()

django/contrib/postgres/fields/array.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ def get_db_prep_value(self, value, connection, prepared=False):
135135
]
136136
return value
137137

138+
def get_db_prep_save(self, value, connection):
139+
if isinstance(value, (list, tuple)):
140+
return [self.base_field.get_db_prep_save(i, connection) for i in value]
141+
return value
142+
138143
def deconstruct(self):
139144
name, path, args, kwargs = super().deconstruct()
140145
if path == "django.contrib.postgres.fields.array.ArrayField":

django/db/models/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
from django.db.models.fields.composite import CompositePrimaryKey
4646
from django.db.models.fields.files import FileField, ImageField
4747
from django.db.models.fields.generated import GeneratedField
48-
from django.db.models.fields.json import JSONField
48+
from django.db.models.fields.json import JSONField, JSONNull
4949
from django.db.models.fields.proxy import OrderWrt
5050
from django.db.models.indexes import * # NOQA
5151
from django.db.models.indexes import __all__ as indexes_all
@@ -97,6 +97,7 @@
9797
"ExpressionWrapper",
9898
"F",
9999
"Func",
100+
"JSONNull",
100101
"OrderBy",
101102
"OuterRef",
102103
"RowRange",

django/db/models/fields/json.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import warnings
23

34
from django import forms
45
from django.core import checks, exceptions
@@ -11,6 +12,7 @@
1112
PostgresOperatorLookup,
1213
Transform,
1314
)
15+
from django.utils.deprecation import RemovedInDjango70Warning, django_file_prefixes
1416
from django.utils.translation import gettext_lazy as _
1517

1618
from . import Field
@@ -148,6 +150,27 @@ def formfield(self, **kwargs):
148150
)
149151

150152

153+
class JSONNull(expressions.Value):
154+
"""Represent JSON `null` primitive."""
155+
156+
def __init__(self):
157+
super().__init__(None, output_field=JSONField())
158+
159+
def __repr__(self):
160+
return f"{self.__class__.__name__}()"
161+
162+
def as_sql(self, compiler, connection):
163+
value = self.output_field.get_db_prep_value(self.value, connection)
164+
if value is None:
165+
value = "null"
166+
return "%s", (value,)
167+
168+
def as_mysql(self, compiler, connection):
169+
sql, params = self.as_sql(compiler, connection)
170+
sql = "JSON_EXTRACT(%s, '$')"
171+
return sql, params
172+
173+
151174
class DataContains(FieldGetDbPrepValueMixin, PostgresOperatorLookup):
152175
lookup_name = "contains"
153176
postgres_operator = "@>"
@@ -311,14 +334,28 @@ def process_rhs(self, compiler, connection):
311334

312335

313336
class JSONExact(lookups.Exact):
337+
# RemovedInDjango70Warning: When the deprecation period is over, remove
338+
# the following line.
314339
can_use_none_as_rhs = True
315340

316341
def process_rhs(self, compiler, connection):
342+
if self.rhs is None and not isinstance(self.lhs, KeyTransform):
343+
warnings.warn(
344+
"Using None as the right-hand side of an exact lookup on JSONField to "
345+
"mean JSON scalar 'null' is deprecated. Use JSONNull() instead (or use "
346+
"the __isnull lookup if you meant SQL NULL).",
347+
RemovedInDjango70Warning,
348+
skip_file_prefixes=django_file_prefixes(),
349+
)
350+
317351
rhs, rhs_params = super().process_rhs(compiler, connection)
352+
353+
# RemovedInDjango70Warning: When the deprecation period is over, remove
354+
# The following if-block entirely.
318355
# Treat None lookup values as null.
319-
if rhs == "%s" and rhs_params == [None]:
320-
rhs_params = ["null"]
321-
if connection.vendor == "mysql":
356+
if rhs == "%s" and (*rhs_params,) == (None,):
357+
rhs_params = ("null",)
358+
if connection.vendor == "mysql" and not isinstance(self.rhs, JSONNull):
322359
func = ["JSON_EXTRACT(%s, '$')"] * len(rhs_params)
323360
rhs %= tuple(func)
324361
return rhs, rhs_params
@@ -526,6 +563,10 @@ def resolve_expression_parameter(self, compiler, connection, sql, param):
526563

527564

528565
class KeyTransformExact(JSONExact):
566+
# RemovedInDjango70Warning: When deprecation period ends, uncomment the
567+
# flag below.
568+
# can_use_none_as_rhs = True
569+
529570
def process_rhs(self, compiler, connection):
530571
if isinstance(self.rhs, KeyTransform):
531572
return super(lookups.Exact, self).process_rhs(compiler, connection)
@@ -552,7 +593,7 @@ def process_rhs(self, compiler, connection):
552593

553594
def as_oracle(self, compiler, connection):
554595
rhs, rhs_params = super().process_rhs(compiler, connection)
555-
if rhs_params == ["null"]:
596+
if rhs_params and (*rhs_params,) == ("null",):
556597
# Field has key and it's NULL.
557598
has_key_expr = HasKeyOrArrayIndex(self.lhs.lhs, self.lhs.key_name)
558599
has_key_sql, has_key_params = has_key_expr.as_oracle(compiler, connection)

django/db/models/lookups.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,12 +232,12 @@ def process_lhs(self, compiler, connection, lhs=None):
232232
lhs_sql = (
233233
connection.ops.lookup_cast(self.lookup_name, field_internal_type) % lhs_sql
234234
)
235-
return lhs_sql, list(params)
235+
return lhs_sql, tuple(params)
236236

237237
def as_sql(self, compiler, connection):
238238
lhs_sql, params = self.process_lhs(compiler, connection)
239239
rhs_sql, rhs_params = self.process_rhs(compiler, connection)
240-
params.extend(rhs_params)
240+
params = (*params, *rhs_params)
241241
rhs_sql = self.get_rhs_op(connection, rhs_sql)
242242
return "%s %s" % (lhs_sql, rhs_sql), params
243243

@@ -725,7 +725,7 @@ def as_sql(self, compiler, connection):
725725
rhs_sql, _ = self.process_rhs(compiler, connection)
726726
rhs_sql = self.get_direct_rhs_sql(connection, rhs_sql)
727727
start, finish = self.year_lookup_bounds(connection, self.rhs)
728-
params.extend(self.get_bound_params(start, finish))
728+
params = (*params, *self.get_bound_params(start, finish))
729729
return "%s %s" % (lhs_sql, rhs_sql), params
730730
return super().as_sql(compiler, connection)
731731

django/db/models/sql/compiler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1703,8 +1703,8 @@ def field_as_sql(self, field, get_placeholder, val):
17031703
sql, params = "%s", [val]
17041704

17051705
# The following hook is only used by Oracle Spatial, which sometimes
1706-
# needs to yield 'NULL' and [] as its placeholder and params instead
1707-
# of '%s' and [None]. The 'NULL' placeholder is produced earlier by
1706+
# needs to yield 'NULL' and () as its placeholder and params instead
1707+
# of '%s' and (None,). The 'NULL' placeholder is produced earlier by
17081708
# OracleOperations.get_geom_placeholder(). The following line removes
17091709
# the corresponding None parameter. See ticket #10888.
17101710
params = self.connection.ops.modify_insert_params(sql, params)

django/test/runner.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import argparse
22
import ctypes
33
import faulthandler
4+
import functools
45
import hashlib
56
import io
67
import itertools
@@ -485,6 +486,16 @@ def _init_worker(
485486
)
486487

487488

489+
def _safe_init_worker(init_worker, counter, *args, **kwargs):
490+
try:
491+
init_worker(counter, *args, **kwargs)
492+
except Exception:
493+
with counter.get_lock():
494+
# Set a value that will not increment above zero any time soon.
495+
counter.value = -1000
496+
raise
497+
498+
488499
def _run_subsuite(args):
489500
"""
490501
Run a suite of tests with a RemoteTestRunner and return a RemoteTestResult.
@@ -558,7 +569,7 @@ def run(self, result):
558569
counter = multiprocessing.Value(ctypes.c_int, 0)
559570
pool = multiprocessing.Pool(
560571
processes=self.processes,
561-
initializer=self.init_worker.__func__,
572+
initializer=functools.partial(_safe_init_worker, self.init_worker.__func__),
562573
initargs=[
563574
counter,
564575
self.initial_settings,
@@ -585,7 +596,11 @@ def run(self, result):
585596

586597
try:
587598
subsuite_index, events = test_results.next(timeout=0.1)
588-
except multiprocessing.TimeoutError:
599+
except multiprocessing.TimeoutError as err:
600+
if counter.value < 0:
601+
err.add_note("ERROR: _init_worker failed, see prior traceback")
602+
pool.close()
603+
raise
589604
continue
590605
except StopIteration:
591606
pool.close()

docs/faq/install.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ needed.
2525
For a development environment -- if you just want to experiment with Django --
2626
you don't need to have a separate web server installed or database server.
2727

28-
Django comes with its own :djadmin:`lightweight development server<runserver>`.
29-
For a production environment, Django follows the WSGI spec, :pep:`3333`, which
30-
means it can run on a variety of web servers. See :doc:`Deploying Django
31-
</howto/deployment/index>` for more information.
28+
Django comes with its own lightweight development server
29+
(:djadmin:`runserver`). For a production environment, Django follows the WSGI
30+
spec, :pep:`3333`, which means it can run on a variety of web servers. See
31+
:doc:`/howto/deployment/index` for more information.
3232

3333
Django runs `SQLite`_ by default, which is included in Python installations.
3434
For a production environment, we recommend PostgreSQL_; but we also officially

docs/howto/error-reporting.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ filtered out of error reports in a production environment (that is, where
195195
.. function:: sensitive_post_parameters(*parameters)
196196

197197
If one of your views receives an :class:`~django.http.HttpRequest` object
198-
with :attr:`POST parameters<django.http.HttpRequest.POST>` susceptible to
198+
with :attr:`~django.http.HttpRequest.POST` parameters susceptible to
199199
contain sensitive information, you may prevent the values of those
200200
parameters from being included in the error reports using the
201201
``sensitive_post_parameters`` decorator::

docs/howto/logging.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,9 @@ root of the project.
186186
Configure a formatter
187187
~~~~~~~~~~~~~~~~~~~~~
188188

189-
By default, the final log output contains the message part of each :class:`log
190-
record <logging.LogRecord>`. Use a formatter if you want to include additional
191-
data. First name and define your formatters - this example defines
189+
By default, the final log output contains the message part of each
190+
:class:`~logging.LogRecord` object. Use a formatter if you want to include
191+
additional data. First name and define your formatters - this example defines
192192
formatters named ``verbose`` and ``simple``:
193193

194194
.. code-block:: python

0 commit comments

Comments
 (0)