diff --git a/app/event/presentation/migrations/0008_callforpresentationschedule_room_roomschedule.py b/app/event/presentation/migrations/0008_callforpresentationschedule_room_roomschedule.py new file mode 100644 index 0000000..af4512b --- /dev/null +++ b/app/event/presentation/migrations/0008_callforpresentationschedule_room_roomschedule.py @@ -0,0 +1,174 @@ +# Generated by Django 5.2 on 2025-07-06 14:47 + +import uuid + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("event", "0003_alter_event_name_alter_event_name_en_and_more"), + ("presentation", "0007_historicalpresentation_summary_and_more"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="CallForPresentationSchedule", + fields=[ + ("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("start_at", models.DateTimeField()), + ("end_at", models.DateTimeField()), + ( + "created_by", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_created_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "deleted_by", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_deleted_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "next_call_for_presentation_schedule", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="presentation.callforpresentationschedule", + ), + ), + ( + "presentation", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="call_for_presentation_schedules", + to="presentation.presentation", + ), + ), + ( + "presentation_type", + models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to="presentation.presentationtype"), + ), + ( + "updated_by", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_updated_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="Room", + fields=[ + ("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("name", models.CharField(max_length=256)), + ( + "created_by", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_created_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "deleted_by", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_deleted_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ("event", models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to="event.event")), + ( + "updated_by", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_updated_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="RoomSchedule", + fields=[ + ("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("start_at", models.DateTimeField()), + ("end_at", models.DateTimeField()), + ( + "created_by", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_created_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "deleted_by", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_deleted_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "presentation", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="presentation.presentation", + ), + ), + ("room", models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to="presentation.room")), + ( + "updated_by", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_updated_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/app/event/presentation/models.py b/app/event/presentation/models.py index e8f07ce..a619633 100644 --- a/app/event/presentation/models.py +++ b/app/event/presentation/models.py @@ -11,17 +11,31 @@ class PresentationQuerySet(BaseAbstractModelQuerySet): def get_all_nested_data(self): - return self.filter_active().prefetch_related( - models.Prefetch( - lookup="categories", - queryset=PresentationCategory.objects.filter_active(), - to_attr="_prefetched_active_categories", - ), - models.Prefetch( - lookup="speakers", - queryset=PresentationSpeaker.objects.filter_active().select_related("user"), - to_attr="_prefetched_active_speakers", - ), + return ( + self.filter_active() + .prefetch_related( + models.Prefetch( + lookup="categories", + queryset=PresentationCategory.objects.filter_active(), + to_attr="_prefetched_active_categories", + ), + models.Prefetch( + lookup="speakers", + queryset=PresentationSpeaker.objects.filter_active().select_related("user", "image"), + to_attr="_prefetched_active_speakers", + ), + models.Prefetch( + lookup="roomschedule_set", + queryset=RoomSchedule.objects.filter_active().select_related("room", "room__event"), + to_attr="_prefetched_active_room_schedules", + ), + models.Prefetch( + lookup="call_for_presentation_schedules", + queryset=CallForPresentationSchedule.objects.filter_active().select_related("presentation_type"), + to_attr="_prefetched_active_call_for_presentation_schedules", + ), + ) + .select_related("image") ) @@ -83,3 +97,25 @@ class PresentationSpeaker(BaseAbstractModel): user = models.ForeignKey(User, on_delete=models.PROTECT) image = models.ForeignKey(PublicFile, on_delete=models.PROTECT, null=True, blank=True) biography = MarkdownField(blank=True, default="") + + +class CallForPresentationSchedule(BaseAbstractModel): + presentation_type = models.ForeignKey(PresentationType, on_delete=models.PROTECT) + presentation = models.ForeignKey( + Presentation, on_delete=models.PROTECT, related_name="call_for_presentation_schedules", null=True, blank=True + ) + start_at = models.DateTimeField() + end_at = models.DateTimeField() + next_call_for_presentation_schedule = models.ForeignKey("self", on_delete=models.SET_NULL, null=True, blank=True) + + +class Room(BaseAbstractModel): + event = models.ForeignKey(Event, on_delete=models.PROTECT) + name = models.CharField(max_length=256) + + +class RoomSchedule(BaseAbstractModel): + room = models.ForeignKey(Room, on_delete=models.PROTECT) + start_at = models.DateTimeField() + end_at = models.DateTimeField() + presentation = models.ForeignKey(Presentation, on_delete=models.PROTECT, null=True, blank=True) diff --git a/app/event/presentation/serializers.py b/app/event/presentation/serializers.py index 03ddc9e..f88ac2e 100644 --- a/app/event/presentation/serializers.py +++ b/app/event/presentation/serializers.py @@ -1,4 +1,10 @@ -from event.presentation.models import Presentation, PresentationCategory, PresentationSpeaker +from event.presentation.models import ( + CallForPresentationSchedule, + Presentation, + PresentationCategory, + PresentationSpeaker, + RoomSchedule, +) from rest_framework import serializers @@ -17,11 +23,40 @@ class Meta: fields = ("id", "nickname", "biography", "image") +class RoomScheduleSerializer(serializers.ModelSerializer): + room_name = serializers.CharField(source="room.name", read_only=True) + event_id = serializers.IntegerField(source="room.event.id", read_only=True) + event_name = serializers.CharField(source="room.event.name", read_only=True) + + class Meta: + model = RoomSchedule + fields = ("id", "room_name", "event_id", "event_name", "start_at", "end_at") + + +class CallForPresentationScheduleSerializer(serializers.ModelSerializer): + presentation_type_name = serializers.CharField(source="presentation_type.name", read_only=True) + + class Meta: + model = CallForPresentationSchedule + fields = ("id", "presentation_type_name", "start_at", "end_at", "next_call_for_presentation_schedule") + + class PresentationSerializer(serializers.ModelSerializer): image = serializers.FileField(source="image.file", read_only=True, allow_null=True) categories = PresentationCategorySerializer(many=True, read_only=True) speakers = PresentationSpeakerSerializer(many=True, read_only=True) + room_schedules = RoomScheduleSerializer(source="room_schedules_set", many=True, read_only=True) + call_for_presentation_schedules = CallForPresentationScheduleSerializer(many=True, read_only=True) class Meta: model = Presentation - fields = ("id", "title", "description", "image", "categories", "speakers") + fields = ( + "id", + "title", + "description", + "image", + "categories", + "speakers", + "room_schedules", + "call_for_presentation_schedules", + ) diff --git a/app/event/presentation/test/conftest.py b/app/event/presentation/test/conftest.py index b694037..96d21fb 100644 --- a/app/event/presentation/test/conftest.py +++ b/app/event/presentation/test/conftest.py @@ -1,13 +1,17 @@ import dataclasses +from datetime import datetime, timedelta import pytest from event.models import Event from event.presentation.models import ( + CallForPresentationSchedule, Presentation, PresentationCategory, PresentationCategoryRelation, PresentationSpeaker, PresentationType, + Room, + RoomSchedule, ) from faker import Faker from model_bakery import baker @@ -26,6 +30,9 @@ class PresentationTestEntity: presentation_category: PresentationCategory presentation_category_relation: PresentationCategoryRelation presentation_speaker: PresentationSpeaker + room: Room + room_schedule: RoomSchedule + call_for_presentation_schedule: CallForPresentationSchedule @pytest.fixture @@ -47,6 +54,8 @@ def create_event(create_user_with_organization_and_relation): def create_presentation_set(create_event): fake = Faker() user, organization, relation, event = create_event + + # 기존 데이터 생성 presentation_type = baker.make(PresentationType, event=event) presentation = baker.make(Presentation, type=presentation_type) presentation_category = baker.make(PresentationCategory, type=presentation_type, name=fake.name()) @@ -55,6 +64,22 @@ def create_presentation_set(create_event): ) presentation_speaker = baker.make(PresentationSpeaker, presentation=presentation, user=user) + # Room과 RoomSchedule 데이터 생성 + room = baker.make(Room, event=event, name=fake.company()) + start_time = datetime.now() + room_schedule = baker.make( + RoomSchedule, room=room, presentation=presentation, start_at=start_time, end_at=start_time + timedelta(hours=1) + ) + + # CallForPresentationSchedule 데이터 생성 + cfp_start = start_time - timedelta(days=30) + call_for_presentation_schedule = baker.make( + CallForPresentationSchedule, + presentation_type=presentation_type, + start_at=cfp_start, + end_at=cfp_start + timedelta(days=14), + ) + return PresentationTestEntity( user=user, organization=organization, @@ -64,6 +89,9 @@ def create_presentation_set(create_event): presentation_category=presentation_category, presentation_category_relation=presentation_category_relation, presentation_speaker=presentation_speaker, + room=room, + room_schedule=room_schedule, + call_for_presentation_schedule=call_for_presentation_schedule, ) diff --git a/app/event/presentation/test/count_queries_test.py b/app/event/presentation/test/count_queries_test.py index 666575c..ae191d5 100644 --- a/app/event/presentation/test/count_queries_test.py +++ b/app/event/presentation/test/count_queries_test.py @@ -10,6 +10,6 @@ def test_count_queries( django_assert_max_num_queries: DjangoAssertNumQueries, create_presentation_set: PresentationTestEntity ): reset_queries() - with django_assert_max_num_queries(3): + with django_assert_max_num_queries(5): queryset = Presentation.objects.get_all_nested_data() list(queryset) # query evaluation