From 7f7be354599ccbe514ee24bab3753d9f2124a363 Mon Sep 17 00:00:00 2001 From: vladislavdacenko Date: Mon, 13 Nov 2023 14:30:41 +0200 Subject: [PATCH 1/9] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=BE=D0=B1=D0=B8=D0=B2?= =?UTF-8?q?=20=D0=BB=D1=804=20=D0=B4=D0=BE=20=D0=BA=D1=96=D0=BD=D1=86?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Blog/serializer.py | 0 Blog/urls.py | 0 BlogProject/yasg.py | 0 requirements.txt | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Blog/serializer.py create mode 100644 Blog/urls.py create mode 100644 BlogProject/yasg.py create mode 100644 requirements.txt diff --git a/Blog/serializer.py b/Blog/serializer.py new file mode 100644 index 0000000..e69de29 diff --git a/Blog/urls.py b/Blog/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/BlogProject/yasg.py b/BlogProject/yasg.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 From b7ab7c2445ff879188cabedb52cd7d7b2eb57af2 Mon Sep 17 00:00:00 2001 From: vladislavdacenko Date: Mon, 13 Nov 2023 14:34:31 +0200 Subject: [PATCH 2/9] =?UTF-8?q?=D0=92=D0=B8=D0=BA=D0=BE=D0=BD=D0=B0=D0=B2?= =?UTF-8?q?=20=D1=83=D1=81=D1=96=20=D0=B7=D0=B0=D0=B2=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8F=20=D0=B2=20=D0=BB=D1=804?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/BlogProject.iml | 2 +- .idea/misc.xml | 2 +- Blog/serializer.py | 14 ++++ Blog/views.py | 163 +++++++++++++++++++++++++++++++++++++++- BlogProject/settings.py | 14 +--- BlogProject/urls.py | 24 ++---- BlogProject/yasg.py | 21 ++++++ db.sqlite3 | Bin 151552 -> 151552 bytes requirements.txt | 3 + 9 files changed, 212 insertions(+), 31 deletions(-) diff --git a/.idea/BlogProject.iml b/.idea/BlogProject.iml index 92de0e4..8a24ab6 100644 --- a/.idea/BlogProject.iml +++ b/.idea/BlogProject.iml @@ -16,7 +16,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index a6c6116..c0ac767 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/Blog/serializer.py b/Blog/serializer.py index e69de29..9963870 100644 --- a/Blog/serializer.py +++ b/Blog/serializer.py @@ -0,0 +1,14 @@ +from rest_framework import serializers +from Blog.models import Post, Comment + + +class PostSerializer(serializers.ModelSerializer): + class Meta: + model = Post + fields = ['title', 'content', 'author', 'publ_date', 'category'] + +class CommentSerializer(serializers.ModelSerializer): + class Meta: + model = Comment + fields = ['post', 'author_of_the_comment', 'content_of_the_comment', 'date_of_creation'] + diff --git a/Blog/views.py b/Blog/views.py index 91ea44a..f92b5c7 100644 --- a/Blog/views.py +++ b/Blog/views.py @@ -1,3 +1,164 @@ from django.shortcuts import render +from drf_yasg.utils import swagger_auto_schema + +from .models import Post, Comment +from .serializer import PostSerializer, CommentSerializer +from django.http import Http404 +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +from drf_yasg import openapi + +class PostList(APIView): + + @swagger_auto_schema( + operation_description="Get a list of posts", + responses={200: openapi.Response('List of posts', PostSerializer(many=True))} + ) + def get(self, request, format=None): + post = Post.objects.all() + serializer = PostSerializer(post, many=True) + return Response(serializer.data) + + @swagger_auto_schema( + operation_description="Create a new post", + request_body=PostSerializer, + responses={201: 'Created', 400: 'Bad Request'} + ) + + def post(self, request, format=None): + serializer = PostSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class PostDetail(APIView): + + def get_object(self, pk): + try: + return Post.objects.get(pk=pk) + except Post.DoesNotExist: + raise Http404 + + @swagger_auto_schema( + operation_description="Get details of a specific post", + manual_parameters=[ + openapi.Parameter('pk', openapi.IN_PATH, description="Post ID", type=openapi.TYPE_INTEGER), + ], + responses={200: openapi.Response('Post details', PostSerializer)} + ) + + def get(self, request, pk, format=None): + post = self.get_object(pk) + serializer = PostSerializer(post) + return Response(serializer.data) + + @swagger_auto_schema( + operation_description="Update details of a specific post", + manual_parameters=[ + openapi.Parameter('pk', openapi.IN_PATH, description="Post ID", type=openapi.TYPE_INTEGER), + ], + request_body=PostSerializer, + responses={200: 'Updated', 400: 'Bad Request'} + ) + + def put(self, request, pk, format=None): + post = self.get_object(pk) + serializer = PostSerializer(post, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @swagger_auto_schema( + operation_description="Delete a specific post", + manual_parameters=[ + openapi.Parameter('pk', openapi.IN_PATH, description="Post ID", type=openapi.TYPE_INTEGER), + ], + responses={204: 'No Content'} + ) + + def delete(self, request, pk, format=None): + post = self.get_object(pk) + post.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class CommentList(APIView): + + @swagger_auto_schema( + operation_description="Get a list of comments", + responses={200: openapi.Response('List of comments', CommentSerializer(many=True))} + ) + + def get(self, request, format=None): + comment = Comment.objects.all() + serializer = CommentSerializer(comment, many=True) + return Response(serializer.data) + + @swagger_auto_schema( + operation_description="Create a new comment", + request_body=CommentSerializer, + responses={201: 'Created', 400: 'Bad Request'} + ) + + def post(self, request, format=None): + serializer = CommentSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class CommentDetail(APIView): + + def get_object(self, pk): + try: + return Comment.objects.get(pk=pk) + except Comment.DoesNotExist: + raise Http404 + + @swagger_auto_schema( + operation_description="Get details of a specific comment", + manual_parameters=[ + openapi.Parameter('pk', openapi.IN_PATH, description="Comment ID", type=openapi.TYPE_INTEGER), + ], + responses={200: openapi.Response('Comment details', CommentSerializer)} + ) + + def get(self, request, pk, format=None): + comment = self.get_object(pk) + serializer = CommentSerializer(comment) + return Response(serializer.data) + + @swagger_auto_schema( + operation_description="Update details of a specific comment", + manual_parameters=[ + openapi.Parameter('pk', openapi.IN_PATH, description="Comment ID", type=openapi.TYPE_INTEGER), + ], + request_body=CommentSerializer, + responses={200: 'Updated', 400: 'Bad Request'} + ) + + def put(self, request, pk, format=None): + comment = self.get_object(pk) + serializer = CommentSerializer(comment, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @swagger_auto_schema( + operation_description="Delete a specific comment", + manual_parameters=[ + openapi.Parameter('pk', openapi.IN_PATH, description="Comment ID", type=openapi.TYPE_INTEGER), + ], + responses={204: 'No Content'} + ) + + def delete(self, request, pk, format=None): + comment = self.get_object(pk) + comment.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + -# Create your views here. diff --git a/BlogProject/settings.py b/BlogProject/settings.py index 9bf9d7d..1efd18c 100644 --- a/BlogProject/settings.py +++ b/BlogProject/settings.py @@ -1,14 +1,3 @@ -""" -Django settings for BlogProject project. - -Generated by 'django-admin startproject' using Django 4.2.6. - -For more information on this file, see -https://docs.djangoproject.com/en/4.2/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/4.2/ref/settings/ -""" from pathlib import Path @@ -38,6 +27,9 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'Blog', + 'rest_framework', + 'drf_yasg', + ] MIDDLEWARE = [ diff --git a/BlogProject/urls.py b/BlogProject/urls.py index ce890c3..3a4e885 100644 --- a/BlogProject/urls.py +++ b/BlogProject/urls.py @@ -1,22 +1,12 @@ -""" -URL configuration for BlogProject project. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/4.2/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" from django.contrib import admin -from django.urls import path +from django.urls import path, re_path, include +from .yasg import urlpatterns as doc_urls + urlpatterns = [ path('admin/', admin.site.urls), + path('api/', include([ + path('blog/', include('Blog.urls')), + ])), ] +urlpatterns += doc_urls \ No newline at end of file diff --git a/BlogProject/yasg.py b/BlogProject/yasg.py index e69de29..b5588d9 100644 --- a/BlogProject/yasg.py +++ b/BlogProject/yasg.py @@ -0,0 +1,21 @@ +from django.urls import re_path, path +from rest_framework import permissions +from drf_yasg.views import get_schema_view +from drf_yasg import openapi + +schema_view = get_schema_view( + openapi.Info( + title="Snippets API", + default_version='v1', + description="Test description", + license=openapi.License(name="BSD License"), + ), + public=True, + permission_classes=(permissions.AllowAny,), +) + +urlpatterns = [ + path('swagger/', schema_view.without_ui(cache_timeout=0), name='schema-json'), + path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), + path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), +] \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 index ae0541903e9dde9739af9e4dac80f907492d4306..3137b2269c4685207d2353599bf0a494154ce23a 100644 GIT binary patch delta 1250 zcmaiz%WoT16vpq&Z61?$a$J?RRHeje9n>hXGf$7ls0$}??bxwXzv3hanX$+9Gq&Tg z^Kca$)-r7=p3r3@>8QtIc zbkBFbyLp${yvuxYo)SNOYl0HL{@{y&GoPbg2+{BYLhvmt!^e<;FX6Z5Ag97sXza8J zvwe7g((1KrqHd(Ccx!pg@(%orSZ2If&Lk>j!*m=EQ3pS%^5SB;RHJj{vlxri)^{gP zo#{mgbu&79f~a&VYPk7g>^#?p*SR*aOuL@0x#KpOS$=Jx8s^c=OW{-vFd!qk&nbu;b?3{sfwOx z(4TTNg0i?^NRhB!ibq$X(iMRdL@Uo*In~SyPEmDAij9}lQI3YWCK1BUb*oUOkuh@+b zFVS6rk76!|>?joLB~@LiEX&nGV<}TiY4Tc16}6m_t@F0DZ*?~BcF$z;u249gh}7bx z@}ws=U$jbUs4-LY#;yhT~X_W>2K8YNFvAua#nfxPzZw^i9d0Mx-RJiGD{Q zTO79*>T+}=nVgR()j%Px_!C-&pH)glpLK2}!6_bpd`2YJO;&hsxV{)O49#=RJ+bUx zlEVwJY9ODl#`7g(!r^w+Rs-`5UwJkjo1D)0L^5qU+v;K+1bLYOU4XlX1_qoQ);8j?04hpLxg}Cv(jwcZR868d?Zi@RWFb z4~8r_K>eR@bo1%`pe7!dZDC zRn~K*47Sic==23b7qz5hl+9VKst+HbNW{l({c@igJq~{%3ic7~!+!JR<{$887p5qh zCcv>icu5YkgJ2JKV2AAf4yN`l#s_Pg>1w_Df$B1mR1xeY!S)WYfi}={fc4VHFvaxr z5%?d7s2xDvhgz+@R}`2S3<$vk2m*=QpZ&pU%piD|8KEeb(C);XTrBQ2BgwvWNXSwJ1`|ka6Gbbu@qW0Uxz(L2X7`RwC^|n5dj);abBDlsj zb`i%Jj_fXT64^+g#iKR4q*uhY(ks!e&E*X(yB+B6b-Sfk&8%7R=(@F;MuY76J1ttt z#Vu-c>?71gn+L zTnznzZ^Ac&4>YkSrBbe%|3&z!t(`;;DOCRFUetNGLWKb;jLK-MlY(1SaeX!Q59TXB z0@Xm6aEUN~+6?kxuXe?y@lJT>D_+spDW_G?437FoOn-3PHxil}o8%du2${i9sIk`N Sk?_d&9UOzgNrx|UTK_L^ws*4t diff --git a/requirements.txt b/requirements.txt index e69de29..fa5e2a5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1,3 @@ +Django~=4.2.6 +djangorestframework==3.14.0 +drf-yasg==1.21.7 From 86111d76d96546597fac51ada521df1bd2530fa7 Mon Sep 17 00:00:00 2001 From: vladislavdacenko Date: Mon, 13 Nov 2023 14:34:38 +0200 Subject: [PATCH 3/9] =?UTF-8?q?=D0=92=D0=B8=D0=BA=D0=BE=D0=BD=D0=B0=D0=B2?= =?UTF-8?q?=20=D1=83=D1=81=D1=96=20=D0=B7=D0=B0=D0=B2=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8F=20=D0=B2=20=D0=BB=D1=804?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Blog/urls.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Blog/urls.py b/Blog/urls.py index e69de29..a7779a6 100644 --- a/Blog/urls.py +++ b/Blog/urls.py @@ -0,0 +1,13 @@ +from django.urls import path +from . import views + +from rest_framework.urlpatterns import format_suffix_patterns + +urlpatterns = [ + path('post/', views.PostList.as_view()), + path('post//', views.PostDetail.as_view()), + path('comment/', views.CommentList.as_view()), + path('comment//', views.CommentDetail.as_view()), +] + +urlpatterns = format_suffix_patterns(urlpatterns) \ No newline at end of file From 9f66a81ea1c96b4a93603215ef09709f8dbbb963 Mon Sep 17 00:00:00 2001 From: vladislavdacenko Date: Mon, 13 Nov 2023 14:46:51 +0200 Subject: [PATCH 4/9] finish --- Blog/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Blog/views.py b/Blog/views.py index f92b5c7..0450f2c 100644 --- a/Blog/views.py +++ b/Blog/views.py @@ -138,6 +138,7 @@ def get(self, request, pk, format=None): ], request_body=CommentSerializer, responses={200: 'Updated', 400: 'Bad Request'} + ) def put(self, request, pk, format=None): From 215c4634569325e2ca0d454f5268ae2bf0c9f814 Mon Sep 17 00:00:00 2001 From: vladislavdacenko Date: Sun, 3 Dec 2023 13:30:09 +0200 Subject: [PATCH 5/9] =?UTF-8?q?=D0=94=D0=BE=D0=B4=D0=B0=D0=B2=20=D0=B0?= =?UTF-8?q?=D0=B2=D1=82=D0=BE=D1=80=D0=B8=D0=B7=D0=B0=D1=86=D1=96=D1=8E=20?= =?UTF-8?q?=D1=96=20=D0=BF=D0=B5=D1=80=D0=BC=D1=96=D1=88=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Blog/urls.py | 3 ++- Blog/views.py | 7 ++++++- BlogProject/settings.py | 7 +++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Blog/urls.py b/Blog/urls.py index a7779a6..6cb9b44 100644 --- a/Blog/urls.py +++ b/Blog/urls.py @@ -1,4 +1,4 @@ -from django.urls import path +from django.urls import path, include from . import views from rest_framework.urlpatterns import format_suffix_patterns @@ -8,6 +8,7 @@ path('post//', views.PostDetail.as_view()), path('comment/', views.CommentList.as_view()), path('comment//', views.CommentDetail.as_view()), + path('drf-auth', include('rest_framework.urls')), ] urlpatterns = format_suffix_patterns(urlpatterns) \ No newline at end of file diff --git a/Blog/views.py b/Blog/views.py index 0450f2c..f354b97 100644 --- a/Blog/views.py +++ b/Blog/views.py @@ -1,5 +1,6 @@ from django.shortcuts import render from drf_yasg.utils import swagger_auto_schema +from rest_framework.permissions import IsAuthenticatedOrReadOnly from .models import Post, Comment from .serializer import PostSerializer, CommentSerializer @@ -10,13 +11,14 @@ from drf_yasg import openapi class PostList(APIView): + permission_classes = [IsAuthenticatedOrReadOnly] @swagger_auto_schema( operation_description="Get a list of posts", responses={200: openapi.Response('List of posts', PostSerializer(many=True))} ) def get(self, request, format=None): - post = Post.objects.all() + post = Post.objects.all() serializer = PostSerializer(post, many=True) return Response(serializer.data) @@ -34,6 +36,7 @@ def post(self, request, format=None): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class PostDetail(APIView): + permission_classes = [IsAuthenticatedOrReadOnly] def get_object(self, pk): try: @@ -86,6 +89,7 @@ def delete(self, request, pk, format=None): class CommentList(APIView): + permission_classes = [IsAuthenticatedOrReadOnly] @swagger_auto_schema( operation_description="Get a list of comments", @@ -111,6 +115,7 @@ def post(self, request, format=None): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class CommentDetail(APIView): + permission_classes = [IsAuthenticatedOrReadOnly] def get_object(self, pk): try: diff --git a/BlogProject/settings.py b/BlogProject/settings.py index 1efd18c..20ad7ae 100644 --- a/BlogProject/settings.py +++ b/BlogProject/settings.py @@ -115,3 +115,10 @@ # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.BasicAuthentication', + 'rest_framework.authentication.SessionAuthentication', + ] +} \ No newline at end of file From d9adb43eaf1ff934cdeca7692e38d3378f21c154 Mon Sep 17 00:00:00 2001 From: vladislavdacenko Date: Thu, 7 Dec 2023 13:46:18 +0200 Subject: [PATCH 6/9] =?UTF-8?q?=D0=94=D0=BE=D0=B4=D0=B0=D0=B2=20=D0=BA?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=BE=D0=BC=D0=BD=D1=96=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B2=D1=96=D1=80=D0=BA=D0=B8=20=D1=96=20=D0=BA=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D0=BC=D0=BD=D1=96=20=D0=B2=D0=B0=D0=BB=D1=96?= =?UTF-8?q?=D0=B4=D0=B0=D1=86=D1=96=D1=97=20=D1=81=D0=B5=D1=80=D1=96=D0=B0?= =?UTF-8?q?=D0=BB=D0=B0=D0=B9=D0=B7=D0=B5=D1=80=D1=96=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Blog/admin.py | 1 - Blog/models.py | 1 + Blog/serializer.py | 50 +++++++++++++++++++++++++++++++++++++++- Blog/urls.py | 10 ++++++-- Blog/views.py | 17 ++++++++++---- BlogProject/settings.py | 5 ++++ db.sqlite3 | Bin 151552 -> 151552 bytes 7 files changed, 76 insertions(+), 8 deletions(-) diff --git a/Blog/admin.py b/Blog/admin.py index 133200a..06d6bc8 100644 --- a/Blog/admin.py +++ b/Blog/admin.py @@ -1,4 +1,3 @@ -from django.contrib import admin from django.contrib import admin from .models import Post, Comment diff --git a/Blog/models.py b/Blog/models.py index aa6aae7..4523881 100644 --- a/Blog/models.py +++ b/Blog/models.py @@ -1,6 +1,7 @@ from django.db import models from django.contrib.auth.models import User + class Post(models.Model): title = models.CharField(max_length=255) content = models.TextField() diff --git a/Blog/serializer.py b/Blog/serializer.py index 9963870..75cb3ec 100644 --- a/Blog/serializer.py +++ b/Blog/serializer.py @@ -1,5 +1,53 @@ -from rest_framework import serializers from Blog.models import Post, Comment +from rest_framework import serializers +from django.contrib.auth.models import User + +class UserRegistrationSerializer(serializers.ModelSerializer): + password = serializers.CharField(write_only=True) + + class Meta: + model = User + fields = ['email', 'first_name', 'last_name', 'password'] + + def validate_email(self, value): + # Кастомна валідація для формату електронної пошти + if not value.endswith('@gmail.com'): + raise serializers.ValidationError('Електронна пошта повинна закінчуватися на @gmail.com.') + return value + + def validate_password(self, value): + # Кастомна валідація для паролю (наприклад, довжина паролю) + if len(value) < 8: + raise serializers.ValidationError('Пароль повинен бути не менше 8 символів.') + return value + + def validate(self, data): + # Перевірка, чи користувач з таким ім'ям вже існує + username = data.get('email') + if User.objects.filter(username=username).exists(): + raise serializers.ValidationError('Користувач з таким іменем вже існує.') + return data + def create(self, validated_data): + validated_data['username'] = validated_data['email'] + user = User.objects.create_user(**validated_data) + return user + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ('username', 'email', 'password') + + def validate_email(self, value): + # Кастомна валідація для формату електронної пошти + if not value.endswith('@gmail.com'): + raise serializers.ValidationError('Електронна пошта повинна закінчуватися на @gmail.com.') + return value + + def validate_username(self, value): + # Кастомна валідація для унікальності імені користувача + if User.objects.filter(username=value).exists(): + raise serializers.ValidationError('Користувач з таким іменем вже існує.') + return value class PostSerializer(serializers.ModelSerializer): diff --git a/Blog/urls.py b/Blog/urls.py index 6cb9b44..0daab44 100644 --- a/Blog/urls.py +++ b/Blog/urls.py @@ -1,5 +1,6 @@ -from django.urls import path, include +from django.urls import path, include, re_path from . import views +from .views import register_user from rest_framework.urlpatterns import format_suffix_patterns @@ -8,7 +9,12 @@ path('post//', views.PostDetail.as_view()), path('comment/', views.CommentList.as_view()), path('comment//', views.CommentDetail.as_view()), - path('drf-auth', include('rest_framework.urls')), + path('drf-auth/', include('rest_framework.urls')), #в гуглі + path('register/', register_user, name='register_user'), # в postman + #path('auth/', include('djoser.urls')), + #path('auth/', include('djoser.urls.authtoken')), + + ] urlpatterns = format_suffix_patterns(urlpatterns) \ No newline at end of file diff --git a/Blog/views.py b/Blog/views.py index f354b97..20f1e56 100644 --- a/Blog/views.py +++ b/Blog/views.py @@ -1,14 +1,24 @@ -from django.shortcuts import render from drf_yasg.utils import swagger_auto_schema from rest_framework.permissions import IsAuthenticatedOrReadOnly - from .models import Post, Comment from .serializer import PostSerializer, CommentSerializer from django.http import Http404 from rest_framework.views import APIView +from drf_yasg import openapi from rest_framework.response import Response from rest_framework import status -from drf_yasg import openapi +from rest_framework.decorators import api_view +from .serializer import UserRegistrationSerializer + +@api_view(['POST']) +def register_user(request): + serializer = UserRegistrationSerializer(data=request.data) + + if serializer.is_valid(): + serializer.save() + return Response({'message': 'User registered successfully'}, status=status.HTTP_201_CREATED) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class PostList(APIView): permission_classes = [IsAuthenticatedOrReadOnly] @@ -116,7 +126,6 @@ def post(self, request, format=None): class CommentDetail(APIView): permission_classes = [IsAuthenticatedOrReadOnly] - def get_object(self, pk): try: return Comment.objects.get(pk=pk) diff --git a/BlogProject/settings.py b/BlogProject/settings.py index 20ad7ae..1e13200 100644 --- a/BlogProject/settings.py +++ b/BlogProject/settings.py @@ -28,7 +28,9 @@ 'django.contrib.staticfiles', 'Blog', 'rest_framework', + 'rest_framework.authtoken', 'drf_yasg', + 'djoser', ] @@ -120,5 +122,8 @@ 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.TokenAuthentication', + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ] } \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 index 3137b2269c4685207d2353599bf0a494154ce23a..b8c1143651b6e463fb176ad8f8b4a417a2c8ddf6 100644 GIT binary patch delta 1664 zcmcJPO>Y}j6o&2CO&XkZN@-QmqBd07pkin4oo}Ov&Uoyx9oysBiR~DnGPcL#@qGDX z#^dp#Zd!GVO1na+C=1q9D5}DS1>z5M!zye*{D7=kVY_LlXi#Cn>Pq(%Oy0m^r)1u01j@jYmnFQi?$2E5)2tz}&vj0ldDtMM+sFKE!(UxVJ6S$r#`8 z>bWfqt7i>Pu?3H#Gj$nkWH5AnAn9sDvSnND+qV=$QuRpPGD^p{8L4lZk|Cd-^$|oN z76vekQY!#NF(wL_2%sp2q+b?DsJ#~zc9Zi{`Rv*7_3%XgyHoi}u7k6EzMR;ySqyEV zLP`-b`l^8IY2Z1{4xPQ(jjv(-jh>pK&*Aep#apFh;G~Y`ueL1&?rVn0gR9Y zqzJmNO9NH=t0mr(>MhyStdjsZZuXTLC=BK)U6d+juV|zhrCA@C z@z+%oR0Kl-K~qx0P|XuT*b2a+kcy&21jTR?GP@rJmtVL5dk33fyfnqQ*@JqrA*4!W z*Q<1$GGEKb2d!1pO?b6ZlIKzand`uI<)&033Nm5kg`Bs;ieQ*%o3gvZOV+wPl#Grp z-#R|07T_)Ul-s{aBPP5<5eP{Fr_)x;+&?ld&RtkQ?)Z>zk?)Z&kvqum$aCaq|Lsl- zDO?FK{?nbba4`_}pA6=BF7u0l%l;XVGnWIH{}eUB{pVx+{8BKa`OY0KKU;bj(k9FD zNoaiUM(|Abevu`+Bey1FbeqIos;@f`Cu()n87Y|7m1_+$BBzI?c04Y+D!W;bZ^vMZ zvy`IdV3od*ZO8Z$i)x(`TBS%L=h(d64BuvSSH*k9Qs1oBU2UXfHc;|*nchP2jKw>K z$mFVHU8+>;gr~y-7sfnjsZE{EGosht@u&ujXL1#n=BuK!-Ef40l^Vnfi8$zsf?ZBK zRvc|~`)Hw+u;^?dYlX9HqD$w)HBIE$DClvVu@%jGiW#Sk4x#cKk%~EBM5Q4YqDnOerI>dMQRL9ha5J2G<{Bq{3Njlc}ksAtyAQKzO5L*29K9#kcob`+WR zAheqU`oCft|4UB)7pB~l6JwOvXgXU2G9HJUqjHz6X@&~&trRKJE{0dLR=b&KCR~^s zYV`IACNTNTW?MJ;1^|Ph5Z{0HM4Vd+B8Cq!kOSlwpCY-{K-T{zhXtoeZ{i!6 zCO!V)$;$o1rw4~m{U6+&y!OC{{D%C5e1klgyyhU>wE*k?=TQb<4Zq$(U>bh>|k!JZno;XeW&5P|+M AmH+?% From 02375a39635069cd87ac9281edf40c3e29e712dc Mon Sep 17 00:00:00 2001 From: vladislavdacenko Date: Thu, 7 Dec 2023 14:11:10 +0200 Subject: [PATCH 7/9] =?UTF-8?q?=D0=94=D0=BE=D0=B4=D0=B0=D0=B2=20=D0=B2?= =?UTF-8?q?=D1=81=D1=96=20=D0=B2=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=96=20=D0=B1=D1=96=D0=B1=D0=BB=D1=96=D0=BE=D1=82?= =?UTF-8?q?=D0=B5=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fa5e2a5..26b2513 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,33 @@ -Django~=4.2.6 +asgiref==3.7.2 +certifi==2023.11.17 +cffi==1.16.0 +charset-normalizer==3.3.2 +cryptography==41.0.7 +defusedxml==0.8.0rc2 +Django==4.2.6 +django-templated-mail==1.1.1 djangorestframework==3.14.0 +djangorestframework-simplejwt==5.3.0 +djoser==2.2.2 drf-yasg==1.21.7 +factory-boy==3.3.0 +Faker==20.1.0 +idna==3.6 +inflection==0.5.1 +oauthlib==3.2.2 +packaging==23.2 +pycparser==2.21 +PyJWT==2.8.0 +python-dateutil==2.8.2 +python3-openid==3.2.0 +pytz==2023.3.post1 +PyYAML==6.0.1 +requests==2.31.0 +requests-oauthlib==1.3.1 +six==1.16.0 +social-auth-app-django==5.4.0 +social-auth-core==4.5.1 +sqlparse==0.4.4 +typing_extensions==4.8.0 +uritemplate==4.1.1 +urllib3==2.1.0 From 7788d2241dc0b4f12392688b4e2810f44c68983e Mon Sep 17 00:00:00 2001 From: vladislavdacenko Date: Sat, 9 Dec 2023 12:45:04 +0200 Subject: [PATCH 8/9] =?UTF-8?q?=D0=94=D0=BE=D0=B4=D0=B0=D0=B2=20=D0=B4?= =?UTF-8?q?=D0=B5=D0=BA=D1=96=D0=BB=D1=8C=D0=BA=D0=B0=20=D1=82=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D1=96=D0=B2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Blog/tests.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/Blog/tests.py b/Blog/tests.py index 7ce503c..2596f16 100644 --- a/Blog/tests.py +++ b/Blog/tests.py @@ -1,3 +1,89 @@ from django.test import TestCase +from rest_framework.test import APITestCase + +from .models import User +from .serializer import UserRegistrationSerializer + + +class UserRegistrationAPITests(APITestCase): + + def setUp(self): + self.valid_data = { + 'email': 'test@gmail.com', + 'first_name': 'Test', + 'last_name': 'User', + 'password': '12345678', + } + + def test_registration_success(self): + response = self.client.post('/api/blog/register/', data=self.valid_data) + self.assertEqual(response.status_code, 201) + + user = User.objects.get(email='test@gmail.com') + self.assertEqual(user.first_name, 'Test') + self.assertEqual(user.last_name, 'User') + self.assertTrue(user.check_password('12345678')) + + def test_registration_with_invalid_email(self): + invalid_data = self.valid_data.copy() + invalid_data['email'] = 'test' + response = self.client.post('/api/blog/register/', data=invalid_data) + self.assertEqual(response.status_code, 400) + + # Перевірка наявності 'email' у відповіді + self.assertIn('email', response.json()) + + # Перевірка тексту помилки, використовуючи 'in' + expected_error = 'Enter a valid email address.' + actual_error = response.json()['email'][0] + self.assertIn(expected_error, actual_error) + + def test_registration_with_too_short_password(self): + invalid_data = self.valid_data.copy() + invalid_data['password'] = '123' + response = self.client.post('/api/blog/register/', data=invalid_data) + self.assertEqual(response.status_code, 400) + + # Перевірка наявності 'password' у відповіді + self.assertIn('password', response.json()) + + # Перевірка тексту помилки + self.assertEqual(response.json()['password'][0], 'Пароль повинен бути не менше 8 символів.') + + def test_registration_with_existing_username(self): + self.client.post('/api/blog/register/', data=self.valid_data) + response = self.client.post('/api/blog/register/', data=self.valid_data) + self.assertEqual(response.status_code, 400) + + # Перевірка наявності 'non_field_errors' у відповіді + self.assertIn('non_field_errors', response.json()) + + # Перевірка тексту помилки для 'non_field_errors' + expected_error = 'Користувач з таким іменем вже існує.' + actual_error = response.json()['non_field_errors'][0] + self.assertIn(expected_error, actual_error) + + def test_registration_with_non_gmail_email(self): + invalid_data = self.valid_data.copy() + invalid_data['email'] = 'test@yahoo.com' + response = self.client.post('/api/blog/register/', data=invalid_data) + self.assertEqual(response.status_code, 400) + + # Перевірка наявності 'email' у відповіді + self.assertIn('email', response.json()) + + # Перевірка тексту помилки для 'email' + expected_error = 'Електронна пошта повинна закінчуватися на @gmail.com.' + actual_error = response.json()['email'][0] + self.assertIn(expected_error, actual_error) + + def test_registration_with_valid_email(self): + valid_data = self.valid_data.copy() + valid_data['email'] = 'test@gmail.com' + response = self.client.post('/api/blog/register/', data=valid_data) + self.assertEqual(response.status_code, 201) + + # Перевірка, що користувач був успішно створений + user = User.objects.get(email='test@gmail.com') + self.assertEqual(user.email, 'test@gmail.com') -# Create your tests here. From 7504bec5bff71b020ff5dc070ffe25b5c88dbfe3 Mon Sep 17 00:00:00 2001 From: vladislavdacenko Date: Mon, 11 Dec 2023 15:33:22 +0200 Subject: [PATCH 9/9] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=B2?= =?UTF-8?q?=20=D0=BF=D0=B5=D1=80=D0=BC=D1=96=D1=88=D0=B5=D0=BD=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Blog/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Blog/views.py b/Blog/views.py index 20f1e56..e0e8740 100644 --- a/Blog/views.py +++ b/Blog/views.py @@ -1,5 +1,5 @@ from drf_yasg.utils import swagger_auto_schema -from rest_framework.permissions import IsAuthenticatedOrReadOnly +from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAdminUser from .models import Post, Comment from .serializer import PostSerializer, CommentSerializer from django.http import Http404 @@ -46,7 +46,7 @@ def post(self, request, format=None): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class PostDetail(APIView): - permission_classes = [IsAuthenticatedOrReadOnly] + permission_classes = [IsAdminUser] def get_object(self, pk): try: @@ -125,7 +125,7 @@ def post(self, request, format=None): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class CommentDetail(APIView): - permission_classes = [IsAuthenticatedOrReadOnly] + permission_classes = [IsAdminUser] def get_object(self, pk): try: return Comment.objects.get(pk=pk)