Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 10 additions & 49 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ permissions:
jobs:
docs:
runs-on: ubuntu-24.04
name: spelling
defaults:
run:
working-directory: docs
timeout-minutes: 60
steps:
- name: Checkout
Expand All @@ -37,51 +39,10 @@ jobs:
cache-dependency-path: 'docs/requirements.txt'
- name: Install system spell checker
run: sudo apt update && sudo apt install -y aspell aspell-en
- run: python -m pip install -r docs/requirements.txt
- name: Build docs
run: |
cd docs
sphinx-build -b spelling -n -q -W -d _build/doctrees -D language=en_US -j auto . _build/spelling

blacken-docs:
runs-on: ubuntu-latest
name: blacken-docs
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.14'
- run: python -m pip install blacken-docs
- name: Build docs
run: |
cd docs
make black
RESULT=`cat _build/black/output.txt`
if [ "$RESULT" -gt 0 ]; then
echo "💥 📢 Code blocks in documentation must be reformatted with blacken-docs 📢 💥"
fi;
exit $RESULT

lint-docs:
runs-on: ubuntu-latest
name: lint-docs
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.14'
- run: python -m pip install sphinx-lint
- name: Build docs
run: |
cd docs
make lint
- run: python -m pip install -r requirements.txt
- name: Lint
run: make lint
- name: Black
run: make black
- name: Spelling
run: SPHINXOPTS="-q -W" make spelling
3 changes: 2 additions & 1 deletion django/db/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
ExpressionWrapper,
F,
Func,
JSONNull,
OrderBy,
OuterRef,
RowRange,
Expand All @@ -45,7 +46,7 @@
from django.db.models.fields.composite import CompositePrimaryKey
from django.db.models.fields.files import FileField, ImageField
from django.db.models.fields.generated import GeneratedField
from django.db.models.fields.json import JSONField, JSONNull
from django.db.models.fields.json import JSONField
from django.db.models.fields.proxy import OrderWrt
from django.db.models.indexes import * # NOQA
from django.db.models.indexes import __all__ as indexes_all
Expand Down
24 changes: 24 additions & 0 deletions django/db/models/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,30 @@ def empty_result_set_value(self):
return self.value


@deconstructible(path="django.db.models.JSONNull")
class JSONNull(Value):
"""Represent JSON `null` primitive."""

def __init__(self):
from django.db.models import JSONField

super().__init__(None, output_field=JSONField())

def __repr__(self):
return f"{self.__class__.__name__}()"

def as_sql(self, compiler, connection):
value = self.output_field.get_db_prep_value(self.value, connection)
if value is None:
value = "null"
return "%s", (value,)

def as_mysql(self, compiler, connection):
sql, params = self.as_sql(compiler, connection)
sql = "JSON_EXTRACT(%s, '$')"
return sql, params


class RawSQL(Expression):
allowed_default = True

Expand Down
29 changes: 4 additions & 25 deletions django/db/models/fields/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
PostgresOperatorLookup,
Transform,
)
from django.utils.deconstruct import deconstructible
from django.utils.deprecation import RemovedInDjango70Warning, django_file_prefixes
from django.utils.translation import gettext_lazy as _

Expand Down Expand Up @@ -151,28 +150,6 @@ def formfield(self, **kwargs):
)


@deconstructible(path="django.db.models.JSONNull")
class JSONNull(expressions.Value):
"""Represent JSON `null` primitive."""

def __init__(self):
super().__init__(None, output_field=JSONField())

def __repr__(self):
return f"{self.__class__.__name__}()"

def as_sql(self, compiler, connection):
value = self.output_field.get_db_prep_value(self.value, connection)
if value is None:
value = "null"
return "%s", (value,)

def as_mysql(self, compiler, connection):
sql, params = self.as_sql(compiler, connection)
sql = "JSON_EXTRACT(%s, '$')"
return sql, params


class DataContains(FieldGetDbPrepValueMixin, PostgresOperatorLookup):
lookup_name = "contains"
postgres_operator = "@>"
Expand Down Expand Up @@ -357,7 +334,9 @@ def process_rhs(self, compiler, connection):
# Treat None lookup values as null.
if rhs == "%s" and (*rhs_params,) == (None,):
rhs_params = ("null",)
if connection.vendor == "mysql" and not isinstance(self.rhs, JSONNull):
if connection.vendor == "mysql" and not isinstance(
self.rhs, expressions.JSONNull
):
func = ["JSON_EXTRACT(%s, '$')"] * len(rhs_params)
rhs %= tuple(func)
return rhs, rhs_params
Expand Down Expand Up @@ -464,7 +443,7 @@ def as_oracle(self, compiler, connection):
if (
connection.features.supports_primitives_in_json_field
and isinstance(self.rhs, expressions.ExpressionList)
and JSONNull() in self.rhs.get_source_expressions()
and expressions.JSONNull() in self.rhs.get_source_expressions()
):
# Break the lookup into multiple exact lookups combined with OR, as
# Oracle does not support directly extracting JSON scalar null as a
Expand Down
29 changes: 23 additions & 6 deletions docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -171,16 +171,33 @@ spelling:

black:
@mkdir -p $(BUILDDIR)/black
find . -name "*.txt" -not -path "./_build/*" -not -path "./_theme/*" \
@find . -name "*.txt" -not -path "./_build/*" -not -path "./_theme/*" \
| xargs blacken-docs --rst-literal-block; echo $$? > "$(BUILDDIR)/black/output.txt"
@echo
@echo "Code blocks reformatted"
@RESULT=`cat $(BUILDDIR)/black/output.txt`; \
if [ "$$RESULT" -gt 0 ]; then \
echo "💥 📢 Code blocks in documentation must be reformatted with blacken-docs 📢 💥"; \
exit $$RESULT; \
else \
echo "Code blocks in documentation are properly formatted ✅"; \
fi

lint:
$(PYTHON) lint.py
@echo
@echo "Documentation lint complete."

check: spelling black lint
@echo
@echo "Style and spelling checks completed."
check:
@echo "=== Spelling check ==="; \
$(MAKE) --no-print-directory SPHINXOPTS="-q -W" spelling; SPELLING=$$?; \
echo ""; \
echo "=== Code formatting check ==="; \
$(MAKE) --no-print-directory black; BLACK=$$?; \
echo ""; \
echo "=== Lint check ==="; \
$(MAKE) --no-print-directory lint; LINT=$$?; \
echo ""; \
if [ $$SPELLING -ne 0 ] || [ $$BLACK -ne 0 ] || [ $$LINT -ne 0 ]; then \
echo "Some checks failed."; \
exit 1; \
fi; \
echo "All style and spelling checks passed."