090500
This commit is contained in:
0
config/accounts/__init__.py
Normal file
0
config/accounts/__init__.py
Normal file
3
config/accounts/admin.py
Normal file
3
config/accounts/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
config/accounts/apps.py
Normal file
6
config/accounts/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AccountsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'accounts'
|
||||
0
config/accounts/management/__init__.py
Normal file
0
config/accounts/management/__init__.py
Normal file
0
config/accounts/management/commands/__init__.py
Normal file
0
config/accounts/management/commands/__init__.py
Normal file
26
config/accounts/management/commands/createUser.py
Normal file
26
config/accounts/management/commands/createUser.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from django.core.management.base import BaseCommand, CommandParser
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Create Application User'
|
||||
|
||||
def add_arguments(self, parser: CommandParser) -> None:
|
||||
parser.add_argument('--email', type=str, help="User's email")
|
||||
parser.add_argument('--name', type=str, help="User's name")
|
||||
parser.add_argument('--password', type=str, help="User's password")
|
||||
|
||||
def handle(self, *args, **options) -> None:
|
||||
email: str = options['email']
|
||||
name: str = options['name']
|
||||
password: str = options['password']
|
||||
|
||||
User = get_user_model()
|
||||
if email and name and password:
|
||||
if not User.objects.filter(email=email).exists() and not User.objects.filter(name=name).exists():
|
||||
User.objects.create_user(email=email, password=password, name=name)
|
||||
self.stdout.write(self.style.SUCCESS('Admin user created successfully.'))
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING('Admin user already exists.'))
|
||||
else:
|
||||
self.stdout.write(self.style.ERROR('Please provide --email, --name, and --password arguments.'))
|
||||
0
config/accounts/migrations/__init__.py
Normal file
0
config/accounts/migrations/__init__.py
Normal file
80
config/accounts/models.py
Normal file
80
config/accounts/models.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from __future__ import annotations
|
||||
from django.contrib.auth.models import AbstractUser, BaseUserManager
|
||||
from django.db import models
|
||||
|
||||
|
||||
#
|
||||
# Управление учётными записями
|
||||
class UserManager(BaseUserManager):
|
||||
def create_user(
|
||||
self, email: str, password: str | None = None, **other_fields
|
||||
) -> User:
|
||||
user = User(email=email, **other_fields)
|
||||
|
||||
if password:
|
||||
user.set_password(password)
|
||||
else:
|
||||
user.set_unusable_password()
|
||||
|
||||
user.save()
|
||||
return user
|
||||
|
||||
def create_superuser(self, email: str, password: str | None = None, **other_fields) -> User:
|
||||
other_fields.setdefault("is_staff", True)
|
||||
other_fields.setdefault("is_superuser", True)
|
||||
other_fields.setdefault("is_active", True)
|
||||
|
||||
if other_fields.get("is_staff") is not True:
|
||||
raise ValueError("Superuser must be assigned to is_staff=True.")
|
||||
if other_fields.get("is_superuser") is not True:
|
||||
raise ValueError("Superuser must be assigned to is_superuser=True.")
|
||||
|
||||
return self.create_user(email, password, **other_fields)
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
GENDER_CHOICES = (
|
||||
('n', 'Не указано'),
|
||||
('m', 'Мужчина'),
|
||||
('f', 'Женщина'),
|
||||
)
|
||||
# remove default fields
|
||||
# first_name = None
|
||||
# last_name = None
|
||||
|
||||
first_name = models.CharField(max_length=60, null=True, blank=True, verbose_name='Имя')
|
||||
last_name = models.CharField(max_length=60, null=True, blank=True, verbose_name='Фамилия')
|
||||
gender = models.CharField(verbose_name='Пол', max_length=10, choices=GENDER_CHOICES, default='n')
|
||||
email = models.EmailField(verbose_name='Email Address', unique=True)
|
||||
username = models.CharField(max_length=60, verbose_name='Ник')
|
||||
bio = models.TextField(blank=True, verbose_name='О себе')
|
||||
image = models.URLField(null=True, blank=True, verbose_name='Аватар')
|
||||
# avatar = models.ImageField(upload_to='avatar_users/%Y/%m/%d/', null=True, blank=True)
|
||||
|
||||
followers = models.ManyToManyField("self", blank=True, symmetrical=False, verbose_name='Подписчики')
|
||||
|
||||
address = models.CharField(verbose_name='Адрес', max_length=255, blank=True, null=True)
|
||||
# country = models.ForeignKey('chatroom.Country', on_delete=models.SET_NULL, null=True)
|
||||
# state = models.ForeignKey('chatroom.State', on_delete=models.SET_NULL, null=True)
|
||||
date_joined = models.DateTimeField(verbose_name='Дата регистрации', blank=True, null=True, auto_now_add=True)
|
||||
last_updated = models.DateTimeField(verbose_name='Последнее обновление', blank=True, null=True, auto_now=True)
|
||||
last_login = models.DateTimeField(verbose_name='Последняя авторизация', blank=True, null=True, auto_now=True)
|
||||
status = models.BooleanField(default=False, verbose_name='Статус')
|
||||
|
||||
EMAIL_FIELD = 'email' # Указываем поле для аутентификации по email
|
||||
USERNAME_FIELD = 'email' # Указываем поле email как основной идентификатор пользователя
|
||||
REQUIRED_FIELDS = [] # Поля, которые должны быть заполнены при создании пользователя
|
||||
|
||||
objects = UserManager()
|
||||
|
||||
def get_full_name(self) -> str:
|
||||
if self.first_name and self.last_name:
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
else:
|
||||
return self.username
|
||||
|
||||
def get_short_name(self) -> str:
|
||||
if self.first_name and self.last_name:
|
||||
return f"{self.first_name[0]}{self.last_name}"
|
||||
else:
|
||||
return self.username
|
||||
41
config/accounts/serializers.py
Normal file
41
config/accounts/serializers.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from rest_framework import serializers
|
||||
from config.accounts.models import User
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'email', 'password', 'first_name', 'last_name', 'gender', 'bio', 'image', 'status')
|
||||
extra_kwargs = {'password': {'write_only': True}}
|
||||
|
||||
def create(self, validated_data):
|
||||
password = validated_data.pop('password')
|
||||
user = User(
|
||||
**validated_data
|
||||
)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
for key, value in validated_data.items():
|
||||
if key == 'password':
|
||||
instance.set_password(value)
|
||||
else:
|
||||
setattr(instance, key, value)
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
class ProfileSerializer(serializers.ModelSerializer):
|
||||
following = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'bio', 'image', 'following')
|
||||
|
||||
def get_following(self, obj):
|
||||
user = self.context.get('request').user
|
||||
if user.is_authenticated:
|
||||
return obj.followers.filter(pk=user.id).exists()
|
||||
return False
|
||||
155
config/accounts/tests.py
Normal file
155
config/accounts/tests.py
Normal file
@@ -0,0 +1,155 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework.test import APITestCase
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
from rest_framework_simplejwt.tokens import AccessToken
|
||||
|
||||
# from config.accounts.models import User
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class AccountRegistrationTestCase(APITestCase):
|
||||
def test_account_registration(self):
|
||||
url = '/api/users'
|
||||
user_data = {
|
||||
'user': {
|
||||
'email': 'test@example.com',
|
||||
'password': 'testpassword',
|
||||
'username': 'testuser',
|
||||
}
|
||||
}
|
||||
|
||||
response = self.client.post(url, user_data, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
def test_account_registration_invalid_data(self):
|
||||
url = '/api/users'
|
||||
invalid_user_data = {
|
||||
'user': {}
|
||||
}
|
||||
|
||||
response = self.client.post(url, invalid_user_data, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class AccountLoginTestCase(APITestCase):
|
||||
def setUp(self):
|
||||
self.email = 'test@example.com'
|
||||
self.username = 'testuser'
|
||||
self.password = 'testpassword'
|
||||
self.user = User.objects.create_user(
|
||||
email=self.email,
|
||||
username=self.username,
|
||||
password=self.password
|
||||
)
|
||||
self.url = '/api/users/login'
|
||||
|
||||
def tearDown(self):
|
||||
self.user.delete
|
||||
|
||||
def test_account_login(self):
|
||||
user_data = {
|
||||
'user': {
|
||||
'email': self.email,
|
||||
'password': self.password,
|
||||
}
|
||||
}
|
||||
|
||||
response = self.client.post(self.url, user_data, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
||||
|
||||
def test_account_login_invalid_data(self):
|
||||
invalid_user_data = {
|
||||
'user': {}
|
||||
}
|
||||
|
||||
response = self.client.post(self.url, invalid_user_data, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class UserViewTestCase(APITestCase):
|
||||
def setUp(self):
|
||||
self.email = 'test@example.com'
|
||||
self.username = 'testuser'
|
||||
self.password = 'testpassword'
|
||||
self.user = User.objects.create_user(
|
||||
email=self.email,
|
||||
username=self.username,
|
||||
password=self.password
|
||||
)
|
||||
self.access_token = str(AccessToken.for_user(self.user))
|
||||
self.client.credentials(
|
||||
HTTP_AUTHORIZATION='Token ' + self.access_token
|
||||
)
|
||||
self.url = reverse('user-account')
|
||||
|
||||
def test_user_view_get(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_user_view_put(self):
|
||||
updated_email = 'updated@example.com'
|
||||
updated_bio = 'Updated bio'
|
||||
updated_image = 'http://example.com/updated-image.jpg'
|
||||
user_data = {
|
||||
'user': {
|
||||
'email': updated_email,
|
||||
'bio': updated_bio,
|
||||
'image': updated_image,
|
||||
}
|
||||
}
|
||||
|
||||
response = self.client.put(self.url, user_data, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ProfileDetailViewTestCase(APITestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
email='test@example.com',
|
||||
username='testuser',
|
||||
password='testpassword'
|
||||
)
|
||||
self.access_token = str(AccessToken.for_user(self.user))
|
||||
self.client.credentials(
|
||||
HTTP_AUTHORIZATION='Token ' + self.access_token
|
||||
)
|
||||
self.url = f'/api/profiles/{self.user.username}'
|
||||
|
||||
def test_profile_detail_view_get(self):
|
||||
response = self.client.get(self.url)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_profile_detail_view_follow(self):
|
||||
second_user = User.objects.create_user(
|
||||
email='test2@gmail.com',
|
||||
username='test2user',
|
||||
password='password'
|
||||
)
|
||||
follow_url = f'/api/profiles/{second_user.username}/follow'
|
||||
|
||||
response = self.client.post(follow_url)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_profile_detail_view_unfollow(self):
|
||||
second_user = User.objects.create_user(
|
||||
email='test2@gmail.com',
|
||||
username='test2user',
|
||||
password='password'
|
||||
)
|
||||
second_user.followers.add(self.user)
|
||||
unfollow_url = f'/api/profiles/{second_user.username}/follow'
|
||||
|
||||
response = self.client.delete(unfollow_url)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
18
config/accounts/urls.py
Normal file
18
config/accounts/urls.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from config.accounts import views
|
||||
from .views import ProfileDetailView
|
||||
|
||||
profile_router = DefaultRouter(trailing_slash=False)
|
||||
profile_router.register('profiles', views.ProfileDetailView)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('users/login', views.account_login, name='account-login'),
|
||||
path('users', views.account_registration, name="account-registration"),
|
||||
path('user', views.UserView.as_view(), name='user-account'),
|
||||
#path('users/profiles/', ProfileDetailView.as_view(), name='user-profiles'),
|
||||
path('logout/', views.signout, name='logout'),
|
||||
path('', include(profile_router.urls))
|
||||
]
|
||||
161
config/accounts/views.py
Normal file
161
config/accounts/views.py
Normal file
@@ -0,0 +1,161 @@
|
||||
from rest_framework.decorators import api_view, action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status, views, viewsets
|
||||
from django.contrib.auth import authenticate
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.permissions import AllowAny
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib.auth import logout
|
||||
from django.http import JsonResponse
|
||||
|
||||
from config.accounts.models import User
|
||||
from config.accounts.serializers import UserSerializer, ProfileSerializer
|
||||
|
||||
|
||||
@api_view(['POST', ])
|
||||
def account_registration(request):
|
||||
try:
|
||||
user_data = request.data.get('user')
|
||||
|
||||
serializer = UserSerializer(data=user_data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return Response({"user": serializer.data}, status=status.HTTP_201_CREATED)
|
||||
|
||||
except Exception:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@api_view(['POST', ])
|
||||
@permission_classes([AllowAny]) # Разрешение для всех источников
|
||||
@csrf_exempt
|
||||
def account_login(request):
|
||||
try:
|
||||
user_data = request.data.get('user')
|
||||
user = authenticate(email=user_data['email'], password=user_data['password'])
|
||||
serializer = UserSerializer(user)
|
||||
jwt_token = RefreshToken.for_user(user)
|
||||
serializer_data = serializer.data
|
||||
serializer_data['token'] = str(jwt_token.access_token)
|
||||
response_data = {
|
||||
"user": serializer_data,
|
||||
}
|
||||
return Response(response_data, status=status.HTTP_202_ACCEPTED)
|
||||
|
||||
# except Exception:
|
||||
# return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Добавляем заголовок Access-Control-Allow-Origin
|
||||
# ТЕСТ
|
||||
# response = JsonResponse({"message": "Success!"})
|
||||
# response["Access-Control-Allow-Origin"] = "http://localhost:3000" # Разрешаем только локальный хост
|
||||
# return response
|
||||
|
||||
except Exception:
|
||||
# return JsonResponse({"message": "Error!"}, status=400)
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class UserView(views.APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, format=None):
|
||||
user = self.request.user
|
||||
serializer = UserSerializer(user)
|
||||
# Создание JWT-токена для пользователя
|
||||
token = RefreshToken.for_user(user)
|
||||
# Добавление ответа
|
||||
response_data = {
|
||||
"user": serializer.data,
|
||||
"token": {
|
||||
"access_token": str(token.access_token),
|
||||
"refresh_token": str(token),
|
||||
}
|
||||
}
|
||||
return Response(response_data, status=status.HTTP_200_OK)
|
||||
|
||||
def put(self, request, format=None, pk=None):
|
||||
user = self.request.user
|
||||
user_data = request.data.get('user')
|
||||
|
||||
user.email = user_data['email']
|
||||
user.bio = user_data['bio']
|
||||
user.image = user_data['image']
|
||||
user.save()
|
||||
|
||||
serializer = UserSerializer(user)
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ProfileDetailView(viewsets.ModelViewSet):
|
||||
queryset = User.objects.all()
|
||||
serializer_class = ProfileSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
lookup_field = 'username'
|
||||
http_method_names = ['get', 'post', 'delete']
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action == 'list':
|
||||
return [IsAuthenticatedOrReadOnly(), ]
|
||||
return super().get_permissions()
|
||||
|
||||
def list(self, request, username=None, *args, **kwargs):
|
||||
try:
|
||||
profile = User.objects.get(username=username)
|
||||
serializer = self.get_serializer(profile)
|
||||
return Response({"profile": serializer.data})
|
||||
|
||||
except Exception:
|
||||
return Response({"errors": {
|
||||
"body": [
|
||||
"Invalid User"
|
||||
]
|
||||
}})
|
||||
|
||||
@action(detail=True, methods=['post', 'delete'])
|
||||
def follow(self, request, username=None, *args, **kwargs):
|
||||
if request.method == 'POST':
|
||||
|
||||
profile = self.get_object()
|
||||
follower = request.user
|
||||
if profile == follower:
|
||||
return Response({"errors": {
|
||||
"body": [
|
||||
"Invalid follow Request"
|
||||
]
|
||||
}}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
profile.followers.add(follower)
|
||||
serializer = self.get_serializer(profile)
|
||||
return Response({"profile": serializer.data})
|
||||
|
||||
elif request.method == 'DELETE':
|
||||
|
||||
profile = self.get_object()
|
||||
follower = request.user
|
||||
if profile == follower:
|
||||
return Response({"errors": {
|
||||
"body": [
|
||||
"Invalid follow Request"
|
||||
]
|
||||
}}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if not profile.followers.filter(pk=follower.id).exists():
|
||||
return Response({"errors": {
|
||||
"body": [
|
||||
"Invalid follow Request"
|
||||
]
|
||||
}}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
profile.followers.remove(follower)
|
||||
serializer = self.get_serializer(profile)
|
||||
return Response({"profile": serializer.data})
|
||||
|
||||
|
||||
def signout(request):
|
||||
logout(request)
|
||||
return redirect("main")
|
||||
Reference in New Issue
Block a user