diff --git a/api_yamdb/api/filters.py b/api_yamdb/api/filters.py new file mode 100644 index 0000000..d5e2f84 --- /dev/null +++ b/api_yamdb/api/filters.py @@ -0,0 +1,5 @@ +import django_filters as filters + + +class TitleFilter(filters.FilterSet): + ... diff --git a/api_yamdb/api/mixins.py b/api_yamdb/api/mixins.py new file mode 100644 index 0000000..fae340b --- /dev/null +++ b/api_yamdb/api/mixins.py @@ -0,0 +1,10 @@ +from rest_framework import mixins, viewsets + + +class ListCreateDestroyViewSet( + mixins.ListModelMixin, + mixins.CreateModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet +): + pass diff --git a/api_yamdb/api/models.py b/api_yamdb/api/models.py deleted file mode 100644 index 71a8362..0000000 --- a/api_yamdb/api/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/api_yamdb/api/permissions.py b/api_yamdb/api/permissions.py new file mode 100644 index 0000000..e3d0eb2 --- /dev/null +++ b/api_yamdb/api/permissions.py @@ -0,0 +1,26 @@ +from rest_framework import permissions + + +class IsAdmin(permissions.BasePermission): + + def has_permission(self, request, view): + return request.user.is_authenticated and request.user.is_admin + + +class IsOwnerAdminModeratorOrReadOnly(permissions.IsAuthenticatedOrReadOnly): + message = 'Изменить контент может только автор, админ или модератор.' + + def has_object_permission(self, request, view, obj): + return (request.method in permissions.SAFE_METHODS + or request.user.is_admin + or request.user.is_moderator + or request.user == obj.author) + + +class IsAdminOrReadOnly(permissions.BasePermission): + message = 'Изменить контент может только админ.' + + def has_permission(self, request, view): + return (request.method in permissions.SAFE_METHODS + or (request.user.is_authenticated + and request.user.is_admin)) diff --git a/api_yamdb/api/serializers.py b/api_yamdb/api/serializers.py new file mode 100644 index 0000000..cc41952 --- /dev/null +++ b/api_yamdb/api/serializers.py @@ -0,0 +1,23 @@ +from django.contrib.auth import get_user_model +from rest_framework import serializers + +User = get_user_model() + + +class UsersSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ('username', 'email', 'first_name', 'last_name', + 'bio', 'role') + + +class CreateUserSerializer(serializers.ModelSerializer): + + class Meta: + fields = ('username', 'email') + model = User + + +class UserJWTTokenCreateSerializer(serializers.Serializer): + confirmation_code = serializers.CharField(required=True) + username = serializers.CharField(required=True) diff --git a/api_yamdb/api/urls.py b/api_yamdb/api/urls.py index 52154b6..21a50d8 100644 --- a/api_yamdb/api/urls.py +++ b/api_yamdb/api/urls.py @@ -1,12 +1,16 @@ from django.urls import include, path -from rest_framework.routers import DefaultRouter +from rest_framework import routers -from .views import ... -v1_router = DefaultRouter() +from api.views import (UsersViewSet, user_create_view, + user_jwt_token_create_view) + +v1_router = routers.DefaultRouter() +v1_router.register('users', UsersViewSet, basename='users') -v1_router.register(...) urlpatterns = [ path('v1/', include(v1_router.urls)), + path('v1/auth/signup/', user_create_view), + path('v1/auth/token/', user_jwt_token_create_view) ] diff --git a/api_yamdb/api/views.py b/api_yamdb/api/views.py index 91ea44a..f70b3fc 100644 --- a/api_yamdb/api/views.py +++ b/api_yamdb/api/views.py @@ -1,3 +1,81 @@ -from django.shortcuts import render +from http import HTTPStatus -# Create your views here. +from django.contrib.auth import get_user_model +from django.contrib.auth.tokens import default_token_generator +from django.core.mail import send_mail +from django.shortcuts import get_object_or_404 +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets, permissions, filters +from rest_framework.decorators import action, api_view, permission_classes +from rest_framework.pagination import LimitOffsetPagination +from rest_framework.response import Response +from rest_framework_simplejwt.tokens import AccessToken + +from .permissions import (IsAdmin, IsAdminOrReadOnly, + IsOwnerAdminModeratorOrReadOnly) +from .serializers import (UsersSerializer, CreateUserSerializer, + UserJWTTokenCreateSerializer) +from users.models import User + + + +class UsersViewSet(viewsets.ModelViewSet): + queryset = User.objects.all() + permission_classes = [IsAdmin] + serializer_class = UsersSerializer + lookup_field = 'username' + lookup_value_regex = '[^/]+' + pagination_class = LimitOffsetPagination + + @action(methods=['patch', 'get'], detail=False, + permission_classes=[permissions.IsAuthenticated], + url_path='me', url_name='me') + def me(self, request, *args, **kwargs): + instance = self.request.user + serializer = self.get_serializer(instance) + if self.request.method == 'PATCH': + serializer = self.get_serializer( + instance, data=request.data, partial=True) + serializer.is_valid(raise_exception=True) + serializer.save(role=self.request.user.role) + return Response(serializer.data) + + +@api_view(['POST']) +@permission_classes([permissions.AllowAny]) +def user_create_view(request): + serializer = CreateUserSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + email = serializer.validated_data.get('email') + username = serializer.validated_data.get('username') + serializer.save() + confirmation_code = default_token_generator.make_token( + User.objects.get(email=email, username=username) + ) + MESSAGE = (f'Здравствуйте, {username}! ' + f'Ваш код подтверждения: {confirmation_code}') + send_mail(message=MESSAGE, + subject='Confirmation code', + recipient_list=[email], + from_email=None) + return Response(serializer.data, status=HTTPStatus.OK) + + +@api_view(['POST']) +@permission_classes([permissions.AllowAny]) +def user_jwt_token_create_view(request): + serializer = UserJWTTokenCreateSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + confirmation_code = serializer.validated_data.get('confirmation_code') + username = serializer.validated_data.get('username') + user = get_object_or_404(User, username=username) + if default_token_generator.check_token(user, confirmation_code): + token = AccessToken.for_user(user) + return Response( + data={'token': str(token)}, + status=HTTPStatus.OK + ) + return Response( + 'Неверный код подтверждения или имя пользователя!', + status=HTTPStatus.BAD_REQUEST + ) diff --git a/api_yamdb/api_yamdb/settings.py b/api_yamdb/api_yamdb/settings.py index 5cb43cf..050bdf9 100644 --- a/api_yamdb/api_yamdb/settings.py +++ b/api_yamdb/api_yamdb/settings.py @@ -8,7 +8,7 @@ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'Секретный ключ' -# SECURITY WARNING: don't run with debug turned on in production! +# SECURITY WARNING: don't run with debug tFurned on in production! DEBUG = True ALLOWED_HOSTS = ['*'] @@ -23,11 +23,11 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'users', 'django_filters', 'rest_framework', 'reviews.apps.ReviewsConfig', 'api.apps.ApiConfig', + 'users', ] MIDDLEWARE = [ diff --git a/api_yamdb/reviews/models.py b/api_yamdb/reviews/models.py index 71a8362..3b33804 100644 --- a/api_yamdb/reviews/models.py +++ b/api_yamdb/reviews/models.py @@ -1,3 +1,14 @@ from django.db import models -# Create your models here. +from .validators import validate_actual_year + + +class Title(models.Model): + name = models.CharField( + max_length=256, + verbose_name='Название' + ) + year = models.PositiveSmallIntegerField( + verbose_name='Год выпуска', + validators=[validate_actual_year] + ) diff --git a/api_yamdb/reviews/validators.py b/api_yamdb/reviews/validators.py new file mode 100644 index 0000000..2e1ed99 --- /dev/null +++ b/api_yamdb/reviews/validators.py @@ -0,0 +1,10 @@ +from django.core.exceptions import ValidationError +from django.utils import timezone + + +def validate_actual_year(value): + if value > timezone.now().year: + raise ValidationError( + ('Год %(value)s ещё не наступил'), + params={'value': value}, + )