-
Notifications
You must be signed in to change notification settings - Fork 707
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
Develop #744
base: master
Are you sure you want to change the base?
Develop #744
Changes from all commits
96d14b6
d0de65c
8b39b44
6c6f535
7101b9e
fe84acb
4424147
222e87d
18d9009
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Setting the |
||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
from django.core.exceptions import ValidationError | ||
from django.db import models | ||
from django.conf import settings | ||
from django.utils.timezone import make_aware, is_naive | ||
|
||
|
||
class CinemaHall(models.Model): | ||
|
@@ -60,11 +61,17 @@ class Meta: | |
def __str__(self): | ||
return self.movie.title + " " + str(self.show_time) | ||
|
||
def save(self, *args, **kwargs): | ||
if is_naive(self.show_time): | ||
self.show_time = make_aware(self.show_time) | ||
super().save(*args, **kwargs) | ||
|
||
|
||
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 | ||
) | ||
|
||
def __str__(self): | ||
|
@@ -76,10 +83,14 @@ 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() | ||
|
@@ -90,34 +101,41 @@ def clean(self): | |
(self.seat, "seat", "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( | ||
{ | ||
ticket_attr_name: f"{ticket_attr_name} " | ||
f"number must be in available range: " | ||
f"(1, {cinema_hall_attr_name}): " | ||
f"(1, {count_attrs})" | ||
ticket_attr_name: ( | ||
f"{ticket_attr_name}" | ||
f" number must be in available range: " | ||
f"(1, {cinema_hall_attr_name}): (1, {count_attrs})" | ||
) | ||
} | ||
) | ||
|
||
def save( | ||
self, | ||
force_insert=False, | ||
force_update=False, | ||
using=None, | ||
update_fields=None, | ||
self, | ||
force_insert=False, | ||
force_update=False, | ||
using=None, | ||
update_fields=None, | ||
): | ||
self.full_clean() | ||
super(Ticket, self).save( | ||
force_insert, force_update, using, update_fields | ||
( | ||
super(Ticket, self) | ||
.save( | ||
force_insert, | ||
force_update, | ||
using, | ||
update_fields | ||
) | ||
) | ||
|
||
def __str__(self): | ||
return ( | ||
f"{str(self.movie_session)} (row: {self.row}, seat: {self.seat})" | ||
) | ||
return (f"{str(self.movie_session)}" | ||
f" (row: {self.row}, seat: {self.seat})") | ||
|
||
class Meta: | ||
unique_together = ("movie_session", "row", "seat") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,11 @@ | ||
from django.db import transaction | ||
from rest_framework import serializers | ||
from rest_framework.exceptions import ValidationError | ||
|
||
from cinema.models import Genre, Actor, CinemaHall, Movie, MovieSession | ||
from cinema.models import ( | ||
Genre, Actor, CinemaHall, | ||
Movie, MovieSession, Order, Ticket | ||
) | ||
|
||
|
||
class GenreSerializer(serializers.ModelSerializer): | ||
|
@@ -29,7 +34,9 @@ class Meta: | |
|
||
class MovieListSerializer(MovieSerializer): | ||
genres = serializers.SlugRelatedField( | ||
many=True, read_only=True, slug_field="name" | ||
many=True, | ||
read_only=True, | ||
slug_field="name" | ||
) | ||
actors = serializers.SlugRelatedField( | ||
many=True, read_only=True, slug_field="full_name" | ||
|
@@ -51,14 +58,16 @@ class Meta: | |
fields = ("id", "show_time", "movie", "cinema_hall") | ||
|
||
|
||
class MovieSessionListSerializer(MovieSessionSerializer): | ||
class MovieSessionListSerializer(serializers.ModelSerializer): | ||
movie_title = serializers.CharField(source="movie.title", read_only=True) | ||
cinema_hall_name = serializers.CharField( | ||
source="cinema_hall.name", read_only=True | ||
source="cinema_hall.name", | ||
read_only=True | ||
) | ||
cinema_hall_capacity = serializers.IntegerField( | ||
source="cinema_hall.capacity", read_only=True | ||
) | ||
tickets_available = serializers.SerializerMethodField() | ||
|
||
class Meta: | ||
model = MovieSession | ||
|
@@ -68,13 +77,140 @@ class Meta: | |
"movie_title", | ||
"cinema_hall_name", | ||
"cinema_hall_capacity", | ||
"tickets_available", | ||
) | ||
|
||
def get_tickets_available(self, obj): | ||
total_capacity = obj.cinema_hall.capacity | ||
tickets_sold = Ticket.objects.filter(movie_session=obj).count() | ||
return total_capacity - tickets_sold | ||
|
||
|
||
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): | ||
""" | ||
Retrieve all taken places (row and seat) for the movie session. | ||
""" | ||
tickets = Ticket.objects.filter(movie_session=obj) | ||
return [{"row": ticket.row, "seat": ticket.seat} for ticket in tickets] | ||
|
||
|
||
class TicketSerializer(serializers.ModelSerializer): | ||
movie_session = serializers.PrimaryKeyRelatedField( | ||
queryset=MovieSession.objects.all() | ||
) | ||
|
||
class Meta: | ||
model = Ticket | ||
fields = ( | ||
"id", | ||
"row", | ||
"seat", | ||
"movie_session", | ||
) | ||
|
||
def validate(self, data): | ||
""" | ||
Validate ticket creation. | ||
""" | ||
movie_session = data.get("movie_session") | ||
row = data.get("row") | ||
seat = data.get("seat") | ||
|
||
# Ensure row and seat are within the cinema hall's capacity | ||
cinema_hall = movie_session.cinema_hall | ||
if row < 1 or row > cinema_hall.rows: | ||
raise ValidationError( | ||
{ | ||
"row": ( | ||
f"Row {row} is out of range (1 to {cinema_hall.rows})." | ||
) | ||
} | ||
) | ||
if seat < 1 or seat > cinema_hall.seats_in_row: | ||
raise ValidationError( | ||
{ | ||
"seat": ( | ||
f"Seat {seat} is out of range (1 to " | ||
f"{cinema_hall.seats_in_row})." | ||
) | ||
} | ||
) | ||
|
||
# Ensure row and seat are not already taken for this movie session | ||
if Ticket.objects.filter( | ||
movie_session=movie_session, row=row, seat=seat | ||
).exists(): | ||
raise ValidationError( | ||
{ | ||
"seat": ( | ||
f"Row {row}, Seat {seat} is already taken " | ||
f"for this session." | ||
) | ||
} | ||
) | ||
|
||
return data | ||
|
||
|
||
class TicketMovieListSerializer(TicketSerializer): | ||
movie_session = MovieSessionListSerializer(read_only=True) | ||
|
||
class Meta: | ||
model = Ticket | ||
fields = ( | ||
"id", | ||
"row", | ||
"seat", | ||
"movie_session", | ||
) | ||
|
||
|
||
class OrderSerializer(serializers.ModelSerializer): | ||
tickets = TicketSerializer(many=True, allow_empty=False) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
||
class Meta: | ||
model = Order | ||
fields = ( | ||
"id", | ||
"tickets", | ||
"created_at", | ||
) | ||
|
||
def create(self, validated_data): | ||
tickets_data = validated_data.pop("tickets") | ||
user = self.context["request"].user | ||
|
||
with transaction.atomic(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using |
||
# Create the order | ||
order = Order.objects.create(user=user) | ||
|
||
for ticket_data in tickets_data: | ||
# No need to fetch `movie_session` as it is already validated | ||
Ticket.objects.create(order=order, **ticket_data) | ||
|
||
return order | ||
|
||
|
||
class OrderListSerializer(serializers.ModelSerializer): | ||
tickets = TicketMovieListSerializer( | ||
many=True, | ||
read_only=False, | ||
allow_empty=False | ||
) | ||
|
||
class Meta: | ||
model = Order | ||
fields = ( | ||
"id", | ||
"tickets", | ||
"created_at", | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test for retrieving actors checks that the full names are returned correctly and in the expected order. This ensures that the API endpoint is functioning as intended. |
||
|
||
def test_post_actors(self): | ||
response = self.client.post( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This assertion checks that the 'Blue' cinema hall's details are correctly retrieved, ensuring the GET operation works as expected. |
||
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"]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This assertion checks that the 'VIP' cinema hall's details are correctly retrieved, ensuring the GET operation works as expected. |
||
|
||
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): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
default
value is set to123
, butpreserve_default
is set toFalse
. This means that the default value will not be preserved for existing rows in the database. If you intend to apply this default value to existing records, consider settingpreserve_default
toTrue
or handling existing records separately.