Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api views from ddd #10

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,7 @@


class DoctorateAdmissionAdmin(admin.ModelAdmin):
def save_form(self, request, form, change):
"""
Set the author if the admission doctorate is being created
"""
admission_doctorate = form.save(commit=False)
if not change:
admission_doctorate.author = request.user.person
return admission_doctorate
pass


admin.site.register(DoctorateAdmission, DoctorateAdmissionAdmin)
248 changes: 248 additions & 0 deletions api/generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
# ##############################################################################
#
# OSIS stands for Open Student Information System. It's an application
# designed to manage the core business of higher education institutions,
# such as universities, faculties, institutes and professional schools.
# The core business involves the administration of students, teachers,
# courses, programs and so on.
#
# Copyright (C) 2015-2021 Université catholique de Louvain (http://www.uclouvain.be)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# A copy of this license - GNU General Public License - is available
# at the root of the source code of this program. If not,
# see http://www.gnu.org/licenses/.
#
# ##############################################################################
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"] = {
"name": "UCLouvain - OSIS",
"url": "https://github.com/uclouvain/osis"
}
schema["servers"] = [
{
"url": "https://{environment}.osis.uclouvain.be/api/v1/admission/",
"variables": {
"environment": {
"default": "dev",
"enum": [
"dev",
"qa",
"test"
]
}
}
},
{
"url": "https://osis.uclouvain.be/api/v1/admission/",
"description": "Production server"
}
]
schema["security"] = [{"Token": []}]
schema['components']["securitySchemes"] = {
"Token": {
"type": "apiKey",
"in": "header",
"name": "Authorization",
"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)
14 changes: 8 additions & 6 deletions api/urls_v1.py → api/url_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
# see http://www.gnu.org/licenses/.
#
# ##############################################################################
from django.urls import path

from rest_framework.routers import DefaultRouter

from admission.api.views import DoctorateAdmissionViewSet
from admission.api import views

app_name = "admission_api_v1"
router = DefaultRouter()
router.register(r'', DoctorateAdmissionViewSet, basename='doctorate')
urlpatterns = router.urls
urlpatterns = [
path('propositions', views.PropositionListViewSet.as_view()),
path('propositions/<uuid:uuid>', views.PropositionViewSet.as_view()),
path('autocomplete/sector', views.AutocompleteSectorViewSet.as_view()),
path('autocomplete/sector/<str:sigle>/doctorates', views.AutocompleteDoctoratViewSet.as_view()),
]
8 changes: 6 additions & 2 deletions api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@
#
# ##############################################################################

from .doctorate import DoctorateAdmissionViewSet
from admission.api.views.doctorate import *
from admission.api.views.autocomplete import *

__all__ = [
"DoctorateAdmissionViewSet",
"PropositionViewSet",
"PropositionListViewSet",
"AutocompleteDoctoratViewSet",
"AutocompleteSectorViewSet",
]
65 changes: 65 additions & 0 deletions api/views/autocomplete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# ##############################################################################
#
# OSIS stands for Open Student Information System. It's an application
# designed to manage the core business of higher education institutions,
# such as universities, faculties, institutes and professional schools.
# The core business involves the administration of students, teachers,
# courses, programs and so on.
#
# Copyright (C) 2015-2021 Université catholique de Louvain (http://www.uclouvain.be)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# A copy of this license - GNU General Public License - is available
# at the root of the source code of this program. If not,
# see http://www.gnu.org/licenses/.
#
# ##############################################################################
from django.db.models import F
from rest_framework.generics import ListAPIView
from rest_framework.response import Response

from admission.contrib import serializers
from admission.contrib.models import EntityProxy
from base.models.enums.entity_type import SECTOR
from ddd.logic.admission.preparation.projet_doctoral.commands import SearchDoctoratCommand
from infrastructure.messages_bus import message_bus_instance


class AutocompleteSectorViewSet(ListAPIView):
"""Autocomplete sectors"""
pagination_class = None
filter_backends = None
serializer_class = serializers.SectorDTOSerializer

def list(self, request, **kwargs):
# TODO revert to command once it's in the shared kernel
qs = EntityProxy.objects.with_acronym().with_title().with_type().filter(type=SECTOR).annotate(
sigle=F('acronym'),
intitule_fr=F('title'),
intitule_en=F('title'),
).values('sigle', 'intitule_fr', 'intitule_en')
serializer = serializers.SectorDTOSerializer(instance=qs, many=True)
return Response(serializer.data)


class AutocompleteDoctoratViewSet(ListAPIView):
"""Autocomplete doctorates given a sector"""
pagination_class = None
filter_backends = None
serializer_class = serializers.DoctoratDTOSerializer

def list(self, request, **kwargs):
doctorat_list = message_bus_instance.invoke(
SearchDoctoratCommand(sigle_secteur_entite_gestion=kwargs.get('sigle'))
)
serializer = serializers.DoctoratDTOSerializer(instance=doctorat_list, many=True)
return Response(serializer.data)
Loading