From 058c5a7c51893ccf027f8957a2cd45b3da73162a Mon Sep 17 00:00:00 2001 From: Ivan Shakhman Date: Sun, 29 Sep 2024 23:25:17 +0300 Subject: [PATCH 1/6] Solution --- ...emahall_genre_movie_actors_movie_genres.py | 47 ++++++ ...ame_seat_in_row_cinemahall_seats_in_row.py | 18 +++ cinema/models.py | 23 +++ cinema/serializers.py | 53 ++++++- cinema/urls.py | 42 +++++- cinema/views.py | 136 +++++++++++++++--- 6 files changed, 291 insertions(+), 28 deletions(-) create mode 100644 cinema/migrations/0002_actor_cinemahall_genre_movie_actors_movie_genres.py create mode 100644 cinema/migrations/0003_rename_seat_in_row_cinemahall_seats_in_row.py diff --git a/cinema/migrations/0002_actor_cinemahall_genre_movie_actors_movie_genres.py b/cinema/migrations/0002_actor_cinemahall_genre_movie_actors_movie_genres.py new file mode 100644 index 000000000..112240235 --- /dev/null +++ b/cinema/migrations/0002_actor_cinemahall_genre_movie_actors_movie_genres.py @@ -0,0 +1,47 @@ +# Generated by Django 4.1 on 2024-09-29 18:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cinema', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Actor', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('first_name', models.CharField(max_length=255)), + ('last_name', models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name='CinemaHall', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('rows', models.IntegerField()), + ('seat_in_row', models.IntegerField()), + ], + ), + migrations.CreateModel( + name='Genre', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, unique=True)), + ], + ), + migrations.AddField( + model_name='movie', + name='actors', + field=models.ManyToManyField(to='cinema.actor'), + ), + migrations.AddField( + model_name='movie', + name='genres', + field=models.ManyToManyField(to='cinema.genre'), + ), + ] diff --git a/cinema/migrations/0003_rename_seat_in_row_cinemahall_seats_in_row.py b/cinema/migrations/0003_rename_seat_in_row_cinemahall_seats_in_row.py new file mode 100644 index 000000000..6277c7d1b --- /dev/null +++ b/cinema/migrations/0003_rename_seat_in_row_cinemahall_seats_in_row.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1 on 2024-09-29 20:03 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('cinema', '0002_actor_cinemahall_genre_movie_actors_movie_genres'), + ] + + operations = [ + migrations.RenameField( + model_name='cinemahall', + old_name='seat_in_row', + new_name='seats_in_row', + ), + ] diff --git a/cinema/models.py b/cinema/models.py index cc477513f..d4ba26c07 100644 --- a/cinema/models.py +++ b/cinema/models.py @@ -1,10 +1,33 @@ from django.db import models +class Actor(models.Model): + first_name = models.CharField(max_length=255) + last_name = models.CharField(max_length=255) + + def __str__(self): + return f"{self.first_name} {self.last_name}" + + +class Genre(models.Model): + name = models.CharField(max_length=255, unique=True) + + def __str__(self): + return self.name + + class Movie(models.Model): title = models.CharField(max_length=255) description = models.TextField() duration = models.IntegerField() + actors = models.ManyToManyField(Actor) + genres = models.ManyToManyField(Genre) def __str__(self): return self.title + + +class CinemaHall(models.Model): + name = models.CharField(max_length=255) + rows = models.IntegerField() + seats_in_row = models.IntegerField() diff --git a/cinema/serializers.py b/cinema/serializers.py index 050db5771..14fd56412 100644 --- a/cinema/serializers.py +++ b/cinema/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers -from cinema.models import Movie +from cinema.models import Movie, Genre, Actor, CinemaHall class MovieSerializer(serializers.Serializer): @@ -22,3 +22,54 @@ def update(self, instance, validated_data): instance.save() return instance + + +class GenreSerializer(serializers.Serializer): + id = serializers.IntegerField(read_only=True) + name = serializers.CharField(max_length=255) + + def create(self, validated_data): + return Genre.objects.create(**validated_data) + + def update(self, instance, validated_data): + instance.name = validated_data.get("name", instance.name) + instance.save() + return instance + + +class ActorSerializer(serializers.Serializer): + id = serializers.IntegerField(read_only=True) + first_name = serializers.CharField(max_length=255) + last_name = serializers.CharField(max_length=255) + + def create(self, validated_data): + return Actor.objects.create(**validated_data) + + def update(self, instance, validated_data): + instance.first_name = ( + validated_data.get("first_name", instance.first_name) + ) + instance.last_name = ( + validated_data.get("last_name", instance.last_name) + ) + instance.save() + return instance + + +class CinemaHallSerializer(serializers.Serializer): + id = serializers.IntegerField(read_only=True) + name = serializers.CharField(max_length=255) + rows = serializers.IntegerField() + seats_in_row = serializers.IntegerField() + + def create(self, validated_data): + return CinemaHall.objects.create(**validated_data) + + def update(self, instance, validated_data): + instance.name = validated_data.get("name", instance.name) + instance.rows = validated_data.get("rows", instance.rows) + instance.seats_in_row = ( + validated_data.get("seats_in_row", instance.seats_in_row) + ) + instance.save() + return instance diff --git a/cinema/urls.py b/cinema/urls.py index 1ae7d5cb0..8a02c0035 100644 --- a/cinema/urls.py +++ b/cinema/urls.py @@ -1,10 +1,44 @@ -from django.urls import path +from django.urls import path, include +from rest_framework import routers -from cinema.views import movie_list, movie_detail +from cinema.views import ( + GenreList, + GenreDetail, + ActorList, + ActorDetail, + CinemaHallViewSet, MovieViewSet +) + +cinema_halls = CinemaHallViewSet.as_view(actions={ + "get": "list", + "post": "create", +}) +cinema_hall_detail = CinemaHallViewSet.as_view(actions={ + "get": "retrieve", + "put": "update", + "patch": "partial_update", + "delete": "destroy" +}) + +router = routers.DefaultRouter() +router.register("movies", MovieViewSet) urlpatterns = [ - path("movies/", movie_list, name="movie-list"), - path("movies//", movie_detail, name="movie-detail"), + path("genres/", GenreList.as_view(), name="genre-list"), + path("genres//", GenreDetail.as_view(), name="genre-detail"), + path("actors/", ActorList.as_view(), name="actor-list"), + path("actors//", ActorDetail.as_view(), name="actor-detail"), + path( + "cinema_halls/", + cinema_halls, + name="cinema-hall-list" + ), + path( + "cinema_halls//", + cinema_hall_detail, + name="cinema-hall-detail" + ), + path("", include(router.urls)), ] app_name = "cinema" diff --git a/cinema/views.py b/cinema/views.py index 78ba8a79c..31b87c2fe 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -1,45 +1,135 @@ -from rest_framework.decorators import api_view +from django.shortcuts import get_object_or_404 +from rest_framework import status, mixins +from rest_framework.generics import GenericAPIView +from rest_framework.mixins import ( + ListModelMixin, + CreateModelMixin, + RetrieveModelMixin, + UpdateModelMixin, + DestroyModelMixin +) from rest_framework.response import Response -from rest_framework import status +from rest_framework.views import APIView +from rest_framework.viewsets import GenericViewSet, ModelViewSet + +from cinema.models import Movie, Genre, Actor, CinemaHall +from cinema.serializers import ( + MovieSerializer, + GenreSerializer, + ActorSerializer, + CinemaHallSerializer +) -from django.shortcuts import get_object_or_404 -from cinema.models import Movie -from cinema.serializers import MovieSerializer +class GenreList(APIView): + def get(self, request): + genres = Genre.objects.all() + serializer = GenreSerializer(genres, many=True) -@api_view(["GET", "POST"]) -def movie_list(request): - if request.method == "GET": - movies = Movie.objects.all() - serializer = MovieSerializer(movies, many=True) return Response(serializer.data, status=status.HTTP_200_OK) - if request.method == "POST": - serializer = MovieSerializer(data=request.data) + def post(self, request): + serializer = GenreSerializer(data=request.data) + if serializer.is_valid(): serializer.save() - return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response( + serializer.data, + status=status.HTTP_201_CREATED + ) + + return Response( + serializer.errors, + status=status.HTTP_400_BAD_REQUEST + ) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) +class GenreDetail(APIView): -@api_view(["GET", "PUT", "DELETE"]) -def movie_detail(request, pk): - movie = get_object_or_404(Movie, pk=pk) + def get_object(self, pk): + return get_object_or_404(Genre, pk=pk) - if request.method == "GET": - serializer = MovieSerializer(movie) + def get(self, request, pk): + serializer = GenreSerializer(self.get_object(pk)) return Response(serializer.data, status=status.HTTP_200_OK) - if request.method == "PUT": - serializer = MovieSerializer(movie, data=request.data) + def put(self, request, pk): + serializer = GenreSerializer(self.get_object(pk), data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - if request.method == "DELETE": - movie.delete() + def patch(self, request, pk): + serializer = GenreSerializer(self.get_object(pk), data=request.data) + if serializer.is_valid(): + serializer.save() + return Response( + serializer.data, + status=status.HTTP_200_OK + ) + + return Response( + serializer.errors, + status=status.HTTP_400_BAD_REQUEST + ) + + def delete(self, request, pk): + self.get_object(pk).delete() return Response(status=status.HTTP_204_NO_CONTENT) + + +class ActorList( + mixins.ListModelMixin, + GenericAPIView, + mixins.CreateModelMixin +): + queryset = Actor.objects.all() + serializer_class = ActorSerializer + + def get(self, request, *args, **kwargs): + return self.list(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + return self.create(request, *args, **kwargs) + + +class ActorDetail( + GenericAPIView, + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin +): + queryset = Actor.objects.all() + serializer_class = ActorSerializer + + def get(self, request, *args, **kwargs): + return self.retrieve(request, *args, **kwargs) + + def put(self, request, *args, **kwargs): + return self.update(request, *args, **kwargs) + + def patch(self, request, *args, **kwargs): + return self.partial_update(request, *args, **kwargs) + + def delete(self, request, *args, **kwargs): + return self.destroy(request, *args, **kwargs) + + +class CinemaHallViewSet( + GenericViewSet, + ListModelMixin, + CreateModelMixin, + RetrieveModelMixin, + UpdateModelMixin, + DestroyModelMixin +): + queryset = CinemaHall.objects.all() + serializer_class = CinemaHallSerializer + + +class MovieViewSet(ModelViewSet): + queryset = Movie.objects.all() + serializer_class = MovieSerializer From dd8be9ad988b1e8f6246c6162f7a565868eafc9b Mon Sep 17 00:00:00 2001 From: Ivan Shakhman Date: Mon, 30 Sep 2024 11:43:09 +0300 Subject: [PATCH 2/6] forget particial for patch) --- cinema/views.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cinema/views.py b/cinema/views.py index 31b87c2fe..8ab7c9e76 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -63,7 +63,11 @@ def put(self, request, pk): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def patch(self, request, pk): - serializer = GenreSerializer(self.get_object(pk), data=request.data) + serializer = GenreSerializer( + self.get_object(pk), + data=request.data, + partial=True + ) if serializer.is_valid(): serializer.save() return Response( From e38f370e23b203970c89f9f650a3a18a5d083b8e Mon Sep 17 00:00:00 2001 From: Ivan Shakhman Date: Mon, 30 Sep 2024 20:10:05 +0300 Subject: [PATCH 3/6] fix --- cinema/models.py | 4 ++-- cinema/urls.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cinema/models.py b/cinema/models.py index d4ba26c07..9a22a2403 100644 --- a/cinema/models.py +++ b/cinema/models.py @@ -20,8 +20,8 @@ class Movie(models.Model): title = models.CharField(max_length=255) description = models.TextField() duration = models.IntegerField() - actors = models.ManyToManyField(Actor) - genres = models.ManyToManyField(Genre) + actors = models.ManyToManyField(Actor, related_name="movies") + genres = models.ManyToManyField(Genre, related_name="movies") def __str__(self): return self.title diff --git a/cinema/urls.py b/cinema/urls.py index 8a02c0035..77fb17729 100644 --- a/cinema/urls.py +++ b/cinema/urls.py @@ -1,4 +1,5 @@ from django.urls import path, include + from rest_framework import routers from cinema.views import ( From 05ba135def2943e4fb424c52739332e8138c57e5 Mon Sep 17 00:00:00 2001 From: Ivan Shakhman Date: Mon, 30 Sep 2024 20:24:21 +0300 Subject: [PATCH 4/6] forget about migrations --- ...4_alter_movie_actors_alter_movie_genres.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 cinema/migrations/0004_alter_movie_actors_alter_movie_genres.py diff --git a/cinema/migrations/0004_alter_movie_actors_alter_movie_genres.py b/cinema/migrations/0004_alter_movie_actors_alter_movie_genres.py new file mode 100644 index 000000000..5a078f2fe --- /dev/null +++ b/cinema/migrations/0004_alter_movie_actors_alter_movie_genres.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1 on 2024-09-30 17:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cinema', '0003_rename_seat_in_row_cinemahall_seats_in_row'), + ] + + 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'), + ), + ] From 78d7b1d6b817aef69cb4aa834c287aaa4f911535 Mon Sep 17 00:00:00 2001 From: Ivan Shakhman Date: Wed, 2 Oct 2024 11:25:57 +0300 Subject: [PATCH 5/6] remove extra returns and add raise_exeption --- cinema/views.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/cinema/views.py b/cinema/views.py index 8ab7c9e76..97f60fd02 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -32,18 +32,13 @@ def get(self, request): def post(self, request): serializer = GenreSerializer(data=request.data) - if serializer.is_valid(): + if serializer.is_valid(raise_exception=True): serializer.save() return Response( serializer.data, status=status.HTTP_201_CREATED ) - return Response( - serializer.errors, - status=status.HTTP_400_BAD_REQUEST - ) - class GenreDetail(APIView): @@ -56,29 +51,23 @@ def get(self, request, pk): def put(self, request, pk): serializer = GenreSerializer(self.get_object(pk), data=request.data) - if serializer.is_valid(): + if serializer.is_valid(raise_exception=True): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - def patch(self, request, pk): serializer = GenreSerializer( self.get_object(pk), data=request.data, partial=True ) - if serializer.is_valid(): + if serializer.is_valid(raise_exception=True): serializer.save() return Response( serializer.data, status=status.HTTP_200_OK ) - return Response( - serializer.errors, - status=status.HTTP_400_BAD_REQUEST - ) def delete(self, request, pk): self.get_object(pk).delete() From 68b9e6780ce0f15cd4ff09dc54b9322db8beab53 Mon Sep 17 00:00:00 2001 From: Ivan Shakhman Date: Wed, 2 Oct 2024 11:26:56 +0300 Subject: [PATCH 6/6] flake.... --- cinema/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cinema/views.py b/cinema/views.py index 97f60fd02..8544e1245 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -68,7 +68,6 @@ def patch(self, request, pk): status=status.HTTP_200_OK ) - def delete(self, request, pk): self.get_object(pk).delete() return Response(status=status.HTTP_204_NO_CONTENT)