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 #700

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 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
14 changes: 6 additions & 8 deletions cinema/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,8 @@ def __str__(self):

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)
Copy link
Contributor

Choose a reason for hiding this comment

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

why?


def __str__(self):
return str(self.created_at)
Expand All @@ -78,9 +77,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")
row = models.IntegerField()
seat = models.IntegerField()

Expand Down Expand Up @@ -122,8 +120,8 @@ def save(
)

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

Choose a reason for hiding this comment

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

There is a syntax error in the __str__ method of the Ticket class. The line continuation character \ should be placed at the end of line 123, not at the beginning of line 124. This will fix the syntax error.

Copy link
Contributor

Choose a reason for hiding this comment

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

😭 whyyyy

Choose a reason for hiding this comment

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

There is a syntax error in the __str__ method of the Ticket class. The line continuation character \ should be placed at the end of line 123, not at the beginning of line 124. This will fix the syntax error.


class Meta:
unique_together = ("movie_session", "row", "seat")
Expand Down
13 changes: 13 additions & 0 deletions cinema/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from rest_framework.permissions import BasePermission, SAFE_METHODS


class IsAdminOrIfAuthenticatedReadOnly(BasePermission):
def has_permission(self, request, view):
return bool(
(
request.method in SAFE_METHODS
and request.user
and request.user.is_authenticated
)
or (request.user and request.user.is_staff)
)
30 changes: 15 additions & 15 deletions cinema/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ class Meta:


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")
Copy link
Contributor

Choose a reason for hiding this comment

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

💀

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

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


class MovieSessionSerializer(serializers.ModelSerializer):
Expand All @@ -61,10 +62,10 @@ 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_title = serializers.CharField(source="movie.title",
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 @@ -85,9 +86,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 +91 to +92

Choose a reason for hiding this comment

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

The validate_ticket method call is missing the error_to_raise parameter. According to the validate_ticket method definition in the Ticket model, this parameter is required. You should pass ValidationError as the fourth argument to match the method signature.

Comment on lines +91 to +92

Choose a reason for hiding this comment

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

The validate_ticket method call is missing the error_to_raise parameter. According to the method definition in the Ticket model, this parameter is required. Please ensure to pass the appropriate exception class, such as serializers.ValidationError, to handle validation errors correctly.

return data

class Meta:
Expand All @@ -96,7 +96,8 @@ class Meta:


class TicketListSerializer(TicketSerializer):
movie_session = MovieSessionListSerializer(many=False, read_only=True)
movie_session = MovieSessionListSerializer(many=False,
read_only=True)


class TicketSeatsSerializer(TicketSerializer):
Expand All @@ -108,9 +109,8 @@ class Meta:
class MovieSessionDetailSerializer(MovieSessionSerializer):
movie = MovieListSerializer(many=False, read_only=True)
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
12 changes: 3 additions & 9 deletions cinema/tests/test_movie_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ def test_get_movies(self):
def test_retrieve_movie(self):
movie = sample_movie()
movie.genres.add(Genre.objects.create(name="Genre"))
movie.actors.add(
Actor.objects.create(first_name="Actor", last_name="Last")
)
movie.actors.add(Actor.objects.create(first_name="Actor", last_name="Last"))

url = detail_url(movie.id)
response = self.client.get(url)
Expand Down Expand Up @@ -114,16 +112,12 @@ def test_put_movie(self):
}

response = self.client.put(url, payload)
self.assertEqual(
response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED
)
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)

Choose a reason for hiding this comment

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

The test_put_movie method expects a 405 Method Not Allowed response. Ensure that the API is configured to disallow PUT requests on the movie detail endpoint if this is the expected behavior. Otherwise, adjust the test to match the actual API behavior.

Choose a reason for hiding this comment

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

The test_put_movie method expects a 405 Method Not Allowed response. Please verify that the API is configured to disallow PUT requests on the movie endpoint if this is the intended behavior. Otherwise, adjust the test to match the actual API behavior.


def test_delete_movie(self):
movie = sample_movie()

url = detail_url(movie.id)
response = self.client.delete(url)

self.assertEqual(
response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED
)
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)

Choose a reason for hiding this comment

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

The test_delete_movie method expects a 405 Method Not Allowed response. Verify that the API is set up to disallow DELETE requests on the movie detail endpoint. If DELETE requests are allowed, update the test to reflect the correct expected status code.

Copy link
Contributor

Choose a reason for hiding this comment

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

agree, usually delete methor returns 302 status

Choose a reason for hiding this comment

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

The test_delete_movie method expects a 405 Method Not Allowed response. Please verify that the API is configured to disallow DELETE requests on the movie endpoint if this is the intended behavior. Otherwise, adjust the test to match the actual API behavior.

68 changes: 51 additions & 17 deletions cinema/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
from datetime import datetime

from django.db.models import F, Count
from rest_framework import viewsets
from rest_framework import viewsets, mixins
from rest_framework.authentication import TokenAuthentication
from rest_framework.pagination import PageNumberPagination
from rest_framework.permissions import IsAuthenticated

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

from cinema.serializers import (
GenreSerializer,
Expand All @@ -21,24 +29,43 @@
)


class GenreViewSet(viewsets.ModelViewSet):
class GenreViewSet(
viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin
):
queryset = Genre.objects.all()
serializer_class = GenreSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAdminOrIfAuthenticatedReadOnly,)


class ActorViewSet(viewsets.ModelViewSet):
class ActorViewSet(
viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin
):
queryset = Actor.objects.all()
serializer_class = ActorSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAdminOrIfAuthenticatedReadOnly,)


class CinemaHallViewSet(viewsets.ModelViewSet):
class CinemaHallViewSet(
viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin
):
queryset = CinemaHall.objects.all()
serializer_class = CinemaHallSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAdminOrIfAuthenticatedReadOnly,)


class MovieViewSet(viewsets.ModelViewSet):
class MovieViewSet(
viewsets.GenericViewSet,
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
):
queryset = Movie.objects.prefetch_related("genres", "actors")
serializer_class = MovieSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAdminOrIfAuthenticatedReadOnly,)

@staticmethod
def _params_to_ints(qs):
Expand Down Expand Up @@ -76,17 +103,18 @@ def get_serializer_class(self):
return MovieSerializer


class MovieSessionViewSet(viewsets.ModelViewSet):
queryset = (
MovieSession.objects.all()
.select_related("movie", "cinema_hall")
.annotate(
tickets_available=F("cinema_hall__rows")
* F("cinema_hall__seats_in_row")
- Count("tickets")
)
)
class MovieSessionViewSet(
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet,
):
queryset = MovieSession.objects.all()
serializer_class = MovieSessionSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAdminOrIfAuthenticatedReadOnly,)

def get_queryset(self):
date = self.request.query_params.get("date")
Expand Down Expand Up @@ -118,12 +146,18 @@ class OrderPagination(PageNumberPagination):
max_page_size = 100


class OrderViewSet(viewsets.ModelViewSet):
class OrderViewSet(
viewsets.GenericViewSet,
mixins.ListModelMixin,
mixins.CreateModelMixin
):
queryset = Order.objects.prefetch_related(
"tickets__movie_session__movie", "tickets__movie_session__cinema_hall"
)
serializer_class = OrderSerializer
pagination_class = OrderPagination
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)

def get_queryset(self):
return Order.objects.filter(user=self.request.user)
Expand Down
5 changes: 3 additions & 2 deletions cinema_service/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = (
"django-insecure-6vubhk2$++agnctay_4pxy_8cq)mosmn(*-#2b^v4cgsh-^!i3"
"django-insecure-6vubhk2$++agnctay_4"
"pxy_8cq)mosmn(*-#2b^v4cgsh-^!i3"
Comment on lines +24 to +25

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 and visible in the settings file. For security reasons, it's important to keep this key secret in production. Consider using environment variables or a separate configuration file to manage sensitive information.

Comment on lines +24 to +25

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, which poses a security risk. Consider using environment variables to manage sensitive information securely, especially for production environments. This aligns with best practices for Django projects.

)

# SECURITY WARNING: don't run with debug turned on in production!
Expand Down Expand Up @@ -125,7 +126,7 @@

USE_I18N = True

USE_TZ = False
USE_TZ = True


# Static files (CSS, JavaScript, Images)
Expand Down
1 change: 1 addition & 0 deletions cinema_service/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
urlpatterns = [
path("admin/", admin.site.urls),
path("api/cinema/", include("cinema.urls", namespace="cinema")),
path("api/user/", include("user.urls", namespace="user")),
path("__debug__/", include("debug_toolbar.urls")),
]
34 changes: 34 additions & 0 deletions user/serializers.py
Original file line number Diff line number Diff line change
@@ -1 +1,35 @@
# write your code here
from django.contrib.auth import get_user_model
from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):

class Meta:
model = get_user_model()
fields = (
"id",
"username",
"email",
"password",
"is_staff",
)
read_only = (
"id",
"is_staff",
)

Choose a reason for hiding this comment

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

The read_only attribute in the Meta class should be read_only_fields. This is the correct attribute name for specifying fields that should be read-only in Django REST Framework serializers.

extra_kwargs = {"password": {"write_only": True, "min_length": 5}}

def create(self, validated_data):
"""create user with encrypted password"""
return get_user_model().objects.create_user(**validated_data)

def update(self, instance, validated_data):
password = validated_data.pop("password", None)
user = super().update(instance, validated_data)

if password:
user.set_password(password)
user.save()

return user
8 changes: 2 additions & 6 deletions user/tests/test_user_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ def test_password_too_short(self):
res = self.client.post(CREATE_USER_URL, payload)

self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
user_exists = (
get_user_model().objects.filter(email=payload["email"]).exists()
)
user_exists = get_user_model().objects.filter(email=payload["email"]).exists()
self.assertFalse(user_exists)

def test_create_token_for_user(self):
Expand All @@ -79,9 +77,7 @@ def test_create_token_for_user(self):

def test_create_token_invalid_credentials(self):
"""Test that token is not created if invalid credentials are given"""
create_user(
username="user12345", email="[email protected]", password="test123"
)
create_user(username="user12345", email="[email protected]", password="test123")
payload = {
"username": "user12345",
"email": "[email protected]",
Expand Down
13 changes: 13 additions & 0 deletions user/urls.py
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
# write your code here
from django.urls import path

from user.views import (CreateUserView,
LoginUserView,
ManageUserView)

app_name = "user"

urlpatterns = [
path("register/", CreateUserView.as_view(), name="create"),
path("login/", LoginUserView.as_view(), name="login"),
path("me/", ManageUserView.as_view(), name="manage"),
]
24 changes: 24 additions & 0 deletions user/views.py
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
# write your code here
from rest_framework import generics
from rest_framework.authentication import TokenAuthentication
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.permissions import IsAuthenticated
from rest_framework.settings import api_settings

from user.serializers import UserSerializer


class CreateUserView(generics.CreateAPIView):
serializer_class = UserSerializer


class LoginUserView(ObtainAuthToken):
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES


class ManageUserView(generics.RetrieveUpdateAPIView):
serializer_class = UserSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)

def get_object(self):
return self.request.user
Loading