Skip to content
Merged
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
11 changes: 11 additions & 0 deletions app/admin_api/serializers/event/event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from core.const.serializer import COMMON_ADMIN_FIELDS
from core.serializer.base_abstract_serializer import BaseAbstractSerializer
from core.serializer.json_schema_serializer import JsonSchemaSerializer
from event.models import Event
from rest_framework import serializers


class EventAdminSerializer(BaseAbstractSerializer, JsonSchemaSerializer, serializers.ModelSerializer):
class Meta:
model = Event
fields = COMMON_ADMIN_FIELDS + ("organization", "name_ko", "name_en")
29 changes: 29 additions & 0 deletions app/admin_api/serializers/event/presentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from core.const.serializer import COMMON_ADMIN_FIELDS
from core.serializer.base_abstract_serializer import BaseAbstractSerializer
from core.serializer.json_schema_serializer import JsonSchemaSerializer
from event.presentation.models import Presentation, PresentationCategory, PresentationSpeaker, PresentationType
from rest_framework import serializers


class PresentationTypeAdminSerializer(BaseAbstractSerializer, JsonSchemaSerializer, serializers.ModelSerializer):
class Meta:
model = PresentationType
fields = COMMON_ADMIN_FIELDS + ("event", "name_ko", "name_en")


class PresentationCategoryAdminSerializer(BaseAbstractSerializer, JsonSchemaSerializer, serializers.ModelSerializer):
class Meta:
model = PresentationCategory
fields = COMMON_ADMIN_FIELDS + ("type", "name_ko", "name_en")


class PresentationAdminSerializer(BaseAbstractSerializer, JsonSchemaSerializer, serializers.ModelSerializer):
class Meta:
model = Presentation
fields = COMMON_ADMIN_FIELDS + ("title_ko", "title_en")


class PresentationSpeakerAdminSerializer(BaseAbstractSerializer, JsonSchemaSerializer, serializers.ModelSerializer):
class Meta:
model = PresentationSpeaker
fields = COMMON_ADMIN_FIELDS + ("presentation", "user", "biography_ko", "biography_en")
17 changes: 17 additions & 0 deletions app/admin_api/serializers/event/sponsor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from core.const.serializer import COMMON_ADMIN_FIELDS
from core.serializer.base_abstract_serializer import BaseAbstractSerializer
from core.serializer.json_schema_serializer import JsonSchemaSerializer
from event.sponsor.models import Sponsor, SponsorTier
from rest_framework import serializers


class SponsorTierAdminSerializer(BaseAbstractSerializer, JsonSchemaSerializer, serializers.ModelSerializer):
class Meta:
model = SponsorTier
fields = COMMON_ADMIN_FIELDS + ("event", "name_ko", "name_en", "order")


class SponsorAdminSerializer(BaseAbstractSerializer, JsonSchemaSerializer, serializers.ModelSerializer):
class Meta:
model = Sponsor
fields = COMMON_ADMIN_FIELDS + ("event", "logo", "page", "name_ko", "name_en")
60 changes: 58 additions & 2 deletions app/admin_api/serializers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,28 @@
from user.models import UserExt


class UserAdminSerializer(JsonSchemaSerializer, ReadOnlyModelSerializer, serializers.ModelSerializer):
class UserAdminSerializer(JsonSchemaSerializer, serializers.ModelSerializer):
str_repr = serializers.CharField(source="__str__", read_only=True)

class Meta:
model = UserExt
fields = ("id", "username", "email", "first_name", "last_name", "is_staff", "is_active", "date_joined")
fields = (
"id",
"is_active",
"username",
"nickname_ko",
"nickname_en",
"email",
"is_superuser",
"str_repr",
"date_joined",
"last_login",
)
extra_kwargs = {
"id": {"read_only": True},
"date_joined": {"read_only": True},
"last_login": {"read_only": True},
}


class UserAdminSignInSerializerData(typing.TypedDict):
Expand All @@ -35,4 +53,42 @@ def validate(self, attrs: UserAdminSignInSerializerData) -> UserAdminSignInSeria
if not (self.user and self.user.check_password(attrs["password"])):
raise serializers.ValidationError("User not found or inactive or wrong password.")

if not self.user.is_superuser:
raise serializers.PermissionDenied("Only permissioned users can sign in using this route.")

return attrs


class UserAdminPasswordChangeSerializerData(typing.TypedDict):
old_password: str
new_password: str
new_password_confirm: str


class UserAdminPasswordChangeSerializer(JsonSchemaSerializer, ReadOnlyModelSerializer):
old_password = serializers.CharField(write_only=True, required=True)
new_password = serializers.CharField(write_only=True, required=True)
new_password_confirm = serializers.CharField(write_only=True, required=True)

class Meta:
model = UserExt
fields = ("old_password", "new_password", "new_password_confirm")

def validate(self, attrs: UserAdminPasswordChangeSerializerData) -> UserAdminPasswordChangeSerializerData:
user: UserExt = self.instance
if not user.check_password(attrs["old_password"]):
raise serializers.ValidationError("Old password is incorrect.")

if attrs["old_password"] == attrs["new_password"]:
raise serializers.ValidationError("New password cannot be the same as the old password.")

if attrs["new_password"] != attrs["new_password_confirm"]:
raise serializers.ValidationError("New password and confirmation do not match.")

return attrs

def save(self, **kwargs: typing.Any) -> UserExt:
user: UserExt = self.instance
user.set_password(self.validated_data["new_password"])
user.save(update_fields=["password"])
return user
19 changes: 19 additions & 0 deletions app/admin_api/urls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
from admin_api.views.cms import PageAdminViewSet, SitemapAdminViewSet
from admin_api.views.event.event import EventAdminViewSet
from admin_api.views.event.presentation import (
PresentationAdminViewSet,
PresentationSpeakerAdminViewSet,
PresentationTypeAdminViewSet,
)
from admin_api.views.event.sponsor import SponsorAdminViewSet, SponsorTierAdminViewSet
from admin_api.views.file import PublicFileAdminViewSet
from admin_api.views.user import UserAdminViewSet
from django.urls import include, path
Expand All @@ -14,8 +21,20 @@
admin_file_router = routers.SimpleRouter()
admin_file_router.register("publicfile", PublicFileAdminViewSet, basename="admin-public-file")

admin_event_router = routers.SimpleRouter()
admin_event_router.register("event", EventAdminViewSet)
admin_event_router.register("sponsortier", SponsorTierAdminViewSet)
admin_event_router.register("sponsor", SponsorAdminViewSet)
admin_event_router.register("presentationtype", PresentationTypeAdminViewSet)
admin_event_router.register("presentation", PresentationAdminViewSet)
admin_event_router.register(
"presentation/(?P<presentation_id>{UUID_V4_PATTERN})/speaker",
PresentationSpeakerAdminViewSet,
)

urlpatterns = [
path("cms/", include(admin_cms_router.urls)),
path("file/", include(admin_file_router.urls)),
path("user/", include(admin_user_router.urls)),
path("event/", include(admin_event_router.urls)),
]
19 changes: 19 additions & 0 deletions app/admin_api/views/event/event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from __future__ import annotations

from admin_api.serializers.event.event import EventAdminSerializer
from core.const.tag import OpenAPITag
from core.permissions import IsSuperUser
from core.viewset.json_schema_viewset import JsonSchemaViewSet
from drf_spectacular.utils import extend_schema, extend_schema_view
from event.models import Event
from rest_framework import viewsets

ADMIN_METHODS = ["list", "retrieve", "create", "update", "partial_update", "destroy"]


@extend_schema_view(**{m: extend_schema(tags=[OpenAPITag.ADMIN_EVENT_EVENT]) for m in ADMIN_METHODS})
class EventAdminViewSet(JsonSchemaViewSet, viewsets.ModelViewSet):
http_method_names = ["get", "post", "patch", "delete"]
serializer_class = EventAdminSerializer
permission_classes = [IsSuperUser]
queryset = Event.objects.filter_active().select_related("created_by", "updated_by", "deleted_by")
122 changes: 122 additions & 0 deletions app/admin_api/views/event/presentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from __future__ import annotations

from admin_api.serializers.event.presentation import (
PresentationAdminSerializer,
PresentationCategoryAdminSerializer,
PresentationSpeakerAdminSerializer,
PresentationTypeAdminSerializer,
)
from core.const.regex import UUID_V4_PATTERN
from core.const.tag import OpenAPITag
from core.permissions import IsSuperUser
from core.viewset.json_schema_viewset import JsonSchemaViewSet
from django.db import models
from drf_spectacular.utils import extend_schema, extend_schema_view
from event.presentation.models import (
Presentation,
PresentationCategory,
PresentationCategoryRelation,
PresentationSpeaker,
PresentationType,
)
from rest_framework import decorators, exceptions, request, response, status, viewsets

ADMIN_METHODS = ["list", "retrieve", "create", "update", "partial_update", "destroy"]


@extend_schema_view(**{m: extend_schema(tags=[OpenAPITag.ADMIN_EVENT_PRESENTATION]) for m in ADMIN_METHODS})
class PresentationTypeAdminViewSet(JsonSchemaViewSet, viewsets.ModelViewSet):
http_method_names = ["get", "post", "patch", "delete"]
serializer_class = PresentationTypeAdminSerializer
permission_classes = [IsSuperUser]
queryset = PresentationType.objects.filter_active().select_related("created_by", "updated_by", "deleted_by")

@extend_schema(
tags=[OpenAPITag.ADMIN_EVENT_PRESENTATION],
responses={status.HTTP_200_OK: PresentationCategoryAdminSerializer(many=True)},
)
@decorators.action(detail=True, methods=["get"], url_path="categories")
def list_categories(self, *args: tuple, **kwargs: dict) -> response.Response:
categories = PresentationCategory.objects.filter_active().filter(type=self.get_object())
return response.Response(data=PresentationCategoryAdminSerializer(instance=categories, many=True).data)

@extend_schema(
tags=[OpenAPITag.ADMIN_EVENT_PRESENTATION],
request=PresentationCategoryAdminSerializer,
responses={status.HTTP_201_CREATED: PresentationCategoryAdminSerializer},
)
@decorators.action(detail=True, methods=["post"], url_path="categories")
def add_category(self, request: request.Request, *args: tuple, **kwargs: dict) -> response.Response:
serializer = PresentationCategoryAdminSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return response.Response(data=serializer.data, status=status.HTTP_201_CREATED)

@extend_schema(
tags=[OpenAPITag.ADMIN_EVENT_PRESENTATION],
request=PresentationCategoryAdminSerializer,
responses={status.HTTP_200_OK: PresentationCategoryAdminSerializer},
)
@decorators.action(detail=True, methods=["patch"], url_path=f"categories/(?P<category_id>{UUID_V4_PATTERN})")
def update_category(self, request: request.Request, pk: str, category_id: str, *args, **kwargs):
if not (category := PresentationCategory.objects.filter_active().filter(type_id=pk, id=category_id).first()):
raise exceptions.NotFound(detail="Category not found.")

serializer = PresentationCategoryAdminSerializer(instance=category, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return response.Response(data=serializer.data)

@extend_schema(tags=[OpenAPITag.ADMIN_EVENT_PRESENTATION], responses={status.HTTP_204_NO_CONTENT: None})
@decorators.action(detail=True, methods=["delete"], url_path=f"categories/(?P<category_id>{UUID_V4_PATTERN})")
def delete_category(self, pk: str, category_id: str, *args: tuple, **kwargs: dict) -> response.Response:
if not (category := PresentationCategory.objects.filter_active().filter(type_id=pk, id=category_id).first()):
raise exceptions.NotFound(detail="Category not found.")

category.delete()
return response.Response(status=status.HTTP_204_NO_CONTENT)


@extend_schema_view(**{m: extend_schema(tags=[OpenAPITag.ADMIN_EVENT_PRESENTATION]) for m in ADMIN_METHODS})
class PresentationAdminViewSet(JsonSchemaViewSet, viewsets.ModelViewSet):
http_method_names = ["get", "post", "patch", "delete"]
serializer_class = PresentationAdminSerializer
permission_classes = [IsSuperUser]
queryset = Presentation.objects.get_all_nested_data().select_related("created_by", "updated_by", "deleted_by")

@extend_schema(
tags=[OpenAPITag.ADMIN_EVENT_PRESENTATION],
responses={status.HTTP_200_OK: PresentationCategoryAdminSerializer(many=True)},
)
@decorators.action(detail=True, methods=["get"], url_path="categories")
def list_categories(self, *args: tuple, **kwargs: dict) -> response.Response:
categories = self.get_object().active_categories
return response.Response(data=PresentationCategoryAdminSerializer(instance=categories, many=True).data)

@extend_schema(tags=[OpenAPITag.ADMIN_EVENT_PRESENTATION], responses={status.HTTP_201_CREATED: None})
@decorators.action(detail=True, methods=["post"], url_path="categories/(?P<category_id>{UUID_V4_PATTERN})")
def add_category(self, pk: str, category_id: str, *args: tuple, **kwargs: dict) -> response.Response:
PresentationCategoryRelation.objects.get_or_create(presentation_id=pk, category_id=category_id)
return response.Response(status=status.HTTP_201_CREATED)

@extend_schema(tags=[OpenAPITag.ADMIN_EVENT_PRESENTATION], responses={status.HTTP_204_NO_CONTENT: None})
@decorators.action(detail=True, methods=["delete"], url_path="categories/(?P<category_id>{UUID_V4_PATTERN})")
def remove_category(self, pk: str, category_id: str, *args: tuple, **kwargs: dict) -> response.Response:
if not (
relation := PresentationCategoryRelation.objects.filter(presentation_id=pk, category_id=category_id).first()
):
raise exceptions.NotFound(detail="Category is not associated with this presentation.")

relation.delete()
return response.Response(status=status.HTTP_204_NO_CONTENT)


@extend_schema_view(**{m: extend_schema(tags=[OpenAPITag.ADMIN_EVENT_PRESENTATION]) for m in ADMIN_METHODS})
class PresentationSpeakerAdminViewSet(JsonSchemaViewSet, viewsets.ModelViewSet):
http_method_names = ["get", "post", "patch", "delete"]
serializer_class = PresentationSpeakerAdminSerializer
permission_classes = [IsSuperUser]
queryset = PresentationSpeaker.objects.filter_active().select_related("created_by", "updated_by", "deleted_by")

def get_queryset(self) -> models.QuerySet[PresentationSpeaker]:
return super().get_queryset().filter(presentation_id=self.kwargs["presentation_id"])
78 changes: 78 additions & 0 deletions app/admin_api/views/event/sponsor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from __future__ import annotations

from admin_api.serializers.event.sponsor import SponsorAdminSerializer, SponsorTierAdminSerializer
from core.const.regex import UUID_V4_PATTERN
from core.const.tag import OpenAPITag
from core.permissions import IsSuperUser
from core.viewset.json_schema_viewset import JsonSchemaViewSet
from django.db import models
from drf_spectacular.utils import extend_schema, extend_schema_view
from event.sponsor.models import Sponsor, SponsorTier, SponsorTierSponsorRelation
from rest_framework import decorators, exceptions, response, status, viewsets

ADMIN_METHODS = ["list", "retrieve", "create", "update", "partial_update", "destroy"]


@extend_schema_view(**{m: extend_schema(tags=[OpenAPITag.ADMIN_EVENT_SPONSOR]) for m in ADMIN_METHODS})
class SponsorTierAdminViewSet(JsonSchemaViewSet, viewsets.ModelViewSet):
http_method_names = ["get", "post", "patch", "delete"]
serializer_class = SponsorTierAdminSerializer
permission_classes = [IsSuperUser]
queryset = (
SponsorTier.objects.filter_active()
.prefetch_related(
models.Prefetch(
lookup="sponsors",
queryset=Sponsor.objects.filter_active().select_related("created_by", "updated_by", "deleted_by"),
to_attr="_prefetched_active_sponsors",
),
)
.select_related("created_by", "updated_by", "deleted_by")
)

@extend_schema(
tags=[OpenAPITag.ADMIN_EVENT_SPONSOR],
responses={status.HTTP_200_OK: SponsorAdminSerializer(many=True)},
)
@decorators.action(detail=True, methods=["get"], url_path="sponsors")
def list_sponsors(self, *args: tuple, **kwargs: dict) -> response.Response:
tier: SponsorTier = self.get_object()
return response.Response(data=SponsorAdminSerializer(instance=tier.active_sponsors, many=True).data)

@extend_schema(tags=[OpenAPITag.ADMIN_EVENT_SPONSOR], responses={status.HTTP_201_CREATED: SponsorAdminSerializer})
@decorators.action(detail=True, methods=["post"], url_path=f"sponsors/(?P<sponsor_id>{UUID_V4_PATTERN})")
def add_sponsor(self, sponsor_id: str, *args: tuple, **kwargs: dict) -> response.Response:
tier: SponsorTier = self.get_object()
if not (sponsor := Sponsor.objects.filter_active().filter(id=sponsor_id).first()):
raise exceptions.NotFound(detail="Sponsor not found")

SponsorTierSponsorRelation.objects.get_or_create(tier=tier, sponsor=sponsor)
return response.Response(data=SponsorAdminSerializer(instance=sponsor).data, status=status.HTTP_201_CREATED)

@extend_schema(tags=[OpenAPITag.ADMIN_EVENT_SPONSOR], responses={status.HTTP_204_NO_CONTENT: None})
@decorators.action(detail=True, methods=["delete"], url_path=f"sponsors/(?P<sponsor_id>{UUID_V4_PATTERN})")
def remove_sponsor(self, pk: str, sponsor_id: str, *args: tuple, **kwargs: dict) -> response.Response:
if not (relation := SponsorTierSponsorRelation.objects.filter(tier_id=pk, sponsor_id=sponsor_id).first()):
raise exceptions.NotFound(detail="Sponsor is not associated with this tier")

relation.delete()
return response.Response(status=status.HTTP_204_NO_CONTENT)


@extend_schema_view(**{m: extend_schema(tags=[OpenAPITag.ADMIN_EVENT_SPONSOR]) for m in ADMIN_METHODS})
class SponsorAdminViewSet(JsonSchemaViewSet, viewsets.ModelViewSet):
http_method_names = ["get", "post", "patch", "delete"]
serializer_class = SponsorAdminSerializer
permission_classes = [IsSuperUser]
queryset = Sponsor.objects.filter_active().select_related("created_by", "updated_by", "deleted_by")

@extend_schema(
tags=[OpenAPITag.ADMIN_EVENT_SPONSOR],
responses={status.HTTP_200_OK: SponsorTierAdminSerializer(many=True)},
)
@decorators.action(detail=True, methods=["get"], url_path="tiers")
def list_tiers(self, *args: tuple, **kwargs: dict) -> response.Response:
sponsor: Sponsor = self.get_object()
tier_id_qs = SponsorTierSponsorRelation.objects.filter(sponsor=sponsor).values_list("tier_id", flat=True)
tiers = SponsorTier.objects.filter_active().filter(id__in=tier_id_qs)
return response.Response(data=SponsorTierAdminSerializer(instance=tiers, many=True).data)
Loading