From a479fc447d2f59538906548421cb8bb74a96689c Mon Sep 17 00:00:00 2001 From: rblotsky Date: Sun, 13 Sep 2026 13:24:53 -0400 Subject: [PATCH 1/3] Added mkdocs-redirects plugin to add a redirect from topics/writable-nested-serializers to api-guide/serializer-relations --- docs/topics/writable-nested-serializers.md | 47 ---- mkdocs.yml | 5 + pyproject.toml | 288 +++++++++++---------- 3 files changed, 150 insertions(+), 190 deletions(-) delete mode 100644 docs/topics/writable-nested-serializers.md diff --git a/docs/topics/writable-nested-serializers.md b/docs/topics/writable-nested-serializers.md deleted file mode 100644 index 3bac84ffa9..0000000000 --- a/docs/topics/writable-nested-serializers.md +++ /dev/null @@ -1,47 +0,0 @@ -> To save HTTP requests, it may be convenient to send related documents along with the request. -> -> — [JSON API specification for Ember Data][cite]. - -# Writable nested serializers - -Although flat data structures serve to properly delineate between the individual entities in your service, there are cases where it may be more appropriate or convenient to use nested data structures. - -Nested data structures are easy enough to work with if they're read-only - simply nest your serializer classes and you're good to go. However, there are a few more subtleties to using writable nested serializers, due to the dependencies between the various model instances, and the need to save or delete multiple instances in a single action. - -## One-to-many data structures - -*Example of a **read-only** nested serializer. Nothing complex to worry about here.* - - class ToDoItemSerializer(serializers.ModelSerializer): - class Meta: - model = ToDoItem - fields = ['text', 'is_completed'] - - class ToDoListSerializer(serializers.ModelSerializer): - items = ToDoItemSerializer(many=True, read_only=True) - - class Meta: - model = ToDoList - fields = ['title', 'items'] - -Some example output from our serializer. - - { - 'title': 'Leaving party preparations', - 'items': [ - {'text': 'Compile playlist', 'is_completed': True}, - {'text': 'Send invites', 'is_completed': False}, - {'text': 'Clean house', 'is_completed': False} - ] - } - -Let's take a look at updating our nested one-to-many data structure. - -### Validation errors - -### Adding and removing items - -### Making PATCH requests - - -[cite]: http://jsonapi.org/format/#url-based-json-api diff --git a/mkdocs.yml b/mkdocs.yml index f047f2aed2..435b977491 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -68,6 +68,11 @@ markdown_extensions: emoji_index: !!python/name:material.extensions.emoji.twemoji emoji_generator: !!python/name:material.extensions.emoji.to_svg +plugins: + - search + - redirects: + redirect_maps: + 'topics/writable-nested-serializers.md': 'api-guide/relations.md' nav: - Home: 'index.md' - Tutorial: diff --git a/pyproject.toml b/pyproject.toml index f83e49eb0f..038694e4db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,143 +1,145 @@ -[build-system] -build-backend = "setuptools.build_meta" -requires = [ "setuptools>=77.0.3" ] - -[project] -name = "djangorestframework" -description = "Web APIs for Django, made easy." -readme = "README.md" -license = "BSD-3-Clause" -license-files = [ "LICENSE.md" ] -authors = [ { name = "Tom Christie", email = "tom@tomchristie.com" } ] -requires-python = ">=3.10" -classifiers = [ - "Development Status :: 5 - Production/Stable", - "Environment :: Web Environment", - "Framework :: Django", - "Framework :: Django :: 5.2", - "Framework :: Django :: 6.0", - "Framework :: Django :: 6.1", - "Intended Audience :: Developers", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - "Topic :: Internet :: WWW/HTTP", -] -dynamic = [ "version" ] -dependencies = [ "django>=5.2" ] -urls.Changelog = "https://www.django-rest-framework.org/community/release-notes/" -urls.Funding = "https://fund.django-rest-framework.org/topics/funding/" -urls.Homepage = "https://www.django-rest-framework.org" -urls.Source = "https://github.com/encode/django-rest-framework" - -[dependency-groups] -dev = [ - { include-group = "docs" }, - { include-group = "optional" }, - { include-group = "test" }, -] -test = [ - "dj-database-url>=3.1.0", - "importlib-metadata<10.0", - # Pytest for running the tests. - "pytest==9.*", - "pytest-cov==7.*", - "pytest-django>=4.5.2,<5", - -] -docs = [ - # MkDocs to build our documentation. - "mkdocs==1.6.1", - "mkdocs-material[imaging]==9.7.7", - # pylinkvalidator to check for broken links in documentation. - "pylinkvalidator==0.3", -] -optional = [ - # Optional packages which may be used with REST framework. - "django-filter", - "django-guardian>=2.4.0,<3.4", - "inflection==0.5.1", - "legacy-cgi; python_version>='3.13'", - "markdown>=3.3.7", - "psycopg[binary]>=3.1.8", - "pygments==2.21.*", - "pyyaml>=5.3.1,<6.1", - "requests", - "uritemplate", -] -django52 = [ "django>=5.2,<6.0" ] -django60 = [ "django>=6.0,<6.1" ] -django61 = [ "django>=6.1,<6.2" ] -djangomain = [ "django @ https://github.com/django/django/archive/main.tar.gz" ] - -[tool.setuptools] - -[tool.setuptools.dynamic] -version = { attr = "rest_framework.__version__" } - -[tool.setuptools.packages.find] -include = [ "rest_framework*" ] - -[tool.setuptools.package-data] -"rest_framework" = [ - "templates/**/*", - "static/**/*", - "locale/**/*.mo", -] - -[tool.isort] -skip = [ ".tox" ] -atomic = true -multi_line_output = 5 -extra_standard_library = [ "types" ] -known_third_party = [ "pytest", "_pytest", "django", "uritemplate" ] -known_first_party = [ "rest_framework", "tests" ] - -[tool.flake8] -extend-ignore = [ "E501", "W503", "W504", "B" ] -extend-select = [ "B006" ] -banned-modules = "json = use from rest_framework.utils import json!" - -[tool.codespell] -# Ref: https://github.com/codespell-project/codespell#using-a-config-file -skip = "*/kickstarter-announcement.md,*.js,*.map,*.po,*.css,locale" -ignore-words = "codespell-ignore-words.txt" -ignore-words-list = "commitish" -builtin = "clear,rare,code,names,en-GB_to_en-US" - -[tool.pyproject-fmt] -max_supported_python = "3.14" -keep_full_version = true - -[tool.pytest] -addopts = [ - "--tb=short", - "--strict-markers", - "-ra", - "--no-migrations", -] -testpaths = [ "tests" ] -markers = [ - "requires_postgres: marks tests as requiring a PostgreSQL database backend", -] -filterwarnings = [ - "ignore:'cgi' is deprecated:DeprecationWarning", -] - -[tool.coverage.run] -# NOTE: source is ignored with pytest-cov (but uses the same). -source = [ "." ] -include = [ "rest_framework/*", "tests/*" ] -branch = true - -[tool.coverage.report] -include = [ "rest_framework/*", "tests/*" ] -exclude_lines = [ - "pragma: no cover", - "raise NotImplementedError", -] +[build-system] +build-backend = "setuptools.build_meta" +requires = [ "setuptools>=77.0.3" ] + +[project] +name = "djangorestframework" +description = "Web APIs for Django, made easy." +readme = "README.md" +license = "BSD-3-Clause" +license-files = [ "LICENSE.md" ] +authors = [ { name = "Tom Christie", email = "tom@tomchristie.com" } ] +requires-python = ">=3.10" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Django :: 5.2", + "Framework :: Django :: 6.0", + "Framework :: Django :: 6.1", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Internet :: WWW/HTTP", +] +dynamic = [ "version" ] +dependencies = [ "django>=5.2" ] +urls.Changelog = "https://www.django-rest-framework.org/community/release-notes/" +urls.Funding = "https://fund.django-rest-framework.org/topics/funding/" +urls.Homepage = "https://www.django-rest-framework.org" +urls.Source = "https://github.com/encode/django-rest-framework" + +[dependency-groups] +dev = [ + { include-group = "docs" }, + { include-group = "optional" }, + { include-group = "test" }, +] +test = [ + "dj-database-url>=3.1.0", + "importlib-metadata<10.0", + # Pytest for running the tests. + "pytest==9.*", + "pytest-cov==7.*", + "pytest-django>=4.5.2,<5", + +] +docs = [ + # MkDocs to build our documentation. + "mkdocs==1.6.1", + "mkdocs-material[imaging]==9.7.7", + # MkDocs Redirects to add redirects to documentation + "mkdocs-redirects==1.2.3", + # pylinkvalidator to check for broken links in documentation. + "pylinkvalidator==0.3", +] +optional = [ + # Optional packages which may be used with REST framework. + "django-filter", + "django-guardian>=2.4.0,<3.4", + "inflection==0.5.1", + "legacy-cgi; python_version>='3.13'", + "markdown>=3.3.7", + "psycopg[binary]>=3.1.8", + "pygments==2.21.*", + "pyyaml>=5.3.1,<6.1", + "requests", + "uritemplate", +] +django52 = [ "django>=5.2,<6.0" ] +django60 = [ "django>=6.0,<6.1" ] +django61 = [ "django>=6.1,<6.2" ] +djangomain = [ "django @ https://github.com/django/django/archive/main.tar.gz" ] + +[tool.setuptools] + +[tool.setuptools.dynamic] +version = { attr = "rest_framework.__version__" } + +[tool.setuptools.packages.find] +include = [ "rest_framework*" ] + +[tool.setuptools.package-data] +"rest_framework" = [ + "templates/**/*", + "static/**/*", + "locale/**/*.mo", +] + +[tool.isort] +skip = [ ".tox" ] +atomic = true +multi_line_output = 5 +extra_standard_library = [ "types" ] +known_third_party = [ "pytest", "_pytest", "django", "uritemplate" ] +known_first_party = [ "rest_framework", "tests" ] + +[tool.flake8] +extend-ignore = [ "E501", "W503", "W504", "B" ] +extend-select = [ "B006" ] +banned-modules = "json = use from rest_framework.utils import json!" + +[tool.codespell] +# Ref: https://github.com/codespell-project/codespell#using-a-config-file +skip = "*/kickstarter-announcement.md,*.js,*.map,*.po,*.css,locale" +ignore-words = "codespell-ignore-words.txt" +ignore-words-list = "commitish" +builtin = "clear,rare,code,names,en-GB_to_en-US" + +[tool.pyproject-fmt] +max_supported_python = "3.14" +keep_full_version = true + +[tool.pytest] +addopts = [ + "--tb=short", + "--strict-markers", + "-ra", + "--no-migrations", +] +testpaths = [ "tests" ] +markers = [ + "requires_postgres: marks tests as requiring a PostgreSQL database backend", +] +filterwarnings = [ + "ignore:'cgi' is deprecated:DeprecationWarning", +] + +[tool.coverage.run] +# NOTE: source is ignored with pytest-cov (but uses the same). +source = [ "." ] +include = [ "rest_framework/*", "tests/*" ] +branch = true + +[tool.coverage.report] +include = [ "rest_framework/*", "tests/*" ] +exclude_lines = [ + "pragma: no cover", + "raise NotImplementedError", +] From de9cdf831f1a2a0e16bc8ee00307bcf2557f5abe Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 15 Sep 2026 08:08:05 +0100 Subject: [PATCH 2/3] Revert a number of changes from pyproject.toml --- pyproject.toml | 61 +++++++++++++++++++++----------------------------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 038694e4db..75d9e92083 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,6 @@ test = [ "pytest==9.*", "pytest-cov==7.*", "pytest-django>=4.5.2,<5", - ] docs = [ # MkDocs to build our documentation. @@ -59,10 +58,14 @@ docs = [ # pylinkvalidator to check for broken links in documentation. "pylinkvalidator==0.3", ] +django52 = [ "django>=5.2,<6.0" ] +django60 = [ "django>=6.0,<6.1" ] +django61 = [ "django>=6.1,<6.2" ] +djangomain = [ "django @ https://github.com/django/django/archive/main.tar.gz" ] optional = [ # Optional packages which may be used with REST framework. "django-filter", - "django-guardian>=2.4.0,<3.4", + "django-guardian>=2.4.0,<3.5", "inflection==0.5.1", "legacy-cgi; python_version>='3.13'", "markdown>=3.3.7", @@ -72,33 +75,23 @@ optional = [ "requests", "uritemplate", ] -django52 = [ "django>=5.2,<6.0" ] -django60 = [ "django>=6.0,<6.1" ] -django61 = [ "django>=6.1,<6.2" ] -djangomain = [ "django @ https://github.com/django/django/archive/main.tar.gz" ] [tool.setuptools] - -[tool.setuptools.dynamic] -version = { attr = "rest_framework.__version__" } - -[tool.setuptools.packages.find] -include = [ "rest_framework*" ] - -[tool.setuptools.package-data] -"rest_framework" = [ - "templates/**/*", - "static/**/*", +packages.find.include = [ "rest_framework*" ] +package-data.rest_framework = [ "locale/**/*.mo", + "static/**/*", + "templates/**/*", ] +dynamic.version = { attr = "rest_framework.__version__" } [tool.isort] -skip = [ ".tox" ] atomic = true multi_line_output = 5 extra_standard_library = [ "types" ] -known_third_party = [ "pytest", "_pytest", "django", "uritemplate" ] +known_third_party = [ "_pytest", "django", "pytest", "uritemplate" ] known_first_party = [ "rest_framework", "tests" ] +skip = [ ".tox" ] [tool.flake8] extend-ignore = [ "E501", "W503", "W504", "B" ] @@ -106,15 +99,15 @@ extend-select = [ "B006" ] banned-modules = "json = use from rest_framework.utils import json!" [tool.codespell] -# Ref: https://github.com/codespell-project/codespell#using-a-config-file -skip = "*/kickstarter-announcement.md,*.js,*.map,*.po,*.css,locale" +builtin = "clear,rare,code,names,en-GB_to_en-US" ignore-words = "codespell-ignore-words.txt" ignore-words-list = "commitish" -builtin = "clear,rare,code,names,en-GB_to_en-US" +# Ref: https://github.com/codespell-project/codespell#using-a-config-file +skip = "*/kickstarter-announcement.md,*.js,*.map,*.po,*.css,locale" [tool.pyproject-fmt] -max_supported_python = "3.14" keep_full_version = true +max_supported_python = "3.14" [tool.pytest] addopts = [ @@ -123,23 +116,21 @@ addopts = [ "-ra", "--no-migrations", ] -testpaths = [ "tests" ] -markers = [ - "requires_postgres: marks tests as requiring a PostgreSQL database backend", -] filterwarnings = [ "ignore:'cgi' is deprecated:DeprecationWarning", ] +markers = [ + "requires_postgres: marks tests as requiring a PostgreSQL database backend", +] +testpaths = [ "tests" ] -[tool.coverage.run] +[tool.coverage] # NOTE: source is ignored with pytest-cov (but uses the same). -source = [ "." ] -include = [ "rest_framework/*", "tests/*" ] -branch = true - -[tool.coverage.report] -include = [ "rest_framework/*", "tests/*" ] -exclude_lines = [ +run.source = [ "." ] +run.include = [ "rest_framework/*", "tests/*" ] +run.branch = true +report.include = [ "rest_framework/*", "tests/*" ] +report.exclude_lines = [ "pragma: no cover", "raise NotImplementedError", ] From 0760c9218f5e8bbfd77caf581758b4e1068ce71a Mon Sep 17 00:00:00 2001 From: Bruno Alla Date: Tue, 15 Sep 2026 08:13:08 +0100 Subject: [PATCH 3/3] Redirect to section --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 435b977491..751e2d8877 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -72,7 +72,7 @@ plugins: - search - redirects: redirect_maps: - 'topics/writable-nested-serializers.md': 'api-guide/relations.md' + 'topics/writable-nested-serializers.md': 'api-guide/relations.md#writable-nested-serializers' nav: - Home: 'index.md' - Tutorial: