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
1 change: 1 addition & 0 deletions app/core/const/tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class OpenAPITag:
EVENT = "Event"
EVENT_PRESENTATION = "Event > Presentation"
EVENT_SPONSOR = "Event > Sponsor"
EVENT_PRESENTATION_BOOKMARK = "Event > Presentation Bookmark"

SHOP_USER = "Shop > 고객"
SHOP_PRODUCT = "Shop > 상품"
Expand Down
2 changes: 2 additions & 0 deletions app/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
from django.urls import include, path, re_path, resolvers
from django.views.decorators.cache import cache_page
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
from event.presentation.urls import bookmark_urlpatterns

# type: ignore[assignment]
v1_apis: list[resolvers.URLPattern | resolvers.URLResolver] = [
path("cms/", include("cms.urls")),
path("admin-api/", include("admin_api.urls")),
path("participant-portal/", include("participant_portal_api.urls")),
path("event/presentation/", include("event.presentation.urls")),
path("events/<uuid:event_id>/presentation-bookmarks/", include(bookmark_urlpatterns)),
path("event/sponsor/", include("event.sponsor.urls")),
path("event/", include("event.urls")),
path("external-api/", include("external_api.urls")),
Expand Down
27 changes: 27 additions & 0 deletions app/event/presentation/filters.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
from enum import StrEnum

from core.models import BaseAbstractModelQuerySet
from django.db.models import Q
from django_filters import rest_framework as filters
from django_filters.constants import EMPTY_VALUES
from event.filters import EventFilterMixin
from event.presentation.models import PresentationBookmark
from rest_framework import exceptions


class PresentationBookmarkErrorCode(StrEnum):
EVENT_NOT_FOUND = "event_not_found"


class PresentationFilterSet(EventFilterMixin):
Expand All @@ -14,3 +22,22 @@ def filter_by_type_names(self, queryset: BaseAbstractModelQuerySet, name: str, v
return queryset

return queryset.filter(Q(type__name_ko__in=values) | Q(type__name_en__in=values))


class PresentationBookmarkFilterSet(filters.FilterSet):
event = filters.UUIDFilter(method="filter_by_event")

class Meta:
model = PresentationBookmark
fields = ["event"]

def filter_by_event(
self, queryset: BaseAbstractModelQuerySet, name: str, values: list[str]
) -> BaseAbstractModelQuerySet:
filtered_queryset = queryset.filter(presentation__type__event__id=values)
if not filtered_queryset.exists():
raise exceptions.NotFound(
detail="해당 행사 정보가 없습니다.",
code=PresentationBookmarkErrorCode.EVENT_NOT_FOUND,
)
return filtered_queryset
56 changes: 56 additions & 0 deletions app/event/presentation/migrations/0017_presentationbookmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Generated by Django 6.0.6 on 2026-07-11 08:37

import uuid

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("presentation", "0016_room_order"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name="PresentationBookmark",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
(
"presentation",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="bookmarks",
to="presentation.presentation",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="presentation_bookmarks",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"constraints": [
models.UniqueConstraint(
fields=("user", "presentation"),
name="uq__prst_bkmk__user__presentation",
)
],
},
),
]
18 changes: 18 additions & 0 deletions app/event/presentation/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,21 @@ class RoomSchedule(BaseAbstractModel):

def __str__(self) -> str:
return f"[{self.room}] {self.start_at} - {self.end_at} ({self.presentation})"


class PresentationBookmark(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="presentation_bookmarks")
presentation = models.ForeignKey(Presentation, on_delete=models.CASCADE, related_name="bookmarks")
created_at = models.DateTimeField(auto_now_add=True)

class Meta:
constraints = [
models.UniqueConstraint(
fields=["user", "presentation"],
name="uq__prst_bkmk__user__presentation",
)
]

def __str__(self) -> str:
return f"[Bookmark] {self.user} - {self.presentation}"
28 changes: 27 additions & 1 deletion app/event/presentation/serializers.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,39 @@
from typing import Any

from event.presentation.models import (
CallForPresentationSchedule,
Presentation,
PresentationBookmark,
PresentationCategory,
PresentationSpeaker,
PresentationType,
RoomSchedule,
)
from event.serializers import EventSerializer
from rest_framework import serializers
from rest_framework import exceptions, serializers


class NotFoundPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
def fail(self, key, **kwargs):
if key == "does_not_exist":
raise exceptions.NotFound("해당 세션 정보가 없습니다.")
super().fail(key, **kwargs)


class PresentationBookmarkRequestSerializer(serializers.Serializer):
presentation = NotFoundPrimaryKeyRelatedField(queryset=Presentation.objects.filter(deleted_at__isnull=True))

def create(self, validated_data: Any) -> tuple[PresentationBookmark, bool]:
validated_data["user"] = self.context["request"].user
return PresentationBookmark.objects.get_or_create(**validated_data)


class PresentationBookmarkListResponseSerializer(serializers.Serializer):
presentation_ids = serializers.ListField(child=serializers.UUIDField())


class PresentationBookmarkResponseSerializer(serializers.Serializer):
presentation_id = serializers.UUIDField()


class PresentationTypeSerializer(serializers.ModelSerializer):
Expand Down
Loading
Loading