Skip to content

Commit

Permalink
Update schema and generator
Browse files Browse the repository at this point in the history
  • Loading branch information
SebCorbin committed Sep 22, 2021
1 parent 6f43eea commit 869d49c
Show file tree
Hide file tree
Showing 3 changed files with 386 additions and 92 deletions.
184 changes: 183 additions & 1 deletion api/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,18 @@
# see http://www.gnu.org/licenses/.
#
# ##############################################################################
from rest_framework.schemas.openapi import SchemaGenerator
from collections import OrderedDict

from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator
from rest_framework.serializers import Serializer

from base.models.utils.utils import ChoiceEnum


class AdmissionSchemaGenerator(SchemaGenerator):
def get_schema(self, *args, **kwargs):
schema = super().get_schema(*args, **kwargs)
schema["openapi"] = "3.0.0"
schema["info"]["title"] = "Admission API"
schema["info"]["description"] = "This API delivers data for the Admission project."
schema["info"]["contact"] = {
Expand Down Expand Up @@ -63,4 +69,180 @@ def get_schema(self, *args, **kwargs):
"description": "Enter your token in the format **Token <token>**"
}
}
schema['components']['parameters'] = {
"X-User-FirstName": {
"in": "header",
"name": "X-User-FirstName",
"schema": {
"type": "string"
},
"required": False
},
"X-User-LastName": {
"in": "header",
"name": "X-User-LastName",
"schema": {
"type": "string"
},
"required": False
},
"X-User-Email": {
"in": "header",
"name": "X-User-Email",
"schema": {
"type": "string"
},
"required": False
},
"X-User-GlobalID": {
"in": "header",
"name": "X-User-GlobalID",
"schema": {
"type": "string"
},
"required": False
},
"Accept-Language": {
"in": "header",
"name": "Accept-Language",
"description": "The header advertises which languages the client is able to understand, and which "
"locale variant is preferred. (By languages, we mean natural languages, such as "
"English, and not programming languages.)",
"schema": {
"$ref": "#/components/schemas/AcceptedLanguageEnum"
},
"required": False
}
}
schema['components']['responses'] = {
"Unauthorized": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"BadRequest": {
"description": "Bad request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"NotFound": {
"description": "The specified resource was not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
schema['components']['schemas']['Error'] = {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
]
}
schema['components']['schemas']['AcceptedLanguageEnum'] = {
"type": "string",
"enum": [
"en",
"fr-be"
]
}
for path, path_content in schema['paths'].items():
for method, method_content in path_content.items():
method_content['parameters'].extend([
{'$ref': '#/components/parameters/Accept-Language'},
{'$ref': '#/components/parameters/X-User-FirstName'},
{'$ref': '#/components/parameters/X-User-LastName'},
{'$ref': '#/components/parameters/X-User-Email'},
{'$ref': '#/components/parameters/X-User-GlobalID'},
])
method_content['responses'].update({
"400": {
"$ref": "#/components/responses/BadRequest"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"404": {
"$ref": "#/components/responses/NotFound"
}
})
return schema


class DetailedAutoSchema(AutoSchema):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.enums = {}

def get_request_body(self, path, method):
if method not in ('PUT', 'PATCH', 'POST'):
return {}

self.request_media_types = self.map_parsers(path, method)

serializer = self.get_serializer(path, method, for_response=False)

if not isinstance(serializer, Serializer):
item_schema = {}
else:
item_schema = self._get_reference(serializer)

return {
'content': {
ct: {'schema': item_schema}
for ct in self.request_media_types
}
}

def get_components(self, path, method):
if method.lower() == 'delete':
return {}

components = {}
for with_response in [True, False]:
serializer = self.get_serializer(path, method, for_response=with_response)
if not isinstance(serializer, Serializer):
return {}
component_name = self.get_component_name(serializer)
content = self.map_serializer(serializer)
components[component_name] = content

for enum_name, enum in self.enums.items():
components[enum_name] = enum

return components

def get_serializer(self, path, method, for_response=True):
raise NotImplementedError

def map_choicefield(self, field):
# The only way to retrieve the original enum is to compare choices
for declared_enum in ChoiceEnum.__subclasses__():
if OrderedDict(declared_enum.choices()) == field.choices:
self.enums[declared_enum.__name__] = super().map_choicefield(field)
return {
'$ref': "#/components/responses/{}".format(declared_enum.__name__)
}
return super().map_choicefield(field)
58 changes: 15 additions & 43 deletions api/views/doctorate.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,58 +26,24 @@
from rest_framework import mixins, status
from rest_framework.generics import GenericAPIView, ListCreateAPIView
from rest_framework.response import Response
from rest_framework.schemas.openapi import AutoSchema
from rest_framework.serializers import Serializer

from admission.api.generator import DetailedAutoSchema
from admission.contrib import serializers
from backoffice.settings.rest_framework.common_views import DisplayExceptionsByFieldNameAPIMixin
from ddd.logic.admission.preparation.projet_doctoral.commands import (
CompleterPropositionCommand, GetPropositionCommand,
InitierPropositionCommand,
SearchPropositionsCommand,
)
from ddd.logic.admission.preparation.projet_doctoral.domain.validator.exceptions import (
BureauCDEInconsistantException,
ContratTravailInconsistantException,
InstitutionInconsistanteException,
JustificationRequiseException,
)
from infrastructure.messages_bus import message_bus_instance


class DetailedAutoSchema(AutoSchema):
def get_request_body(self, path, method):
if method not in ('PUT', 'PATCH', 'POST'):
return {}

self.request_media_types = self.map_parsers(path, method)

serializer = self.get_serializer(path, method, for_response=False)

if not isinstance(serializer, Serializer):
item_schema = {}
else:
item_schema = self._get_reference(serializer)

return {
'content': {
ct: {'schema': item_schema}
for ct in self.request_media_types
}
}

def get_components(self, path, method):
if method.lower() == 'delete':
return {}

components = {}
for with_response in [True, False]:
serializer = self.get_serializer(path, method, for_response=with_response)
if not isinstance(serializer, Serializer):
return {}
component_name = self.get_component_name(serializer)
content = self.map_serializer(serializer)
components[component_name] = content

return components

def get_serializer(self, path, method, for_response=True):
raise NotImplementedError


class PropositionListSchema(DetailedAutoSchema):
def get_operation_id_base(self, path, method, action):
return '_proposition' if method == 'POST' else '_propositions'
Expand All @@ -90,10 +56,16 @@ def get_serializer(self, path, method, for_response=True):
return serializers.PropositionSearchDTOSerializer()


class PropositionListViewSet(ListCreateAPIView):
class PropositionListViewSet(DisplayExceptionsByFieldNameAPIMixin, ListCreateAPIView):
schema = PropositionListSchema()
pagination_class = None
filter_backends = None
field_name_by_exception = {
JustificationRequiseException: ['justification'],
InstitutionInconsistanteException: ['institution'],
ContratTravailInconsistantException: ['type_contrat_travail'],
BureauCDEInconsistantException: ['bureau_cde'],
}

def list(self, request, **kwargs):
proposition_list = message_bus_instance.invoke(
Expand Down
Loading

0 comments on commit 869d49c

Please sign in to comment.