diff --git a/apps/global_notice/__init__.py b/apps/global_notice/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/global_notice/admin.py b/apps/global_notice/admin.py new file mode 100644 index 00000000..ebd77bef --- /dev/null +++ b/apps/global_notice/admin.py @@ -0,0 +1,15 @@ +from django.contrib import admin + +from ara.classes.admin import MetaDataModelAdmin + +from .models import GlobalNotice + + +@admin.register(GlobalNotice) +class GlobalNoticeAdmin(MetaDataModelAdmin): + list_display = ("ko_title", "en_title", "started_at", "expired_at") + fields = ( + ("ko_title", "ko_content"), + ("en_title", "en_content"), + ("started_at", "expired_at"), + ) diff --git a/apps/global_notice/apps.py b/apps/global_notice/apps.py new file mode 100644 index 00000000..67d2d120 --- /dev/null +++ b/apps/global_notice/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class GlobalNoticeConfig(AppConfig): + name = "apps.global_notice" diff --git a/apps/global_notice/migrations/0001_initial.py b/apps/global_notice/migrations/0001_initial.py new file mode 100644 index 00000000..03f5e2bf --- /dev/null +++ b/apps/global_notice/migrations/0001_initial.py @@ -0,0 +1,65 @@ +# Generated by Django 4.2.5 on 2024-01-02 14:41 + +import datetime + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="GlobalNotice", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created_at", + models.DateTimeField( + db_index=True, + default=django.utils.timezone.now, + verbose_name="생성 시간", + ), + ), + ( + "updated_at", + models.DateTimeField( + auto_now=True, db_index=True, verbose_name="수정 시간" + ), + ), + ( + "deleted_at", + models.DateTimeField( + db_index=True, + default=datetime.datetime( + 1, 1, 1, 0, 0, tzinfo=datetime.timezone.utc + ), + verbose_name="삭제 시간", + ), + ), + ("ko_title", models.CharField(max_length=256, verbose_name="한글 제목")), + ("en_title", models.CharField(max_length=256, verbose_name="영문 제목")), + ("ko_content", models.TextField(verbose_name="한글 본문")), + ("en_content", models.TextField(verbose_name="영문 본문")), + ("started_at", models.DateTimeField(verbose_name="모달 노출 시작 시간")), + ("expired_at", models.DateTimeField(verbose_name="모달 노출 종료 시간")), + ], + options={ + "verbose_name": "글로벌 공지", + "verbose_name_plural": "글로벌 공지 목록", + "ordering": ("-created_at",), + "abstract": False, + }, + ), + ] diff --git a/apps/global_notice/migrations/0002_alter_globalnotice_id.py b/apps/global_notice/migrations/0002_alter_globalnotice_id.py new file mode 100644 index 00000000..3029a4a5 --- /dev/null +++ b/apps/global_notice/migrations/0002_alter_globalnotice_id.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.3 on 2024-01-05 07:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("global_notice", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="globalnotice", + name="id", + field=models.AutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ] diff --git a/apps/global_notice/migrations/__init__.py b/apps/global_notice/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/global_notice/models.py b/apps/global_notice/models.py new file mode 100644 index 00000000..3d494af9 --- /dev/null +++ b/apps/global_notice/models.py @@ -0,0 +1,16 @@ +from django.db import models + +from ara.db.models import MetaDataModel + + +class GlobalNotice(MetaDataModel): + ko_title = models.CharField(verbose_name="한글 제목", max_length=256) + en_title = models.CharField(verbose_name="영문 제목", max_length=256) + ko_content = models.TextField(verbose_name="한글 본문") + en_content = models.TextField(verbose_name="영문 본문") + started_at = models.DateTimeField(verbose_name="모달 노출 시작 시간") + expired_at = models.DateTimeField(verbose_name="모달 노출 종료 시간") + + class Meta(MetaDataModel.Meta): + verbose_name = "글로벌 공지" + verbose_name_plural = "글로벌 공지 목록" diff --git a/apps/global_notice/serializers.py b/apps/global_notice/serializers.py new file mode 100644 index 00000000..82915d4d --- /dev/null +++ b/apps/global_notice/serializers.py @@ -0,0 +1,17 @@ +from ara.classes.serializers.meta_data import MetaDataModelSerializer + +from .models import GlobalNotice + + +class GlobalNoticeSerializer(MetaDataModelSerializer): + class Meta: + model = GlobalNotice + fields = [ + "id", + "ko_title", + "en_title", + "ko_content", + "en_content", + "started_at", + "expired_at", + ] diff --git a/apps/global_notice/urls.py b/apps/global_notice/urls.py new file mode 100644 index 00000000..536835d9 --- /dev/null +++ b/apps/global_notice/urls.py @@ -0,0 +1,12 @@ +from django.urls import include, path +from rest_framework.routers import DefaultRouter + +from .views import GlobalNoticeViewSet + +router = DefaultRouter() +router.register("", GlobalNoticeViewSet, basename="global_notices") + + +urlpatterns = [ + path("api/global_notices/", include(router.urls)), +] diff --git a/apps/global_notice/views.py b/apps/global_notice/views.py new file mode 100644 index 00000000..a732f5cb --- /dev/null +++ b/apps/global_notice/views.py @@ -0,0 +1,14 @@ +from django.utils import timezone +from rest_framework import viewsets + +from .models import GlobalNotice +from .serializers import GlobalNoticeSerializer + + +class GlobalNoticeViewSet(viewsets.ReadOnlyModelViewSet): + queryset = GlobalNotice.objects.filter( + started_at__lte=timezone.now(), + expired_at__gte=timezone.now(), + ) + serializer_class = GlobalNoticeSerializer + pagination_class = None diff --git a/ara/settings/django.py b/ara/settings/django.py index 86792c29..9d6b4340 100644 --- a/ara/settings/django.py +++ b/ara/settings/django.py @@ -30,6 +30,7 @@ "django_filters", "apps.core", "apps.user", + "apps.global_notice", "apps.calendar", ] diff --git a/ara/urls.py b/ara/urls.py index 2c8a2e94..2341a60e 100644 --- a/ara/urls.py +++ b/ara/urls.py @@ -26,6 +26,7 @@ path("api/admin/", admin.site.urls), path("", include(("apps.core.urls", "core"))), path("", include(("apps.user.urls", "user"))), + path("", include(("apps.global_notice.urls", "global_notice"))), path("", include(("apps.calendar.urls", "calendar"))), path("api/schema/", SpectacularAPIView.as_view(), name="schema"), path( diff --git a/tests/test_global_notice.py b/tests/test_global_notice.py new file mode 100644 index 00000000..f222aa78 --- /dev/null +++ b/tests/test_global_notice.py @@ -0,0 +1,81 @@ +import pytest +from django.utils import timezone +from rest_framework import status + +from apps.global_notice.models import GlobalNotice +from tests.conftest import RequestSetting, TestCase + + +@pytest.mark.usefixtures("set_user_client") +class TestGlobalNotice(TestCase, RequestSetting): + N = 50 + + def test_list(self): + """ + 비로그인도 허용 + """ + for i in range(self.N): + GlobalNotice.objects.create( + ko_title=f"글로벌 공지 제목 {i}", + en_title=f"global_notice title {i}", + ko_content=f"글로벌 공지 본문 {i}", + en_content=f"global_notice content {i}", + started_at=timezone.now() - timezone.timedelta(days=1), + expired_at=timezone.now() + timezone.timedelta(days=1), + ) + res = self.http_request(self.user, "get", "global_notices") + assert len(res.data) == self.N + + res = self.http_request(None, "get", "global_notices") + assert len(res.data) == self.N + + def test_get(self): + """ + 비로그인도 허용 + """ + global_notice = GlobalNotice.objects.create( + ko_title="글로벌 공지 제목", + en_title="global_notice title", + ko_content="글로벌 공지 본문", + en_content="global_notice content", + started_at=timezone.now() - timezone.timedelta(days=1), + expired_at=timezone.now() + timezone.timedelta(days=1), + ) + res = self.http_request(self.user, "get", f"global_notices/{global_notice.id}") + assert res.data["ko_title"] == global_notice.ko_title + assert res.data["en_title"] == global_notice.en_title + + res = self.http_request(None, "get", f"global_notices/{global_notice.id}") + assert res.data["ko_title"] == global_notice.ko_title + assert res.data["en_title"] == global_notice.en_title + + def test_filter(self): + """ + expired, not started 필터링 테스트 + """ + global_notice_expired = GlobalNotice.objects.create( + ko_title="글로벌 공지 제목", + en_title="global_notice title", + ko_content="글로벌 공지 본문", + en_content="global_notice content", + started_at=timezone.now() - timezone.timedelta(days=2), + expired_at=timezone.now() - timezone.timedelta(days=1), + ) + global_notice_not_started = GlobalNotice.objects.create( + ko_title="글로벌 공지 제목", + en_title="global_notice title", + ko_content="글로벌 공지 본문", + en_content="global_notice content", + started_at=timezone.now() + timezone.timedelta(days=1), + expired_at=timezone.now() + timezone.timedelta(days=2), + ) + + res = self.http_request( + self.user, "get", f"global_notices/{global_notice_expired.id}" + ) + assert res.status_code == status.HTTP_404_NOT_FOUND + + res = self.http_request( + self.user, "get", f"global_notices/{global_notice_not_started.id}" + ) + assert res.status_code == status.HTTP_404_NOT_FOUND