diff --git a/.coveragerc b/.coveragerc index 8941aad..2560379 100644 --- a/.coveragerc +++ b/.coveragerc @@ -4,6 +4,7 @@ include = **/serializers.py **/views.py **/models.py + api/utils.py omit = **/venv/** **/virtualenv/** diff --git a/api/parties/comments/serializers.py b/api/parties/comments/serializers.py index 2818b97..1a08f3a 100644 --- a/api/parties/comments/serializers.py +++ b/api/parties/comments/serializers.py @@ -20,7 +20,7 @@ class Meta: fields = ['text'] def validate(self, attrs): - slug = self.context['request'].path_info.split('/')[3] + slug = self.context['view'].kwargs['party_slug'] party = Party.objects.get(slug=slug) author = self.context['request'].user.profile diff --git a/api/parties/comments/urls.py b/api/parties/comments/urls.py index 24cf758..4ca5b9a 100644 --- a/api/parties/comments/urls.py +++ b/api/parties/comments/urls.py @@ -3,6 +3,6 @@ from api.parties.comments.views import CommentAPIViewSet router = SimpleRouter() -router.register('', CommentAPIViewSet) +router.register('', CommentAPIViewSet, base_name='comment') urlpatterns = router.urls diff --git a/api/parties/comments/views.py b/api/parties/comments/views.py index 50d09a3..01a15f0 100644 --- a/api/parties/comments/views.py +++ b/api/parties/comments/views.py @@ -9,22 +9,21 @@ class CommentAPIViewSet(viewsets.ModelViewSet): - queryset = Comment.objects.filter(is_active=True) lookup_field = 'slug' permission_classes = [CommentAPIPermission] pagination_class = None def get_queryset(self): slug = self.kwargs['party_slug'] - queryset = Comment.objects.filter( - party__slug=slug).order_by('created_at') + queryset = Comment.objects.filter(party__slug=slug) for instance in queryset: instance.party.update_party_info() - return queryset + return queryset.order_by('created_at') def get_object(self): instance = super(CommentAPIViewSet, self).get_object() instance.party.update_party_info() + self.check_object_permissions(self.request, instance) return instance def get_serializer_class(self): diff --git a/api/parties/owner/views.py b/api/parties/owner/views.py index 79323e0..8b0071a 100644 --- a/api/parties/owner/views.py +++ b/api/parties/owner/views.py @@ -21,6 +21,7 @@ def get_object(self): slug=self.kwargs['party_slug'] ) instance.update_party_info() + self.check_object_permissions(self.request, instance) return instance def get_serializer_class(self): @@ -49,5 +50,5 @@ def update(self, request, *args, **kwargs): ) ) return Response(PartyOwnerSerializer(instance).data) - except Exception as e: + except ValueError as e: raise ValidationError(detail=str(e)) diff --git a/api/parties/participants/serializers.py b/api/parties/participants/serializers.py index 860a7b3..107c797 100644 --- a/api/parties/participants/serializers.py +++ b/api/parties/participants/serializers.py @@ -1,4 +1,4 @@ -from django.conf import settings +from django.contrib.sites.shortcuts import get_current_site from rest_framework import serializers from api.profiles.serializers import ProfileUsernamePictureSerializer @@ -12,12 +12,13 @@ class Meta: model = Party fields = ['title', 'current_people', 'participants'] - @staticmethod - def _set_profile_picture_url(data): + def _set_profile_picture_url(self, data): + domain = get_current_site(self.context['request']) for datum in data: if datum['profile_picture']: + pass datum['profile_picture'] = \ - '{}{}'.format(settings.HOST, datum['profile_picture']) + 'http://{}{}'.format(domain, datum['profile_picture']) return data def get_participants(self, instance): diff --git a/api/parties/participants/views.py b/api/parties/participants/views.py index 02a1d2e..6292afd 100644 --- a/api/parties/participants/views.py +++ b/api/parties/participants/views.py @@ -21,6 +21,7 @@ def get_object(self): slug=self.kwargs['party_slug'] ) instance.update_party_info() + self.check_object_permissions(self.request, instance) return instance def _get_party_and_profile(self, request): @@ -42,7 +43,7 @@ def create(self, request, *args, **kwargs): ) ) return Response(serializer.data, status=status.HTTP_201_CREATED) - except Exception as e: + except ValueError as e: raise ValidationError(detail=str(e)) def destroy(self, request, *args, **kwargs): @@ -50,5 +51,5 @@ def destroy(self, request, *args, **kwargs): try: instance.remove_participants(participant=profile) return Response(status=status.HTTP_204_NO_CONTENT) - except Exception as e: + except ValueError as e: raise ValidationError(detail=str(e)) diff --git a/api/parties/permissions.py b/api/parties/permissions.py index d0e6ba8..ed3467a 100644 --- a/api/parties/permissions.py +++ b/api/parties/permissions.py @@ -1,7 +1,7 @@ -from rest_framework import permissions +from rest_framework.permissions import IsAuthenticated -class PartyAPIPermission(permissions.IsAuthenticated): +class PartyAPIPermission(IsAuthenticated): def has_object_permission(self, request, view, obj): if request.method != 'GET': has_permission = obj.party_owner == request.user.profile diff --git a/api/parties/serializers.py b/api/parties/serializers.py index 53cb02d..745215c 100644 --- a/api/parties/serializers.py +++ b/api/parties/serializers.py @@ -1,3 +1,4 @@ +from django.utils import timezone from rest_framework import serializers from apps.parties.models import Party from api.profiles.serializers import ProfileUsernamePictureSerializer @@ -25,6 +26,22 @@ class Meta: 'max_people' ] + def validate(self, attrs): + max_people = attrs.get('max_people') + start_time = attrs.get('start_time') + + today = timezone.localtime() + date_difference = (start_time - today).days + + if date_difference < 0: + msg = '현재 시각 이전에 시작하는 파티를 주최할 수 없습니다' + raise serializers.ValidationError(msg) + if max_people < 2: + msg = '참여 가능 인원은 2명 이상이어야 합니다' + raise serializers.ValidationError(msg) + + return attrs + def create(self, validated_data): model_class = self.Meta.model user = self.context['request'].user @@ -47,6 +64,24 @@ class Meta: 'max_people' ] + def validate(self, attrs): + start_time = attrs.get('start_time') + max_people = attrs.get('max_people') + + if start_time: + today = timezone.localtime() + date_difference = (start_time - today).days + if date_difference < 0: + msg = '파티의 시작 시간을 현재 시각보다 빠르게 설정할 수 없습니다.' + raise serializers.ValidationError(msg) + + if max_people: + if max_people < self.instance.current_people: + msg = '파티의 최대 인원을 현재 인원보다 작게 설정할 수 없습니다.' + raise serializers.ValidationError(msg) + + return attrs + def update(self, instance, validated_data): return Party.objects.update_party( instance, diff --git a/api/parties/urls.py b/api/parties/urls.py index e18709a..7b6b63b 100644 --- a/api/parties/urls.py +++ b/api/parties/urls.py @@ -22,6 +22,6 @@ ] router = routers.SimpleRouter() -router.register('', PartyAPIViewSet) +router.register('', PartyAPIViewSet, base_name='party') urlpatterns += router.urls diff --git a/api/parties/views.py b/api/parties/views.py index f75de55..dd505fa 100644 --- a/api/parties/views.py +++ b/api/parties/views.py @@ -1,5 +1,4 @@ -from rest_framework import status, viewsets -from rest_framework.exceptions import ValidationError, APIException +from rest_framework import viewsets from rest_framework.generics import ListAPIView, get_object_or_404 from rest_framework.filters import SearchFilter, OrderingFilter from rest_framework.response import Response @@ -14,7 +13,6 @@ class PartyAPIViewSet(viewsets.ModelViewSet): - queryset = Party.objects.all() lookup_field = 'party_slug' pagination_class = PartyAPIPagination permission_classes = [PartyAPIPermission] @@ -31,7 +29,7 @@ class PartyAPIViewSet(viewsets.ModelViewSet): } def get_queryset(self): - queryset = super(PartyAPIViewSet, self).get_queryset() + queryset = Party.objects.all() for instance in queryset: instance.update_party_info() return queryset @@ -41,22 +39,12 @@ def get_object(self): self.get_queryset(), slug=self.kwargs['party_slug'] ) + self.check_object_permissions(self.request, instance) return instance def get_serializer_class(self): return self.SERIALIZERS[self.request.method] - def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - try: - self.perform_create(serializer) - return Response(serializer.data, status=status.HTTP_201_CREATED) - except ValueError as e: - raise ValidationError(detail=str(e)) - except Exception as e: - raise APIException(detail=str(e)) - def update(self, request, *args, **kwargs): instance = self.get_object() serializer = self.get_serializer( @@ -65,23 +53,16 @@ def update(self, request, *args, **kwargs): partial=True ) serializer.is_valid(raise_exception=True) - try: - self.perform_update(serializer) - - send_push_to_multiple_user( - [participant for participant in instance.participants.all()], - instance, - '[파티 정보 수정됨]', - '[{}] 의 정보가 수정되었습니다.'.format( - instance.title - ) + self.perform_update(serializer) + send_push_to_multiple_user( + [participant for participant in instance.participants.all()], + instance, + '[파티 정보 수정됨]', + '[{}] 의 정보가 수정되었습니다.'.format( + instance.title ) - - return Response(serializer.data) - except ValueError as e: - raise ValidationError(detail=str(e)) - except Exception as e: - raise APIException(detail=str(e)) + ) + return Response(serializer.data) class JoinedPartyAPIView(ListAPIView): diff --git a/api/users/forgot/urls.py b/api/users/forgot/urls.py index a7ebfe2..255b95f 100644 --- a/api/users/forgot/urls.py +++ b/api/users/forgot/urls.py @@ -2,8 +2,6 @@ from api.users.forgot.views import ForgotPasswordAPIView -app_name = 'forgot' - urlpatterns = [ path('', ForgotPasswordAPIView.as_view()) ] diff --git a/api/users/login/__init__.py b/api/users/login/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/users/login/serializers.py b/api/users/login/serializers.py new file mode 100644 index 0000000..aef2032 --- /dev/null +++ b/api/users/login/serializers.py @@ -0,0 +1,34 @@ +from rest_framework import serializers +from rest_framework.compat import authenticate + + +class LoginSerializer(serializers.Serializer): + fcm_token = serializers.CharField(max_length=300) + email = serializers.EmailField(label='이메일') + password = serializers.CharField( + label='비밀번호', + style={ + 'input_type': 'password' + }, + trim_whitespace=False + ) + + def validate(self, attrs): + email = attrs.get('email') + password = attrs.get('password') + + if email and password: + user = authenticate( + request=self.context.get('request'), + email=email, + password=password + ) + if not user: + msg = '이메일 혹은 비밀번호가 잘못되었습니다' + raise serializers.ValidationError(msg, code='authorization') + else: + msg = '이메일과 비밀번호는 필수 항목입니다' + raise serializers.ValidationError(msg, code='authorization') + + attrs['user'] = user + return attrs diff --git a/api/users/login/views.py b/api/users/login/views.py new file mode 100644 index 0000000..0804935 --- /dev/null +++ b/api/users/login/views.py @@ -0,0 +1,34 @@ +from django.conf import settings +from django.contrib.sites.shortcuts import get_current_site +from rest_framework import generics +from rest_framework.authtoken.models import Token +from rest_framework.permissions import AllowAny +from rest_framework.response import Response + +from api.users.serializers import UserSerializer +from api.users.login.serializers import LoginSerializer +from apps.users.models import User + + +class LoginAPIView(generics.GenericAPIView): + permission_classes = [AllowAny] + serializer_class = LoginSerializer + + def post(self, request): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + user = serializer.validated_data['user'] + user.update_fcm_token(serializer.validated_data['fcm_token']) + User.objects.reactivate_user(user) + token, created = Token.objects.get_or_create(user=user) + + data = UserSerializer(user).data + data['token'] = token.key + data['profile_picture'] = \ + 'http://{}{}{}'.format( + get_current_site(self.request), + settings.MEDIA_URL, + user.profile.profile_picture + ) + + return Response(data) diff --git a/api/users/permissions.py b/api/users/permissions.py index a07ec24..71f9f4d 100644 --- a/api/users/permissions.py +++ b/api/users/permissions.py @@ -1,7 +1,7 @@ -from rest_framework import permissions +from rest_framework.permissions import BasePermission -class UserAPIPermission(permissions.BasePermission): +class UserAPIPermission(BasePermission): def has_permission(self, request, view): if request.method == 'POST': return True diff --git a/api/users/serializers.py b/api/users/serializers.py index 525bc47..5a44dec 100644 --- a/api/users/serializers.py +++ b/api/users/serializers.py @@ -1,41 +1,8 @@ from rest_framework import serializers -from rest_framework.compat import authenticate from apps.users.models import User -class LoginSerializer(serializers.Serializer): - fcm_token = serializers.CharField(max_length=300) - email = serializers.EmailField(label='이메일') - password = serializers.CharField( - label='비밀번호', - style={ - 'input_type': 'password' - }, - trim_whitespace=False - ) - - def validate(self, attrs): - email = attrs.get('email') - password = attrs.get('password') - - if email and password: - user = authenticate( - request=self.context.get('request'), - email=email, - password=password - ) - if not user: - msg = '이메일 혹은 비밀번호가 잘못되었습니다.' - raise serializers.ValidationError(msg, code='authorization') - else: - msg = '이메일과 비밀번호는 필수 항목입니다' - raise serializers.ValidationError(msg, code='authorization') - - attrs['user'] = user - return attrs - - class UserSerializer(serializers.ModelSerializer): class Meta: model = User diff --git a/api/users/urls.py b/api/users/urls.py index cd212e0..5de057a 100644 --- a/api/users/urls.py +++ b/api/users/urls.py @@ -1,16 +1,16 @@ from django.urls import path, include from rest_framework import routers -from api.users.views import LoginAPIView -from api.users.views import UserAPIViewset +from api.users.login.views import LoginAPIView +from api.users.views import UserAPIViewSet router = routers.SimpleRouter() -router.register('', UserAPIViewset) +router.register('', UserAPIViewSet) urlpatterns = [ path('login/', LoginAPIView.as_view()), - path('validate/', include('api.users.validate.urls', namespace='validate')), - path('forgot/', include('api.users.forgot.urls', namespace='forgot')), + path('validate/', include('api.users.validate.urls')), + path('forgot/', include('api.users.forgot.urls')), ] urlpatterns += router.urls diff --git a/api/users/validate/urls.py b/api/users/validate/urls.py index 7398b20..fe051bf 100644 --- a/api/users/validate/urls.py +++ b/api/users/validate/urls.py @@ -3,8 +3,6 @@ from api.users.validate.views import \ EmailValidateAPIView, UsernameValidateAPIView -app_name = 'validate' - urlpatterns = [ path('email/', EmailValidateAPIView.as_view()), path('username/', UsernameValidateAPIView.as_view()) diff --git a/api/users/views.py b/api/users/views.py index 79af6ba..e80cde6 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -1,54 +1,14 @@ -from django.conf import settings -from rest_framework import generics, status, viewsets +from rest_framework import status, viewsets from rest_framework.authtoken.models import Token -from rest_framework.permissions import AllowAny from rest_framework.response import Response from api.users.permissions import UserAPIPermission from api.users.serializers import \ - LoginSerializer, UserSerializer, \ - UserCreateSerializer, UserPasswordSerializer + UserSerializer, UserCreateSerializer, UserPasswordSerializer from apps.users.models import User -class LoginAPIView(generics.GenericAPIView): - permission_classes = [AllowAny] - serializer_class = LoginSerializer - - def post(self, request): - serializer = self.get_serializer( - data=request.data, - context={ - 'request': request - } - ) - serializer.is_valid(raise_exception=True) - user = serializer.validated_data['user'] - user.update_fcm_token(serializer.validated_data['fcm_token']) - User.objects.reactivate_user(user) - token, created = Token.objects.get_or_create(user=user) - - if user.profile.profile_picture: - profile_picture = '{}{}{}'.format( - settings.HOST, - settings.MEDIA_URL, - user.profile.profile_picture - ) - else: - profile_picture = None - - data = { - 'token': token.key, - 'uuid': user.uuid, - 'email': user.email, - 'username': user.username, - 'profile_picture': profile_picture - } - - return Response(data) - - -class UserAPIViewset(viewsets.ModelViewSet): +class UserAPIViewSet(viewsets.ModelViewSet): SERIALIZERS = { "GET": UserSerializer, "POST": UserCreateSerializer, diff --git a/pg_rest_api/settings/environments/development.py b/pg_rest_api/settings/environments/development.py index 2e633c0..b7241d4 100644 --- a/pg_rest_api/settings/environments/development.py +++ b/pg_rest_api/settings/environments/development.py @@ -1,8 +1,6 @@ import os from pg_rest_api.settings import BASE_DIR -HOST = 'http://127.0.0.1:8000' - DEBUG = True ALLOWED_HOSTS = ['*'] diff --git a/pg_rest_api/settings/environments/staging.py b/pg_rest_api/settings/environments/staging.py index 617aa78..7b774b6 100644 --- a/pg_rest_api/settings/environments/staging.py +++ b/pg_rest_api/settings/environments/staging.py @@ -1,8 +1,6 @@ import django_heroku import dj_database_url -HOST = 'https://partygwam-staging.herokuapp.com' - DEBUG = True ALLOWED_HOSTS = [ 'herokuapp.com', diff --git a/tests/parties/comments/test_view.py b/tests/parties/comments/test_view.py index 667642e..7deedfb 100644 --- a/tests/parties/comments/test_view.py +++ b/tests/parties/comments/test_view.py @@ -75,7 +75,7 @@ def test_create_comment(self): request = self.factory.post(path, data, 'json') force_authenticate(request, user) - response = self.view(request) + response = self.view(request, party_slug=party.slug) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -87,6 +87,6 @@ def test_create_comment_when_not_participating(self): request = self.factory.post(path, data, 'json') force_authenticate(request, user) - response = self.view(request) + response = self.view(request, party_slug=party.slug) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/tests/parties/owner/test_view.py b/tests/parties/owner/test_view.py index 4dcc857..e895a05 100644 --- a/tests/parties/owner/test_view.py +++ b/tests/parties/owner/test_view.py @@ -91,4 +91,4 @@ def test_pass_party_owner_when_not_owner(self): force_authenticate(request, self.users[2]) response = self.view(request, party_slug=slug) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/tests/users/test_view.py b/tests/users/test_view.py index 06fa7f5..6b03dc2 100644 --- a/tests/users/test_view.py +++ b/tests/users/test_view.py @@ -2,7 +2,8 @@ from django.test import TestCase from rest_framework.test import APIRequestFactory, force_authenticate -from api.users.views import LoginAPIView, UserAPIViewset +from api.users.views import UserAPIViewSet +from api.users.login.views import LoginAPIView from apps.users.models import User @@ -50,7 +51,7 @@ def test_login_with_deactivated_user(self): class UserAPIViewSetTest(BaseUserAPIViewTest): def setUp(self): super(UserAPIViewSetTest, self).setUp() - self.view = UserAPIViewset.as_view({'post': 'create', 'put': 'update'}) + self.view = UserAPIViewSet.as_view({'post': 'create', 'put': 'update'}) def _send_create_request(self, email, username, password): data = {