Skip to content

Commit

Permalink
Merge pull request #39 from maykinmedia/feature/drf-spectacular
Browse files Browse the repository at this point in the history
Feature/drf spectacular
  • Loading branch information
annashamray authored May 14, 2024
2 parents 32a3a6e + ae73ded commit 0523b9c
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 139 deletions.
21 changes: 0 additions & 21 deletions django_loose_fk/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@

from . import drf, fields, filters

try:
import drf_yasg
except ImportError:
drf_yasg = None


class DjangoLooseFkConfig(AppConfig):
name = "django_loose_fk"
Expand All @@ -19,24 +14,8 @@ def ready(self) -> None:

register_serializer_field()
filters.register_field_default()
register_yasg_fields()


def register_serializer_field() -> None:
mapping = serializers.ModelSerializer.serializer_field_mapping
mapping[fields.FkOrURLField] = drf.FKOrURLField


def register_yasg_fields() -> None:
# no drf yasg installed
if drf_yasg is None:
return

from drf_yasg import openapi
from drf_yasg.inspectors.field import basic_type_info

# since it subclasses basic types present, we need to get the most specific
# classes hit first
type_info = (openapi.TYPE_STRING, openapi.FORMAT_URI)
basic_type_info.insert(0, (fields.FkOrURLField, type_info))
basic_type_info.insert(0, (drf.FKOrURLField, type_info))
Empty file.
38 changes: 0 additions & 38 deletions django_loose_fk/inspectors/fields.py

This file was deleted.

41 changes: 0 additions & 41 deletions django_loose_fk/inspectors/query.py

This file was deleted.

2 changes: 2 additions & 0 deletions django_loose_fk/oas_extensions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .fields import LooseFkFieldExtension # noqa
from .query import LooseFkFilterExtension # noqa
26 changes: 26 additions & 0 deletions django_loose_fk/oas_extensions/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Define a field extension that can handle the custom fields.
Requires drf-spectacular, which can be installed as the [openapi] optional group
dependency.
"""

from drf_spectacular.extensions import OpenApiSerializerFieldExtension


class LooseFkFieldExtension(OpenApiSerializerFieldExtension):
target_class = "django_loose_fk.drf.FKOrURLField"
match_subclasses = True

def map_serializer_field(self, auto_schema, direction):
default_schema = auto_schema._map_serializer_field(
self.target, direction, bypass_extensions=True
)

return {
**default_schema,
"type": "string",
"format": "uri",
"minLength": self.target.min_length,
"maxLength": self.target.max_length,
}
32 changes: 32 additions & 0 deletions django_loose_fk/oas_extensions/query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Define a filter extension that can handle the custom fields.
Requires drf-spectacular, which can be installed as the [openapi] optional group
dependency.
"""

from drf_spectacular.contrib.django_filters import DjangoFilterExtension

from .. import filters


class LooseFkFilterExtension(DjangoFilterExtension):
"""add "uri" format to loose-fk query params"""

priority = 1

def resolve_filter_field(
self, auto_schema, model, filterset_class, field_name, filter_field
):
schemas = super().resolve_filter_field(
auto_schema, model, filterset_class, field_name, filter_field
)

if isinstance(filter_field, filters.FkOrUrlFieldFilter):
for schema in schemas:
if "items" in schema["schema"]:
schema["schema"]["items"]["format"] = "uri"
else:
schema["schema"]["format"] = "uri"

return schemas
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ tests_require =

[options.extras_require]
openapi =
drf-yasg
drf-spectacular
tests =
psycopg2
pytest
Expand Down
1 change: 1 addition & 0 deletions testapp/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class ZaakSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Zaak
fields = ("url", "zaaktype", "name")
extra_kwargs = {"zaaktype": {"max_length": 1000}}


class ZaakObjectSerializer(serializers.HyperlinkedModelSerializer):
Expand Down
8 changes: 8 additions & 0 deletions testapp/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.apps import AppConfig


class TestAppConfig(AppConfig):
name = "testapp"

def ready(self):
from django_loose_fk import oas_extensions # noqa
2 changes: 2 additions & 0 deletions testapp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,5 @@ def get_db():
ALLOWED_HOSTS = ["testserver.com", "testserver.local"]

USE_TZ = False

REST_FRAMEWORK = {"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema"}
68 changes: 30 additions & 38 deletions tests/test_api_docs.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,45 @@
from django_filters.rest_framework.backends import DjangoFilterBackend
from drf_yasg import openapi
from drf_yasg.inspectors.field import get_basic_type_info
from drf_spectacular.generators import SchemaGenerator

from django_loose_fk.inspectors.query import FilterInspector
from testapp.api import ZaakObjectViewSet, ZaakSerializer, ZaakViewSet

def test_field_schema():
schema = SchemaGenerator().get_schema(request=None, public=True)

def test_type_info():
field = ZaakSerializer().fields["zaaktype"]

type_info = get_basic_type_info(field)

assert type_info == {
"type": openapi.TYPE_STRING,
"format": openapi.FORMAT_URI,
"min_length": 1,
zaaktype_schema = schema["components"]["schemas"]["Zaak"]["properties"]["zaaktype"]
assert zaaktype_schema == {
"type": "string",
"format": "uri",
"maxLength": 1000,
"minLength": None,
}


def test_filter_introspection():
viewset = ZaakViewSet()
inspector = FilterInspector(viewset, "/foo", "get", [], None)
filter_backend = DjangoFilterBackend()
def test_filter_schema():
schema = SchemaGenerator().get_schema(request=None, public=True)

parameters = inspector.get_filter_parameters(filter_backend)
parameters_schema = schema["paths"]["/zaken/"]["get"]["parameters"]

assert len(parameters) == 2
assert len(parameters_schema) == 2

zaaktype_param = parameters[0]
assert zaaktype_param.name == "zaaktype"
assert zaaktype_param.type == openapi.TYPE_STRING
assert zaaktype_param.format == openapi.FORMAT_URI
zaaktype_param = parameters_schema[0]
assert zaaktype_param["name"] == "zaaktype"
assert zaaktype_param["schema"]["type"] == "string"
assert zaaktype_param["schema"]["format"] == "uri"

zaaktype_in_param = parameters[1]
assert zaaktype_in_param.name == "zaaktype__in"
assert zaaktype_in_param.type == openapi.TYPE_STRING
assert zaaktype_in_param.format == openapi.FORMAT_URI
zaaktype_in_param = parameters_schema[1]
assert zaaktype_in_param["name"] == "zaaktype__in"
assert zaaktype_in_param["schema"]["type"] == "array"
assert zaaktype_in_param["schema"]["items"]["type"] == "string"
assert zaaktype_in_param["schema"]["items"]["format"] == "uri"


def test_declared_filter_introspection():
viewset = ZaakObjectViewSet()
inspector = FilterInspector(viewset, "/foo", "get", [], None)
filter_backend = DjangoFilterBackend()
def test_declared_filter_schema():
schema = SchemaGenerator().get_schema(request=None, public=True)

parameters = inspector.get_filter_parameters(filter_backend)
parameters_schema = schema["paths"]["/zaakobjectfk/"]["get"]["parameters"]

assert len(parameters) == 1
param = parameters[0]
assert len(parameters_schema) == 1
param = parameters_schema[0]

assert param.name == "zaak"
assert param.type == openapi.TYPE_STRING
assert param.format == openapi.FORMAT_URI
assert param["name"] == "zaak"
assert param["schema"]["type"] == "string"
assert param["schema"]["format"] == "uri"

0 comments on commit 0523b9c

Please sign in to comment.