diff --git a/cinema/permissions.py b/cinema/permissions.py new file mode 100644 index 00000000..a12518eb --- /dev/null +++ b/cinema/permissions.py @@ -0,0 +1,8 @@ +from rest_framework import permissions + + +class IsAdminOrIfAuthenticatedReadOnly(permissions.BasePermission): + def has_permission(self, request, view): + return request.user.is_authenticated \ + if request.method in permissions.SAFE_METHODS\ + else request.user.is_staff diff --git a/cinema/urls.py b/cinema/urls.py index 5ad6fb5b..b0ff747a 100644 --- a/cinema/urls.py +++ b/cinema/urls.py @@ -1,23 +1,23 @@ -from django.urls import path, include -from rest_framework import routers - -from cinema.views import ( - GenreViewSet, - ActorViewSet, - CinemaHallViewSet, - MovieViewSet, - MovieSessionViewSet, - OrderViewSet, -) - -router = routers.DefaultRouter() -router.register("genres", GenreViewSet) -router.register("actors", ActorViewSet) -router.register("cinema_halls", CinemaHallViewSet) -router.register("movies", MovieViewSet) -router.register("movie_sessions", MovieSessionViewSet) -router.register("orders", OrderViewSet) - -urlpatterns = [path("", include(router.urls))] - -app_name = "cinema" +from django.urls import path, include +from rest_framework import routers + +from cinema.views import ( + GenreViewSet, + ActorViewSet, + CinemaHallViewSet, + MovieViewSet, + MovieSessionViewSet, + OrderViewSet, +) + +router = routers.DefaultRouter() +router.register("genres", GenreViewSet) +router.register("actors", ActorViewSet) +router.register("cinema_halls", CinemaHallViewSet) +router.register("movies", MovieViewSet) +router.register("movie_sessions", MovieSessionViewSet) +router.register("orders", OrderViewSet) + +urlpatterns = [path("", include(router.urls))] + +app_name = "cinema" diff --git a/cinema/views.py b/cinema/views.py index a191bf5f..42015dfa 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -1,138 +1,164 @@ -from datetime import datetime - -from django.db.models import F, Count -from rest_framework import viewsets -from rest_framework.pagination import PageNumberPagination - -from cinema.models import Genre, Actor, CinemaHall, Movie, MovieSession, Order - -from cinema.serializers import ( - GenreSerializer, - ActorSerializer, - CinemaHallSerializer, - MovieSerializer, - MovieSessionSerializer, - MovieSessionListSerializer, - MovieDetailSerializer, - MovieSessionDetailSerializer, - MovieListSerializer, - OrderSerializer, - OrderListSerializer, -) - - -class GenreViewSet(viewsets.ModelViewSet): - queryset = Genre.objects.all() - serializer_class = GenreSerializer - - -class ActorViewSet(viewsets.ModelViewSet): - queryset = Actor.objects.all() - serializer_class = ActorSerializer - - -class CinemaHallViewSet(viewsets.ModelViewSet): - queryset = CinemaHall.objects.all() - serializer_class = CinemaHallSerializer - - -class MovieViewSet(viewsets.ModelViewSet): - queryset = Movie.objects.prefetch_related("genres", "actors") - serializer_class = MovieSerializer - - @staticmethod - def _params_to_ints(qs): - """Converts a list of string IDs to a list of integers""" - return [int(str_id) for str_id in qs.split(",")] - - def get_queryset(self): - """Retrieve the movies with filters""" - title = self.request.query_params.get("title") - genres = self.request.query_params.get("genres") - actors = self.request.query_params.get("actors") - - queryset = self.queryset - - if title: - queryset = queryset.filter(title__icontains=title) - - if genres: - genres_ids = self._params_to_ints(genres) - queryset = queryset.filter(genres__id__in=genres_ids) - - if actors: - actors_ids = self._params_to_ints(actors) - queryset = queryset.filter(actors__id__in=actors_ids) - - return queryset.distinct() - - def get_serializer_class(self): - if self.action == "list": - return MovieListSerializer - - if self.action == "retrieve": - return MovieDetailSerializer - - return MovieSerializer - - -class MovieSessionViewSet(viewsets.ModelViewSet): - queryset = ( - MovieSession.objects.all() - .select_related("movie", "cinema_hall") - .annotate( - tickets_available=F("cinema_hall__rows") - * F("cinema_hall__seats_in_row") - - Count("tickets") - ) - ) - serializer_class = MovieSessionSerializer - - def get_queryset(self): - date = self.request.query_params.get("date") - movie_id_str = self.request.query_params.get("movie") - - queryset = self.queryset - - if date: - date = datetime.strptime(date, "%Y-%m-%d").date() - queryset = queryset.filter(show_time__date=date) - - if movie_id_str: - queryset = queryset.filter(movie_id=int(movie_id_str)) - - return queryset - - def get_serializer_class(self): - if self.action == "list": - return MovieSessionListSerializer - - if self.action == "retrieve": - return MovieSessionDetailSerializer - - return MovieSessionSerializer - - -class OrderPagination(PageNumberPagination): - page_size = 10 - max_page_size = 100 - - -class OrderViewSet(viewsets.ModelViewSet): - queryset = Order.objects.prefetch_related( - "tickets__movie_session__movie", "tickets__movie_session__cinema_hall" - ) - serializer_class = OrderSerializer - pagination_class = OrderPagination - - def get_queryset(self): - return Order.objects.filter(user=self.request.user) - - def get_serializer_class(self): - if self.action == "list": - return OrderListSerializer - - return OrderSerializer - - def perform_create(self, serializer): - serializer.save(user=self.request.user) +from datetime import datetime + +from django.db.models import F, Count +from rest_framework import viewsets, mixins +from rest_framework.authentication import TokenAuthentication +from rest_framework.pagination import PageNumberPagination +from cinema.permissions import IsAdminOrIfAuthenticatedReadOnly +from rest_framework.permissions import IsAuthenticated + +from cinema.models import Genre, Actor, CinemaHall, Movie, MovieSession, Order + +from cinema.serializers import ( + GenreSerializer, + ActorSerializer, + CinemaHallSerializer, + MovieSerializer, + MovieSessionSerializer, + MovieSessionListSerializer, + MovieDetailSerializer, + MovieSessionDetailSerializer, + MovieListSerializer, + OrderSerializer, + OrderListSerializer, +) + + +class GenreViewSet(mixins.CreateModelMixin, + mixins.ListModelMixin, + viewsets.GenericViewSet): + queryset = Genre.objects.all() + serializer_class = GenreSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAdminOrIfAuthenticatedReadOnly] + + +class ActorViewSet(mixins.CreateModelMixin, + mixins.ListModelMixin, + viewsets.GenericViewSet): + queryset = Actor.objects.all() + serializer_class = ActorSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAdminOrIfAuthenticatedReadOnly] + + +class CinemaHallViewSet(mixins.CreateModelMixin, + mixins.ListModelMixin, + viewsets.GenericViewSet): + queryset = CinemaHall.objects.all() + serializer_class = CinemaHallSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAdminOrIfAuthenticatedReadOnly] + + +class MovieViewSet(mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + viewsets.GenericViewSet): + queryset = Movie.objects.prefetch_related("genres", "actors") + serializer_class = MovieSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAdminOrIfAuthenticatedReadOnly] + + @staticmethod + def _params_to_ints(qs): + """Converts a list of string IDs to a list of integers""" + return [int(str_id) for str_id in qs.split(",")] + + def get_queryset(self): + """Retrieve the movies with filters""" + title = self.request.query_params.get("title") + genres = self.request.query_params.get("genres") + actors = self.request.query_params.get("actors") + + queryset = self.queryset + + if title: + queryset = queryset.filter(title__icontains=title) + + if genres: + genres_ids = self._params_to_ints(genres) + queryset = queryset.filter(genres__id__in=genres_ids) + + if actors: + actors_ids = self._params_to_ints(actors) + queryset = queryset.filter(actors__id__in=actors_ids) + + return queryset.distinct() + + def get_serializer_class(self): + if self.action == "list": + return MovieListSerializer + + if self.action == "retrieve": + return MovieDetailSerializer + + return MovieSerializer + + +class MovieSessionViewSet(viewsets.ModelViewSet): + queryset = ( + MovieSession.objects.all() + .select_related("movie", "cinema_hall") + .annotate( + tickets_available=F("cinema_hall__rows") + * F("cinema_hall__seats_in_row") + - Count("tickets") + ) + ) + serializer_class = MovieSessionSerializer + authentication_classes = [TokenAuthentication] + permission_classes = [IsAdminOrIfAuthenticatedReadOnly] + + def get_queryset(self): + date = self.request.query_params.get("date") + movie_id_str = self.request.query_params.get("movie") + + queryset = self.queryset + + if date: + date = datetime.strptime(date, "%Y-%m-%d").date() + queryset = queryset.filter(show_time__date=date) + + if movie_id_str: + queryset = queryset.filter(movie_id=int(movie_id_str)) + + return queryset + + def get_serializer_class(self): + if self.action == "list": + return MovieSessionListSerializer + + if self.action == "retrieve": + return MovieSessionDetailSerializer + + return MovieSessionSerializer + + +class OrderPagination(PageNumberPagination): + page_size = 10 + max_page_size = 100 + + +class OrderViewSet(viewsets.GenericViewSet, + mixins.ListModelMixin, + mixins.CreateModelMixin): + 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) + + def get_serializer_class(self): + if self.action == "list": + return OrderListSerializer + + return OrderSerializer + + def perform_create(self, serializer): + serializer.save(user=self.request.user) diff --git a/cinema_service/urls.py b/cinema_service/urls.py index bf903c00..4381bac7 100644 --- a/cinema_service/urls.py +++ b/cinema_service/urls.py @@ -1,8 +1,9 @@ -from django.contrib import admin -from django.urls import path, include - -urlpatterns = [ - path("admin/", admin.site.urls), - path("api/cinema/", include("cinema.urls", namespace="cinema")), - path("__debug__/", include("debug_toolbar.urls")), -] +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path("admin/", admin.site.urls), + path("api/cinema/", include("cinema.urls", namespace="cinema")), + path("api/user/", include("user.urls", namespace="user")), + path("__debug__/", include("debug_toolbar.urls")), +] diff --git a/user/serializers.py b/user/serializers.py index fa56336e..c67189a8 100644 --- a/user/serializers.py +++ b/user/serializers.py @@ -1 +1,24 @@ -# write your code here +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): + return User.objects.create_user(**validated_data) + + 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/urls.py b/user/urls.py index fa56336e..f90e49b4 100644 --- a/user/urls.py +++ b/user/urls.py @@ -1 +1,12 @@ -# write your code here +from django.urls import path + +from user.views import UserLoginView, UserRegistrationView, UserDetailView + + +app_name = "user" + +urlpatterns = [ + path("me/", UserDetailView.as_view(), name="manage"), + path("register/", UserRegistrationView.as_view(), name="create"), + path("login/", UserLoginView.as_view(), name="login"), +] diff --git a/user/views.py b/user/views.py index fa56336e..6a55488c 100644 --- a/user/views.py +++ b/user/views.py @@ -1 +1,24 @@ -# write your code here +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 UserRegistrationView(generics.CreateAPIView): + serializer_class = UserSerializer + + +class UserLoginView(ObtainAuthToken): + renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + + +class UserDetailView(generics.RetrieveUpdateAPIView): + serializer_class = UserSerializer + permission_classes = [IsAuthenticated] + authentication_classes = [TokenAuthentication] + + def get_object(self): + return self.request.user