diff --git a/cinema/models.py b/cinema/models.py index f18f166c..2b87fec7 100644 --- a/cinema/models.py +++ b/cinema/models.py @@ -1,6 +1,6 @@ +from django.conf import settings from django.core.exceptions import ValidationError from django.db import models -from django.conf import settings class CinemaHall(models.Model): @@ -84,14 +84,13 @@ class Ticket(models.Model): row = models.IntegerField() seat = models.IntegerField() - def clean(self): + @staticmethod + def validate_ticket(row, seat, cinema_hall): 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 - ) + count_attrs = getattr(cinema_hall, cinema_hall_attr_name) if not (1 <= ticket_attr_value <= count_attrs): raise ValidationError( { @@ -102,16 +101,11 @@ def clean(self): } ) - def save( - self, - force_insert=False, - force_update=False, - using=None, - update_fields=None, - ): - self.full_clean() - super(Ticket, self).save( - force_insert, force_update, using, update_fields + def clean(self): + Ticket.validate_ticket( + self.row, + self.seat, + self.movie_session.cinema_hall ) def __str__(self): diff --git a/cinema/serializers.py b/cinema/serializers.py index a1a4d7d4..2b11c5a7 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): @@ -35,6 +45,10 @@ class MovieListSerializer(MovieSerializer): many=True, read_only=True, slug_field="full_name" ) + class Meta: + model = Movie + fields = "__all__" + class MovieDetailSerializer(MovieSerializer): genres = GenreSerializer(many=True, read_only=True) @@ -59,6 +73,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 +83,69 @@ class Meta: "movie_title", "cinema_hall_name", "cinema_hall_capacity", + "tickets_available" ) +class TicketPlacesSerializer(serializers.ModelSerializer): + class Meta: + model = Ticket + fields = ("row", "seat") + + class MovieSessionDetailSerializer(MovieSessionSerializer): movie = MovieListSerializer(many=False, read_only=True) cinema_hall = CinemaHallSerializer(many=False, read_only=True) + taken_places = TicketPlacesSerializer( + source="tickets", many=True, read_only=True + ) class Meta: model = MovieSession - fields = ("id", "show_time", "movie", "cinema_hall") + fields = ( + "id", + "show_time", + "movie", + "cinema_hall", + "taken_places" + ) + + +class TicketSerializer(serializers.ModelSerializer): + + def validate(self, attrs): + data = super(TicketSerializer, self).validate(attrs=attrs) + Ticket.validate_ticket( + attrs["row"], + attrs["seat"], + attrs["movie_session"].cinema_hall, + ) + return data + + class Meta: + model = Ticket + fields = ("id", "row", "seat", "movie_session") + + +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_data = validated_data.pop("tickets") + order = Order.objects.create(**validated_data) + for ticket_data in tickets_data: + Ticket.objects.create(order=order, **ticket_data) + 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..b997592c 100644 --- a/cinema/urls.py +++ b/cinema/urls.py @@ -6,7 +6,7 @@ ActorViewSet, CinemaHallViewSet, MovieViewSet, - MovieSessionViewSet, + MovieSessionViewSet, OrderViewSet, ) router = routers.DefaultRouter() @@ -15,6 +15,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..f6a37098 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -1,6 +1,10 @@ +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 +from cinema.models import Genre, Actor, CinemaHall, Movie, MovieSession, Order from cinema.serializers import ( GenreSerializer, @@ -12,6 +16,8 @@ MovieDetailSerializer, MovieSessionDetailSerializer, MovieListSerializer, + OrderSerializer, + OrderListSerializer, ) @@ -43,9 +49,40 @@ def get_serializer_class(self): return MovieSerializer + @staticmethod + def _make_int_from_str(query): + return [int(num) for num in query.split(",")] + + def get_queryset(self): + query_set = self.queryset.prefetch_related("actors", "genres") + title = self.request.query_params.get("title") + genres = self.request.query_params.get("genres") + actors = self.request.query_params.get("actors") + + if title: + query_set = query_set.filter(title__icontains=title) + + if genres: + genres_ids = self._make_int_from_str(genres) + query_set = query_set.filter(genres__id__in=genres_ids) + + if actors: + actors_ids = self._make_int_from_str(actors) + query_set = query_set.filter(actors__id__in=actors_ids) + + return query_set.distinct() + class MovieSessionViewSet(viewsets.ModelViewSet): - queryset = MovieSession.objects.all() + queryset = ( + MovieSession.objects.all() + .select_related("movie", "cinema_hall") + .annotate( + tickets_available=F("cinema_hall__rows") + * F("cinema_hall__seats_in_row") + - Count("tickets") - 1 + ) + ) serializer_class = MovieSessionSerializer def get_serializer_class(self): @@ -56,3 +93,40 @@ def get_serializer_class(self): return MovieSessionDetailSerializer return MovieSessionSerializer + + def get_queryset(self): + query_set = self.queryset + movie = self.request.query_params.get("movie") + date = self.request.query_params.get("date") + if movie: + movie_ids = int(movie) + query_set = query_set.filter(movie__id=movie_ids) + + if date: + date = datetime.strptime(date, "%Y-%m-%d").date() + query_set = query_set.filter(show_time__date=date) + + return query_set + + +class OrderPagination(PageNumberPagination): + page_size = 10 + page_size_query_param = "page_size" + max_page_size = 100 + + +class OrderViewSet(viewsets.ModelViewSet): + queryset = Order.objects.select_related("user") + serializer_class = OrderSerializer + pagination_class = OrderPagination + + def get_queryset(self): + return self.queryset.filter(user=self.request.user) + + def perform_create(self, serializer): + serializer.save(user=self.request.user) + + def get_serializer_class(self): + if self.action == "list": + return OrderListSerializer + return OrderSerializer