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

1from django.contrib.contenttypes.models import ContentType 

2from django.urls import resolve, reverse 

3from drf_spectacular.generators import EndpointEnumerator, SchemaGenerator 

4 

5from apis_core.generic.abc import GenericModel 

6 

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. 

30 

31 

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 

39 

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) 

51 

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 

73 

74 

75class CustomSchemaGenerator(SchemaGenerator): 

76 endpoint_inspector_cls = CustomEndpointEnumerator