diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ca736986..0852bba1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] env: PYTHON: ${{ matrix.python-version }} steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 07cd7d8f..9502f476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Note that in line with [Django REST framework policy](https://www.django-rest-framework.org/topics/release-notes/), any parts of the framework not mentioned in the documentation should generally be considered private API, and may be subject to change. +## [Unreleased] + +### Added + +* Added support for Django REST framework 3.16. +* Added support for Django 5.2. + +### Fixed + +* Ensured that interpreting `include` query parameter is done in internal Python naming. + This adds full support for using multipart field names for includes while configuring `JSON_API_FORMAT_FIELD_NAMES`. +* Ensured that sparse fieldset fully supports `JSON_API_FORMAT_FIELD_NAMES`. + +### Removed + +* Removed support for Python 3.8. +* Removed support for Django REST framework 3.14. +* Removed support for Django 5.0. + + ## [7.1.0] - 2024-10-25 +This is the last release supporting Python 3.8, Django 5.0 and Django REST framework 3.14. + ### Fixed * Handled zero as a valid ID for resource (regression since 6.1.0) diff --git a/README.rst b/README.rst index 0c9b842f..c0e95a19 100644 --- a/README.rst +++ b/README.rst @@ -92,9 +92,9 @@ As a Django REST framework JSON:API (short DJA) we are trying to address followi Requirements ------------ -1. Python (3.8, 3.9, 3.10, 3.11, 3.12, 3.13) -2. Django (4.2, 5.0, 5.1) -3. Django REST framework (3.14, 3.15) +1. Python (3.9, 3.10, 3.11, 3.12, 3.13) +2. Django (4.2, 5.1, 5.2) +3. Django REST framework (3.15, 3.16) We **highly** recommend and only officially support the latest patch release of each Python, Django and REST framework series. @@ -149,9 +149,9 @@ installed and activated: $ git clone https://github.com/django-json-api/django-rest-framework-json-api.git $ cd django-rest-framework-json-api $ pip install -Ur requirements.txt - $ django-admin migrate --settings=example.settings - $ django-admin loaddata drf_example --settings=example.settings - $ django-admin runserver --settings=example.settings + $ django-admin migrate --settings=example.settings --pythonpath . + $ django-admin loaddata drf_example --settings=example.settings --pythonpath . + $ django-admin runserver --settings=example.settings --pythonpath . Browse to diff --git a/docs/getting-started.md b/docs/getting-started.md index a7de353a..4052450b 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -51,9 +51,9 @@ like the following: ## Requirements -1. Python (3.8, 3.9, 3.10, 3.11, 3.12, 3.13) -2. Django (4.2, 5.0, 5.1) -3. Django REST framework (3.14, 3.15) +1. Python (3.9, 3.10, 3.11, 3.12, 3.13) +2. Django (4.2, 5.1, 5.2) +3. Django REST framework (3.15, 3.16) We **highly** recommend and only officially support the latest patch release of each Python, Django and REST framework series. diff --git a/example/requirements.txt b/example/requirements.txt deleted file mode 100644 index 2f4213ee..00000000 --- a/example/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Requirements specifically for the example app -Django>=1.11 -django-debug-toolbar -django-polymorphic>=2.0 -djangorestframework -inflection -pluggy -py -pyparsing -pytz -sqlparse -django-filter>=2.0 diff --git a/requirements/requirements-codestyle.txt b/requirements/requirements-codestyle.txt index f5e5e92c..1d4184ad 100644 --- a/requirements/requirements-codestyle.txt +++ b/requirements/requirements-codestyle.txt @@ -1,5 +1,5 @@ -black==24.10.0 -flake8==7.1.1 -flake8-bugbear==24.8.19 -flake8-isort==6.1.1 -isort==5.13.2 +black==25.1.0 +flake8==7.2.0 +flake8-bugbear==24.12.12 +flake8-isort==6.1.2 +isort==6.0.1 diff --git a/requirements/requirements-documentation.txt b/requirements/requirements-documentation.txt index aa120a8e..76f3bf9a 100644 --- a/requirements/requirements-documentation.txt +++ b/requirements/requirements-documentation.txt @@ -1,3 +1,3 @@ recommonmark==0.7.1 Sphinx==8.1.3 -sphinx_rtd_theme==3.0.1 +sphinx_rtd_theme==3.0.2 diff --git a/requirements/requirements-packaging.txt b/requirements/requirements-packaging.txt index e957043a..54882779 100644 --- a/requirements/requirements-packaging.txt +++ b/requirements/requirements-packaging.txt @@ -1 +1 @@ -twine==5.1.1 +twine==6.1.0 diff --git a/requirements/requirements-testing.txt b/requirements/requirements-testing.txt index b56d8185..35caa0a9 100644 --- a/requirements/requirements-testing.txt +++ b/requirements/requirements-testing.txt @@ -1,7 +1,7 @@ -factory-boy==3.3.1 -Faker==30.6.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-django==4.9.0 +factory-boy==3.3.3 +Faker==37.1.0 +pytest==8.3.5 +pytest-cov==6.1.1 +pytest-django==4.11.1 pytest-factoryboy==2.7.0 -syrupy==4.7.2 +syrupy==4.9.1 diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py index 8c19934f..b670338f 100644 --- a/rest_framework_json_api/renderers.py +++ b/rest_framework_json_api/renderers.py @@ -6,7 +6,6 @@ from collections import defaultdict from collections.abc import Iterable -import inflection from django.db.models import Manager from django.template import loader from django.utils.encoding import force_str @@ -277,9 +276,6 @@ def extract_included( current_serializer, "included_serializers", dict() ) included_resources = copy.copy(included_resources) - included_resources = [ - inflection.underscore(value) for value in included_resources - ] for field_name, field in iter(fields.items()): # Skip URL field diff --git a/rest_framework_json_api/serializers.py b/rest_framework_json_api/serializers.py index d59dbd88..26f6b02e 100644 --- a/rest_framework_json_api/serializers.py +++ b/rest_framework_json_api/serializers.py @@ -1,6 +1,5 @@ from collections.abc import Mapping -import inflection from django.core.exceptions import ObjectDoesNotExist from django.db.models.query import QuerySet from django.utils.module_loading import import_string as import_class_from_dotted_path @@ -27,6 +26,7 @@ get_resource_type_from_instance, get_resource_type_from_model, get_resource_type_from_serializer, + undo_format_field_name, ) @@ -90,7 +90,10 @@ def _readable_fields(self): sparse_fieldset_query_param ) if sparse_fieldset_value is not None: - sparse_fields = sparse_fieldset_value.split(",") + sparse_fields = [ + undo_format_field_name(sparse_field) + for sparse_field in sparse_fieldset_value.split(",") + ] return ( field for field in readable_fields @@ -129,7 +132,7 @@ def validate_path(serializer_class, field_path, path): serializers = getattr(serializer_class, "included_serializers", None) if serializers is None: raise ParseError("This endpoint does not support the include parameter") - this_field_name = inflection.underscore(field_path[0]) + this_field_name = field_path[0] this_included_serializer = serializers.get(this_field_name) if this_included_serializer is None: raise ParseError( diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index 805f5f09..2dd79677 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -316,10 +316,18 @@ def get_resource_id(resource_instance, resource): def get_included_resources(request, serializer=None): - """Build a list of included resources.""" + """ + Build a list of included resources. + + This method ensures that returned includes are in Python internally used + format. + """ include_resources_param = request.query_params.get("include") if request else None if include_resources_param: - return include_resources_param.split(",") + return [ + undo_format_field_name(include) + for include in include_resources_param.split(",") + ] else: return get_default_included_resources_from_serializer(serializer) diff --git a/setup.py b/setup.py index 652ab85b..de61b0d1 100755 --- a/setup.py +++ b/setup.py @@ -86,7 +86,6 @@ def get_package_data(package): "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -107,7 +106,7 @@ def get_package_data(package): }, install_requires=[ "inflection>=0.5.0", - "djangorestframework>=3.14", + "djangorestframework>=3.15", "django>=4.2", ], extras_require={ @@ -116,6 +115,6 @@ def get_package_data(package): "openapi": ["pyyaml>=5.4", "uritemplate>=3.0.1"], }, setup_requires=wheel, - python_requires=">=3.8", + python_requires=">=3.9", zip_safe=False, ) diff --git a/tests/conftest.py b/tests/conftest.py index 865244e0..77b3676b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,5 @@ import pytest -from rest_framework.test import APIClient +from rest_framework.test import APIClient, APIRequestFactory from tests.models import ( BasicModel, @@ -98,3 +98,8 @@ def nested_related_source( @pytest.fixture def client(): return APIClient() + + +@pytest.fixture +def rf(): + return APIRequestFactory() diff --git a/tests/test_serializers.py b/tests/test_serializers.py index 9d4200a3..98cb2850 100644 --- a/tests/test_serializers.py +++ b/tests/test_serializers.py @@ -1,5 +1,6 @@ import pytest from django.db import models +from rest_framework.request import Request from rest_framework.utils import model_meta from rest_framework_json_api import serializers @@ -84,3 +85,22 @@ class Meta: "verified", "uuid", ] + + +def test_readable_fields_with_sparse_fields(client, rf, settings): + class TestSerializer(serializers.Serializer): + name = serializers.CharField() + value = serializers.CharField() + multi_part_name = serializers.CharField() + + class Meta: + resource_name = "test" + + settings.JSON_API_FORMAT_FIELD_NAMES = "camelize" + request = Request(rf.get("/test/", {"fields[test]": "value,multiPartName"})) + context = {"request": request} + serializer = TestSerializer(context=context) + assert [field.field_name for field in serializer._readable_fields] == [ + "value", + "multi_part_name", + ] diff --git a/tests/test_utils.py b/tests/test_utils.py index a3beb12e..08e36b6a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,6 +2,7 @@ from rest_framework import status from rest_framework.fields import Field from rest_framework.generics import GenericAPIView +from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -13,6 +14,7 @@ format_link_segment, format_resource_type, format_value, + get_included_resources, get_related_resource_type, get_resource_id, get_resource_name, @@ -456,3 +458,22 @@ def test_get_resource_id(resource_instance, resource, expected): ) def test_format_error_object(message, pointer, response, result): assert result == format_error_object(message, pointer, response) + + +@pytest.mark.parametrize( + "format_type,include_param,expected_includes", + [ + ("dasherize", "author-bio", ["author_bio"]), + ("dasherize", "author-bio,author-type", ["author_bio", "author_type"]), + ("dasherize", "author-bio.author-type", ["author_bio.author_type"]), + ("camelize", "authorBio", ["author_bio"]), + ], +) +def test_get_included_resources( + rf, include_param, expected_includes, format_type, settings +): + settings.JSON_API_FORMAT_FIELD_NAMES = format_type + + request = Request(rf.get("/test/", {"include": include_param})) + includes = get_included_resources(request) + assert includes == expected_includes diff --git a/tox.ini b/tox.ini index a2accaad..8dd0e759 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,8 @@ [tox] envlist = - py{38,39,310,311,312}-django42-drf{314,315,master}, - py{310,311,312}-django{50,51}-drf{314,315,master}, - py313-django51-drf{master}, + py{39,310,311,312}-django42-drf{315,316,master}, + py{310,311,312}-django{51,52}-drf{315,316,master}, + py{313}-django{51,52}-drf{316,master}, black, docs, lint @@ -10,10 +10,10 @@ envlist = [testenv] deps = django42: Django>=4.2,<4.3 - django50: Django>=5.0,<5.1 django51: Django>=5.1,<5.2 - drf314: djangorestframework>=3.14,<3.15 + django52: Django>=5.2,<5.3 drf315: djangorestframework>=3.15,<3.16 + drf316: djangorestframework>=3.16,<3.17 drfmaster: https://github.com/encode/django-rest-framework/archive/master.zip -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy