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/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
new file mode 100644
index 0000000..75cb3ec
--- /dev/null
+++ b/Blog/serializer.py
@@ -0,0 +1,62 @@
+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):
+ 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/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.
diff --git a/Blog/urls.py b/Blog/urls.py
new file mode 100644
index 0000000..0daab44
--- /dev/null
+++ b/Blog/urls.py
@@ -0,0 +1,20 @@
+from django.urls import path, include, re_path
+from . import views
+from .views import register_user
+
+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()),
+ 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 91ea44a..e0e8740 100644
--- a/Blog/views.py
+++ b/Blog/views.py
@@ -1,3 +1,179 @@
-from django.shortcuts import render
+from drf_yasg.utils import swagger_auto_schema
+from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAdminUser
+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 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]
+
+ @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):
+ permission_classes = [IsAdminUser]
+
+ 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):
+ permission_classes = [IsAuthenticatedOrReadOnly]
+
+ @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):
+ permission_classes = [IsAdminUser]
+ 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..1e13200 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,11 @@
'django.contrib.messages',
'django.contrib.staticfiles',
'Blog',
+ 'rest_framework',
+ 'rest_framework.authtoken',
+ 'drf_yasg',
+ 'djoser',
+
]
MIDDLEWARE = [
@@ -123,3 +117,13 @@
# 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',
+ 'rest_framework.authentication.TokenAuthentication',
+ 'rest_framework_simplejwt.authentication.JWTAuthentication',
+
+ ]
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..b5588d9
--- /dev/null
+++ 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 ae05419..b8c1143 100644
Binary files a/db.sqlite3 and b/db.sqlite3 differ
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..26b2513
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,33 @@
+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