From 6f70bec397d1f35ebac3afd95c7bc9ebdd3069a9 Mon Sep 17 00:00:00 2001 From: Qndndn Date: Thu, 2 Nov 2023 15:48:10 +0000 Subject: [PATCH] feat(calendar): add model, viewset, serializer --- apps/calendar/__init__.py | 0 apps/calendar/admin.py | 3 + apps/calendar/apps.py | 6 ++ apps/calendar/migrations/__init__.py | 0 apps/calendar/models.py | 33 +++++++ apps/calendar/serializers/__init__.py | 0 apps/calendar/serializers/calendar.py | 93 ++++++++++++++++++++ apps/calendar/tests.py | 3 + apps/calendar/views/__init__.py | 0 apps/calendar/views/viewsets/__init__.py | 1 + apps/calendar/views/viewsets/calendar.py | 45 ++++++++++ apps/calendar/views/viewsets/user_profile.py | 45 ++++++++++ 12 files changed, 229 insertions(+) create mode 100644 apps/calendar/__init__.py create mode 100644 apps/calendar/admin.py create mode 100644 apps/calendar/apps.py create mode 100644 apps/calendar/migrations/__init__.py create mode 100644 apps/calendar/models.py create mode 100644 apps/calendar/serializers/__init__.py create mode 100644 apps/calendar/serializers/calendar.py create mode 100644 apps/calendar/tests.py create mode 100644 apps/calendar/views/__init__.py create mode 100644 apps/calendar/views/viewsets/__init__.py create mode 100644 apps/calendar/views/viewsets/calendar.py create mode 100644 apps/calendar/views/viewsets/user_profile.py diff --git a/apps/calendar/__init__.py b/apps/calendar/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/calendar/admin.py b/apps/calendar/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/apps/calendar/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/calendar/apps.py b/apps/calendar/apps.py new file mode 100644 index 00000000..79dbdc1a --- /dev/null +++ b/apps/calendar/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CalendarConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "Calendar" diff --git a/apps/calendar/migrations/__init__.py b/apps/calendar/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/calendar/models.py b/apps/calendar/models.py new file mode 100644 index 00000000..ad5bd1c6 --- /dev/null +++ b/apps/calendar/models.py @@ -0,0 +1,33 @@ +from django.db import models + +from ara.db.models import MetaDataModel + + +class Calendar(MetaDataModel): + tag = models.ForeignKey( + verbose_name="태그", + to="core.Tag", + on_delete=models.CASCADE, + related_name="event_set", + db_index=True, + ) + is_allday = models.BooleanField( + verbose_name="하루종일", + default=False, + ) + start_at = models.DateTimeField( + verbose_name="시작 시간", + blank=True, + null=True, + default=None, + ) + end_at = models.DateTimeField( + verbose_name="종료 시간", + blank=True, + null=True, + default=None, + ) + title = models.CharField( + verbose_name="제목", + max_length=512, + ) diff --git a/apps/calendar/serializers/__init__.py b/apps/calendar/serializers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/calendar/serializers/calendar.py b/apps/calendar/serializers/calendar.py new file mode 100644 index 00000000..271a9a85 --- /dev/null +++ b/apps/calendar/serializers/calendar.py @@ -0,0 +1,93 @@ +from Calendar.models import Calendar +from rest_framework import serializers + + +class CalendarSerializer(serializers.ModelSerializer): + class Meta: + model = Calendar + fields = "__all__" + + +from dateutil.relativedelta import relativedelta +from django.utils import timezone +from django.utils.translation import gettext +from rest_framework import serializers + +from apps.user.models import UserProfile +from ara.classes.serializers import MetaDataModelSerializer + + +class UserProfileSerializer(MetaDataModelSerializer): + ... + + +class UserProfileUpdateActionSerializer(MetaDataModelSerializer): + class Meta(MetaDataModelSerializer.Meta): + read_only_fields = ( + "sid", + "user", + ) + + def validate_nickname(self, value) -> str: + nickname_changed = self.instance and value != self.instance.nickname + if nickname_changed and not self.instance.can_change_nickname(): + next_change_date = self.instance.nickname_updated_at + relativedelta( + months=3 + ) + raise serializers.ValidationError( + gettext( + "Nicknames can only be changed every 3 months. (can't change until %(date)s)" + ) + % {"date": next_change_date.strftime("%Y/%m/%d")} + ) + return value + + def update(self, instance, validated_data): + new_nickname = validated_data.get("nickname") + old_nickname = instance.nickname if instance else None + if instance and new_nickname and old_nickname != new_nickname: + validated_data["nickname_updated_at"] = timezone.now() + return super(BaseUserProfileSerializer, self).update(instance, validated_data) + + +class PublicUserProfileSerializer(BaseUserProfileSerializer): + class Meta(BaseUserProfileSerializer.Meta): + fields = ( + "picture", + "nickname", + "user", + "is_official", + "is_school_admin", + ) + + +class MyPageUserProfileSerializer(BaseUserProfileSerializer): + num_articles = serializers.SerializerMethodField() + num_comments = serializers.SerializerMethodField() + num_positive_votes = serializers.SerializerMethodField() + + @staticmethod + def get_num_articles(obj): + from apps.core.models import Article + + num_articles = Article.objects.filter(created_by=obj.user).count() + return num_articles + + @staticmethod + def get_num_comments(obj): + from apps.core.models import Comment + + num_comments = Comment.objects.filter(created_by=obj.user).count() + return num_comments + + @staticmethod + def get_num_positive_votes(obj): + from apps.core.models import Vote + + num_article_votes = Vote.objects.filter( + parent_article__created_by=obj.user, is_positive=True + ).count() + num_comment_votes = Vote.objects.filter( + parent_comment__created_by=obj.user, is_positive=True + ).count() + return num_article_votes + num_comment_votes diff --git a/apps/calendar/tests.py b/apps/calendar/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/apps/calendar/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/calendar/views/__init__.py b/apps/calendar/views/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/calendar/views/viewsets/__init__.py b/apps/calendar/views/viewsets/__init__.py new file mode 100644 index 00000000..8b2bb321 --- /dev/null +++ b/apps/calendar/views/viewsets/__init__.py @@ -0,0 +1 @@ +from .calendar import * diff --git a/apps/calendar/views/viewsets/calendar.py b/apps/calendar/views/viewsets/calendar.py new file mode 100644 index 00000000..d72281b5 --- /dev/null +++ b/apps/calendar/views/viewsets/calendar.py @@ -0,0 +1,45 @@ +from django.utils import timezone +from rest_framework import decorators, mixins, response, status + +from apps.user.models import UserProfile +from apps.user.permissions.user_profile import UserProfilePermission +from apps.user.serializers.user_profile import ( + PublicUserProfileSerializer, + UserProfileSerializer, + UserProfileUpdateActionSerializer, +) +from ara.classes.viewset import ActionAPIViewSet + + +class CalendarViewSet( + mixins.RetrieveModelMixin, mixins.UpdateModelMixin, ActionAPIViewSet +): + queryset = UserProfile.objects.all() + serializer_class = UserProfileSerializer + action_serializer_class = { + "update": UserProfileUpdateActionSerializer, + "partial_update": UserProfileUpdateActionSerializer, + } + permission_classes = (UserProfilePermission,) + + def retrieve(self, request, *args, **kwargs): + profile = self.get_object() + if request.user == profile.user: + return super().retrieve(request, *args, **kwargs) + else: + return response.Response(PublicUserProfileSerializer(profile).data) + + @decorators.action(detail=True, methods=["patch"]) + def agree_terms_of_service(self, request, *args, **kwargs): + # BAD_REQUEST if user already agree with the terms of service + if request.user.profile.agree_terms_of_service_at is not None: + return response.Response( + status=status.HTTP_400_BAD_REQUEST, + ) + + request.user.profile.agree_terms_of_service_at = timezone.now() + request.user.profile.save() + + return response.Response( + status=status.HTTP_200_OK, + ) diff --git a/apps/calendar/views/viewsets/user_profile.py b/apps/calendar/views/viewsets/user_profile.py new file mode 100644 index 00000000..dc1febdc --- /dev/null +++ b/apps/calendar/views/viewsets/user_profile.py @@ -0,0 +1,45 @@ +from django.utils import timezone +from rest_framework import decorators, mixins, response, status + +from apps.user.models import UserProfile +from apps.user.permissions.user_profile import UserProfilePermission +from apps.user.serializers.user_profile import ( + PublicUserProfileSerializer, + UserProfileSerializer, + UserProfileUpdateActionSerializer, +) +from ara.classes.viewset import ActionAPIViewSet + + +class UserProfileViewSet( + mixins.RetrieveModelMixin, mixins.UpdateModelMixin, ActionAPIViewSet +): + queryset = UserProfile.objects.all() + serializer_class = UserProfileSerializer + action_serializer_class = { + "update": UserProfileUpdateActionSerializer, + "partial_update": UserProfileUpdateActionSerializer, + } + permission_classes = (UserProfilePermission,) + + def retrieve(self, request, *args, **kwargs): + profile = self.get_object() + if request.user == profile.user: + return super().retrieve(request, *args, **kwargs) + else: + return response.Response(PublicUserProfileSerializer(profile).data) + + @decorators.action(detail=True, methods=["patch"]) + def agree_terms_of_service(self, request, *args, **kwargs): + # BAD_REQUEST if user already agree with the terms of service + if request.user.profile.agree_terms_of_service_at is not None: + return response.Response( + status=status.HTTP_400_BAD_REQUEST, + ) + + request.user.profile.agree_terms_of_service_at = timezone.now() + request.user.profile.save() + + return response.Response( + status=status.HTTP_200_OK, + )