From ce0448241e68e8dad30a4adb4bc364f9b6185350 Mon Sep 17 00:00:00 2001 From: ddungiii Date: Thu, 2 Nov 2023 14:13:29 +0000 Subject: [PATCH 01/15] feat(globalNotice): init global_notice app --- apps/global_notice/__init__.py | 0 apps/global_notice/admin.py | 3 +++ apps/global_notice/apps.py | 6 ++++++ apps/global_notice/migrations/__init__.py | 0 apps/global_notice/models.py | 9 +++++++++ apps/global_notice/tests.py | 3 +++ apps/global_notice/urls.py | 3 +++ apps/global_notice/views.py | 3 +++ apps/global_notice/views/__init__.py | 0 apps/global_notice/views/router.py | 7 +++++++ 10 files changed, 34 insertions(+) create mode 100644 apps/global_notice/__init__.py create mode 100644 apps/global_notice/admin.py create mode 100644 apps/global_notice/apps.py create mode 100644 apps/global_notice/migrations/__init__.py create mode 100644 apps/global_notice/models.py create mode 100644 apps/global_notice/tests.py create mode 100644 apps/global_notice/urls.py create mode 100644 apps/global_notice/views.py create mode 100644 apps/global_notice/views/__init__.py create mode 100644 apps/global_notice/views/router.py 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..8c38f3f3 --- /dev/null +++ b/apps/global_notice/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/global_notice/apps.py b/apps/global_notice/apps.py new file mode 100644 index 00000000..530a1802 --- /dev/null +++ b/apps/global_notice/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ModalConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "modal" 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..8952e9c9 --- /dev/null +++ b/apps/global_notice/models.py @@ -0,0 +1,9 @@ +from django.db import models + +from ara.db.models import MetaDataModel + + +class GlobalNotice(MetaDataModel): + title = models.CharField() + content = models.TextField() + expired_at = models.DateTimeField() diff --git a/apps/global_notice/tests.py b/apps/global_notice/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/apps/global_notice/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/global_notice/urls.py b/apps/global_notice/urls.py new file mode 100644 index 00000000..5826b601 --- /dev/null +++ b/apps/global_notice/urls.py @@ -0,0 +1,3 @@ +from django.urls import include, path + +urlpatterns = [] diff --git a/apps/global_notice/views.py b/apps/global_notice/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/apps/global_notice/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/apps/global_notice/views/__init__.py b/apps/global_notice/views/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/global_notice/views/router.py b/apps/global_notice/views/router.py new file mode 100644 index 00000000..96494f50 --- /dev/null +++ b/apps/global_notice/views/router.py @@ -0,0 +1,7 @@ +from rest_framework import routers + +router = routers.DefaultRouter() + +router.register( + prefix=r"notice", +) From 3b1060a1a0fe05a83542d22209ca2ceb3f2f409b Mon Sep 17 00:00:00 2001 From: ddungiii Date: Thu, 9 Nov 2023 11:06:59 +0000 Subject: [PATCH 02/15] feat(global_notice) Add routing urls --- apps/global_notice/apps.py | 4 ++-- apps/global_notice/models.py | 10 +++++++--- apps/global_notice/urls.py | 4 +++- apps/global_notice/views.py | 6 ++++-- apps/global_notice/views/__init__.py | 0 apps/global_notice/views/router.py | 7 ------- ara/settings/django.py | 1 + ara/urls.py | 1 + 8 files changed, 18 insertions(+), 15 deletions(-) delete mode 100644 apps/global_notice/views/__init__.py delete mode 100644 apps/global_notice/views/router.py diff --git a/apps/global_notice/apps.py b/apps/global_notice/apps.py index 530a1802..17d4ea6d 100644 --- a/apps/global_notice/apps.py +++ b/apps/global_notice/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -class ModalConfig(AppConfig): +class GlobalNoticeConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" - name = "modal" + name = "apps.global_notice" diff --git a/apps/global_notice/models.py b/apps/global_notice/models.py index 8952e9c9..1a8cbe2d 100644 --- a/apps/global_notice/models.py +++ b/apps/global_notice/models.py @@ -4,6 +4,10 @@ class GlobalNotice(MetaDataModel): - title = models.CharField() - content = models.TextField() - expired_at = models.DateTimeField() + class Meta(MetaDataModel.Meta): + verbose_name = "글로벌 공지" + + title = models.CharField(verbose_name="제목", max_length=256) + content = models.TextField(verbose_name="본문") + started_at = models.DateTimeField(verbose_name="모달 노출 시작 시간") + expired_at = models.DateTimeField(verbose_name="모달 노출 종료 시간") diff --git a/apps/global_notice/urls.py b/apps/global_notice/urls.py index 5826b601..95014578 100644 --- a/apps/global_notice/urls.py +++ b/apps/global_notice/urls.py @@ -1,3 +1,5 @@ from django.urls import include, path -urlpatterns = [] +from apps.global_notice.views import GlobalNoticeViewSet + +urlpatterns = [path("api/global_notice", GlobalNoticeViewSet.as_view())] diff --git a/apps/global_notice/views.py b/apps/global_notice/views.py index 91ea44a2..0eaad1a5 100644 --- a/apps/global_notice/views.py +++ b/apps/global_notice/views.py @@ -1,3 +1,5 @@ -from django.shortcuts import render +from rest_framework.views import APIView -# Create your views here. + +class GlobalNoticeViewSet(APIView): + pass diff --git a/apps/global_notice/views/__init__.py b/apps/global_notice/views/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/global_notice/views/router.py b/apps/global_notice/views/router.py deleted file mode 100644 index 96494f50..00000000 --- a/apps/global_notice/views/router.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework import routers - -router = routers.DefaultRouter() - -router.register( - prefix=r"notice", -) diff --git a/ara/settings/django.py b/ara/settings/django.py index 49409645..6c375cbe 100644 --- a/ara/settings/django.py +++ b/ara/settings/django.py @@ -30,6 +30,7 @@ "django_filters", "apps.core", "apps.user", + "apps.global_notice", ] MIDDLEWARE = [ diff --git a/ara/urls.py b/ara/urls.py index 50e0990a..07f58973 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("api/schema/", SpectacularAPIView.as_view(), name="schema"), path( "api/schema/swagger/", From b55b5ff2f38715d1fc766b6bf87e1545e2694e56 Mon Sep 17 00:00:00 2001 From: ddungiii Date: Thu, 9 Nov 2023 11:51:40 +0000 Subject: [PATCH 03/15] feat(global-notice): Add modelViewset, modelSerializer --- apps/global_notice/serializers.py | 8 ++++++++ apps/global_notice/urls.py | 6 +++++- apps/global_notice/views.py | 10 +++++++--- 3 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 apps/global_notice/serializers.py diff --git a/apps/global_notice/serializers.py b/apps/global_notice/serializers.py new file mode 100644 index 00000000..c72bb0af --- /dev/null +++ b/apps/global_notice/serializers.py @@ -0,0 +1,8 @@ +from apps.global_notice.models import GlobalNotice +from ara.classes.serializers.meta_data import MetaDataModelSerializer + + +class GlobalNoticeSerializer(MetaDataModelSerializer): + class Meta: + model = GlobalNotice + fields = ["title", "content"] diff --git a/apps/global_notice/urls.py b/apps/global_notice/urls.py index 95014578..22c8a7f9 100644 --- a/apps/global_notice/urls.py +++ b/apps/global_notice/urls.py @@ -1,5 +1,9 @@ from django.urls import include, path +from rest_framework.routers import DefaultRouter from apps.global_notice.views import GlobalNoticeViewSet -urlpatterns = [path("api/global_notice", GlobalNoticeViewSet.as_view())] +router = DefaultRouter() +router.register(r"/api/globalNotice", GlobalNoticeViewSet, basename="globalNotice") + +urlpatterns = router.urls diff --git a/apps/global_notice/views.py b/apps/global_notice/views.py index 0eaad1a5..4cf81a03 100644 --- a/apps/global_notice/views.py +++ b/apps/global_notice/views.py @@ -1,5 +1,9 @@ -from rest_framework.views import APIView +from rest_framework import viewsets +from apps.global_notice.models import GlobalNotice +from apps.global_notice.serializers import GlobalNoticeSerializer -class GlobalNoticeViewSet(APIView): - pass + +class GlobalNoticeViewSet(viewsets.ModelViewSet): + queryset = GlobalNotice.objects.all() + serializer_class = GlobalNoticeSerializer From 26601704b0f795a527f9b372752442e5f3a7d01a Mon Sep 17 00:00:00 2001 From: ddungiii Date: Thu, 23 Nov 2023 13:20:22 +0000 Subject: [PATCH 04/15] feat(global-notice): init migration --- apps/global_notice/migrations/0001_initial.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 apps/global_notice/migrations/0001_initial.py diff --git a/apps/global_notice/migrations/0001_initial.py b/apps/global_notice/migrations/0001_initial.py new file mode 100644 index 00000000..3fe0c140 --- /dev/null +++ b/apps/global_notice/migrations/0001_initial.py @@ -0,0 +1,62 @@ +# Generated by Django 4.2.5 on 2023-11-09 13:13 + +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="삭제 시간", + ), + ), + ("title", models.CharField(max_length=256, verbose_name="제목")), + ("content", models.TextField(verbose_name="본문")), + ("started_at", models.DateTimeField(verbose_name="모달 노출 시작 시간")), + ("expired_at", models.DateTimeField(verbose_name="모달 노출 종료 시간")), + ], + options={ + "verbose_name": "글로벌 공지", + "ordering": ("-created_at",), + "abstract": False, + }, + ), + ] From 3698979944b87f379eda78a9da80c4ce3d1bbda3 Mon Sep 17 00:00:00 2001 From: ddungiii Date: Tue, 2 Jan 2024 12:49:16 +0000 Subject: [PATCH 05/15] feat(global-notice): add admin --- apps/global_notice/admin.py | 9 ++++++++- apps/global_notice/models.py | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/global_notice/admin.py b/apps/global_notice/admin.py index 8c38f3f3..173df46b 100644 --- a/apps/global_notice/admin.py +++ b/apps/global_notice/admin.py @@ -1,3 +1,10 @@ from django.contrib import admin -# Register your models here. +from apps.global_notice.models import GlobalNotice +from ara.classes.admin import MetaDataModelAdmin + + +@admin.register(GlobalNotice) +class FAQAdmin(MetaDataModelAdmin): + list_display = ("title", "content", "started_at", "expired_at") + search_fields = ("title", "content") diff --git a/apps/global_notice/models.py b/apps/global_notice/models.py index 1a8cbe2d..0e011f4f 100644 --- a/apps/global_notice/models.py +++ b/apps/global_notice/models.py @@ -6,6 +6,7 @@ class GlobalNotice(MetaDataModel): class Meta(MetaDataModel.Meta): verbose_name = "글로벌 공지" + verbose_name_plural = "글로벌 공지 목록" title = models.CharField(verbose_name="제목", max_length=256) content = models.TextField(verbose_name="본문") From e45fa08ac1fbbca90a3ab163f8cb4b4542f70f05 Mon Sep 17 00:00:00 2001 From: ddungiii Date: Tue, 2 Jan 2024 13:58:46 +0000 Subject: [PATCH 06/15] feat(global-notice): add test --- apps/global_notice/permissions.py | 17 ++++ apps/global_notice/serializers.py | 2 +- apps/global_notice/tests.py | 3 - apps/global_notice/urls.py | 2 +- apps/global_notice/views.py | 20 ++++- tests/conftest.py | 2 +- tests/test_global_notice.py | 140 ++++++++++++++++++++++++++++++ 7 files changed, 178 insertions(+), 8 deletions(-) create mode 100644 apps/global_notice/permissions.py delete mode 100644 apps/global_notice/tests.py create mode 100644 tests/test_global_notice.py diff --git a/apps/global_notice/permissions.py b/apps/global_notice/permissions.py new file mode 100644 index 00000000..05705c53 --- /dev/null +++ b/apps/global_notice/permissions.py @@ -0,0 +1,17 @@ +from rest_framework import permissions +from rest_framework.request import Request +from rest_framework.views import APIView + + +class IsGlobalNoticeAthenticated(permissions.IsAuthenticated): + def has_permission(self, request: Request, view: APIView) -> bool: + if request.method not in permissions.SAFE_METHODS: + return request.user.is_staff or request.user.is_superuser + + # SAFE_METHODS는 비로그인 허용 + return True + + +class GlobalNoticePermission(permissions.BasePermission): + def has_permission(self, request: Request, view: APIView) -> bool: + return super().has_permission(request, view) diff --git a/apps/global_notice/serializers.py b/apps/global_notice/serializers.py index c72bb0af..dcafd78e 100644 --- a/apps/global_notice/serializers.py +++ b/apps/global_notice/serializers.py @@ -5,4 +5,4 @@ class GlobalNoticeSerializer(MetaDataModelSerializer): class Meta: model = GlobalNotice - fields = ["title", "content"] + fields = ["title", "content", "started_at", "expired_at"] diff --git a/apps/global_notice/tests.py b/apps/global_notice/tests.py deleted file mode 100644 index 7ce503c2..00000000 --- a/apps/global_notice/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/apps/global_notice/urls.py b/apps/global_notice/urls.py index 22c8a7f9..238c0669 100644 --- a/apps/global_notice/urls.py +++ b/apps/global_notice/urls.py @@ -4,6 +4,6 @@ from apps.global_notice.views import GlobalNoticeViewSet router = DefaultRouter() -router.register(r"/api/globalNotice", GlobalNoticeViewSet, basename="globalNotice") +router.register(r"api/global_notices", GlobalNoticeViewSet, basename="global_notices") urlpatterns = router.urls diff --git a/apps/global_notice/views.py b/apps/global_notice/views.py index 4cf81a03..ced7b4e8 100644 --- a/apps/global_notice/views.py +++ b/apps/global_notice/views.py @@ -1,9 +1,25 @@ -from rest_framework import viewsets +from django.utils import timezone +from rest_framework import permissions, viewsets from apps.global_notice.models import GlobalNotice +from apps.global_notice.permissions import ( + GlobalNoticePermission, + IsGlobalNoticeAthenticated, +) from apps.global_notice.serializers import GlobalNoticeSerializer class GlobalNoticeViewSet(viewsets.ModelViewSet): - queryset = GlobalNotice.objects.all() + queryset = GlobalNotice.objects.filter( + started_at__lte=timezone.now(), expired_at__gte=timezone.now() + ) serializer_class = GlobalNoticeSerializer + + permission_classes = ( + IsGlobalNoticeAthenticated, + GlobalNoticePermission, + ) + action_permission_classes = { + "create": (permissions.IsAuthenticated, GlobalNoticePermission) + } + pagination_class = None diff --git a/tests/conftest.py b/tests/conftest.py index 7ab8a049..b5fa62db 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,7 +26,7 @@ def set_admin_client(request): agree_terms_of_service_at=timezone.now(), ) client = APIClient() - client.force_authenticate(user=request.cls.user) + client.force_authenticate(user=request.cls.admin) request.cls.api_client = client diff --git a/tests/test_global_notice.py b/tests/test_global_notice.py new file mode 100644 index 00000000..c12a32d6 --- /dev/null +++ b/tests/test_global_notice.py @@ -0,0 +1,140 @@ +import pytest +from django.utils import timezone +from rest_framework import status + +from apps.core.views import status +from apps.global_notice.models import GlobalNotice +from tests.conftest import RequestSetting, TestCase + + +@pytest.mark.usefixtures("set_admin_client", "set_user_client") +class TestGlobalNotice(TestCase, RequestSetting): + N = 50 + + def test_list(self): + """ + 비로그인도 허용 + """ + for i in range(self.N): + GlobalNotice.objects.create( + title=f"global_notice title {i}", + 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( + title=f"global_notice title", + content=f"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["title"] == global_notice.title + + res = self.http_request(None, "get", f"global_notices/{global_notice.id}") + assert res.data["title"] == global_notice.title + + def test_filter(self): + """ + expired, not started 필터링 테스트 + """ + global_notice_expired = GlobalNotice.objects.create( + title="global_notice title", + 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( + title="global_notice title", + 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 + + def test_create(self): + notice_data = { + "title": "global_notice title", + "content": "global_notice content", + "started_at": timezone.now() - timezone.timedelta(days=1), + "expired_at": timezone.now() + timezone.timedelta(days=1), + } + res_user = self.http_request(self.user, "post", "global_notices", notice_data) + assert res_user.status_code == status.HTTP_403_FORBIDDEN + + for _ in range(self.N): + res_admin = self.http_request( + self.admin, "post", "global_notices", notice_data + ) + assert res_admin.status_code == status.HTTP_201_CREATED + + assert GlobalNotice.objects.count() == self.N + + def test_update(self): + global_notice = GlobalNotice.objects.create( + title="global_notice title", + content="global_notice content", + started_at=timezone.now() - timezone.timedelta(days=1), + expired_at=timezone.now() + timezone.timedelta(days=1), + ) + new_title = "new title" + new_content = "new content" + + res_user = self.http_request( + self.user, + "patch", + f"global_notices/{global_notice.id}", + {"title": new_title, "content": new_content}, + ) + assert res_user.status_code == status.HTTP_403_FORBIDDEN + + res_admin = self.http_request( + self.admin, + "patch", + f"global_notices/{global_notice.id}", + {"title": new_title, "content": new_content}, + ) + assert res_admin.status_code == status.HTTP_200_OK + assert res_admin.data["title"] == new_title + assert res_admin.data["content"] == new_content + + def test_destroy(self): + global_notice = GlobalNotice.objects.create( + title="global_notice title", + content="global_notice content", + started_at=timezone.now() - timezone.timedelta(days=1), + expired_at=timezone.now() + timezone.timedelta(days=1), + ) + res_user = self.http_request( + self.user, + "delete", + f"global_notices/{global_notice.id}", + ) + assert res_user.status_code == status.HTTP_403_FORBIDDEN + + res_admin = self.http_request( + self.admin, + "delete", + f"global_notices/{global_notice.id}", + ) + assert res_admin.status_code == status.HTTP_204_NO_CONTENT + assert GlobalNotice.objects.count() == 0 From 225aec9e8b9823d4c8688eea423eaa5f6fef73ee Mon Sep 17 00:00:00 2001 From: ddungiii Date: Tue, 2 Jan 2024 14:06:49 +0000 Subject: [PATCH 07/15] fix(global-notice): wrong import --- tests/test_global_notice.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_global_notice.py b/tests/test_global_notice.py index c12a32d6..b7b67db9 100644 --- a/tests/test_global_notice.py +++ b/tests/test_global_notice.py @@ -2,7 +2,6 @@ from django.utils import timezone from rest_framework import status -from apps.core.views import status from apps.global_notice.models import GlobalNotice from tests.conftest import RequestSetting, TestCase From 23bd73e8cbb8926edd153e68b4dd89a1025bd266 Mon Sep 17 00:00:00 2001 From: ddungiii Date: Tue, 2 Jan 2024 15:08:12 +0000 Subject: [PATCH 08/15] fix(global-notice): seperate korean, english versions of title and content --- apps/global_notice/admin.py | 10 +++- apps/global_notice/migrations/0001_initial.py | 9 ++- apps/global_notice/models.py | 6 +- apps/global_notice/serializers.py | 9 ++- tests/test_global_notice.py | 60 ++++++++++++------- 5 files changed, 63 insertions(+), 31 deletions(-) diff --git a/apps/global_notice/admin.py b/apps/global_notice/admin.py index 173df46b..e2106ffa 100644 --- a/apps/global_notice/admin.py +++ b/apps/global_notice/admin.py @@ -5,6 +5,10 @@ @admin.register(GlobalNotice) -class FAQAdmin(MetaDataModelAdmin): - list_display = ("title", "content", "started_at", "expired_at") - search_fields = ("title", "content") +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/migrations/0001_initial.py b/apps/global_notice/migrations/0001_initial.py index 3fe0c140..03f5e2bf 100644 --- a/apps/global_notice/migrations/0001_initial.py +++ b/apps/global_notice/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.5 on 2023-11-09 13:13 +# Generated by Django 4.2.5 on 2024-01-02 14:41 import datetime @@ -48,13 +48,16 @@ class Migration(migrations.Migration): verbose_name="삭제 시간", ), ), - ("title", models.CharField(max_length=256, verbose_name="제목")), - ("content", models.TextField(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/models.py b/apps/global_notice/models.py index 0e011f4f..e27742be 100644 --- a/apps/global_notice/models.py +++ b/apps/global_notice/models.py @@ -8,7 +8,9 @@ class Meta(MetaDataModel.Meta): verbose_name = "글로벌 공지" verbose_name_plural = "글로벌 공지 목록" - title = models.CharField(verbose_name="제목", max_length=256) - content = models.TextField(verbose_name="본문") + 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="모달 노출 종료 시간") diff --git a/apps/global_notice/serializers.py b/apps/global_notice/serializers.py index dcafd78e..b545880c 100644 --- a/apps/global_notice/serializers.py +++ b/apps/global_notice/serializers.py @@ -5,4 +5,11 @@ class GlobalNoticeSerializer(MetaDataModelSerializer): class Meta: model = GlobalNotice - fields = ["title", "content", "started_at", "expired_at"] + fields = [ + "ko_title", + "en_title", + "ko_content", + "en_content", + "started_at", + "expired_at", + ] diff --git a/tests/test_global_notice.py b/tests/test_global_notice.py index b7b67db9..3678f973 100644 --- a/tests/test_global_notice.py +++ b/tests/test_global_notice.py @@ -16,8 +16,10 @@ def test_list(self): """ for i in range(self.N): GlobalNotice.objects.create( - title=f"global_notice title {i}", - content=f"global_notice content {i}", + 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), ) @@ -32,30 +34,38 @@ def test_get(self): 비로그인도 허용 """ global_notice = GlobalNotice.objects.create( - title=f"global_notice title", - content=f"global_notice content", + 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["title"] == global_notice.title + 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["title"] == global_notice.title + 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( - title="global_notice title", - content="global_notice content", + 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( - title="global_notice title", - content="global_notice content", + 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), ) @@ -72,8 +82,10 @@ def test_filter(self): def test_create(self): notice_data = { - "title": "global_notice title", - "content": "global_notice content", + "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), } @@ -90,19 +102,21 @@ def test_create(self): def test_update(self): global_notice = GlobalNotice.objects.create( - title="global_notice title", - content="global_notice content", + 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), ) - new_title = "new title" - new_content = "new content" + new_ko_title = "새로운 제목" + new_ko_content = "새로운 본문" res_user = self.http_request( self.user, "patch", f"global_notices/{global_notice.id}", - {"title": new_title, "content": new_content}, + {"ko_title": new_ko_title, "ko_content": new_ko_content}, ) assert res_user.status_code == status.HTTP_403_FORBIDDEN @@ -110,16 +124,18 @@ def test_update(self): self.admin, "patch", f"global_notices/{global_notice.id}", - {"title": new_title, "content": new_content}, + {"ko_title": new_ko_title, "ko_content": new_ko_content}, ) assert res_admin.status_code == status.HTTP_200_OK - assert res_admin.data["title"] == new_title - assert res_admin.data["content"] == new_content + assert res_admin.data["ko_title"] == new_ko_title + assert res_admin.data["ko_content"] == new_ko_content def test_destroy(self): global_notice = GlobalNotice.objects.create( - title="global_notice title", - content="global_notice content", + 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), ) From 0abc8757f11998ce54e0a54288bd09391150f649 Mon Sep 17 00:00:00 2001 From: yuwol Date: Fri, 5 Jan 2024 08:01:36 +0000 Subject: [PATCH 09/15] refactor(global-notice): use relative paths for imports --- apps/global_notice/admin.py | 3 ++- apps/global_notice/serializers.py | 3 ++- apps/global_notice/urls.py | 2 +- apps/global_notice/views.py | 9 +++------ 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/global_notice/admin.py b/apps/global_notice/admin.py index e2106ffa..ebd77bef 100644 --- a/apps/global_notice/admin.py +++ b/apps/global_notice/admin.py @@ -1,8 +1,9 @@ from django.contrib import admin -from apps.global_notice.models import GlobalNotice from ara.classes.admin import MetaDataModelAdmin +from .models import GlobalNotice + @admin.register(GlobalNotice) class GlobalNoticeAdmin(MetaDataModelAdmin): diff --git a/apps/global_notice/serializers.py b/apps/global_notice/serializers.py index b545880c..9d32789f 100644 --- a/apps/global_notice/serializers.py +++ b/apps/global_notice/serializers.py @@ -1,6 +1,7 @@ -from apps.global_notice.models import GlobalNotice from ara.classes.serializers.meta_data import MetaDataModelSerializer +from .models import GlobalNotice + class GlobalNoticeSerializer(MetaDataModelSerializer): class Meta: diff --git a/apps/global_notice/urls.py b/apps/global_notice/urls.py index 238c0669..dc46e5e1 100644 --- a/apps/global_notice/urls.py +++ b/apps/global_notice/urls.py @@ -1,7 +1,7 @@ from django.urls import include, path from rest_framework.routers import DefaultRouter -from apps.global_notice.views import GlobalNoticeViewSet +from .views import GlobalNoticeViewSet router = DefaultRouter() router.register(r"api/global_notices", GlobalNoticeViewSet, basename="global_notices") diff --git a/apps/global_notice/views.py b/apps/global_notice/views.py index ced7b4e8..ef54118c 100644 --- a/apps/global_notice/views.py +++ b/apps/global_notice/views.py @@ -1,12 +1,9 @@ from django.utils import timezone from rest_framework import permissions, viewsets -from apps.global_notice.models import GlobalNotice -from apps.global_notice.permissions import ( - GlobalNoticePermission, - IsGlobalNoticeAthenticated, -) -from apps.global_notice.serializers import GlobalNoticeSerializer +from .models import GlobalNotice +from .permissions import GlobalNoticePermission, IsGlobalNoticeAthenticated +from .serializers import GlobalNoticeSerializer class GlobalNoticeViewSet(viewsets.ModelViewSet): From f10e003edcffc01e8eecc61bf42b73517a715840 Mon Sep 17 00:00:00 2001 From: yuwol Date: Fri, 5 Jan 2024 08:02:23 +0000 Subject: [PATCH 10/15] feat(global-notice): remove default auto field --- apps/global_notice/apps.py | 1 - .../migrations/0002_alter_globalnotice_id.py | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 apps/global_notice/migrations/0002_alter_globalnotice_id.py diff --git a/apps/global_notice/apps.py b/apps/global_notice/apps.py index 17d4ea6d..67d2d120 100644 --- a/apps/global_notice/apps.py +++ b/apps/global_notice/apps.py @@ -2,5 +2,4 @@ class GlobalNoticeConfig(AppConfig): - default_auto_field = "django.db.models.BigAutoField" name = "apps.global_notice" 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" + ), + ), + ] From f342e548a1779da852a4edd045cbdc73dfe0b700 Mon Sep 17 00:00:00 2001 From: yuwol Date: Fri, 5 Jan 2024 08:06:56 +0000 Subject: [PATCH 11/15] refactor(global-notice): move Meta under db fields --- apps/global_notice/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/global_notice/models.py b/apps/global_notice/models.py index e27742be..3d494af9 100644 --- a/apps/global_notice/models.py +++ b/apps/global_notice/models.py @@ -4,13 +4,13 @@ class GlobalNotice(MetaDataModel): - class Meta(MetaDataModel.Meta): - verbose_name = "글로벌 공지" - verbose_name_plural = "글로벌 공지 목록" - 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 = "글로벌 공지 목록" From e4ca55f33a38ac2aa6b633d899f4b26d64a5fc0a Mon Sep 17 00:00:00 2001 From: yuwol Date: Fri, 5 Jan 2024 08:07:29 +0000 Subject: [PATCH 12/15] feat(global-notice): add `id` in serializer fields --- apps/global_notice/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/global_notice/serializers.py b/apps/global_notice/serializers.py index 9d32789f..82915d4d 100644 --- a/apps/global_notice/serializers.py +++ b/apps/global_notice/serializers.py @@ -7,6 +7,7 @@ class GlobalNoticeSerializer(MetaDataModelSerializer): class Meta: model = GlobalNotice fields = [ + "id", "ko_title", "en_title", "ko_content", From 394dd4e8ee1103ee2e938bbba197121f1c45a0f4 Mon Sep 17 00:00:00 2001 From: yuwol Date: Fri, 5 Jan 2024 08:56:43 +0000 Subject: [PATCH 13/15] refactor(global-notice): update url patterns --- apps/global_notice/urls.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/global_notice/urls.py b/apps/global_notice/urls.py index dc46e5e1..536835d9 100644 --- a/apps/global_notice/urls.py +++ b/apps/global_notice/urls.py @@ -4,6 +4,9 @@ from .views import GlobalNoticeViewSet router = DefaultRouter() -router.register(r"api/global_notices", GlobalNoticeViewSet, basename="global_notices") +router.register("", GlobalNoticeViewSet, basename="global_notices") -urlpatterns = router.urls + +urlpatterns = [ + path("api/global_notices/", include(router.urls)), +] From c46a07b0e8dfc113001f515cdfee04ff6768a08e Mon Sep 17 00:00:00 2001 From: ddungiii Date: Fri, 5 Jan 2024 14:24:15 +0000 Subject: [PATCH 14/15] refactor(global-notice): modify viewset to readonly --- apps/global_notice/permissions.py | 17 ------- apps/global_notice/views.py | 11 +---- tests/test_global_notice.py | 76 +------------------------------ 3 files changed, 2 insertions(+), 102 deletions(-) delete mode 100644 apps/global_notice/permissions.py diff --git a/apps/global_notice/permissions.py b/apps/global_notice/permissions.py deleted file mode 100644 index 05705c53..00000000 --- a/apps/global_notice/permissions.py +++ /dev/null @@ -1,17 +0,0 @@ -from rest_framework import permissions -from rest_framework.request import Request -from rest_framework.views import APIView - - -class IsGlobalNoticeAthenticated(permissions.IsAuthenticated): - def has_permission(self, request: Request, view: APIView) -> bool: - if request.method not in permissions.SAFE_METHODS: - return request.user.is_staff or request.user.is_superuser - - # SAFE_METHODS는 비로그인 허용 - return True - - -class GlobalNoticePermission(permissions.BasePermission): - def has_permission(self, request: Request, view: APIView) -> bool: - return super().has_permission(request, view) diff --git a/apps/global_notice/views.py b/apps/global_notice/views.py index ef54118c..c2f20f07 100644 --- a/apps/global_notice/views.py +++ b/apps/global_notice/views.py @@ -2,21 +2,12 @@ from rest_framework import permissions, viewsets from .models import GlobalNotice -from .permissions import GlobalNoticePermission, IsGlobalNoticeAthenticated from .serializers import GlobalNoticeSerializer -class GlobalNoticeViewSet(viewsets.ModelViewSet): +class GlobalNoticeViewSet(viewsets.ReadOnlyModelViewSet): queryset = GlobalNotice.objects.filter( started_at__lte=timezone.now(), expired_at__gte=timezone.now() ) serializer_class = GlobalNoticeSerializer - - permission_classes = ( - IsGlobalNoticeAthenticated, - GlobalNoticePermission, - ) - action_permission_classes = { - "create": (permissions.IsAuthenticated, GlobalNoticePermission) - } pagination_class = None diff --git a/tests/test_global_notice.py b/tests/test_global_notice.py index 3678f973..f222aa78 100644 --- a/tests/test_global_notice.py +++ b/tests/test_global_notice.py @@ -6,7 +6,7 @@ from tests.conftest import RequestSetting, TestCase -@pytest.mark.usefixtures("set_admin_client", "set_user_client") +@pytest.mark.usefixtures("set_user_client") class TestGlobalNotice(TestCase, RequestSetting): N = 50 @@ -79,77 +79,3 @@ def test_filter(self): self.user, "get", f"global_notices/{global_notice_not_started.id}" ) assert res.status_code == status.HTTP_404_NOT_FOUND - - def test_create(self): - notice_data = { - "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_user = self.http_request(self.user, "post", "global_notices", notice_data) - assert res_user.status_code == status.HTTP_403_FORBIDDEN - - for _ in range(self.N): - res_admin = self.http_request( - self.admin, "post", "global_notices", notice_data - ) - assert res_admin.status_code == status.HTTP_201_CREATED - - assert GlobalNotice.objects.count() == self.N - - def test_update(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), - ) - new_ko_title = "새로운 제목" - new_ko_content = "새로운 본문" - - res_user = self.http_request( - self.user, - "patch", - f"global_notices/{global_notice.id}", - {"ko_title": new_ko_title, "ko_content": new_ko_content}, - ) - assert res_user.status_code == status.HTTP_403_FORBIDDEN - - res_admin = self.http_request( - self.admin, - "patch", - f"global_notices/{global_notice.id}", - {"ko_title": new_ko_title, "ko_content": new_ko_content}, - ) - assert res_admin.status_code == status.HTTP_200_OK - assert res_admin.data["ko_title"] == new_ko_title - assert res_admin.data["ko_content"] == new_ko_content - - def test_destroy(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_user = self.http_request( - self.user, - "delete", - f"global_notices/{global_notice.id}", - ) - assert res_user.status_code == status.HTTP_403_FORBIDDEN - - res_admin = self.http_request( - self.admin, - "delete", - f"global_notices/{global_notice.id}", - ) - assert res_admin.status_code == status.HTTP_204_NO_CONTENT - assert GlobalNotice.objects.count() == 0 From cb5219f68c159c77a7398ed1fd2a5055e661ec29 Mon Sep 17 00:00:00 2001 From: yuwol Date: Sat, 6 Jan 2024 06:53:25 +0000 Subject: [PATCH 15/15] chore(global-notice): format codes --- apps/global_notice/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/global_notice/views.py b/apps/global_notice/views.py index c2f20f07..a732f5cb 100644 --- a/apps/global_notice/views.py +++ b/apps/global_notice/views.py @@ -1,5 +1,5 @@ from django.utils import timezone -from rest_framework import permissions, viewsets +from rest_framework import viewsets from .models import GlobalNotice from .serializers import GlobalNoticeSerializer @@ -7,7 +7,8 @@ class GlobalNoticeViewSet(viewsets.ReadOnlyModelViewSet): queryset = GlobalNotice.objects.filter( - started_at__lte=timezone.now(), expired_at__gte=timezone.now() + started_at__lte=timezone.now(), + expired_at__gte=timezone.now(), ) serializer_class = GlobalNoticeSerializer pagination_class = None