From 06bbee981f96314498eb8aafbf474fd0ba8f66ff Mon Sep 17 00:00:00 2001 From: MU-Software Date: Thu, 29 Aug 2024 00:03:31 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=202024=EB=85=84=20=EC=84=B8=EC=85=98?= =?UTF-8?q?=EC=9D=80=20pretalx=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyconkr/external_apis/pretalx/client.py | 57 +++++++++++++++++ pyconkr/external_apis/pretalx/serializers.py | 66 ++++++++++++++++++++ pyconkr/settings.py | 11 +++- session/viewsets.py | 28 ++++++++- 4 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 pyconkr/external_apis/pretalx/client.py create mode 100644 pyconkr/external_apis/pretalx/serializers.py diff --git a/pyconkr/external_apis/pretalx/client.py b/pyconkr/external_apis/pretalx/client.py new file mode 100644 index 0000000..89a8b45 --- /dev/null +++ b/pyconkr/external_apis/pretalx/client.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +import logging +import traceback +import typing +import urllib.parse + +import requests +from django.conf import settings + +from .serializers import PretalxPaginatedSessionSerializer + +logger = logging.getLogger(__name__) + +RequestMethodType = typing.Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"] + + +class PretalxException(Exception): + pass + + +class PretalxClient: + DEFAULT_TIMEOUT = 5 + + def _request(self, method: RequestMethodType, endpoint: str, *args: tuple, **kwargs: dict) -> requests.Response: + url = urllib.parse.urljoin(settings.PRETALX.API_URL, endpoint) + request_default_headers = { + "Authorization": f"Token {settings.PRETALX.API_KEY}", + "Content-Type": "application/json", + } | kwargs.pop("headers", {}) + request_default_kwargs = { + "headers": request_default_headers, + "timeout": self.DEFAULT_TIMEOUT, + } | kwargs + + try: + return requests.request(method, url, *args, **request_default_kwargs) + except Exception as e: + logger.error(traceback.format_exception(e)) + raise PretalxException("Pretalx API 요청에 실패했습니다.") from e + + def retrieve_sessions(self, event_name: str, only_confirmed: bool = True) -> dict: + """세션 목록 조회""" + endpoint = f"api/events/{event_name}/submissions" + ("?state=confirmed" if only_confirmed else "") + + try: + result = self._request("GET", endpoint) + result.raise_for_status() + + parsed_result = PretalxPaginatedSessionSerializer(data=result.json()) + parsed_result.is_valid(raise_exception=True) + return parsed_result.validated_data + except Exception as e: + raise PretalxException("세션 목록을 가져오지 못했습니다, 잠시 후에 다시 시도해주세요.") from e + + +pretalx_client = PretalxClient() diff --git a/pyconkr/external_apis/pretalx/serializers.py b/pyconkr/external_apis/pretalx/serializers.py new file mode 100644 index 0000000..187580a --- /dev/null +++ b/pyconkr/external_apis/pretalx/serializers.py @@ -0,0 +1,66 @@ +from rest_framework import serializers + + +class PretalxSpeakerSerializer(serializers.Serializer): + code = serializers.CharField() + name = serializers.CharField() + biography = serializers.CharField() + avatar = serializers.CharField() + + +class PretalxSlotSerializer(serializers.Serializer): + start = serializers.DateTimeField() + end = serializers.DateTimeField() + room = serializers.CharField() + room_id = serializers.IntegerField() + + +class PretalxQuestionSerializer(serializers.Serializer): + id = serializers.IntegerField() + question = serializers.DictField() + required = serializers.BooleanField() + target = serializers.CharField() + options = serializers.ListField() + + +class PretalxAnswerSerializer(serializers.Serializer): + id = serializers.IntegerField() + question = PretalxQuestionSerializer() + answer = serializers.CharField() + answer_file = serializers.CharField() + submission = serializers.CharField() + person = serializers.CharField() + options = serializers.ListField() + + +class PretalxSessionSerializer(serializers.Serializer): + code = serializers.CharField() + submission_type = serializers.CharField() + submission_type_id = serializers.IntegerField() + state = serializers.CharField() + + image = serializers.CharField() + title = serializers.CharField() + abstract = serializers.CharField() + description = serializers.CharField() + notes = serializers.CharField() + internal_notes = serializers.CharField() + content_locale = serializers.CharField() + + slot = PretalxSlotSerializer() + duration = serializers.IntegerField() + do_not_record = serializers.BooleanField() + is_featured = serializers.BooleanField() + + speakers = PretalxSpeakerSerializer(many=True) + answers = PretalxAnswerSerializer(many=True) + + tags = serializers.ListField() + tag_ids = serializers.ListField() + + +class PretalxPaginatedSessionSerializer(serializers.Serializer): + count = serializers.IntegerField() + next = serializers.CharField() + previous = serializers.CharField() + results = PretalxSessionSerializer(many=True) diff --git a/pyconkr/settings.py b/pyconkr/settings.py index 5baf2ca..d7ad50c 100644 --- a/pyconkr/settings.py +++ b/pyconkr/settings.py @@ -9,9 +9,9 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.1/ref/settings/ """ - import os import pathlib +import types # Build paths inside the project like this: BASE_DIR / "subdir". BASE_DIR = pathlib.Path(__file__).resolve().parent.parent @@ -246,3 +246,12 @@ LOGIN_URL = "/accounts/login/" AWS_QUERYSTRING_AUTH = False + +# External APIs +PRETALX = types.SimpleNamespace( + API_URL=os.getenv("PRETALX_API_URL", "https://pretalx.com"), + API_KEY=os.getenv("PRETALX_API_KEY", ""), + EVENT_NAME={ + "2024": os.getenv("PRETALX_EVENT_TITLE_2024", "pyconkr2024"), + }, +) diff --git a/session/viewsets.py b/session/viewsets.py index 7d2ed3e..95fada2 100644 --- a/session/viewsets.py +++ b/session/viewsets.py @@ -1,6 +1,10 @@ +from django.conf import settings +from drf_spectacular.utils import OpenApiExample, OpenApiResponse, extend_schema from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework.viewsets import ModelViewSet +from pyconkr.external_apis.pretalx.client import pretalx_client +from pyconkr.external_apis.pretalx.serializers import PretalxSessionSerializer from session.models import Session from session.serializers import SessionListSerializer, SessionSerializer @@ -13,8 +17,28 @@ class SessionViewSet(ModelViewSet): def get_serializer_class(self): if self.action == "list": return SessionListSerializer - else: - return SessionSerializer + return SessionSerializer def get_queryset(self): return super().get_queryset().filter(category__year=self.request.version) + + @extend_schema( + examples={ + 200: OpenApiResponse( + response=str, + examples=[ + OpenApiExample(name="2023년 세션 목록", value=SessionListSerializer(many=True)), + OpenApiExample(name="2024년 이후 세션 목록 (Pretalx)", value=PretalxSessionSerializer(many=True)), + ], + ), + }, + ) + def list(self, request, *args, **kwargs): + if request.version == 2023 or request.version not in settings.PRETALX.EVENT_NAME: + return super().list(request, *args, **kwargs) + + pretalx_event_name = settings.PRETALX.EVENT_NAME[request.version] + return pretalx_client.retrieve_sessions( + event_name=pretalx_event_name, + only_confirmed=settings.DEBUG, + )["results"]