diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 09ec383a79f2..04a93af0d710 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -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 @@ -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 diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index b8f95526f756..c5803929c55f 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -28,6 +28,7 @@ ExpressionWrapper, F, Func, + JSONNull, OrderBy, OuterRef, RowRange, @@ -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 diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index d0e91f13d278..9d3c1241e767 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -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 diff --git a/django/db/models/fields/json.py b/django/db/models/fields/json.py index cf4f5953a75c..ed7b4d26a6a6 100644 --- a/django/db/models/fields/json.py +++ b/django/db/models/fields/json.py @@ -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 _ @@ -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 = "@>" @@ -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 @@ -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 diff --git a/docs/Makefile b/docs/Makefile index 3545332a4007..41ed522daf74 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -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."