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

Open
wants to merge 2 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
103 changes: 76 additions & 27 deletions cinema/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from datetime import datetime

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

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

Expand All @@ -17,66 +21,84 @@
MovieSessionDetailSerializer,
MovieListSerializer,
OrderSerializer,
OrderListSerializer,
)


class GenreViewSet(viewsets.ModelViewSet):
from user.permissions import IsAdminOrIfAuthenticatedReadOnly


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


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


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


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

@staticmethod
def _params_to_ints(qs):
"""Converts a list of string IDs to a list of integers"""
return [int(str_id) for str_id in qs.split(",")]

def get_queryset(self):
"""Retrieve the movies with filters"""
title = self.request.query_params.get("title")
genres = self.request.query_params.get("genres")
actors = self.request.query_params.get("actors")

queryset = self.queryset

if title:
queryset = queryset.filter(title__icontains=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)

return queryset.distinct()

def get_serializer_class(self):
if self.action == "list":
return MovieListSerializer

if self.action == "retrieve":
return MovieDetailSerializer

return MovieSerializer


class MovieSessionViewSet(viewsets.ModelViewSet):
class MovieSessionViewSet(ModelViewSet):
queryset = (
MovieSession.objects.all()
.select_related("movie", "cinema_hall")
Expand All @@ -87,6 +109,8 @@ class MovieSessionViewSet(viewsets.ModelViewSet):
)
)
serializer_class = MovieSessionSerializer
permission_classes = [IsAdminOrIfAuthenticatedReadOnly]
authentication_classes = (TokenAuthentication,)

def get_queryset(self):
date = self.request.query_params.get("date")
Expand All @@ -112,27 +136,52 @@ def get_serializer_class(self):

return MovieSessionSerializer

def perform_create(self, serializer):
if not self.request.user.is_staff:
raise PermissionDenied(
"You do not have permission to perform this action."
)
Comment on lines +139 to +143

Choose a reason for hiding this comment

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

Raising a PermissionDenied exception directly in the perform_create method might lead to inconsistent behavior. Consider using permission classes to handle these checks more uniformly.

serializer.save()

def perform_update(self, serializer):
if not self.request.user.is_staff:
raise PermissionDenied(
"You do not have permission to perform this action."
)
Comment on lines +146 to +150

Choose a reason for hiding this comment

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

Raising a PermissionDenied exception directly in the perform_update method might lead to inconsistent behavior. Consider using permission classes to handle these checks more uniformly.

serializer.save()

def perform_destroy(self, instance):
if not self.request.user.is_staff:
raise PermissionDenied(
"You do not have permission to perform this action."
)
Comment on lines +153 to +157

Choose a reason for hiding this comment

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

Raising a PermissionDenied exception directly in the perform_destroy method might lead to inconsistent behavior. Consider using permission classes to handle these checks more uniformly.

instance.delete()


class OrderPagination(PageNumberPagination):
page_size = 10
max_page_size = 100


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

def get_queryset(self):
return Order.objects.filter(user=self.request.user)
authentication_classes = (TokenAuthentication,)
permission_classes = [IsAdminOrIfAuthenticatedReadOnly]

def get_serializer_class(self):
if self.action == "list":
return OrderListSerializer
def get_permissions(self):
if self.action == "create":
return [IsAuthenticated()]
Comment on lines +181 to +183

Choose a reason for hiding this comment

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

The get_permissions method in OrderViewSet should ensure that the IsAuthenticated permission is correctly applied when creating an order without overriding other permissions. This could lead to unauthorized access.

return super().get_permissions()

return OrderSerializer

def perform_create(self, serializer):
serializer.save(user=self.request.user)
def get_queryset(self):
return Order.objects.filter(user=self.request.user)
17 changes: 15 additions & 2 deletions cinema_service/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"""

from pathlib import Path
from rest_framework.views import exception_handler


# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
Expand All @@ -25,7 +27,7 @@
)

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

ALLOWED_HOSTS = []

Expand Down Expand Up @@ -125,7 +127,7 @@

USE_I18N = True

USE_TZ = False
USE_TZ = True


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

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.TokenAuthentication",
"rest_framework.authentication.SessionAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
"EXCEPTION_HANDLER": "rest_framework.views.exception_handler",
}
3 changes: 3 additions & 0 deletions cinema_service/urls.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from django.contrib import admin
from django.urls import path, include
from rest_framework.authtoken import views

urlpatterns = [
path("admin/", admin.site.urls),
path("api/token-auth/", views.obtain_auth_token, name="token-auth"),
path("api/cinema/", include("cinema.urls", namespace="cinema")),
sofiiasavkova marked this conversation as resolved.
Show resolved Hide resolved

Choose a reason for hiding this comment

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

Ensure that the 'cinema/urls.py' file defines an app_name variable to match the namespace specified here. This will help prevent URL name conflicts across different apps.

path("api/user/", include("user.urls")),
path("__debug__/", include("debug_toolbar.urls")),
]
13 changes: 13 additions & 0 deletions user/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from rest_framework.permissions import BasePermission, SAFE_METHODS
from rest_framework.exceptions import NotAuthenticated


class IsAdminOrIfAuthenticatedReadOnly(BasePermission):
def has_permission(self, request, view):
if not request.user.is_authenticated:
raise NotAuthenticated()
sofiiasavkova marked this conversation as resolved.
Show resolved Hide resolved

Choose a reason for hiding this comment

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

Raising a NotAuthenticated exception directly in the has_permission method might lead to inconsistent behavior. Consider returning False instead to allow the framework to handle authentication failures uniformly.


if request.method in SAFE_METHODS and request.user.is_authenticated:
return True

return request.user.is_staff
62 changes: 61 additions & 1 deletion user/serializers.py
Original file line number Diff line number Diff line change
@@ -1 +1,61 @@
# write your code here
from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ("id", "username", "email", "password", "is_staff")
read_only_fields = ("id", "is_staff")
extra_kwargs = {
"password": {"write_only": True, "min_length": 5}
}

def create(self, validated_data):
user = get_user_model().objects.create_user(**validated_data)
return user
sofiiasavkova marked this conversation as resolved.
Show resolved Hide resolved

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

if password:
validate_password(password, user) # Validate the new password
user.set_password(password)
sofiiasavkova marked this conversation as resolved.
Show resolved Hide resolved
user.save()

return user


class AuthTokenSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField(
style={"input_type": "password"}, trim_whitespace=False
)

def validate(self, attrs):
from django.contrib.auth import authenticate

username = attrs.get("username")
password = attrs.get("password")

if username and password:
user = authenticate(
request=self.context.get("request"),
username=username,
password=password
)
if not user:
raise serializers.ValidationError(
"Unable to log in with provided credentials.",
code="authorization",
)
else:
sofiiasavkova marked this conversation as resolved.
Show resolved Hide resolved
raise serializers.ValidationError(
"Must include 'username' and 'password'.",
code="authorization",
)

attrs["user"] = user
return attrs
15 changes: 14 additions & 1 deletion 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,
CreateTokenView,
ManageUserView
)

app_name = "user"

urlpatterns = [
path("register/", CreateUserView.as_view(), name="create"),
path("login/", CreateTokenView.as_view(), name="login"),
path("me/", ManageUserView.as_view(), name="manage"),
]
26 changes: 25 additions & 1 deletion 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, AuthTokenSerializer


class CreateUserView(generics.CreateAPIView):
serializer_class = UserSerializer


class CreateTokenView(ObtainAuthToken):
serializer_class = AuthTokenSerializer
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