FEAT: Modernized to support latest Pythons and DRF JSONAPI

This commit is contained in:
2026-08-23 12:33:13 +02:00
parent 533789645a
commit ddcf8fb039
22 changed files with 718 additions and 138 deletions
+123
View File
@@ -0,0 +1,123 @@
from types import SimpleNamespace
from rest_framework_json_api import serializers
from enhanced_drf_jsonapi.api import (
HardenedGenericAPIView,
HardenedModelViewSet,
PreloadIncludesMixin,
ReasonableModelSerializer,
ReasonableModelViewSet,
)
from enhanced_drf_jsonapi.exceptions import HardenedExceptionHandlingMixin
class QuerySetSpy:
def __init__(self):
self.select_calls = []
self.prefetch_calls = []
def select_related(self, *fields):
self.select_calls.append(fields)
return self
def prefetch_related(self, *fields):
self.prefetch_calls.append(fields)
return self
class QuerySetProvider:
queryset_spy = None
def get_queryset(self, *args, **kwargs):
return self.queryset_spy
class IncludeView(PreloadIncludesMixin, QuerySetProvider):
select_for_includes = {
"__all__": ["owner"],
"author": ["author", "author__profile"],
}
prefetch_for_includes = {
"author": ["author__books"],
"comments": lambda view: [f"comments_for_{view.request.marker}"],
}
def test_preload_includes_applies_requested_and_unconditional_rules(monkeypatch):
queryset = QuerySetSpy()
view = IncludeView()
view.queryset_spy = queryset
view.request = SimpleNamespace(marker="request")
monkeypatch.setattr(
"enhanced_drf_jsonapi.api.get_included_resources",
lambda request: ["author", "comments"],
)
assert view.get_queryset() is queryset
assert queryset.select_calls == [("author", "author__profile"), ("owner",)]
assert queryset.prefetch_calls == [
("author__books",),
("comments_for_request",),
]
def serializer_fields(monkeypatch, method, *, include="", data=None, configured=True):
monkeypatch.setattr(
serializers.ModelSerializer,
"get_field_names",
lambda self, declared_fields, info: ["id", "name"],
)
request = SimpleNamespace(
method=method,
query_params={"include": include} if include else {},
data=data or {},
)
serializer = ReasonableModelSerializer(context={"request": request})
if configured:
serializer.included_serializers = {"owner": object, "comments": object}
return serializer.get_field_names({}, None)
def test_serializer_adds_requested_get_relationships_without_duplicates(monkeypatch):
fields = serializer_fields(monkeypatch, "GET", include="owner,comments,missing,owner")
assert fields == ["id", "name", "owner", "comments"]
def test_serializer_adds_relationships_from_post_and_patch_payloads(monkeypatch):
assert serializer_fields(monkeypatch, "POST", data={"owner": {}, "other": 1}) == [
"id",
"name",
"owner",
]
assert serializer_fields(monkeypatch, "PATCH", data={"comments": []}) == [
"id",
"name",
"comments",
]
def test_serializer_is_safe_without_request_or_included_serializers(monkeypatch):
monkeypatch.setattr(
serializers.ModelSerializer,
"get_field_names",
lambda self, declared_fields, info: ["id"],
)
assert ReasonableModelSerializer().get_field_names({}, None) == ["id"]
assert serializer_fields(monkeypatch, "GET", include="owner", configured=False) == [
"id",
"name",
]
def test_view_classes_preserve_methods_and_offer_opt_in_hardening():
assert ReasonableModelViewSet.http_method_names == [
"get",
"post",
"patch",
"delete",
"head",
"options",
]
assert issubclass(HardenedModelViewSet, HardenedExceptionHandlingMixin)
assert issubclass(HardenedGenericAPIView, HardenedExceptionHandlingMixin)