diff --git a/cinema/migrations/0005_alter_ticket_options.py b/cinema/migrations/0005_alter_ticket_options.py new file mode 100644 index 00000000..16b78c36 --- /dev/null +++ b/cinema/migrations/0005_alter_ticket_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.1 on 2024-10-03 23:09 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("cinema", "0004_alter_genre_name"), + ] + + operations = [ + migrations.AlterModelOptions( + name="ticket", + options={"ordering": ["row", "seat"]}, + ), + ] diff --git a/cinema/migrations/0006_alter_ticket_options.py b/cinema/migrations/0006_alter_ticket_options.py new file mode 100644 index 00000000..7af1480c --- /dev/null +++ b/cinema/migrations/0006_alter_ticket_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.1 on 2024-10-04 10:46 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("cinema", "0005_alter_ticket_options"), + ] + + operations = [ + migrations.AlterModelOptions( + name="ticket", + options={}, + ), + ] diff --git a/cinema/pagination.py b/cinema/pagination.py new file mode 100644 index 00000000..16959e9a --- /dev/null +++ b/cinema/pagination.py @@ -0,0 +1,7 @@ +from rest_framework.pagination import PageNumberPagination + + +class OrderPagination(PageNumberPagination): + page_size = 3 + page_size_query_param = "page_size" + max_page_size = 3 diff --git a/cinema/serializers.py b/cinema/serializers.py index a1a4d7d4..8c131558 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): @@ -59,6 +68,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 +78,55 @@ class Meta: "movie_title", "cinema_hall_name", "cinema_hall_capacity", + "tickets_available", ) +class TicketSerializer(serializers.ModelSerializer): + class Meta: + model = Ticket + fields = ("id", "row", "seat", "movie_session") + + +class TicketListSerializer(TicketSerializer): + movie_session = MovieSessionListSerializer(many=False, read_only=True) + + +class TicketSeatsSerializer(TicketSerializer): + 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 = TicketSeatsSerializer( + 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 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 + + +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..0cad6b7a 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -1,6 +1,18 @@ +from datetime import datetime +from idlelib.query import Query + +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, + Order +) +from cinema.pagination import OrderPagination from cinema.serializers import ( GenreSerializer, @@ -12,6 +24,8 @@ MovieDetailSerializer, MovieSessionDetailSerializer, MovieListSerializer, + OrderSerializer, + OrderListSerializer, ) @@ -31,9 +45,35 @@ class CinemaHallViewSet(viewsets.ModelViewSet): class MovieViewSet(viewsets.ModelViewSet): - queryset = Movie.objects.all() + 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 @@ -48,6 +88,34 @@ class MovieSessionViewSet(viewsets.ModelViewSet): queryset = MovieSession.objects.all() serializer_class = MovieSessionSerializer + def get_queryset(self): + queryset = self.queryset.select_related("movie", "cinema_hall") + + if self.action == "list": + queryset = ( + queryset + .prefetch_related("tickets") + .annotate( + tickets_available=( + F("cinema_hall__rows") + * F("cinema_hall__seats_in_row") + - Count("tickets") + ) + ) + ) + + date = self.request.query_params.get("date") + movie_id_str = self.request.query_params.get("movie") + + 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 @@ -56,3 +124,24 @@ def get_serializer_class(self): return MovieSessionDetailSerializer return MovieSessionSerializer + + +class OrderViewSet(viewsets.ModelViewSet): + queryset = Order.objects.all() + serializer_class = OrderSerializer + pagination_class = OrderPagination + + def get_queryset(self): + return Order.objects.prefetch_related( + "tickets__movie_session__movie", + "tickets__movie_session__cinema_hall" + ).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/settings.py b/cinema_service/settings.py index a7d6c992..57f190e9 100644 --- a/cinema_service/settings.py +++ b/cinema_service/settings.py @@ -90,7 +90,6 @@ } } - # Password validation # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators