Coverage for apis_core/generic/generators.py: 36%
25 statements
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-20 09:24 +0000
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-20 09:24 +0000
1from django.contrib.contenttypes.models import ContentType
2from django.urls import resolve, reverse
3from drf_spectacular.generators import EndpointEnumerator, SchemaGenerator
5from apis_core.generic.abc import GenericModel
7# Custom Schema Generator
8#
9# The CustomSchemaGenerator is meant as a drop in replacement for the
10# "DEFAULT_GENERATOR_CLASS" of drf-spectacular. You can use it like this:
11# SPECTACULAR_SETTINGS["DEFAULT_GENERATOR_CLASS"] = 'apis_core.generic.generators.CustomSchemaGenerator'
12#
13# Reasons:
14# The first reason is, that we are using a custom converter
15# (`apis_core.generic.urls.ContenttypeConverter`) to provide access to views
16# and api views of different contenttypes. The default endpoint generator of
17# does not know about this converter and therefore sees the endpoints using
18# this converter as *one* endpoint with one parameter called `contenttype`.
19# Thats not what we want, so we have to do our own enumeration - we iterate
20# through all contenttypes that inherit from `GenericModel` and create
21# schema endpoints for those programatically.
22# The second reason is, that the autoapi schema generator of DRF Spectacular
23# and our `apis_core.generic.api_views.ModelViewSet` don't work well together.
24# Our ModelViewSet needs the dispatch method to set the model of the
25# `ModelViewSet`, but this method is not being called by the generator while
26# doing the inspection, which means the `ModelViewSet` does not know about the
27# model it should work with and can not provide the correct serializer and filter
28# classes. Therefore we create the callback for the endpoints by hand and set
29# the model from there.
32class CustomEndpointEnumerator(EndpointEnumerator):
33 def _generate_content_type_endpoint(
34 self, content_type: ContentType, method: str = "list"
35 ):
36 """Create a endpoint tuple, usable by the SchemaGenerator of DRF spectacular"""
37 path = reverse("apis_core:generic:genericmodelapi-list", args=[content_type])
38 cls = resolve(path).func.cls
40 if method == "detail":
41 path += "{id}/"
42 regex = path
43 # for now we only do "GET"
44 httpmethod = "GET"
45 # we have to add a attribute, so that the
46 # `initkwargs` argument to the `as_view`
47 # method can contain a `model` argument
48 cls.model = None
49 callback = cls.as_view({"get": method}, model=content_type.model_class())
50 return (path, regex, httpmethod, callback)
52 def get_api_endpoints(self, patterns=None, prefix=""):
53 """
54 Call the EndpointEnumerator's `get_api_endpoints` method to get all
55 the automatically found endpoints, remove the ones that we want to override
56 and the add our custom endpoints to this list.
57 """
58 api_endpoints = super().get_api_endpoints(patterns, prefix)
59 api_endpoints = [
60 endpoint
61 for endpoint in api_endpoints
62 if not endpoint[0].startswith("/apis/api/{contenttype}/")
63 ]
64 for content_type in ContentType.objects.all():
65 if content_type.model_class() is not None and issubclass(
66 content_type.model_class(), GenericModel
67 ):
68 api_endpoints.append(self._generate_content_type_endpoint(content_type))
69 api_endpoints.append(
70 self._generate_content_type_endpoint(content_type, "detail")
71 )
72 return api_endpoints
75class CustomSchemaGenerator(SchemaGenerator):
76 endpoint_inspector_cls = CustomEndpointEnumerator