Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solution #725

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
18 changes: 18 additions & 0 deletions cinema/migrations/0002_movie_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.1 on 2024-12-17 07:38

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("cinema", "0001_initial"),
]

operations = [
migrations.AddField(
model_name="movie",
name="image",
field=models.ImageField(blank=True, null=True, upload_to="uploads/movies/"),
),
]
18 changes: 18 additions & 0 deletions cinema/migrations/0003_alter_movie_duration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.1 on 2024-12-17 06:02

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("cinema", "0002_movie_image"),
]

operations = [
migrations.AlterField(
model_name="movie",
name="duration",
field=models.IntegerField(default=90),
),
]
37 changes: 29 additions & 8 deletions cinema/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import uuid
from pathlib import Path

from django.core.exceptions import ValidationError
from django.db import models
from django.conf import settings
from django.utils.text import slugify
from django.utils import timezone
from datetime import datetime


class CinemaHall(models.Model):
Expand Down Expand Up @@ -38,16 +44,24 @@ def full_name(self):
class Movie(models.Model):
title = models.CharField(max_length=255)
description = models.TextField()
duration = models.IntegerField()
duration = models.IntegerField(default=90)
genres = models.ManyToManyField(Genre)
actors = models.ManyToManyField(Actor)
image = models.ImageField(upload_to="uploads/movies/", blank=True)

Choose a reason for hiding this comment

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

The image field in the Movie model is set to blank=True but not null=True. This means that the database will require a value for this field, even if it's an empty string. If you want to allow the field to be completely optional, consider adding null=True.


class Meta:
ordering = ["title"]

def __str__(self):
return self.title

def rename_image_and_save(self, *args, **kwargs):
if self.image:
ext = Path(self.image.name).suffix
unique_id = uuid.uuid4()
self.image.name = f"{slugify(self.title)}-{unique_id}{ext}"
super().save(*args, **kwargs)
Comment on lines +58 to +63

Choose a reason for hiding this comment

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

The method rename_image_and_save is a custom save method for renaming the image file before saving. However, this method is not being called anywhere in the code. Ensure that you override the save method of the Movie model to call rename_image_and_save instead of super().save() directly.



class MovieSession(models.Model):
show_time = models.DateTimeField()
Expand All @@ -57,15 +71,22 @@ class MovieSession(models.Model):
class Meta:
ordering = ["-show_time"]

def save(self, *args, **kwargs):
if isinstance(self.show_time, str):
self.show_time =\
datetime.strptime(self.show_time, "%Y-%m-%d %H:%M:%S")
if timezone.is_naive(self.show_time):
self.show_time = timezone.make_aware(self.show_time)
super().save(*args, **kwargs)
Comment on lines +74 to +80
Copy link

Choose a reason for hiding this comment

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

? don't need it here, the error with naive timezone is ok in this task


def __str__(self):
return self.movie.title + " " + str(self.show_time)


class Order(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE
)
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)

def __str__(self):
return str(self.created_at)
Expand All @@ -78,9 +99,8 @@ class Ticket(models.Model):
movie_session = models.ForeignKey(
MovieSession, on_delete=models.CASCADE, related_name="tickets"
)
order = models.ForeignKey(
Order, on_delete=models.CASCADE, related_name="tickets"
)
order = models.ForeignKey(Order, on_delete=models.CASCADE,
related_name="tickets")
Comment on lines +102 to +103
Copy link

Choose a reason for hiding this comment

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

we don't have problem with long line here, why changing it?
same in other files and this one in particular

row = models.IntegerField()
seat = models.IntegerField()

Expand Down Expand Up @@ -123,7 +143,8 @@ def save(

def __str__(self):
return (
f"{str(self.movie_session)} (row: {self.row}, seat: {self.seat})"
f"{str(self.movie_session)} "
f"(row: {self.row}, seat: {self.seat})"
)

class Meta:
Expand Down
43 changes: 28 additions & 15 deletions cinema/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,20 @@ class Meta:
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = ("id", "title", "description", "duration", "genres", "actors")
fields = (
"id",
"title",
"description",
"duration",
"genres",
"actors",
"image",
)


class MovieListSerializer(MovieSerializer):
genres = serializers.SlugRelatedField(
many=True, read_only=True, slug_field="name"
)
genres = serializers.SlugRelatedField(many=True, read_only=True,
slug_field="name")
actors = serializers.SlugRelatedField(
many=True, read_only=True, slug_field="full_name"
)
Expand All @@ -51,7 +58,14 @@ class MovieDetailSerializer(MovieSerializer):

class Meta:
model = Movie
fields = ("id", "title", "description", "duration", "genres", "actors")
fields = ("id", "title", "description", "duration",
"genres", "actors", "image")


class MovieImageUploadSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = ["id", "image"]


class MovieSessionSerializer(serializers.ModelSerializer):
Expand All @@ -62,9 +76,9 @@ class Meta:

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
)
movie_image = serializers.ImageField(source="movie.image", 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
)
Expand All @@ -76,6 +90,7 @@ class Meta:
"id",
"show_time",
"movie_title",
"movie_image",
"cinema_hall_name",
"cinema_hall_capacity",
"tickets_available",
Expand All @@ -85,9 +100,8 @@ class Meta:
class TicketSerializer(serializers.ModelSerializer):
def validate(self, attrs):
data = super(TicketSerializer, self).validate(attrs=attrs)
Ticket.validate_ticket(
attrs["row"], attrs["seat"], attrs["movie_session"]
)
Ticket.validate_ticket(attrs["row"], attrs["seat"],
attrs["movie_session"])
Comment on lines +103 to +104

Choose a reason for hiding this comment

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

The validate method in TicketSerializer calls Ticket.validate_ticket with three arguments, but the validate_ticket method in the Ticket model requires four arguments, including error_to_raise. Ensure that the correct number of arguments is passed to validate_ticket.

return data

class Meta:
Expand All @@ -106,11 +120,10 @@ class Meta:


class MovieSessionDetailSerializer(MovieSessionSerializer):
movie = MovieListSerializer(many=False, read_only=True)
movie = MovieDetailSerializer(many=False, read_only=True)

Choose a reason for hiding this comment

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

In MovieSessionDetailSerializer, the movie field is using MovieDetailSerializer, which includes all fields of the movie. Ensure that this level of detail is necessary for the use case, as it might include more data than needed.

cinema_hall = CinemaHallSerializer(many=False, read_only=True)
taken_places = TicketSeatsSerializer(
source="tickets", many=True, read_only=True
)
taken_places = TicketSeatsSerializer(source="tickets", many=True,
read_only=True)

class Meta:
model = MovieSession
Expand Down
4 changes: 1 addition & 3 deletions cinema/tests/test_movie_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ def sample_actor(**params):


def sample_movie_session(**params):
cinema_hall = CinemaHall.objects.create(
name="Blue", rows=20, seats_in_row=20
)
cinema_hall = CinemaHall.objects.create(name="Blue", rows=20, seats_in_row=20)

defaults = {
"show_time": "2022-06-02 14:00:00",
Expand Down
11 changes: 9 additions & 2 deletions cinema/urls.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from django.urls import path, include
from rest_framework import routers

from cinema.views import (
GenreViewSet,
ActorViewSet,
CinemaHallViewSet,
MovieViewSet,
MovieSessionViewSet,
OrderViewSet,
MovieImageUploadView,
)

router = routers.DefaultRouter()
Expand All @@ -18,6 +18,13 @@
router.register("movie_sessions", MovieSessionViewSet)
router.register("orders", OrderViewSet)

urlpatterns = [path("", include(router.urls))]
urlpatterns = [
path("", include(router.urls)),
path(
"movies/<int:pk>/upload-image/",
MovieImageUploadView.as_view(),
name="movie-upload-image",
),
]

app_name = "cinema"
25 changes: 25 additions & 0 deletions cinema/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
from datetime import datetime

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework import status
from django.shortcuts import get_object_or_404

from django.db.models import F, Count
from rest_framework import viewsets, mixins
from rest_framework.authentication import TokenAuthentication
Expand All @@ -8,6 +14,7 @@
from rest_framework.viewsets import GenericViewSet, ReadOnlyModelViewSet

from cinema.models import Genre, Actor, CinemaHall, Movie, MovieSession, Order
from .serializers import MovieImageUploadSerializer
from cinema.permissions import IsAdminOrIfAuthenticatedReadOnly

from cinema.serializers import (
Expand Down Expand Up @@ -103,6 +110,10 @@ def get_serializer_class(self):

return MovieSerializer

def perform_create(self, serializer):
"""Ensure the image field is not set during movie creation"""
serializer.save(image=None)
Comment on lines +113 to +115
Copy link

Choose a reason for hiding this comment

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

it's better to write upload image here with action decorator

Comment on lines +113 to +115

Choose a reason for hiding this comment

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

The perform_create method in MovieViewSet sets the image field to None during movie creation. Ensure this behavior is intentional, as it prevents setting an image during the creation process. If you want to allow image uploads during creation, consider handling this differently.



class MovieSessionViewSet(viewsets.ModelViewSet):
queryset = (
Expand Down Expand Up @@ -173,3 +184,17 @@ def get_serializer_class(self):

def perform_create(self, serializer):
serializer.save(user=self.request.user)


class MovieImageUploadView(APIView):
parser_classes = [MultiPartParser, FormParser]

def post(self, request, pk, *args, **kwargs):
movie = get_object_or_404(Movie, pk=pk)
serializer = MovieImageUploadSerializer(movie,
data=request.data,
partial=True)
if serializer.is_valid():
serializer.save()
Comment on lines +197 to +198
Copy link

Choose a reason for hiding this comment

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

i guess it's better to use raise_exception=True here

return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
13 changes: 6 additions & 7 deletions cinema_service/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = (
SECRET_KEY =\
Copy link

Choose a reason for hiding this comment

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

?

"django-insecure-6vubhk2$++agnctay_4pxy_8cq)mosmn(*-#2b^v4cgsh-^!i3"

Choose a reason for hiding this comment

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

The SECRET_KEY should not be hardcoded in the settings file for production environments. Consider using environment variables to securely manage sensitive information.

Comment on lines +23 to 24

Choose a reason for hiding this comment

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

The SECRET_KEY is hardcoded in the settings file. It's important to use environment variables to manage sensitive information securely, especially in production environments. Consider using os.environ.get('SECRET_KEY') to retrieve the key from an environment variable.

)

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

Choose a reason for hiding this comment

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

The DEBUG setting is set to True. Ensure this is set to False in production to avoid exposing sensitive information.

Choose a reason for hiding this comment

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

The DEBUG setting is currently set to True. This should be set to False in production to prevent exposing sensitive information. Consider using environment variables to toggle this setting based on the environment.

Expand Down Expand Up @@ -102,15 +101,15 @@
},
{
"NAME": "django.contrib.auth.password_validation."
"MinimumLengthValidator",
"MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation."
"CommonPasswordValidator",
"CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation."
"NumericPasswordValidator",
"NumericPasswordValidator",
},
]

Expand All @@ -121,11 +120,11 @@

LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"
TIME_ZONE = "Europe/Kiev"

USE_I18N = True

USE_TZ = False
USE_TZ = True


# Static files (CSS, JavaScript, Images)
Expand Down
21 changes: 18 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
django==4.1
asgiref==3.8.1
black==24.10.0
click==8.1.7
colorama==0.4.6
Django==4.1
django-debug-toolbar==3.2.4
djangorestframework==3.13.1
flake8==5.0.4
flake8-quotes==3.3.1
flake8-variables-names==0.0.5
mccabe==0.7.0
mypy-extensions==1.0.0
packaging==24.2
pathspec==0.12.1
pep8-naming==0.13.2
django-debug-toolbar==3.2.4
djangorestframework==3.13.1
pillow==11.0.0
platformdirs==4.3.6
pycodestyle==2.9.1
pyflakes==2.5.0
pytz==2024.2
sqlparse==0.5.3
tzdata==2024.2
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading