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

implement py-tickets-orders #650

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
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
29 changes: 27 additions & 2 deletions cinema/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,16 @@ 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="movie_sessions"
)
cinema_hall = models.ForeignKey(
CinemaHall,
on_delete=models.CASCADE,
related_name="movie_sessions"
)

class Meta:
ordering = ["-show_time"]
Expand Down Expand Up @@ -121,3 +129,20 @@ def __str__(self):

class Meta:
unique_together = ("movie_session", "row", "seat")

@staticmethod
def validate_seat_and_row(
seat: int,
num_seats: int,
row: int,
num_rows: int,
error_to_raise
):
if not (1 <= seat <= num_seats):
raise error_to_raise({
"seat": f"seat must be in range [1, {num_seats}], not {seat}"
})
if not (1 <= row <= num_rows):
raise error_to_raise({
"seat": f"row must be in range [1, {num_rows}], not {row}"
})
72 changes: 69 additions & 3 deletions cinema/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
from django.db import transaction
from rest_framework import serializers
from rest_framework.fields import SerializerMethodField
from rest_framework.pagination import PageNumberPagination

from cinema.models import Genre, Actor, CinemaHall, Movie, MovieSession
from cinema.models import (
Genre,
Actor,
CinemaHall,
Movie,
MovieSession,
Order,
Ticket
)


class GenreSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -48,7 +59,7 @@ class Meta:
class MovieSessionSerializer(serializers.ModelSerializer):
class Meta:
model = MovieSession
fields = ("id", "show_time", "movie", "cinema_hall")
fields = ("id", "show_time", "movie", "cinema_hall", )


class MovieSessionListSerializer(MovieSessionSerializer):
Expand All @@ -59,6 +70,7 @@ class MovieSessionListSerializer(MovieSessionSerializer):
cinema_hall_capacity = serializers.IntegerField(
source="cinema_hall.capacity", read_only=True
)
tickets_available = serializers.IntegerField(read_only=True)

class Meta:
model = MovieSession
Expand All @@ -68,13 +80,67 @@ class Meta:
"movie_title",
"cinema_hall_name",
"cinema_hall_capacity",
"tickets_available"
)


class TicketSerializer(serializers.ModelSerializer):
class Meta:
model = Ticket
fields = ("id", "row", "seat", "movie_session")

def validate(self, attrs):
Ticket.validate_seat_and_row(

Choose a reason for hiding this comment

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

better call super() here and save the result, then after validation return result

Choose a reason for hiding this comment

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

This to be 100% sure that you return the same thing that is in the parent class

attrs["seat"],
attrs["movie_session"].cinema_hall.seats_in_row,
attrs["row"],
attrs["movie_session"].cinema_hall.rows,
serializers.ValidationError,
)
return attrs


class MovieSessionDetailSerializer(MovieSessionSerializer):
movie = MovieListSerializer(many=False, read_only=True)
cinema_hall = CinemaHallSerializer(many=False, read_only=True)
taken_places = SerializerMethodField()

Choose a reason for hiding this comment

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

You can use nested serializer here - create TicketSeatsSerializer with row / seat fields and pass it to taken_places


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, movie_session):
return [
{"row": ticket.row, "seat": ticket.seat}
for ticket in movie_session.tickets.all()
]


class TicketRetrieveSerializer(TicketSerializer):
movie_session = MovieSessionListSerializer(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.

many is False be default



class OrderSetPagination(PageNumberPagination):
page_size = 1
page_size_query_param = "page_size"
max_page_size = 10

Choose a reason for hiding this comment

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

I think it will be much better to replace this class into a separate module (paginators.py in the same directory ofc)



class OrderSerializer(serializers.ModelSerializer):
tickets = TicketSerializer(many=True, read_only=False, allow_empty=False)

class Meta:
model = Order
fields = ("id", "tickets", "created_at")

def create(self, validated_data):
with transaction.atomic():
tickets_data = validated_data.pop("tickets")
order = Order.objects.create(**validated_data)
for ticket_data in tickets_data:
Ticket.objects.create(order=order, **ticket_data)
return order

Choose a reason for hiding this comment

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

In create/update/delete fucntion you don't need return anything



class OrderListSerializer(OrderSerializer):
tickets = TicketRetrieveSerializer(many=True, read_only=True)
3 changes: 3 additions & 0 deletions cinema/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from django.urls import path, include
from rest_framework import routers

from cinema.serializers import OrderSerializer
from cinema.views import (
GenreViewSet,
ActorViewSet,
CinemaHallViewSet,
MovieViewSet,
MovieSessionViewSet,
OrdersViewSet
)

router = routers.DefaultRouter()
Expand All @@ -15,6 +17,7 @@
router.register("cinema_halls", CinemaHallViewSet)
router.register("movies", MovieViewSet)
router.register("movie_sessions", MovieSessionViewSet)
router.register("orders", OrdersViewSet)

urlpatterns = [path("", include(router.urls))]

Expand Down
87 changes: 85 additions & 2 deletions cinema/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from django.core.serializers import serialize
from rest_framework import viewsets
from rest_framework.pagination import PageNumberPagination
from django.db.models import Count, F

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

from cinema.serializers import (
GenreSerializer,
Expand All @@ -11,7 +14,9 @@
MovieSessionListSerializer,
MovieDetailSerializer,
MovieSessionDetailSerializer,
MovieListSerializer,
MovieListSerializer, OrderSerializer,
OrderListSerializer,
OrderSetPagination
)


Expand All @@ -34,6 +39,10 @@ class MovieViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = MovieSerializer

@staticmethod
def _params_to_ints(query_string):
return [int(str_id) for str_id in query_string.split(",")]

def get_serializer_class(self):
if self.action == "list":
return MovieListSerializer
Expand All @@ -43,11 +52,37 @@ def get_serializer_class(self):

return MovieSerializer

def get_queryset(self):
queryset = self.queryset
genres = self.request.query_params.get("genres")
actors = self.request.query_params.get("actors")
title = self.request.query_params.get("title")

if genres:
genres_ids = self._params_to_ints(genres)
queryset = queryset.filter(genres__id__in=genres_ids)

if actors:
actors_ids = self._params_to_ints(actors)
queryset = queryset.filter(actors__id__in=actors_ids)

if title:
queryset = queryset.filter(title__contains=title)

if self.action in ("list", "retrieve"):
return queryset.prefetch_related("genres", "actors")

return queryset.distinct()


class MovieSessionViewSet(viewsets.ModelViewSet):
queryset = MovieSession.objects.all()
serializer_class = MovieSessionSerializer

@staticmethod
def _params_to_ints(query_string):
return [int(str_id) for str_id in query_string.split(",")]

def get_serializer_class(self):
if self.action == "list":
return MovieSessionListSerializer
Expand All @@ -56,3 +91,51 @@ def get_serializer_class(self):
return MovieSessionDetailSerializer

return MovieSessionSerializer

def get_queryset(self):
queryset = self.queryset

Choose a reason for hiding this comment

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

call super method instead of accesing this attribute directly

movies = self.request.query_params.get("movies")
date = self.request.query_params.get("date")
if movies:
movies_ids = self._params_to_ints(movies)
queryset = queryset.filter(movies__id__in=movies_ids)

if date:
queryset = queryset.filter(show_time__date=date)

if self.action == "list":
queryset = (
queryset
.select_related("movie", "cinema_hall")
.annotate(
tickets_available=(
F("cinema_hall__rows") * F("cinema_hall__seats_in_row")
) - Count("tickets")
)
)
elif self.action in "retrieve":
queryset = queryset.select_related("movie", "cinema_hall")

return queryset.distinct()


class OrdersViewSet(viewsets.ModelViewSet):
queryset = Order.objects.all()
serializer_class = OrderSerializer
pagination_class = OrderSetPagination

def get_queryset(self):
queryset = self.queryset.filter(user=self.request.user)
if self.action == "list":
queryset = queryset.prefetch_related("tickets__movie_session")

Choose a reason for hiding this comment

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

here would be better to specifically prefetch "tickets__movie_session__movie", "tickets__movie_session__cinema_hall"

return queryset

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

def get_serializer_class(self):
serializer = self.serializer_class
if self.action == "list":
serializer = OrderListSerializer

return serializer
8 changes: 7 additions & 1 deletion cinema_service/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@

USE_I18N = True

USE_TZ = False
USE_TZ = True


# Static files (CSS, JavaScript, Images)
Expand All @@ -136,3 +136,9 @@
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS":
"rest_framework.pagination.LimitOffsetPagination",
"PAGE_SIZE": 10

Choose a reason for hiding this comment

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

I recommend you to delete this and write your own custom paginator. It will be very easy, if you watched and read the lessons carefully ofc)
Moreover, try to use this new paginator ONLY with OrderViewSet. Because otherwise tests will fail :(

}
Loading