From c86b00fdadb0967ceab08bd41ffbbfc135b6205e Mon Sep 17 00:00:00 2001 From: Mykola Date: Mon, 2 Dec 2024 17:03:02 +0200 Subject: [PATCH] Solution --- cinema/migrations/0003_movie_duration.py | 6 +- cinema/migrations/0004_alter_genre_name.py | 6 +- ...5_alter_movie_actors_alter_movie_genres.py | 23 +++++ ...alter_moviesession_cinema_hall_and_more.py | 43 +++++++++ cinema/models.py | 45 +++++++--- cinema/serializers.py | 89 ++++++++++++++++++- cinema/tests/test_actor_api.py | 4 +- cinema/tests/test_cinema_hall_api.py | 12 +-- cinema/tests/test_movie_api.py | 4 +- cinema/tests/test_movie_session_api.py | 8 +- cinema/urls.py | 24 ++++- cinema/views.py | 63 ++++++++++++- cinema_service/urls.py | 5 +- 13 files changed, 288 insertions(+), 44 deletions(-) create mode 100644 cinema/migrations/0005_alter_movie_actors_alter_movie_genres.py create mode 100644 cinema/migrations/0006_alter_moviesession_cinema_hall_and_more.py diff --git a/cinema/migrations/0003_movie_duration.py b/cinema/migrations/0003_movie_duration.py index 7355c91a..e75f6794 100644 --- a/cinema/migrations/0003_movie_duration.py +++ b/cinema/migrations/0003_movie_duration.py @@ -6,13 +6,13 @@ class Migration(migrations.Migration): dependencies = [ - ('cinema', '0002_initial'), + ("cinema", "0002_initial"), ] operations = [ migrations.AddField( - model_name='movie', - name='duration', + model_name="movie", + name="duration", field=models.IntegerField(default=123), preserve_default=False, ), diff --git a/cinema/migrations/0004_alter_genre_name.py b/cinema/migrations/0004_alter_genre_name.py index 0a4d429e..a0c446ee 100644 --- a/cinema/migrations/0004_alter_genre_name.py +++ b/cinema/migrations/0004_alter_genre_name.py @@ -6,13 +6,13 @@ class Migration(migrations.Migration): dependencies = [ - ('cinema', '0003_movie_duration'), + ("cinema", "0003_movie_duration"), ] operations = [ migrations.AlterField( - model_name='genre', - name='name', + model_name="genre", + name="name", field=models.CharField(max_length=255, unique=True), ), ] diff --git a/cinema/migrations/0005_alter_movie_actors_alter_movie_genres.py b/cinema/migrations/0005_alter_movie_actors_alter_movie_genres.py new file mode 100644 index 00000000..9d582d9a --- /dev/null +++ b/cinema/migrations/0005_alter_movie_actors_alter_movie_genres.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0.4 on 2024-12-02 13:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("cinema", "0004_alter_genre_name"), + ] + + operations = [ + migrations.AlterField( + model_name="movie", + name="actors", + field=models.ManyToManyField(related_name="movies", to="cinema.actor"), + ), + migrations.AlterField( + model_name="movie", + name="genres", + field=models.ManyToManyField(related_name="movies", to="cinema.genre"), + ), + ] diff --git a/cinema/migrations/0006_alter_moviesession_cinema_hall_and_more.py b/cinema/migrations/0006_alter_moviesession_cinema_hall_and_more.py new file mode 100644 index 00000000..a065568c --- /dev/null +++ b/cinema/migrations/0006_alter_moviesession_cinema_hall_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 4.0.4 on 2024-12-02 13:14 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("cinema", "0005_alter_movie_actors_alter_movie_genres"), + ] + + operations = [ + migrations.AlterField( + model_name="moviesession", + name="cinema_hall", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="sessions", + to="cinema.cinemahall", + ), + ), + migrations.AlterField( + model_name="moviesession", + name="movie", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="sessions", + to="cinema.movie", + ), + ), + migrations.AlterField( + model_name="order", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="orders", + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/cinema/models.py b/cinema/models.py index c42d2a3d..756118f1 100644 --- a/cinema/models.py +++ b/cinema/models.py @@ -27,6 +27,10 @@ class Actor(models.Model): first_name = models.CharField(max_length=255) last_name = models.CharField(max_length=255) + @property + def full_name(self): + return f"{self.first_name} {self.last_name}" + def __str__(self): return self.first_name + " " + self.last_name @@ -35,8 +39,8 @@ class Movie(models.Model): title = models.CharField(max_length=255) description = models.TextField() duration = models.IntegerField() - genres = models.ManyToManyField(Genre) - actors = models.ManyToManyField(Actor) + genres = models.ManyToManyField(Genre, related_name="movies") + actors = models.ManyToManyField(Actor, related_name="movies") class Meta: ordering = ["title"] @@ -47,8 +51,14 @@ def __str__(self): class MovieSession(models.Model): show_time = models.DateTimeField() - movie = models.ForeignKey(Movie, on_delete=models.CASCADE) - cinema_hall = models.ForeignKey(CinemaHall, on_delete=models.CASCADE) + movie = models.ForeignKey( + Movie, + on_delete=models.CASCADE, + related_name="sessions") + + cinema_hall = models.ForeignKey( + CinemaHall, on_delete=models.CASCADE, related_name="sessions" + ) class Meta: ordering = ["-show_time"] @@ -59,8 +69,11 @@ def __str__(self): class Order(models.Model): created_at = models.DateTimeField(auto_now_add=True) + user = models.ForeignKey( - settings.AUTH_USER_MODEL, on_delete=models.CASCADE + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name="orders" ) def __str__(self): @@ -72,11 +85,16 @@ class Meta: class Ticket(models.Model): movie_session = models.ForeignKey( - MovieSession, on_delete=models.CASCADE, related_name="tickets" + MovieSession, + on_delete=models.CASCADE, + related_name="tickets" ) + order = models.ForeignKey( - Order, on_delete=models.CASCADE, related_name="tickets" - ) + Order, + on_delete=models.CASCADE, + related_name="tickets") + row = models.IntegerField() seat = models.IntegerField() @@ -86,8 +104,9 @@ def clean(self): (self.seat, "seat", "count_seats_in_row"), ]: count_attrs = getattr( - self.movie_session.cinema_hall, cinema_hall_attr_name - ) + self.movie_session.cinema_hall, + cinema_hall_attr_name) + if not (1 <= ticket_attr_value <= count_attrs): raise ValidationError( { @@ -99,9 +118,9 @@ def clean(self): ) def __str__(self): - return ( - f"{str(self.movie_session)} (row: {self.row}, seat: {self.seat})" - ) + return (f"" + f"{str(self.movie_session)}" + f" (row: {self.row}, seat: {self.seat})") class Meta: unique_together = ("movie_session", "row", "seat") diff --git a/cinema/serializers.py b/cinema/serializers.py index 612ca7e2..7caac528 100644 --- a/cinema/serializers.py +++ b/cinema/serializers.py @@ -1 +1,88 @@ -# write serializers here +from rest_framework import serializers +from cinema.models import (Actor, + CinemaHall, + Movie, + MovieSession, + Order, + Ticket, + Genre) + + +class CinemaHallSerializer(serializers.ModelSerializer): + class Meta: + model = CinemaHall + fields = ("id", "name", "rows", "seats_in_row", "capacity") + + +class GenreSerializer(serializers.ModelSerializer): + class Meta: + model = Genre + fields = ( + "id", + "name", + ) + + +class ActorSerializer(serializers.ModelSerializer): + class Meta: + model = Actor + fields = ("id", "first_name", "last_name", "full_name") + + +class MovieSerializer(serializers.ModelSerializer): + class Meta: + model = Movie + fields = ("id", "title", "duration", "description", "genres", "actors") + + +class MovieListSerializer(MovieSerializer): + genres = serializers.SlugRelatedField( + many=True, + read_only=True, + slug_field="name") + + actors = serializers.StringRelatedField(many=True) + + class Meta: + model = Movie + fields = "__all__" + + +class MovieRetrieveSerializer(MovieSerializer): + genres = GenreSerializer(many=True, read_only=True) + actors = ActorSerializer(many=True, read_only=True) + + +class MovieSessionSerializer(serializers.ModelSerializer): + class Meta: + model = MovieSession + fields = ("id", "show_time", "movie", "cinema_hall") + read_only_fields = ("id",) + + +class MovieSessionListSerializer(MovieSessionSerializer): + movie_title = serializers.CharField(source="movie.title", read_only=True) + + cinema_hall_name = serializers.CharField( + source="cinema_hall.name", + read_only=True) + + cinema_hall_capacity = serializers.IntegerField( + source="cinema_hall.capacity", read_only=True + ) + + class Meta: + model = MovieSession + fields = ( + "id", + "show_time", + "movie_title", + "cinema_hall_name", + "cinema_hall_capacity", + ) + read_only_fields = ("id",) + + +class MovieSessionRetrieveSerializer(MovieSessionSerializer): + movie = MovieListSerializer(many=False, read_only=True) + cinema_hall = CinemaHallSerializer(many=False, read_only=True) diff --git a/cinema/tests/test_actor_api.py b/cinema/tests/test_actor_api.py index 3fa7a469..097e42ca 100644 --- a/cinema/tests/test_actor_api.py +++ b/cinema/tests/test_actor_api.py @@ -16,9 +16,7 @@ def test_get_actors(self): response = self.client.get("/api/cinema/actors/") self.assertEqual(response.status_code, status.HTTP_200_OK) actors_full_names = [actor["full_name"] for actor in response.data] - self.assertEqual( - sorted(actors_full_names), ["George Clooney", "Keanu Reeves"] - ) + self.assertEqual(sorted(actors_full_names), ["George Clooney", "Keanu Reeves"]) def test_post_actors(self): response = self.client.post( diff --git a/cinema/tests/test_cinema_hall_api.py b/cinema/tests/test_cinema_hall_api.py index d3e9ea77..be74b47a 100644 --- a/cinema/tests/test_cinema_hall_api.py +++ b/cinema/tests/test_cinema_hall_api.py @@ -31,9 +31,7 @@ def test_get_cinema_halls(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[0]["name"], blue_hall["name"]) self.assertEqual(response.data[0]["rows"], blue_hall["rows"]) - self.assertEqual( - response.data[0]["seats_in_row"], blue_hall["seats_in_row"] - ) + self.assertEqual(response.data[0]["seats_in_row"], blue_hall["seats_in_row"]) vip_hall = { "name": "VIP", "rows": 6, @@ -43,9 +41,7 @@ def test_get_cinema_halls(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[1]["name"], vip_hall["name"]) self.assertEqual(response.data[1]["rows"], vip_hall["rows"]) - self.assertEqual( - response.data[1]["seats_in_row"], vip_hall["seats_in_row"] - ) + self.assertEqual(response.data[1]["seats_in_row"], vip_hall["seats_in_row"]) def test_post_cinema_halls(self): response = self.client.post( @@ -72,9 +68,7 @@ def test_get_cinema_hall(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["name"], vip_hall["name"]) self.assertEqual(response.data["rows"], vip_hall["rows"]) - self.assertEqual( - response.data["seats_in_row"], vip_hall["seats_in_row"] - ) + self.assertEqual(response.data["seats_in_row"], vip_hall["seats_in_row"]) self.assertEqual(response.data["capacity"], vip_hall["capacity"]) def test_get_invalid_cinema_hall(self): diff --git a/cinema/tests/test_movie_api.py b/cinema/tests/test_movie_api.py index 9d6b76bf..d4852de5 100644 --- a/cinema/tests/test_movie_api.py +++ b/cinema/tests/test_movie_api.py @@ -83,9 +83,7 @@ def test_get_movie(self): self.assertEqual(response.data["genres"][1]["name"], "Comedy") self.assertEqual(response.data["actors"][0]["first_name"], "Kate") self.assertEqual(response.data["actors"][0]["last_name"], "Winslet") - self.assertEqual( - response.data["actors"][0]["full_name"], "Kate Winslet" - ) + self.assertEqual(response.data["actors"][0]["full_name"], "Kate Winslet") def test_get_invalid_movie(self): response = self.client.get("/api/cinema/movies/100/") diff --git a/cinema/tests/test_movie_session_api.py b/cinema/tests/test_movie_session_api.py index cbd02713..d165662f 100644 --- a/cinema/tests/test_movie_session_api.py +++ b/cinema/tests/test_movie_session_api.py @@ -46,9 +46,7 @@ def test_get_movie_sessions(self): } self.assertEqual(movie_sessions.status_code, status.HTTP_200_OK) for field in movie_session: - self.assertEqual( - movie_sessions.data[0][field], movie_session[field] - ) + self.assertEqual(movie_sessions.data[0][field], movie_session[field]) def test_post_movie_session(self): movies = self.client.post( @@ -67,9 +65,7 @@ def test_get_movie_session(self): response = self.client.get("/api/cinema/movie_sessions/1/") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["movie"]["title"], "Titanic") - self.assertEqual( - response.data["movie"]["description"], "Titanic description" - ) + self.assertEqual(response.data["movie"]["description"], "Titanic description") self.assertEqual(response.data["movie"]["duration"], 123) self.assertEqual(response.data["movie"]["genres"], ["Drama", "Comedy"]) self.assertEqual(response.data["movie"]["actors"], ["Kate Winslet"]) diff --git a/cinema/urls.py b/cinema/urls.py index 420f8e8c..101b2931 100644 --- a/cinema/urls.py +++ b/cinema/urls.py @@ -1 +1,23 @@ -# write urls here +from django.urls import path, include +from rest_framework import routers +from cinema.views import ( + CinemaHallViewSet, + GenreViewSet, + ActorViewSet, + MovieViewSet, + MovieSessionViewSet, +) + +app_name = "cinema" + +router = routers.DefaultRouter() +router.register("cinema_halls", CinemaHallViewSet) +router.register("genres", GenreViewSet) +router.register("actors", ActorViewSet) +router.register("movies", MovieViewSet) + +router.register("movie_sessions", MovieSessionViewSet) + +urlpatterns = [ + path("", include(router.urls)), +] diff --git a/cinema/views.py b/cinema/views.py index ae87bfde..2150fff0 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -1 +1,62 @@ -# write views here +from rest_framework import viewsets +from cinema.models import CinemaHall, Genre, Actor, Movie, MovieSession +from cinema.serializers import ( + CinemaHallSerializer, + GenreSerializer, + ActorSerializer, + MovieSerializer, + MovieListSerializer, + MovieRetrieveSerializer, + MovieSessionSerializer, + MovieSessionRetrieveSerializer, + MovieSessionListSerializer, +) + + +class CinemaHallViewSet(viewsets.ModelViewSet): + queryset = CinemaHall.objects.all() + serializer_class = CinemaHallSerializer + + +class GenreViewSet(viewsets.ModelViewSet): + queryset = Genre.objects.all() + serializer_class = GenreSerializer + + +class ActorViewSet(viewsets.ModelViewSet): + queryset = Actor.objects.all() + serializer_class = ActorSerializer + + +class MovieViewSet(viewsets.ModelViewSet): + queryset = Movie.objects.all().prefetch_related("genres", "actors") + serializer_class = MovieSerializer + + def get_serializer_class(self): + + if self.action == "list": + return MovieListSerializer + elif self.action == "retrieve": + return MovieRetrieveSerializer + + return MovieSerializer + + +class MovieSessionViewSet(viewsets.ModelViewSet): + queryset = MovieSession.objects.all() + serializer_class = MovieSessionSerializer + + def get_serializer_class(self): + if self.action == "list": + return MovieSessionListSerializer + elif self.action == "retrieve": + return MovieSessionRetrieveSerializer + + return MovieSessionSerializer + + def get_queryset(self): + queryset = self.queryset + if self.action in ["list", "retrieve"]: + queryset = queryset.prefetch_related("movie", "cinema_hall") + + return queryset diff --git a/cinema_service/urls.py b/cinema_service/urls.py index 083932c6..ef2ed9c6 100644 --- a/cinema_service/urls.py +++ b/cinema_service/urls.py @@ -1,6 +1,9 @@ from django.contrib import admin -from django.urls import path +from django.urls import path, include urlpatterns = [ path("admin/", admin.site.urls), + path("api/cinema/", + include("cinema.urls", namespace="cinema") + ), ]