From 431587ddc93e1c14567c42f71d20e02e029c6bbf Mon Sep 17 00:00:00 2001 From: Kateryna Date: Fri, 20 Dec 2024 16:35:46 +0000 Subject: [PATCH 1/2] Solution --- .gitignore | 2 +- cinema/models.py | 46 ++++++++++++------ cinema/serializers.py | 95 +++++++++++++++++++++++++++++++++++++- cinema/urls.py | 2 + cinema/views.py | 67 ++++++++++++++++++++++++++- cinema_service/settings.py | 9 +++- 6 files changed, 199 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index fd1f3e48..bbae997b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ venv/ .pytest_cache/ **__pycache__/ *.pyc -app/db.sqlite3 \ No newline at end of file +db.sqlite3 diff --git a/cinema/models.py b/cinema/models.py index f18f166c..366c6ffd 100644 --- a/cinema/models.py +++ b/cinema/models.py @@ -84,23 +84,39 @@ class Ticket(models.Model): row = models.IntegerField() seat = models.IntegerField() + @staticmethod + def validate_ticket( + row: int, + seat: int, + movie_session: MovieSession, + error_to_raise + ): + cinema_hall = movie_session.cinema_hall + errors = {} + + constraints = [ + (row, "row", cinema_hall.rows), + (seat, "seat", cinema_hall.seats_in_row) + ] + + for value, attr_name, limit in constraints: + if not (1 <= value <= limit): + errors[attr_name] = (f"{attr_name.capitalize()} " + f"must be in range 1 to {limit}") + + if errors: + raise error_to_raise(errors) + def clean(self): - for ticket_attr_value, ticket_attr_name, cinema_hall_attr_name in [ - (self.row, "row", "rows"), - (self.seat, "seat", "seats_in_row"), - ]: - count_attrs = getattr( - self.movie_session.cinema_hall, cinema_hall_attr_name + try: + Ticket.validate_ticket( + row=self.row, + seat=self.seat, + movie_session=self.movie_session, + error_to_raise=ValidationError ) - if not (1 <= ticket_attr_value <= count_attrs): - raise ValidationError( - { - ticket_attr_name: f"{ticket_attr_name} " - f"number must be in available range: " - f"(1, {cinema_hall_attr_name}): " - f"(1, {count_attrs})" - } - ) + except ValidationError as e: + raise ValidationError(e.message_dict) def save( self, diff --git a/cinema/serializers.py b/cinema/serializers.py index a1a4d7d4..d0bd68b7 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.SerializerMethodField() class Meta: model = MovieSession @@ -68,13 +78,94 @@ class Meta: "movie_title", "cinema_hall_name", "cinema_hall_capacity", + "tickets_available" ) + def get_tickets_available(self, obj): + total_capacity = obj.cinema_hall.capacity + taken_tickets = Ticket.objects.filter(movie_session=obj).count() + return total_capacity - taken_tickets + 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 TicketSerializer(serializers.ModelSerializer): + class Meta: + model = Ticket + fields = ( + "id", + "row", + "seat", + "movie_session", + ) + + def validate(self, attrs): + Ticket.validate_ticket( + row=attrs["row"], + seat=attrs["seat"], + movie_session=attrs["movie_session"], + error_to_raise=serializers.ValidationError + ) + return attrs + + +class TicketListSerializer(TicketSerializer): + movie_session = MovieSessionListSerializer(many=False, read_only=True) + + class Meta: + model = Ticket + fields = ("id", "row", "seat", "movie_session") + + +class TicketCreateSerializer(TicketSerializer): + movie_session = serializers.PrimaryKeyRelatedField( + queryset=MovieSession.objects.all() + ) + + +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 OrderListSerializer(OrderSerializer): + tickets = TicketListSerializer(many=True, read_only=True) + + class Meta: + model = Order + fields = ( + "id", + "tickets", + "created_at" + ) 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..2f119912 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -1,6 +1,13 @@ from rest_framework import viewsets - -from cinema.models import Genre, Actor, CinemaHall, Movie, MovieSession +from rest_framework.pagination import PageNumberPagination + +from cinema.models import ( + Genre, + Actor, + CinemaHall, + Movie, + MovieSession, Order, +) from cinema.serializers import ( GenreSerializer, @@ -12,9 +19,16 @@ MovieDetailSerializer, MovieSessionDetailSerializer, MovieListSerializer, + OrderSerializer, OrderListSerializer, ) +class OrderPagination(PageNumberPagination): + page_size = 5 + page_size_query_param = "page_size" + max_page_size = 100 + + class GenreViewSet(viewsets.ModelViewSet): queryset = Genre.objects.all() serializer_class = GenreSerializer @@ -34,6 +48,25 @@ class MovieViewSet(viewsets.ModelViewSet): queryset = Movie.objects.all() serializer_class = MovieSerializer + def get_queryset(self): + queryset = Movie.objects.all() + + genres = self.request.query_params.get("genres", None) + if genres: + genres_ids = [int(str_id) for str_id in genres.split(",")] + queryset = queryset.filter(genres__id__in=genres_ids) + + actors = self.request.query_params.get("actors", None) + if actors: + actors_ids = [int(str_id) for str_id in actors.split(",")] + queryset = queryset.filter(actors__id__in=actors_ids) + + title = self.request.query_params.get("title", None) + if title: + queryset = queryset.filter(title__icontains=title) + + return queryset.distinct() + def get_serializer_class(self): if self.action == "list": return MovieListSerializer @@ -48,6 +81,19 @@ class MovieSessionViewSet(viewsets.ModelViewSet): queryset = MovieSession.objects.all() serializer_class = MovieSessionSerializer + def get_queryset(self): + queryset = MovieSession.objects.all() + + date = self.request.query_params.get("date", None) + if date: + queryset = queryset.filter(show_time__date=date) + + movie_id = self.request.query_params.get("movie", None) + if movie_id: + queryset = queryset.filter(movie__id=movie_id) + + return queryset.distinct() + def get_serializer_class(self): if self.action == "list": return MovieSessionListSerializer @@ -56,3 +102,20 @@ 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 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 in ["list", "retrieve"]: + return OrderListSerializer + return OrderSerializer diff --git a/cinema_service/settings.py b/cinema_service/settings.py index a7d6c992..aef845af 100644 --- a/cinema_service/settings.py +++ b/cinema_service/settings.py @@ -120,11 +120,11 @@ LANGUAGE_CODE = "en-us" -TIME_ZONE = "UTC" +TIME_ZONE = "Europe/Lisbon" USE_I18N = True -USE_TZ = False +USE_TZ = True # Static files (CSS, JavaScript, Images) @@ -136,3 +136,8 @@ # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +# REST_FRAMEWORK = { +# "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", +# "PAGE_SIZE": 10, +# } From 10456d694ca8facc81e511e59f9ed1a62f8c81dd Mon Sep 17 00:00:00 2001 From: Kateryna Date: Fri, 20 Dec 2024 16:37:55 +0000 Subject: [PATCH 2/2] Solution --- cinema_service/settings.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cinema_service/settings.py b/cinema_service/settings.py index aef845af..a8da4902 100644 --- a/cinema_service/settings.py +++ b/cinema_service/settings.py @@ -136,8 +136,3 @@ # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" - -# REST_FRAMEWORK = { -# "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", -# "PAGE_SIZE": 10, -# }