From 83ea062a5133eb516849cb93ceac45e43a0df0dd Mon Sep 17 00:00:00 2001 From: Anastasiia Karpenko Date: Wed, 18 Dec 2024 14:45:18 +0200 Subject: [PATCH] Solution --- cinema/permissions.py | 13 ++++++++++++ cinema/views.py | 40 +++++++++++++++++++++++++++++++------ cinema_service/settings.py | 2 +- cinema_service/urls.py | 1 + user/serializers.py | 31 +++++++++++++++++++++++++++- user/tests/test_user_api.py | 8 ++------ user/urls.py | 12 ++++++++++- user/views.py | 29 ++++++++++++++++++++++++++- 8 files changed, 120 insertions(+), 16 deletions(-) create mode 100644 cinema/permissions.py diff --git a/cinema/permissions.py b/cinema/permissions.py new file mode 100644 index 00000000..ae9f0550 --- /dev/null +++ b/cinema/permissions.py @@ -0,0 +1,13 @@ +from rest_framework.permissions import BasePermission, SAFE_METHODS + + +class IsAdminOrIfAuthenticatedReadOnly(BasePermission): + + def has_permission(self, request, view): + if request.user and request.user.is_staff: + return True + + if request.user and request.user.is_authenticated: + return request.method in SAFE_METHODS + + return False diff --git a/cinema/views.py b/cinema/views.py index a191bf5f..e6e2fae4 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -1,10 +1,15 @@ from datetime import datetime from django.db.models import F, Count -from rest_framework import viewsets +from rest_framework import viewsets, status, mixins +from rest_framework.authentication import TokenAuthentication from rest_framework.pagination import PageNumberPagination +from rest_framework.permissions import IsAuthenticated, IsAdminUser +from rest_framework.response import Response +from rest_framework.viewsets import GenericViewSet from cinema.models import Genre, Actor, CinemaHall, Movie, MovieSession, Order +from cinema.permissions import IsAdminOrIfAuthenticatedReadOnly from cinema.serializers import ( GenreSerializer, @@ -21,24 +26,41 @@ ) -class GenreViewSet(viewsets.ModelViewSet): +class GenreViewSet(mixins.ListModelMixin, + mixins.CreateModelMixin, + GenericViewSet): queryset = Genre.objects.all() serializer_class = GenreSerializer + authentication_classes = (TokenAuthentication,) + permission_classes = [IsAdminOrIfAuthenticatedReadOnly] -class ActorViewSet(viewsets.ModelViewSet): +class ActorViewSet(mixins.ListModelMixin, + mixins.CreateModelMixin, + GenericViewSet): queryset = Actor.objects.all() serializer_class = ActorSerializer + authentication_classes = (TokenAuthentication,) + permission_classes = [IsAdminOrIfAuthenticatedReadOnly] -class CinemaHallViewSet(viewsets.ModelViewSet): +class CinemaHallViewSet(mixins.ListModelMixin, + mixins.CreateModelMixin, + GenericViewSet): queryset = CinemaHall.objects.all() serializer_class = CinemaHallSerializer + authentication_classes = (TokenAuthentication,) + permission_classes = [IsAdminOrIfAuthenticatedReadOnly] -class MovieViewSet(viewsets.ModelViewSet): +class MovieViewSet(mixins.ListModelMixin, + mixins.CreateModelMixin, + mixins.RetrieveModelMixin, + GenericViewSet): queryset = Movie.objects.prefetch_related("genres", "actors") serializer_class = MovieSerializer + authentication_classes = (TokenAuthentication,) + permission_classes = [IsAdminOrIfAuthenticatedReadOnly] @staticmethod def _params_to_ints(qs): @@ -87,6 +109,8 @@ class MovieSessionViewSet(viewsets.ModelViewSet): ) ) serializer_class = MovieSessionSerializer + authentication_classes = (TokenAuthentication,) + permission_classes = [IsAdminOrIfAuthenticatedReadOnly] def get_queryset(self): date = self.request.query_params.get("date") @@ -118,12 +142,16 @@ class OrderPagination(PageNumberPagination): max_page_size = 100 -class OrderViewSet(viewsets.ModelViewSet): +class OrderViewSet(mixins.ListModelMixin, + mixins.CreateModelMixin, + GenericViewSet): queryset = Order.objects.prefetch_related( "tickets__movie_session__movie", "tickets__movie_session__cinema_hall" ) serializer_class = OrderSerializer pagination_class = OrderPagination + authentication_classes = (TokenAuthentication,) + permission_classes = [IsAuthenticated] def get_queryset(self): return Order.objects.filter(user=self.request.user) diff --git a/cinema_service/settings.py b/cinema_service/settings.py index 29ea7dea..062c0ac0 100644 --- a/cinema_service/settings.py +++ b/cinema_service/settings.py @@ -125,7 +125,7 @@ USE_I18N = True -USE_TZ = False +USE_TZ = True # Static files (CSS, JavaScript, Images) diff --git a/cinema_service/urls.py b/cinema_service/urls.py index bf903c00..98adb80f 100644 --- a/cinema_service/urls.py +++ b/cinema_service/urls.py @@ -5,4 +5,5 @@ path("admin/", admin.site.urls), path("api/cinema/", include("cinema.urls", namespace="cinema")), path("__debug__/", include("debug_toolbar.urls")), + path("api/user/", include("user.urls", namespace="user")), ] diff --git a/user/serializers.py b/user/serializers.py index fa56336e..c6395bbe 100644 --- a/user/serializers.py +++ b/user/serializers.py @@ -1 +1,30 @@ -# write your code here +from django.contrib.auth import get_user_model +from rest_framework import serializers + +from user.models import User + + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ("id", "username", "email", "password", "is_staff") + read_only_fields = ("id", "is_staff") + extra_kwargs = { + "password": { + "write_only": True, + "min_length": 5, + } + } + + def create(self, validated_data): + user = get_user_model().objects.create_user(**validated_data) + return user + + def update(self, instance, validated_data): + password = validated_data.pop("password", None) + user = super().update(instance, validated_data) + + if password: + user.set_password(password) + user.save() + return user diff --git a/user/tests/test_user_api.py b/user/tests/test_user_api.py index 022dca8b..9e5a6691 100644 --- a/user/tests/test_user_api.py +++ b/user/tests/test_user_api.py @@ -59,9 +59,7 @@ def test_password_too_short(self): res = self.client.post(CREATE_USER_URL, payload) self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) - user_exists = ( - get_user_model().objects.filter(email=payload["email"]).exists() - ) + user_exists = get_user_model().objects.filter(email=payload["email"]).exists() self.assertFalse(user_exists) def test_create_token_for_user(self): @@ -79,9 +77,7 @@ def test_create_token_for_user(self): def test_create_token_invalid_credentials(self): """Test that token is not created if invalid credentials are given""" - create_user( - username="user12345", email="test@test.com", password="test123" - ) + create_user(username="user12345", email="test@test.com", password="test123") payload = { "username": "user12345", "email": "test@test.com", diff --git a/user/urls.py b/user/urls.py index fa56336e..0786e227 100644 --- a/user/urls.py +++ b/user/urls.py @@ -1 +1,11 @@ -# write your code here +from django.urls import path + +from user.views import CreateUserView, LoginUserView, ManageUserView + +urlpatterns = [ + path("register/", CreateUserView.as_view(), name="create"), + path("login/", LoginUserView.as_view(), name="login"), + path("me/", ManageUserView.as_view(), name="manage"), +] + +app_name = "user" diff --git a/user/views.py b/user/views.py index fa56336e..7cdb772b 100644 --- a/user/views.py +++ b/user/views.py @@ -1 +1,28 @@ -# write your code here +from contextvars import Token + +from django.contrib.auth import get_user_model +from rest_framework import generics +from rest_framework.authentication import TokenAuthentication +from rest_framework.authtoken.views import ObtainAuthToken +from rest_framework.permissions import IsAuthenticated +from rest_framework.settings import api_settings + +from user.serializers import UserSerializer + + +class CreateUserView(generics.CreateAPIView): + queryset = get_user_model().objects.all() + serializer_class = UserSerializer + + +class LoginUserView(ObtainAuthToken): + renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + + +class ManageUserView(generics.RetrieveUpdateAPIView): + serializer_class = UserSerializer + authentication_classes = (TokenAuthentication,) + permission_classes = (IsAuthenticated,) + + def get_object(self): + return self.request.user