From 5e1fbe2c125ca18c47b78fb90c42e6a1fc0d5327 Mon Sep 17 00:00:00 2001 From: Pasha Date: Wed, 2 Oct 2024 21:43:56 +0300 Subject: [PATCH 1/3] Solution --- cinema/models.py | 24 ++++++-- cinema/serializers.py | 61 +++++++++++++++++++- cinema/urls.py | 2 + cinema/views.py | 112 ++++++++++++++++++++++++++++++++++++- cinema_service/settings.py | 2 +- 5 files changed, 191 insertions(+), 10 deletions(-) diff --git a/cinema/models.py b/cinema/models.py index f18f166c..9051350e 100644 --- a/cinema/models.py +++ b/cinema/models.py @@ -84,16 +84,22 @@ class Ticket(models.Model): row = models.IntegerField() seat = models.IntegerField() - def clean(self): + @staticmethod + def validate_row_and_seat( + row: int, + seat: int, + movie_session: MovieSession, + error_to_raise + ): for ticket_attr_value, ticket_attr_name, cinema_hall_attr_name in [ - (self.row, "row", "rows"), - (self.seat, "seat", "seats_in_row"), + (row, "row", "rows"), + (seat, "seat", "seats_in_row"), ]: count_attrs = getattr( - self.movie_session.cinema_hall, cinema_hall_attr_name + movie_session.cinema_hall, cinema_hall_attr_name ) if not (1 <= ticket_attr_value <= count_attrs): - raise ValidationError( + raise error_to_raise( { ticket_attr_name: f"{ticket_attr_name} " f"number must be in available range: " @@ -102,6 +108,14 @@ def clean(self): } ) + def clean(self): + Ticket.validate_row_and_seat( + self.row, + self.seat, + self.movie_session, + ValidationError + ) + def save( self, force_insert=False, diff --git a/cinema/serializers.py b/cinema/serializers.py index a1a4d7d4..45ae442d 100644 --- a/cinema/serializers.py +++ b/cinema/serializers.py @@ -1,6 +1,16 @@ +from django.db import transaction from rest_framework import serializers +from rest_framework.validators import UniqueTogetherValidator -from cinema.models import Genre, Actor, CinemaHall, Movie, MovieSession +from cinema.models import ( + Genre, + Actor, + CinemaHall, + Movie, + MovieSession, + Order, + Ticket +) class GenreSerializer(serializers.ModelSerializer): @@ -51,6 +61,22 @@ class Meta: fields = ("id", "show_time", "movie", "cinema_hall") +class TicketSerializer(serializers.ModelSerializer): + class Meta: + model = Ticket + fields = ("id", "movie_session", "row", "seat") + + def validate(self, attrs): + Ticket.validate_row_and_seat( + attrs["row"], + attrs["seat"], + attrs["movie_session"], + serializers.ValidationError + ) + + return attrs + + class MovieSessionListSerializer(MovieSessionSerializer): movie_title = serializers.CharField(source="movie.title", read_only=True) cinema_hall_name = serializers.CharField( @@ -59,6 +85,7 @@ class MovieSessionListSerializer(MovieSessionSerializer): cinema_hall_capacity = serializers.IntegerField( source="cinema_hall.capacity", read_only=True ) + tickets_available = serializers.IntegerField(read_only=True) class Meta: model = MovieSession @@ -68,13 +95,43 @@ class Meta: "movie_title", "cinema_hall_name", "cinema_hall_capacity", + "tickets_available", ) class MovieSessionDetailSerializer(MovieSessionSerializer): movie = MovieListSerializer(many=False, read_only=True) cinema_hall = CinemaHallSerializer(many=False, read_only=True) + taken_places = serializers.SerializerMethodField() class Meta: model = MovieSession - fields = ("id", "show_time", "movie", "cinema_hall") + fields = ("id", "show_time", "movie", "cinema_hall", "taken_places") + + def get_taken_places(self, obj): + tickets = Ticket.objects.filter(movie_session=obj) + return [{"row": ticket.row, "seat": ticket.seat} for ticket in tickets] + + +class OrderSerializer(serializers.ModelSerializer): + tickets = TicketSerializer(many=True, read_only=False, allow_empty=False) + + class Meta: + model = Order + fields = ("id", "created_at", "tickets") + + def create(self, validated_data): + with transaction.atomic(): + tickets = validated_data.pop("tickets") + order = Order.objects.create(**validated_data) + for ticket in tickets: + Ticket.objects.create(order=order, **ticket) + return order + + +class TicketListSerializer(TicketSerializer): + movie_session = MovieSessionListSerializer(many=False, read_only=True) + + +class OrderListSerializer(OrderSerializer): + tickets = TicketListSerializer(many=True, read_only=True) diff --git a/cinema/urls.py b/cinema/urls.py index e3586f00..5ad6fb5b 100644 --- a/cinema/urls.py +++ b/cinema/urls.py @@ -7,6 +7,7 @@ CinemaHallViewSet, MovieViewSet, MovieSessionViewSet, + OrderViewSet, ) router = routers.DefaultRouter() @@ -15,6 +16,7 @@ router.register("cinema_halls", CinemaHallViewSet) router.register("movies", MovieViewSet) router.register("movie_sessions", MovieSessionViewSet) +router.register("orders", OrderViewSet) urlpatterns = [path("", include(router.urls))] diff --git a/cinema/views.py b/cinema/views.py index c4ff85e9..74938944 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -1,6 +1,19 @@ -from rest_framework import viewsets +from datetime import datetime -from cinema.models import Genre, Actor, CinemaHall, Movie, MovieSession +from django.core.serializers import serialize +from django.db.models import Count, F +from rest_framework import viewsets +from rest_framework.pagination import PageNumberPagination + +from cinema.models import ( + Genre, + Actor, + CinemaHall, + Movie, + MovieSession, + Order, + Ticket +) from cinema.serializers import ( GenreSerializer, @@ -12,6 +25,9 @@ MovieDetailSerializer, MovieSessionDetailSerializer, MovieListSerializer, + OrderSerializer, + TicketSerializer, + OrderListSerializer, ) @@ -34,6 +50,10 @@ class MovieViewSet(viewsets.ModelViewSet): queryset = Movie.objects.all() serializer_class = MovieSerializer + @staticmethod + def _params_to_ints_list(query_string): + return [int(str_id) for str_id in query_string.split(",")] + def get_serializer_class(self): if self.action == "list": return MovieListSerializer @@ -43,11 +63,35 @@ def get_serializer_class(self): return MovieSerializer + def get_queryset(self): + queryset = self.queryset + + actors = self.request.query_params.get("actors") + genres = self.request.query_params.get("genres") + title = self.request.query_params.get("title") + + if actors: + actors = self._params_to_ints_list(actors) + queryset = queryset.filter(actors__id__in=actors) + + if genres: + genres = self._params_to_ints_list(genres) + queryset = queryset.filter(genres__id__in=genres) + + if title: + queryset = queryset.filter(title__icontains=title) + + return queryset.distinct() + class MovieSessionViewSet(viewsets.ModelViewSet): queryset = MovieSession.objects.all() serializer_class = MovieSessionSerializer + @staticmethod + def _str_to_date(query_string): + return datetime.strptime(query_string, "%Y-%m-%d").date() + def get_serializer_class(self): if self.action == "list": return MovieSessionListSerializer @@ -56,3 +100,67 @@ def get_serializer_class(self): return MovieSessionDetailSerializer return MovieSessionSerializer + + def get_queryset(self): + queryset = self.queryset.select_related("movie") + + if self.action == "list": + queryset = ( + queryset + .annotate( + tickets_available=( + F("cinema_hall__rows") * F("cinema_hall__seats_in_row") + - Count("tickets") + ) + ) + ) + + movie = self.request.query_params.get("movie") + date = self.request.query_params.get("date") + + if date: + date = self._str_to_date(date) + queryset = queryset.filter(show_time__date=date) + if movie: + queryset = queryset.filter(movie__id=int(movie)) + + return queryset.distinct() + + +class TicketViewSet(viewsets.ModelViewSet): + queryset = Ticket.objects.all() + serializer_class = TicketSerializer + + +class OrderSetPagination(PageNumberPagination): + page_size = 2 + page_size_query_param = "page_size" + max_page_size = 20 + + +class OrderViewSet(viewsets.ModelViewSet): + queryset = Order.objects.all() + pagination_class = OrderSetPagination + + def get_queryset(self): + queryset = self.queryset.filter(user=self.request.user) + + if self.action == "list": + queryset = ( + queryset + .prefetch_related( + "tickets__movie_session__cinema_hall", + "tickets__movie_session__movie", + ) + ) + + return queryset + + def perform_create(self, serializer): + serializer.save(user=self.request.user) + + def get_serializer_class(self): + if self.action == "list": + return OrderListSerializer + + return OrderSerializer diff --git a/cinema_service/settings.py b/cinema_service/settings.py index a7d6c992..068db3d6 100644 --- a/cinema_service/settings.py +++ b/cinema_service/settings.py @@ -124,7 +124,7 @@ USE_I18N = True -USE_TZ = False +USE_TZ = True # Static files (CSS, JavaScript, Images) From 07a2f68c7aa4af372a5a3208fd73dc75c4476d9d Mon Sep 17 00:00:00 2001 From: Pasha Date: Fri, 4 Oct 2024 10:08:10 +0300 Subject: [PATCH 2/3] change hyphenation --- cinema/views.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/cinema/views.py b/cinema/views.py index 74938944..9f796c85 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -105,13 +105,10 @@ def get_queryset(self): queryset = self.queryset.select_related("movie") if self.action == "list": - queryset = ( - queryset - .annotate( - tickets_available=( - F("cinema_hall__rows") * F("cinema_hall__seats_in_row") - - Count("tickets") - ) + queryset = queryset.annotate( + tickets_available=( + F("cinema_hall__rows") * F("cinema_hall__seats_in_row") + - Count("tickets") ) ) @@ -146,12 +143,9 @@ def get_queryset(self): queryset = self.queryset.filter(user=self.request.user) if self.action == "list": - queryset = ( - queryset - .prefetch_related( - "tickets__movie_session__cinema_hall", - "tickets__movie_session__movie", - ) + queryset = queryset.prefetch_related( + "tickets__movie_session__cinema_hall", + "tickets__movie_session__movie", ) return queryset From e8387959441afff4ce329d59447df97d0fb539cf Mon Sep 17 00:00:00 2001 From: Pasha Date: Thu, 10 Oct 2024 10:53:49 +0300 Subject: [PATCH 3/3] Solution 2 --- cinema/models.py | 4 ++-- cinema/pagination.py | 7 +++++++ cinema/serializers.py | 3 +-- cinema/views.py | 11 ++--------- 4 files changed, 12 insertions(+), 13 deletions(-) create mode 100644 cinema/pagination.py diff --git a/cinema/models.py b/cinema/models.py index 9051350e..1cefc6a3 100644 --- a/cinema/models.py +++ b/cinema/models.py @@ -89,8 +89,8 @@ def validate_row_and_seat( row: int, seat: int, movie_session: MovieSession, - error_to_raise - ): + error_to_raise: type[Exception] + ) -> None: for ticket_attr_value, ticket_attr_name, cinema_hall_attr_name in [ (row, "row", "rows"), (seat, "seat", "seats_in_row"), diff --git a/cinema/pagination.py b/cinema/pagination.py new file mode 100644 index 00000000..41aeb3e1 --- /dev/null +++ b/cinema/pagination.py @@ -0,0 +1,7 @@ +from rest_framework.pagination import PageNumberPagination + + +class OrderSetPagination(PageNumberPagination): + page_size = 2 + page_size_query_param = "page_size" + max_page_size = 20 diff --git a/cinema/serializers.py b/cinema/serializers.py index 45ae442d..c6365767 100644 --- a/cinema/serializers.py +++ b/cinema/serializers.py @@ -126,11 +126,10 @@ def create(self, validated_data): order = Order.objects.create(**validated_data) for ticket in tickets: Ticket.objects.create(order=order, **ticket) - return order class TicketListSerializer(TicketSerializer): - movie_session = MovieSessionListSerializer(many=False, read_only=True) + movie_session = MovieSessionListSerializer(read_only=True) class OrderListSerializer(OrderSerializer): diff --git a/cinema/views.py b/cinema/views.py index 9f796c85..7283f3c3 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -1,9 +1,7 @@ from datetime import datetime -from django.core.serializers import serialize from django.db.models import Count, F from rest_framework import viewsets -from rest_framework.pagination import PageNumberPagination from cinema.models import ( Genre, @@ -29,6 +27,7 @@ TicketSerializer, OrderListSerializer, ) +from cinema.pagination import OrderSetPagination class GenreViewSet(viewsets.ModelViewSet): @@ -64,7 +63,7 @@ def get_serializer_class(self): return MovieSerializer def get_queryset(self): - queryset = self.queryset + queryset = self.queryset.prefetch_related("actors", "genres") actors = self.request.query_params.get("actors") genres = self.request.query_params.get("genres") @@ -129,12 +128,6 @@ class TicketViewSet(viewsets.ModelViewSet): serializer_class = TicketSerializer -class OrderSetPagination(PageNumberPagination): - page_size = 2 - page_size_query_param = "page_size" - max_page_size = 20 - - class OrderViewSet(viewsets.ModelViewSet): queryset = Order.objects.all() pagination_class = OrderSetPagination