Skip to content
Open
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
26 changes: 26 additions & 0 deletions app/event/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from django.db.models import Q
from django_filters import rest_framework as filters
from django_filters.constants import EMPTY_VALUES
from event.models import Event


class EventFilterMixin(filters.FilterSet):
event = filters.CharFilter(method="filter_by_event_name")
event_field_prefix = "event"

def filter_by_event_name(self, queryset, name, value):
if value in EMPTY_VALUES:
return queryset

prefix = self.event_field_prefix
return queryset.filter(Q(**{f"{prefix}__name_ko": value}) | Q(**{f"{prefix}__name_en": value}))

def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)

if self.data.get("event") in EMPTY_VALUES:
latest = Event.objects.filter_active().first()
if latest:
queryset = queryset.filter(**{self.event_field_prefix: latest})

return queryset
16 changes: 16 additions & 0 deletions app/event/presentation/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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


class PresentationFilterSet(EventFilterMixin):
event_field_prefix = "type__event"
types = filters.BaseCSVFilter(method="filter_by_type_names")

def filter_by_type_names(self, queryset: BaseAbstractModelQuerySet, name: str, values: list[str]) -> Q:
if values in EMPTY_VALUES:
return queryset

return queryset.filter(Q(type__name_ko__in=values) | Q(type__name_en__in=values))
48 changes: 32 additions & 16 deletions app/event/presentation/test/api_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import http
import urllib.parse
from datetime import datetime

import pytest
from django.urls import reverse
Expand All @@ -22,18 +23,20 @@ def test_presentation_api(api_client: APIClient, create_presentation_set: Presen
def test_presentation_event_type_filter_api(api_client: APIClient):
# Given: 행사 2개에 각각 2 종류의 발표 유형이 있고, 각 발표 유형마다 1개의 발표가 있음.
organization = Organization.objects.create(name="Test Organization")
event_1: Event = Event.objects.create(organization=organization, name="Test Event 1")
event_2: Event = Event.objects.create(organization=organization, name="Test Event 2")
event_1: Event = Event.objects.create(
organization=organization, name="Test Event 1", event_start_at=datetime(2025, 8, 1)
)
event_2: Event = Event.objects.create(
organization=organization, name="Test Event 2", event_start_at=datetime(2026, 8, 1)
)

event_1_prst_type_1 = PresentationType.objects.create(event=event_1, name="Type 1")
event_1_prst_type_2 = PresentationType.objects.create(event=event_1, name="Type 2")
event_2_prst_type_1 = PresentationType.objects.create(event=event_2, name="Type 1")
event_2_prst_type_2 = PresentationType.objects.create(event=event_2, name="Type 2")
PresentationType.objects.create(event=event_2, name="Type 1")
PresentationType.objects.create(event=event_2, name="Type 2")

event_1_prst_type_1_prst = Presentation.objects.create(type=event_1_prst_type_1, title="Presentation 1")
event_1_prst_type_2_prst = Presentation.objects.create(type=event_1_prst_type_2, title="Presentation 2")
event_2_prst_type_1_prst = Presentation.objects.create(type=event_2_prst_type_1, title="Presentation 3")
Presentation.objects.create(type=event_2_prst_type_2, title="Presentation 4")

# When: API 요청을 통해 행사 1의 발표 유형 1과 2에 해당하는 발표를 요청할 시
qs = urllib.parse.urlencode(
Expand All @@ -51,16 +54,29 @@ def test_presentation_event_type_filter_api(api_client: APIClient):
str(event_1_prst_type_2_prst.id),
}

# When: API 요청을 통해 행사 유형은 지정하지 않고 유형 1에 해당하는 발표를 요청할 시
qs = urllib.parse.urlencode({"types": event_1_prst_type_1.name})
response = api_client.get(f"{reverse('v1:presentation-list')}?{qs}")

# Then: 행사 1의 발표 유형 1과 행사 2의 발표 유형 1에 해당하는 발표가 반환되어야 함.
assert response.status_code == http.HTTPStatus.OK
@pytest.mark.django_db
def test_presentation_defaults_to_latest_event(api_client: APIClient):
# Given: 2개의 행사가 있고, 각각 발표가 있음.
organization = Organization.objects.create(name="Test Organization")
old_event = Event.objects.create(
organization=organization, name="PyCon Korea 2025", event_start_at=datetime(2025, 8, 1)
)
new_event = Event.objects.create(
organization=organization, name="PyCon Korea 2026", event_start_at=datetime(2026, 8, 1)
)

old_type = PresentationType.objects.create(event=old_event, name="Talk")
new_type = PresentationType.objects.create(event=new_event, name="Talk")

Presentation.objects.create(type=old_type, title="Old Presentation")
new_prst = Presentation.objects.create(type=new_type, title="New Presentation")

# When: event 파라미터 없이 요청
response = api_client.get(reverse("v1:presentation-list"))

# Then: 최신 행사(2026)의 발표만 반환
assert response.status_code == http.HTTPStatus.OK
response_data = response.json()
assert len(response_data) == 2, "Should return presentations for type 1 across all events"
assert {datum["id"] for datum in response_data} == {
str(event_1_prst_type_1_prst.id),
str(event_2_prst_type_1_prst.id),
}
assert len(response_data) == 1
assert response_data[0]["id"] == str(new_prst.id)
22 changes: 1 addition & 21 deletions app/event/presentation/views.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,12 @@
from core.const.tag import OpenAPITag
from core.models import BaseAbstractModelQuerySet
from django.db.models import Q
from django.utils.decorators import method_decorator
from django_filters import rest_framework as filters
from django_filters.constants import EMPTY_VALUES
from drf_spectacular.utils import extend_schema
from event.presentation.filters import PresentationFilterSet
from event.presentation.models import Presentation, PresentationCategory
from event.presentation.serializers import PresentationSerializer
from rest_framework import mixins, viewsets


class PresentationFilterSet(filters.FilterSet):
event = filters.CharFilter(method="filter_by_event_name")
types = filters.BaseCSVFilter(method="filter_by_type_names")

def filter_by_event_name(self, queryset: BaseAbstractModelQuerySet, name: str, value: str) -> Q:
if value in EMPTY_VALUES:
return queryset

return queryset.filter(Q(type__event__name_ko=value) | Q(type__event__name_en=value))

def filter_by_type_names(self, queryset: BaseAbstractModelQuerySet, name: str, values: list[str]) -> Q:
if values in EMPTY_VALUES:
return queryset

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


@method_decorator(name="list", decorator=extend_schema(tags=[OpenAPITag.EVENT_PRESENTATION]))
class PresentationCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
queryset = PresentationCategory.objects.filter_active()
Expand Down
5 changes: 5 additions & 0 deletions app/event/sponsor/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from event.filters import EventFilterMixin


class SponsorTierFilterSet(EventFilterMixin):
event_field_prefix = "event"
Empty file.
90 changes: 90 additions & 0 deletions app/event/sponsor/test/api_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import http
from datetime import datetime

import pytest
from django.urls import reverse
from event.models import Event
from event.sponsor.models import Sponsor, SponsorTier, SponsorTierSponsorRelation
from file.models import PublicFile
from rest_framework.test import APIClient
from user.models.organization import Organization


@pytest.fixture
def api_client():
return APIClient()


@pytest.fixture
def two_events():
organization = Organization.objects.create(name="Test Organization")
old_event = Event.objects.create(
organization=organization, name="PyCon Korea 2025", event_start_at=datetime(2025, 8, 1)
)
new_event = Event.objects.create(
organization=organization, name="PyCon Korea 2026", event_start_at=datetime(2026, 8, 1)
)
return old_event, new_event


def _make_sponsor(event, name, tier):
logo = PublicFile.objects.create(
file=f"public/{name}.png",
mimetype="image/png",
hash=name,
size=0,
)
sponsor = Sponsor.objects.create(event=event, name=name, logo=logo)
SponsorTierSponsorRelation.objects.create(tier=tier, sponsor=sponsor)
return sponsor


@pytest.mark.django_db
def test_sponsor_defaults_to_latest_event(api_client: APIClient, two_events):
old_event, new_event = two_events

# Given: 각 행사에 후원 등급과 후원사가 있음
old_tier = SponsorTier.objects.create(event=old_event, name="Gold", order=0)
new_tier = SponsorTier.objects.create(event=new_event, name="Gold", order=0)

_make_sponsor(old_event, "Old Sponsor", old_tier)
_make_sponsor(new_event, "New Sponsor", new_tier)

# When: event 파라미터 없이 요청
response = api_client.get(reverse("v1:sponsor-list"))

# Then: 최신 행사(2026)의 후원 등급만 반환
assert response.status_code == http.HTTPStatus.OK
response_data = response.json()
assert len(response_data) == 1
assert response_data[0]["id"] == str(new_tier.id)


@pytest.mark.django_db
def test_sponsor_filter_by_event_name(api_client: APIClient, two_events):
old_event, new_event = two_events

old_tier = SponsorTier.objects.create(event=old_event, name="Gold", order=0)
new_tier = SponsorTier.objects.create(event=new_event, name="Gold", order=0)

_make_sponsor(old_event, "Old Sponsor", old_tier)
_make_sponsor(new_event, "New Sponsor", new_tier)

# When: 2025 행사를 명시적으로 지정
response = api_client.get(reverse("v1:sponsor-list"), {"event": "PyCon Korea 2025"})

# Then: 2025 행사의 후원 등급만 반환
assert response.status_code == http.HTTPStatus.OK
response_data = response.json()
assert len(response_data) == 1
assert response_data[0]["id"] == str(old_tier.id)


@pytest.mark.django_db
def test_sponsor_no_events_returns_empty(api_client: APIClient):
# When: 이벤트가 없을 때 요청
response = api_client.get(reverse("v1:sponsor-list"))

# Then: 빈 응답
assert response.status_code == http.HTTPStatus.OK
assert response.json() == []
2 changes: 2 additions & 0 deletions app/event/sponsor/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.db import models
from django.utils.decorators import method_decorator
from drf_spectacular.utils import extend_schema
from event.sponsor.filters import SponsorTierFilterSet
from event.sponsor.models import Sponsor, SponsorTier
from event.sponsor.serializers import SponsorTierSerializer
from rest_framework import mixins, viewsets
Expand All @@ -17,3 +18,4 @@ class SponsorTierViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
)
)
serializer_class = SponsorTierSerializer
filterset_class = SponsorTierFilterSet
Loading