Skip to content

Commit 833c91c

Browse files
authored
Merge pull request #56 from pythonkr/feature/add-my-schedule-timetable
feat: 세션 시간표 담기 API 작성 및 테스트 로직 추가
2 parents f96289b + 0a53920 commit 833c91c

9 files changed

Lines changed: 827 additions & 22 deletions

File tree

app/core/const/tag.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ class OpenAPITag:
33
EVENT = "Event"
44
EVENT_PRESENTATION = "Event > Presentation"
55
EVENT_SPONSOR = "Event > Sponsor"
6+
EVENT_PRESENTATION_BOOKMARK = "Event > Presentation Bookmark"
67

78
SHOP_USER = "Shop > 고객"
89
SHOP_PRODUCT = "Shop > 상품"

app/core/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@
2323
from django.urls import include, path, re_path, resolvers
2424
from django.views.decorators.cache import cache_page
2525
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
26+
from event.presentation.urls import bookmark_urlpatterns
2627

2728
# type: ignore[assignment]
2829
v1_apis: list[resolvers.URLPattern | resolvers.URLResolver] = [
2930
path("cms/", include("cms.urls")),
3031
path("admin-api/", include("admin_api.urls")),
3132
path("participant-portal/", include("participant_portal_api.urls")),
3233
path("event/presentation/", include("event.presentation.urls")),
34+
path("events/<uuid:event_id>/presentation-bookmarks/", include(bookmark_urlpatterns)),
3335
path("event/sponsor/", include("event.sponsor.urls")),
3436
path("event/", include("event.urls")),
3537
path("external-api/", include("external_api.urls")),

app/event/presentation/filters.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1+
from enum import StrEnum
2+
13
from core.models import BaseAbstractModelQuerySet
24
from django.db.models import Q
35
from django_filters import rest_framework as filters
46
from django_filters.constants import EMPTY_VALUES
57
from event.filters import EventFilterMixin
8+
from event.presentation.models import PresentationBookmark
9+
from rest_framework import exceptions
10+
11+
12+
class PresentationBookmarkErrorCode(StrEnum):
13+
EVENT_NOT_FOUND = "event_not_found"
614

715

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

1624
return queryset.filter(Q(type__name_ko__in=values) | Q(type__name_en__in=values))
25+
26+
27+
class PresentationBookmarkFilterSet(filters.FilterSet):
28+
event = filters.UUIDFilter(method="filter_by_event")
29+
30+
class Meta:
31+
model = PresentationBookmark
32+
fields = ["event"]
33+
34+
def filter_by_event(
35+
self, queryset: BaseAbstractModelQuerySet, name: str, values: list[str]
36+
) -> BaseAbstractModelQuerySet:
37+
filtered_queryset = queryset.filter(presentation__type__event__id=values)
38+
if not filtered_queryset.exists():
39+
raise exceptions.NotFound(
40+
detail="해당 행사 정보가 없습니다.",
41+
code=PresentationBookmarkErrorCode.EVENT_NOT_FOUND,
42+
)
43+
return filtered_queryset
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Generated by Django 6.0.6 on 2026-07-11 08:37
2+
3+
import uuid
4+
5+
import django.db.models.deletion
6+
from django.conf import settings
7+
from django.db import migrations, models
8+
9+
10+
class Migration(migrations.Migration):
11+
dependencies = [
12+
("presentation", "0016_room_order"),
13+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14+
]
15+
16+
operations = [
17+
migrations.CreateModel(
18+
name="PresentationBookmark",
19+
fields=[
20+
(
21+
"id",
22+
models.UUIDField(
23+
default=uuid.uuid4,
24+
editable=False,
25+
primary_key=True,
26+
serialize=False,
27+
),
28+
),
29+
("created_at", models.DateTimeField(auto_now_add=True)),
30+
(
31+
"presentation",
32+
models.ForeignKey(
33+
on_delete=django.db.models.deletion.CASCADE,
34+
related_name="bookmarks",
35+
to="presentation.presentation",
36+
),
37+
),
38+
(
39+
"user",
40+
models.ForeignKey(
41+
on_delete=django.db.models.deletion.CASCADE,
42+
related_name="presentation_bookmarks",
43+
to=settings.AUTH_USER_MODEL,
44+
),
45+
),
46+
],
47+
options={
48+
"constraints": [
49+
models.UniqueConstraint(
50+
fields=("user", "presentation"),
51+
name="uq__prst_bkmk__user__presentation",
52+
)
53+
],
54+
},
55+
),
56+
]

app/event/presentation/models.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,21 @@ class RoomSchedule(BaseAbstractModel):
210210

211211
def __str__(self) -> str:
212212
return f"[{self.room}] {self.start_at} - {self.end_at} ({self.presentation})"
213+
214+
215+
class PresentationBookmark(models.Model):
216+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
217+
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="presentation_bookmarks")
218+
presentation = models.ForeignKey(Presentation, on_delete=models.CASCADE, related_name="bookmarks")
219+
created_at = models.DateTimeField(auto_now_add=True)
220+
221+
class Meta:
222+
constraints = [
223+
models.UniqueConstraint(
224+
fields=["user", "presentation"],
225+
name="uq__prst_bkmk__user__presentation",
226+
)
227+
]
228+
229+
def __str__(self) -> str:
230+
return f"[Bookmark] {self.user} - {self.presentation}"

app/event/presentation/serializers.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,39 @@
1+
from typing import Any
2+
13
from event.presentation.models import (
24
CallForPresentationSchedule,
35
Presentation,
6+
PresentationBookmark,
47
PresentationCategory,
58
PresentationSpeaker,
69
PresentationType,
710
RoomSchedule,
811
)
912
from event.serializers import EventSerializer
10-
from rest_framework import serializers
13+
from rest_framework import exceptions, serializers
14+
15+
16+
class NotFoundPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
17+
def fail(self, key, **kwargs):
18+
if key == "does_not_exist":
19+
raise exceptions.NotFound("해당 세션 정보가 없습니다.")
20+
super().fail(key, **kwargs)
21+
22+
23+
class PresentationBookmarkRequestSerializer(serializers.Serializer):
24+
presentation = NotFoundPrimaryKeyRelatedField(queryset=Presentation.objects.filter(deleted_at__isnull=True))
25+
26+
def create(self, validated_data: Any) -> tuple[PresentationBookmark, bool]:
27+
validated_data["user"] = self.context["request"].user
28+
return PresentationBookmark.objects.get_or_create(**validated_data)
29+
30+
31+
class PresentationBookmarkListResponseSerializer(serializers.Serializer):
32+
presentation_ids = serializers.ListField(child=serializers.UUIDField())
33+
34+
35+
class PresentationBookmarkResponseSerializer(serializers.Serializer):
36+
presentation_id = serializers.UUIDField()
1137

1238

1339
class PresentationTypeSerializer(serializers.ModelSerializer):

0 commit comments

Comments
 (0)