diff --git a/cinema/permissions.py b/cinema/permissions.py new file mode 100644 index 00000000..04c8f57b --- /dev/null +++ b/cinema/permissions.py @@ -0,0 +1,16 @@ +from rest_framework.permissions import BasePermission, SAFE_METHODS + + +class IsAdminOrIfAuthenticatedReadOnly(BasePermission): + def has_permission(self, request, view): + if request.user and request.user.is_staff: + return True + + if request.method in SAFE_METHODS: + return request.user and request.user.is_authenticated + + if (view.__class__.__name__ == "OrderViewSet" + and request.method == "POST"): + return request.user and request.user.is_authenticated + + return False diff --git a/cinema/views.py b/cinema/views.py index a191bf5f..29aa220d 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -2,9 +2,18 @@ from django.db.models import F, Count from rest_framework import viewsets +from rest_framework.authentication import TokenAuthentication from rest_framework.pagination import PageNumberPagination -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, @@ -24,21 +33,29 @@ class GenreViewSet(viewsets.ModelViewSet): queryset = Genre.objects.all() serializer_class = GenreSerializer + authentication_classes = (TokenAuthentication,) + permission_classes = (IsAdminOrIfAuthenticatedReadOnly,) class ActorViewSet(viewsets.ModelViewSet): queryset = Actor.objects.all() serializer_class = ActorSerializer + authentication_classes = (TokenAuthentication,) + permission_classes = (IsAdminOrIfAuthenticatedReadOnly,) class CinemaHallViewSet(viewsets.ModelViewSet): queryset = CinemaHall.objects.all() serializer_class = CinemaHallSerializer + authentication_classes = (TokenAuthentication,) + permission_classes = (IsAdminOrIfAuthenticatedReadOnly,) class MovieViewSet(viewsets.ModelViewSet): queryset = Movie.objects.prefetch_related("genres", "actors") serializer_class = MovieSerializer + authentication_classes = (TokenAuthentication,) + permission_classes = (IsAdminOrIfAuthenticatedReadOnly,) @staticmethod def _params_to_ints(qs): @@ -87,6 +104,8 @@ class MovieSessionViewSet(viewsets.ModelViewSet): ) ) serializer_class = MovieSessionSerializer + authentication_classes = (TokenAuthentication,) + permission_classes = (IsAdminOrIfAuthenticatedReadOnly,) def get_queryset(self): date = self.request.query_params.get("date") @@ -124,6 +143,8 @@ class OrderViewSet(viewsets.ModelViewSet): ) serializer_class = OrderSerializer pagination_class = OrderPagination + authentication_classes = (TokenAuthentication,) + permission_classes = (IsAdminOrIfAuthenticatedReadOnly,) def get_queryset(self): return Order.objects.filter(user=self.request.user) diff --git a/cinema_service/settings.py b/cinema_service/settings.py index 29ea7dea..f666f3f6 100644 --- a/cinema_service/settings.py +++ b/cinema_service/settings.py @@ -11,21 +11,22 @@ """ from pathlib import Path +import os +from dotenv import load_dotenv + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ +load_dotenv() # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = ( - "django-insecure-6vubhk2$++agnctay_4pxy_8cq)mosmn(*-#2b^v4cgsh-^!i3" -) +SECRET_KEY = os.getenv("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = os.getenv("DEBUG") == "True" ALLOWED_HOSTS = [] @@ -80,7 +81,6 @@ WSGI_APPLICATION = "cinema_service.wsgi.application" - # Database # https://docs.djangoproject.com/en/4.0/ref/settings/#databases @@ -91,26 +91,25 @@ } } - # Password validation # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { "NAME": "django.contrib.auth.password_validation." - "UserAttributeSimilarityValidator", + "UserAttributeSimilarityValidator", }, { "NAME": "django.contrib.auth.password_validation." - "MinimumLengthValidator", + "MinimumLengthValidator", }, { "NAME": "django.contrib.auth.password_validation." - "CommonPasswordValidator", + "CommonPasswordValidator", }, { "NAME": "django.contrib.auth.password_validation." - "NumericPasswordValidator", + "NumericPasswordValidator", }, ] @@ -121,12 +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) # https://docs.djangoproject.com/en/4.0/howto/static-files/ @@ -137,3 +135,9 @@ # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +REST_FRAMEWORK = { + "DEFAULT_PERMISSION_CLASSES": [ + "rest_framework.permissions.IsAuthenticated", + ] +} diff --git a/cinema_service/urls.py b/cinema_service/urls.py index bf903c00..a282f874 100644 --- a/cinema_service/urls.py +++ b/cinema_service/urls.py @@ -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")), ] diff --git a/requirements.txt b/requirements.txt index 56e13554..08c6e93b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ flake8-quotes==3.3.1 flake8-variables-names==0.0.5 pep8-naming==0.13.2 django-debug-toolbar==3.2.4 -djangorestframework==3.13.1 \ No newline at end of file +djangorestframework==3.13.1 +python-dotenv~=1.0.1 \ No newline at end of file diff --git a/user/serializers.py b/user/serializers.py index fa56336e..3794d13c 100644 --- a/user/serializers.py +++ b/user/serializers.py @@ -1 +1,27 @@ -# 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_fields = ("id", "is_staff") + extra_kwargs = { + "password": {"write_only": True, + "min_length": 5} + } + + def create(self, validated_data): + 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 diff --git a/user/urls.py b/user/urls.py index fa56336e..40fdbe86 100644 --- a/user/urls.py +++ b/user/urls.py @@ -1 +1,12 @@ -# 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"), +] diff --git a/user/views.py b/user/views.py index fa56336e..8aeba165 100644 --- a/user/views.py +++ b/user/views.py @@ -1 +1,25 @@ -# write your code here +from rest_framework import generics +from rest_framework.authtoken.views import ObtainAuthToken +from rest_framework.settings import api_settings +from rest_framework.authentication import TokenAuthentication +from rest_framework.permissions import IsAuthenticated + + +from user.serializers import UserSerializer + + +class CreateUserView(generics.CreateAPIView): + serializer_class = UserSerializer + + +class CreateTokenView(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