From 9d9e47b6915037c220b77746cc7d1b5d8f0f36b1 Mon Sep 17 00:00:00 2001 From: "pyup.io bot" Date: Tue, 7 May 2024 14:10:31 -0700 Subject: [PATCH 1/4] Scheduled biweekly dependency update for week 18 (#1225) * Update black from 24.4.0 to 24.4.2 * Update flake8-bugbear from 24.2.6 to 24.4.26 * Update sphinx from 7.2.6 to 7.3.7 * Update faker from 24.9.0 to 25.0.1 * Update pytest from 8.1.1 to 8.2.0 --- requirements/requirements-codestyle.txt | 4 ++-- requirements/requirements-documentation.txt | 2 +- requirements/requirements-testing.txt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/requirements-codestyle.txt b/requirements/requirements-codestyle.txt index dc65b0ef..4c270607 100644 --- a/requirements/requirements-codestyle.txt +++ b/requirements/requirements-codestyle.txt @@ -1,5 +1,5 @@ -black==24.4.0 +black==24.4.2 flake8==7.0.0 -flake8-bugbear==24.2.6 +flake8-bugbear==24.4.26 flake8-isort==6.1.1 isort==5.13.2 diff --git a/requirements/requirements-documentation.txt b/requirements/requirements-documentation.txt index 49668994..13a66219 100644 --- a/requirements/requirements-documentation.txt +++ b/requirements/requirements-documentation.txt @@ -1,3 +1,3 @@ recommonmark==0.7.1 -Sphinx==7.2.6 +Sphinx==7.3.7 sphinx_rtd_theme==2.0.0 diff --git a/requirements/requirements-testing.txt b/requirements/requirements-testing.txt index d2c31244..1dfa20d4 100644 --- a/requirements/requirements-testing.txt +++ b/requirements/requirements-testing.txt @@ -1,6 +1,6 @@ factory-boy==3.3.0 -Faker==24.9.0 -pytest==8.1.1 +Faker==25.0.1 +pytest==8.2.0 pytest-cov==5.0.0 pytest-django==4.8.0 pytest-factoryboy==2.7.0 From 21493c1abda91af80de13202407485ef1a21dc50 Mon Sep 17 00:00:00 2001 From: js-nh Date: Wed, 29 May 2024 10:08:18 +0200 Subject: [PATCH 2/4] Added HTTP 429 Too Many Requests as a possible generic error response (#1226) * Add HTTP 429 Too Many Requests as a possible generic error response * Update CHANGELOG.md --------- Co-authored-by: Oliver Sauder --- CHANGELOG.md | 6 ++ example/tests/__snapshots__/test_openapi.ambr | 60 +++++++++++++++++++ rest_framework_json_api/schemas/openapi.py | 1 + 3 files changed, 67 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1d08de6..a61d5faf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ 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 `429 Too Many Requests` as a possible error response in the OpenAPI schema. + ## [7.0.0] - 2024-05-02 ### Added diff --git a/example/tests/__snapshots__/test_openapi.ambr b/example/tests/__snapshots__/test_openapi.ambr index 1de8057b..f72c6ff8 100644 --- a/example/tests/__snapshots__/test_openapi.ambr +++ b/example/tests/__snapshots__/test_openapi.ambr @@ -68,6 +68,16 @@ } }, "description": "[Resource does not exist](https://jsonapi.org/format/#crud-deleting-responses-404)" + }, + "429": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/failure" + } + } + }, + "description": "too many requests" } }, "tags": [ @@ -264,6 +274,16 @@ } }, "description": "[Conflict]([Conflict](https://jsonapi.org/format/#crud-updating-responses-409)" + }, + "429": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/failure" + } + } + }, + "description": "too many requests" } }, "tags": [ @@ -399,6 +419,16 @@ } }, "description": "not found" + }, + "429": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/failure" + } + } + }, + "description": "too many requests" } }, "tags": [ @@ -546,6 +576,16 @@ } }, "description": "not found" + }, + "429": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/failure" + } + } + }, + "description": "too many requests" } }, "tags": [ @@ -754,6 +794,16 @@ } }, "description": "[Conflict](https://jsonapi.org/format/#crud-creating-responses-409)" + }, + "429": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/failure" + } + } + }, + "description": "too many requests" } }, "tags": [ @@ -1341,6 +1391,16 @@ } }, "description": "not found" + }, + "429": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/failure" + } + } + }, + "description": "too many requests" } }, "tags": [ diff --git a/rest_framework_json_api/schemas/openapi.py b/rest_framework_json_api/schemas/openapi.py index 650876e6..b44ce7a4 100644 --- a/rest_framework_json_api/schemas/openapi.py +++ b/rest_framework_json_api/schemas/openapi.py @@ -807,6 +807,7 @@ def _add_generic_failure_responses(self, operation): for code, reason in [ ("400", "bad request"), ("401", "not authorized"), + ("429", "too many requests"), ]: operation["responses"][code] = self._failure_response(reason) From dbe939a5cd23144f46dc1b191f290b4aa59dd8a4 Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Tue, 4 Jun 2024 23:49:35 +0200 Subject: [PATCH 3/4] Ensured that URL and id field are kept when using sparse fields (#1231) Ensured that URL and id field are not filtered out when using sparse fields URL field is considered a field in DRF but is not in JSON:API spec therefore we may not exclude it. ID on the other hand is a required field and may not be filtered. --- CHANGELOG.md | 4 ++ rest_framework_json_api/renderers.py | 5 +- rest_framework_json_api/serializers.py | 7 ++- tests/serializers.py | 12 +++++ tests/test_views.py | 64 +++++++++++++++++++++++--- tests/views.py | 15 ++++++ 6 files changed, 98 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a61d5faf..47a6dec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ any parts of the framework not mentioned in the documentation should generally b * Added `429 Too Many Requests` as a possible error response in the OpenAPI schema. +### Fixed + +* Ensured that URL and id field are kept when using sparse fields (regression since 7.0.0) + ## [7.0.0] - 2024-05-02 ### Added diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py index 8c632f6a..5980b95d 100644 --- a/rest_framework_json_api/renderers.py +++ b/rest_framework_json_api/renderers.py @@ -446,7 +446,10 @@ def _filter_sparse_fields(cls, serializer, fields, resource_name): return { field_name: field for field_name, field, in fields.items() - if field_name in sparse_fields + if field.field_name in sparse_fields + # URL field is not considered a field in JSON:API spec + # but a link so need to keep it + or field.field_name == api_settings.URL_FIELD_NAME } return fields diff --git a/rest_framework_json_api/serializers.py b/rest_framework_json_api/serializers.py index 66650caf..3ba9de86 100644 --- a/rest_framework_json_api/serializers.py +++ b/rest_framework_json_api/serializers.py @@ -94,10 +94,15 @@ def _readable_fields(self): field for field in readable_fields if field.field_name in sparse_fields + # URL field is not considered a field in JSON:API spec + # but a link so need to keep it or field.field_name == api_settings.URL_FIELD_NAME + # ID is a required field which might have been overwritten + # so need to keep it + or field.field_name == "id" ) except AttributeError: - # no type on serializer, must be used only as only nested + # no type on serializer, may only be used nested pass return readable_fields diff --git a/tests/serializers.py b/tests/serializers.py index ddf28f98..c312b83a 100644 --- a/tests/serializers.py +++ b/tests/serializers.py @@ -1,3 +1,5 @@ +from rest_framework.settings import api_settings + from rest_framework_json_api import serializers from tests.models import ( BasicModel, @@ -32,6 +34,16 @@ class Meta: ) +class ForeignKeySourcetHyperlinkedSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = ForeignKeySource + fields = ( + "name", + "target", + api_settings.URL_FIELD_NAME, + ) + + class ManyToManyTargetSerializer(serializers.ModelSerializer): class Meta: fields = ("name",) diff --git a/tests/test_views.py b/tests/test_views.py index 468c2cbd..45f8aaca 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -16,7 +16,9 @@ from tests.serializers import BasicModelSerializer, ForeignKeyTargetSerializer from tests.views import ( BasicModelViewSet, + ForeignKeySourcetHyperlinkedViewSet, ForeignKeySourceViewSet, + ForeignKeyTargetViewSet, ManyToManySourceViewSet, NestedRelatedSourceViewSet, ) @@ -87,7 +89,7 @@ def test_list(self, client, model): @pytest.mark.urls(__name__) def test_list_with_include_foreign_key(self, client, foreign_key_source): - url = reverse("foreign-key-source-list") + url = reverse("foreignkeysource-list") response = client.get(url, data={"include": "target"}) assert response.status_code == status.HTTP_200_OK result = response.json() @@ -156,7 +158,7 @@ def test_list_with_include_nested_related_field( @pytest.mark.urls(__name__) def test_list_with_invalid_include(self, client, foreign_key_source): - url = reverse("foreign-key-source-list") + url = reverse("foreignkeysource-list") response = client.get(url, data={"include": "invalid"}) assert response.status_code == status.HTTP_400_BAD_REQUEST result = response.json() @@ -195,7 +197,7 @@ def test_retrieve(self, client, model): @pytest.mark.urls(__name__) def test_retrieve_with_include_foreign_key(self, client, foreign_key_source): - url = reverse("foreign-key-source-detail", kwargs={"pk": foreign_key_source.pk}) + url = reverse("foreignkeysource-detail", kwargs={"pk": foreign_key_source.pk}) response = client.get(url, data={"include": "target"}) assert response.status_code == status.HTTP_200_OK result = response.json() @@ -208,6 +210,20 @@ def test_retrieve_with_include_foreign_key(self, client, foreign_key_source): } ] == result["included"] + @pytest.mark.urls(__name__) + def test_retrieve_hyperlinked_with_sparse_fields(self, client, foreign_key_source): + url = reverse( + "foreignkeysourcehyperlinked-detail", kwargs={"pk": foreign_key_source.pk} + ) + response = client.get(url, data={"fields[ForeignKeySource]": "name"}) + assert response.status_code == status.HTTP_200_OK + data = response.json()["data"] + assert data["attributes"] == {"name": foreign_key_source.name} + assert "relationships" not in data + assert data["links"] == { + "self": f"http://testserver/foreign_key_sources/{foreign_key_source.pk}/" + } + @pytest.mark.urls(__name__) def test_patch(self, client, model): data = { @@ -239,7 +255,7 @@ def test_delete(self, client, model): @pytest.mark.urls(__name__) def test_create_with_sparse_fields(self, client, foreign_key_target): - url = reverse("foreign-key-source-list") + url = reverse("foreignkeysource-list") data = { "data": { "id": None, @@ -379,6 +395,28 @@ def test_patch_with_custom_id(self, client): } } + @pytest.mark.urls(__name__) + def test_patch_with_custom_id_with_sparse_fields(self, client): + data = { + "data": { + "id": 2_193_102, + "type": "custom", + "attributes": {"body": "hello"}, + } + } + + url = reverse("custom-id") + + response = client.patch(f"{url}?fields[custom]=body", data=data) + assert response.status_code == status.HTTP_200_OK + assert response.json() == { + "data": { + "type": "custom", + "id": "2176ce", # get_id() -> hex + "attributes": {"body": "hello"}, + } + } + # Routing setup @@ -415,13 +453,16 @@ class CustomModelSerializer(serializers.Serializer): id = serializers.IntegerField() -class CustomIdModelSerializer(serializers.Serializer): +class CustomIdSerializer(serializers.Serializer): id = serializers.SerializerMethodField() body = serializers.CharField() def get_id(self, obj): return hex(obj.id)[2:] + class Meta: + resource_name = "custom" + class CustomAPIView(APIView): parser_classes = [JSONParser] @@ -443,14 +484,23 @@ class CustomIdAPIView(APIView): resource_name = "custom" def patch(self, request, *args, **kwargs): - serializer = CustomIdModelSerializer(CustomModel(request.data)) + serializer = CustomIdSerializer( + CustomModel(request.data), context={"request": self.request} + ) return Response(status=status.HTTP_200_OK, data=serializer.data) +# TODO remove basename and use default (lowercase of model) +# this makes using HyperlinkedIdentityField easier and reduces +# configuration in general router = SimpleRouter() router.register(r"basic_models", BasicModelViewSet, basename="basic-model") +router.register(r"foreign_key_sources", ForeignKeySourceViewSet) +router.register(r"foreign_key_targets", ForeignKeyTargetViewSet) router.register( - r"foreign_key_sources", ForeignKeySourceViewSet, basename="foreign-key-source" + r"foreign_key_sources_hyperlinked", + ForeignKeySourcetHyperlinkedViewSet, + "foreignkeysourcehyperlinked", ) router.register( r"many_to_many_sources", ManyToManySourceViewSet, basename="many-to-many-source" diff --git a/tests/views.py b/tests/views.py index 72a7ea59..dba769a6 100644 --- a/tests/views.py +++ b/tests/views.py @@ -2,12 +2,15 @@ from tests.models import ( BasicModel, ForeignKeySource, + ForeignKeyTarget, ManyToManySource, NestedRelatedSource, ) from tests.serializers import ( BasicModelSerializer, ForeignKeySourceSerializer, + ForeignKeySourcetHyperlinkedSerializer, + ForeignKeyTargetSerializer, ManyToManySourceSerializer, NestedRelatedSourceSerializer, ) @@ -25,6 +28,18 @@ class ForeignKeySourceViewSet(ModelViewSet): ordering = ["name"] +class ForeignKeySourcetHyperlinkedViewSet(ModelViewSet): + serializer_class = ForeignKeySourcetHyperlinkedSerializer + queryset = ForeignKeySource.objects.all() + ordering = ["name"] + + +class ForeignKeyTargetViewSet(ModelViewSet): + serializer_class = ForeignKeyTargetSerializer + queryset = ForeignKeyTarget.objects.all() + ordering = ["name"] + + class ManyToManySourceViewSet(ModelViewSet): serializer_class = ManyToManySourceSerializer queryset = ManyToManySource.objects.all() From 3f1ea673d4469f1094d9b1c26616babf32be465d Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Thu, 6 Jun 2024 15:02:58 +0200 Subject: [PATCH 4/4] Release 7.0.1 (#1232) New release as for regression #1228 --- CHANGELOG.md | 2 +- rest_framework_json_api/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47a6dec7..12ca5017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ 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] +## [7.0.1] - 2024-06-06 ### Added diff --git a/rest_framework_json_api/__init__.py b/rest_framework_json_api/__init__.py index 6d2c35b4..9a8c48a3 100644 --- a/rest_framework_json_api/__init__.py +++ b/rest_framework_json_api/__init__.py @@ -1,5 +1,5 @@ __title__ = "djangorestframework-jsonapi" -__version__ = "7.0.0" +__version__ = "7.0.1" __author__ = "" __license__ = "BSD" __copyright__ = "" 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