Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 34 additions & 15 deletions cinema/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.0.2 on 2022-05-03 13:41
# Generated by Django 4.1 on 2026-05-15 09:53

from django.db import migrations, models

Expand All @@ -7,24 +7,43 @@ class Migration(migrations.Migration):

initial = True

dependencies = []
dependencies = [
]

operations = [
migrations.CreateModel(
name="Movie",
name='Actor',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=64)),
('last_name', models.CharField(max_length=64)),
],
),
migrations.CreateModel(
name='CinemaHall',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=64)),
('rows', models.IntegerField()),
('seats_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=256, unique=True)),
],
),
migrations.CreateModel(
name='Movie',
fields=[
Comment on lines +30 to 40
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MovieSerializer.update method doesn't handle actors and genres fields. When updating a movie via PUT or PATCH, these many-to-many fields won't be updated. Consider extracting and setting them similar to the create method.

(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(max_length=255)),
("description", models.TextField()),
("duration", models.IntegerField()),
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('description', models.TextField()),
('duration', models.IntegerField()),
('actors', models.ManyToManyField(related_name='movies', to='cinema.actor')),
('genres', models.ManyToManyField(related_name='movies', to='cinema.genre')),
],
),
]
17 changes: 17 additions & 0 deletions cinema/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ class Movie(models.Model):
title = models.CharField(max_length=255)
description = models.TextField()
duration = models.IntegerField()
actors = models.ManyToManyField("Actor", related_name="movies")
genres = models.ManyToManyField("Genre", related_name="movies")

def __str__(self):
return self.title


class Genre(models.Model):
name = models.CharField(max_length=256, unique=True)


class Actor(models.Model):
first_name = models.CharField(max_length=64)
last_name = models.CharField(max_length=64)


class CinemaHall(models.Model):
name = models.CharField(max_length=64)
rows = models.IntegerField()
seats_in_row = models.IntegerField()
79 changes: 77 additions & 2 deletions cinema/serializers.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
from rest_framework import serializers

from cinema.models import Movie
from cinema.models import Movie, Genre, Actor, CinemaHall


class MovieSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=255)
description = serializers.CharField()
duration = serializers.IntegerField()
actors = serializers.PrimaryKeyRelatedField(
many=True,
queryset=Actor.objects.all()
)
genres = serializers.PrimaryKeyRelatedField(
many=True,
queryset=Genre.objects.all()
)

def create(self, validated_data):
return Movie.objects.create(**validated_data)
actors = validated_data.pop("actors")
genres = validated_data.pop("genres")

movie = Movie.objects.create(**validated_data)

movie.actors.set(actors)
movie.genres.set(genres)

return movie

def update(self, instance, validated_data):
actors = validated_data.get("actors")
genres = validated_data.get("genres")

instance.title = validated_data.get("title", instance.title)
instance.description = validated_data.get(
"description", instance.description
Expand All @@ -21,4 +40,60 @@ def update(self, instance, validated_data):

instance.save()

if actors is not None:
instance.actors.set(actors)

if genres is not None:
instance.genres.set(genres)

return instance


class GenreSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=256)

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=64)
last_name = serializers.CharField(max_length=64)

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=64)
rows = serializers.IntegerField()
seats_in_row = serializers.IntegerField()

def create(self, validated_data):
Comment on lines +78 to +90
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This violates checklist item #2: CreateModelMixin.create() internally raises exceptions when validation fails instead of returning Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST). The post method should manually validate and return errors like GenreList.post does.

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
Comment on lines +97 to 99
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use self.get_object() instead of Actor.objects.get(pk=...) to avoid redundant database query. GenericAPIView already provides this method.

38 changes: 34 additions & 4 deletions cinema/urls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,40 @@
from django.urls import path
from django.urls import path, include

from cinema.views import movie_list, movie_detail
from cinema.views import GenreList, GenreDetail
from cinema.views import ActorList, ActorDetail
from cinema.views import CinemaHallViewSet
from cinema.views import MovieViewSet

from rest_framework.routers import DefaultRouter

cinema_hall_list = CinemaHallViewSet.as_view(
{"get": "list",
"post": "create",
}
)
cinema_hall_detail = CinemaHallViewSet.as_view(
{"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy"}
)

router = DefaultRouter()
router.register("movies", MovieViewSet)

urlpatterns = [
path("movies/", movie_list, name="movie-list"),
path("movies/<int:pk>/", movie_detail, name="movie-detail"),
path("genres/", GenreList.as_view(), name="genre-list"),
path("genres/<int:pk>/", GenreDetail.as_view(), name="genre-detail"),
path("actors/", ActorList.as_view(), name="actor-list"),
path("actors/<int:pk>/", ActorDetail.as_view(), name="actor-detail"),
path("cinema_halls/", cinema_hall_list, name="cinema-hall-list"),
path(
"cinema_halls/<int:pk>/",
cinema_hall_detail,
name="cinema-hall-detail"
),
path("", include(router.urls))

]

app_name = "cinema"
121 changes: 95 additions & 26 deletions cinema/views.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,114 @@
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status

from django.shortcuts import get_object_or_404

from cinema.models import Movie
from cinema.serializers import MovieSerializer
from cinema.models import Movie, Actor, Genre, CinemaHall
from cinema.serializers import (
MovieSerializer,
ActorSerializer,
GenreSerializer,
CinemaHallSerializer)

from rest_framework.views import APIView
from rest_framework.generics import (GenericAPIView,
ListCreateAPIView,
RetrieveUpdateDestroyAPIView)
from rest_framework.mixins import (ListModelMixin,
CreateModelMixin,
RetrieveModelMixin,
UpdateModelMixin,
DestroyModelMixin)
from rest_framework.viewsets import GenericViewSet
from rest_framework.viewsets import ModelViewSet

@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)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
class GenreList(APIView):
def get(self, request, *args, **kwargs):
genres = Genre.objects.prefetch_related("movies")
serializer = GenreSerializer(genres, many=True)
return Response(serializer.data)

return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def post(self, request, *args, **kwargs):
serializers = GenreSerializer(data=request.data)
if serializers.is_valid():
serializers.save()
return Response(serializers.data, status=status.HTTP_201_CREATED)
return Response(serializers.errors, status=status.HTTP_400_BAD_REQUEST)


@api_view(["GET", "PUT", "DELETE"])
def movie_detail(request, pk):
movie = get_object_or_404(Movie, pk=pk)
class GenreDetail(APIView):

if request.method == "GET":
serializer = MovieSerializer(movie)
return Response(serializer.data, status=status.HTTP_200_OK)
def get_object(self, pk):
return get_object_or_404(Genre, pk=pk)

if request.method == "PUT":
serializer = MovieSerializer(movie, data=request.data)
def get(self, request, *args, **kwargs):
genre = self.get_object(kwargs.get("pk"))
serializer = GenreSerializer(genre)
return Response(serializer.data)

def put(self, request, *args, **kwargs):
genre = self.get_object(kwargs.get("pk"))
serializer = GenreSerializer(genre, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

def patch(self, request, *args, **kwargs):
genre = self.get_object(kwargs.get("pk"))
serializer = GenreSerializer(genre, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

if request.method == "DELETE":
movie.delete()
def delete(self, request, *args, **kwargs):
genre = self.get_object(kwargs.get("pk"))
genre.delete()
return Response(status=status.HTTP_204_NO_CONTENT)


class ActorList(GenericAPIView,
ListModelMixin,
CreateModelMixin):
queryset = Actor.objects.prefetch_related("movies")
serializer_class = ActorSerializer

def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)

def post(self, request, *args, **kwargs):
Comment on lines +80 to +81
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This violates checklist item #2: The post method calls self.create() from CreateModelMixin, which raises ValidationError on invalid data instead of returning Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST). Unlike GenreList.post() (lines 33-36) which manually checks is_valid() and returns errors, this relies on mixin exception behavior.

return self.create(request, *args, **kwargs)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checklist item #2 violation: ActorList uses CreateModelMixin which internally calls is_valid(raise_exception=True), not returning serializer.errors in a Response. Consider manually handling validation similar to GenreList.post to return Response(serializers.errors, status=status.HTTP_400_BAD_REQUEST).



class ActorDetail(GenericAPIView,
RetrieveModelMixin,
UpdateModelMixin,
DestroyModelMixin):
queryset = Actor.objects.prefetch_related("movies")
serializer_class = ActorSerializer

def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)

def put(self, request, *args, **kwargs):
Comment on lines +98 to +99
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This violates checklist item #2: The put method calls self.update() from UpdateModelMixin, which raises ValidationError on invalid data instead of returning Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST). Unlike GenreDetail.put() (lines 52-55) which manually checks is_valid() and returns errors.

return self.update(request, *args, **kwargs)

def patch(self, request, *args, **kwargs):
Comment on lines +105 to +106
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This violates checklist item #2: The patch method calls self.partial_update() from UpdateModelMixin, which raises ValidationError on invalid data instead of returning Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST). Unlike GenreDetail.patch() (lines 60-63) which manually checks is_valid() and returns errors.

return self.partial_update(request, *args, **kwargs)

def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
Comment on lines +88 to +114
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checklist item #2 violation: ActorDetail uses UpdateModelMixin which internally handles validation by raising exceptions, not returning serializer.errors. Consider manually handling validation to return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST).



class CinemaHallViewSet(GenericViewSet,
ListCreateAPIView,
RetrieveUpdateDestroyAPIView):
queryset = CinemaHall.objects.all()
serializer_class = CinemaHallSerializer


class MovieViewSet(ModelViewSet):
queryset = Movie.objects.all().prefetch_related("actors", "genres")
serializer_class = MovieSerializer
Loading