diff --git a/.gitignore b/.gitignore index fd1f3e48..0d634a9e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ venv/ .pytest_cache/ **__pycache__/ *.pyc -app/db.sqlite3 \ No newline at end of file +app/db.sqlite3 +db.sqlite3 \ No newline at end of file diff --git a/cinema/pagination.py b/cinema/pagination.py new file mode 100644 index 00000000..587fec42 --- /dev/null +++ b/cinema/pagination.py @@ -0,0 +1,7 @@ +from rest_framework.pagination import PageNumberPagination + + +class OrderPagination(PageNumberPagination): + page_size = 5 + page_size_query_param = "page_size" + max_page_size = 10 diff --git a/cinema/serializers.py b/cinema/serializers.py index a1a4d7d4..bb9b1bef 100644 --- a/cinema/serializers.py +++ b/cinema/serializers.py @@ -1,6 +1,15 @@ +from django.db import transaction from rest_framework import serializers -from cinema.models import Genre, Actor, CinemaHall, Movie, MovieSession +from cinema.models import ( + Genre, + Actor, + CinemaHall, + Movie, + MovieSession, + Ticket, + Order, +) class GenreSerializer(serializers.ModelSerializer): @@ -60,6 +69,8 @@ class MovieSessionListSerializer(MovieSessionSerializer): source="cinema_hall.capacity", read_only=True ) + tickets_available = serializers.IntegerField(read_only=True) + class Meta: model = MovieSession fields = ( @@ -68,13 +79,61 @@ class Meta: "movie_title", "cinema_hall_name", "cinema_hall_capacity", + "tickets_available", ) +class TicketSeatRowSerializer(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 = TicketSeatRowSerializer( + many=True, + read_only=True, + source="tickets", + ) + class Meta: model = MovieSession - fields = ("id", "show_time", "movie", "cinema_hall") + fields = ("id", "show_time", "movie", "cinema_hall", "taken_places") + + +class TicketSerializer(serializers.ModelSerializer): + movie_session = MovieSessionListSerializer(many=False, read_only=True) + + class Meta: + model = Ticket + fields = ("id", "row", "seat", "movie_session") + + def validate(self, data): + data = super(TicketSerializer, self).validate(data) + + ticket = Ticket( + movie_session=data.get("movie_session"), + row=data.get("row"), + seat=data.get("seat"), + ) + ticket.full_clean() + return data + + +class OrderSerializer(serializers.ModelSerializer): + tickets = TicketSerializer(many=True, read_only=False, allow_empty=False) + + class Meta: + model = Order + fields = ["id", "tickets", "created_at"] + + 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 diff --git a/cinema/urls.py b/cinema/urls.py index e3586f00..3b5c862b 100644 --- a/cinema/urls.py +++ b/cinema/urls.py @@ -7,6 +7,8 @@ CinemaHallViewSet, MovieViewSet, MovieSessionViewSet, + TicketViewSet, + OrderViewSet, ) router = routers.DefaultRouter() @@ -15,6 +17,8 @@ router.register("cinema_halls", CinemaHallViewSet) router.register("movies", MovieViewSet) router.register("movie_sessions", MovieSessionViewSet) +router.register("tickets", TicketViewSet) +router.register("orders", OrderViewSet) urlpatterns = [path("", include(router.urls))] diff --git a/cinema/views.py b/cinema/views.py index c4ff85e9..d69709c1 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -1,7 +1,17 @@ +from django.db.models import F, Count from rest_framework import viewsets -from cinema.models import Genre, Actor, CinemaHall, Movie, MovieSession +from cinema.models import ( + Genre, + Actor, + CinemaHall, + Movie, + MovieSession, + Ticket, + Order, +) +from cinema.pagination import OrderPagination from cinema.serializers import ( GenreSerializer, ActorSerializer, @@ -12,6 +22,8 @@ MovieDetailSerializer, MovieSessionDetailSerializer, MovieListSerializer, + TicketSerializer, + OrderSerializer, ) @@ -43,6 +55,28 @@ def get_serializer_class(self): return MovieSerializer + def get_queryset(self): + queryset = super().get_queryset() + if self.action == ("list", "retrieve"): + queryset = self.queryset.prefetch_related("actors", "genres") + + actors = self.request.query_params.get("actors") + genres = self.request.query_params.get("genres") + title = self.request.query_params.get("title") + + if actors: + actors_ids = [int(str_id) for str_id in actors.split(",")] + queryset = queryset.filter(actors__id__in=actors_ids) + + if genres: + genres_ids = [int(str_id) for str_id in genres.split(",")] + queryset = queryset.filter(genres__id__in=genres_ids) + + if title: + queryset = queryset.filter(title__icontains=title) + + return queryset.distinct() + class MovieSessionViewSet(viewsets.ModelViewSet): queryset = MovieSession.objects.all() @@ -56,3 +90,47 @@ def get_serializer_class(self): return MovieSessionDetailSerializer return MovieSessionSerializer + + def get_queryset(self): + queryset = super().get_queryset() + + if self.action == "retrieve": + queryset = queryset.select_related("movie") + + elif self.action == "list": + queryset = ( + queryset.select_related("cinema_hall").annotate( + tickets_available=( + F("cinema_hall__rows") * F("cinema_hall__seats_in_row") # noqa + - Count("tickets") # noqa + ) + ) + ).order_by("id") + + movie = self.request.query_params.get("movie") + date = self.request.query_params.get("date") + + if movie: + queryset = queryset.filter(movie__id=movie) + + if date: + queryset = queryset.filter(show_time__date=date) + + return queryset.distinct() + + +class TicketViewSet(viewsets.ModelViewSet): + queryset = Ticket.objects.all() + serializer_class = TicketSerializer + + +class OrderViewSet(viewsets.ModelViewSet): + queryset = Order.objects.all() + 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)