Стартавая модель авторизации в приложении

This commit is contained in:
adm
2024-02-22 15:16:15 +03:00
parent bbe3fd7892
commit 645bddb07f
22 changed files with 744 additions and 1 deletions

View File

@@ -1,3 +1,18 @@
# accounts # accounts
Пример авторизации и управление пользователями. Пример авторизации и управление пользователями.
Для локаольного развертывания требуется сделать следующие шаги.
Переименовать local_settings.py_orig в local_settings.py
Исправьте следующие строки
SECRET_KEY Укажите свой ключ безопасности
DEBUG Выберите включен или выключен дебаг
ALLOWED_HOSTS Укажите свои сетевые адреса
CORS_ORIGIN_WHITELIST Добавьте свой список
CSRF_TRUSTED_ORIGINS Добавьте свой список
DB_CONFIG_SQLL или DB_CONFIG_PSQL выбирете один из доступных способов хранения данных
DB_CONFIG_PSQL Укажите актуальные учётные данные
В проекте в основу взять Django 4.2 Это стабильная ветка на данный момент, в версии 5.0 есть ряд изменений которые требую существенных изменений в проекте.
pip install -r requirements.txt
python manage.py runserver

4
config/asgi.py Normal file
View File

@@ -0,0 +1,4 @@
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
application = get_asgi_application()

View File

@@ -0,0 +1,48 @@
import os
from pathlib import Path
from django.core.management.utils import get_random_secret_key
#BASE_DIR = Path(__file__).resolve().parent.parent
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TEMPLATES_DIR = os.path.join(BASE_DIR, "templates")
STATICFILES_DIR = os.path.join(BASE_DIR, "staticfiles")
STATIC_DIR = os.path.join(BASE_DIR, "static")
MEDIA_DIR = os.path.join(BASE_DIR, "media")
LOGS_DIR = os.path.join(BASE_DIR, "logs")
SECRET_KEY = '***********************'
DEBUG = True
#DEBUG = False
ALLOWED_HOSTS = []
# For SQLlITE3 database
DB_CONFIG_SQLL = {
'ENGINE': os.getenv('DB_ENGINE', 'django.db.backends.sqlite3'),
'NAME': os.getenv('DB_NAME', 'db.sqlite3'),
}
# For PostgreSQL database (comment it if not needed)
DB_CONFIG_PSQL = {
'ENGINE': os.getenv('DB_ENGINE', 'django.db.backends.postgresql'),
'HOST': os.getenv('DB_HOST', '127.0.0.1'),
'PORT': os.getenv('DB_PORT', 5432),
'NAME': os.getenv('DB_NAME', '_db'),
'USER': os.getenv('DB_USER', 'admdb'),
'PASSWORD': os.getenv('DB_PASS', '**********')
}
CORS_ORIGIN_WHITELIST = [
'http://localhost:8000',
'https://localhost:8000',
'http://localhost:8001',
'https://localhost:8001',
'http://127.0.0.1:8000',
'https://127.0.0.1:8000',
'http://127.0.0.1:8001',
'https://127.0.0.1:8001',
'https://google.com',
'http://google.com',
]
CSRF_TRUSTED_ORIGINS = [
'http://127.0.0.1:8001',
'https://127.0.0.1:8001',
'http://127.0.0.1:8000',
'https://127.0.0.1:8000',
]

49
config/logging.py Normal file
View File

@@ -0,0 +1,49 @@
import os
from config.local_settings import LOGS_DIR
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{asctime} {levelname} {module} {process:d} {thread:d} '
'{message}',
'style': '{',
},
'simple': {
'format': '{asctime} {levelname} {message}',
'style': '{',
},
}, # formatters
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'simple',
},
'file': {
'level': 'DEBUG',
'formatter': 'verbose',
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': os.path.join(LOGS_DIR, "debug.log"),
'when': 'midnight',
'backupCount': 30,
},
}, # handlers
'loggers': {
'': { # root logger
'handlers': ['console', 'file'],
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO').upper(),
},
'customauth': {
'handlers': ['console', 'file'],
'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG').upper(),
'propagate': False, # required to eliminate duplication on root
},
# 'app_name': {
# 'handlers': ['console', 'file'],
# 'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG').upper(),
# 'propagate': False, # required to eliminate duplication on root
# },
}, # loggers
} # logging

178
config/settings.py Normal file
View File

@@ -0,0 +1,178 @@
import os
from pathlib import Path
from datetime import timedelta
# Вставка из файла конфика, нужно данный файл вынесити за пределы проекта в ENV часть.
from .local_settings import (
SECRET_KEY, DEBUG, ALLOWED_HOSTS, DB_CONFIG,
TEMPLATES_DIR, STATICFILES_DIR, STATIC_DIR, MEDIA_DIR, LOGS_DIR, CORS_ORIGIN_WHITELIST, CSRF_TRUSTED_ORIGINS
)
from .logging import LOGGING
# Списки сокращений
SETTINGS_DIR = os.path.dirname(os.path.abspath(__file__))
BASE_DIR = os.path.dirname(SETTINGS_DIR)
TEMPLATES_DIR = os.getenv('TEMPLATES_DIR', TEMPLATES_DIR)
STATICFILES_DIR = os.getenv('STATICFILES_DIR', STATICFILES_DIR)
STATIC_DIR = os.getenv('STATIC_DIR', STATIC_DIR)
MEDIA_DIR = os.getenv('MEDIA_DIR', MEDIA_DIR)
LOGS_DIR = os.getenv('LOGS_DIR', LOGS_DIR)
SECRET_KEY = SECRET_KEY
DEBUG = DEBUG
ALLOWED_HOSTS = ALLOWED_HOSTS
# Определение приложения
# Встроенные в framework
DJANGO_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
# Стронние приложения
THIRD_PARTY_APPS = [
'rest_framework',
'rest_framework_simplejwt',
'django_filters',
'taggit',
'drf_yasg',
'corsheaders',
]
# Внетренние приложения
LOCAL_APPS = [
'system.accounts.apps.AccountsConfig',
'system.main.apps.MainConfig',
]
# Сборщик всех приложений в один список
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'config.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [TEMPLATES_DIR, ],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'config.wsgi.application'
# База данныех для изменеия используйте один теговое обозначение
# DB_CONFIG_SQLL = SQLlITE3
# DB_CONFIG_PSQL = PostgreSQL
DATABASES = {
'default': os.getenv('DB_CONFIG_SQLL', DB_CONFIG_SQLL)
}
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',},
]
LANGUAGE_CODE = 'ru-ru'
TIME_ZONE = 'Europe/Moscow'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
STATIC_ROOT = STATIC_DIR
STATICFILES_DIRS = [STATICFILES_DIR, ]
MEDIA_URL = '/media/'
MEDIA_ROOT = MEDIA_DIR
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Настройка аутентификации
AUTH_USER_MODEL = 'accounts.User'
# Logging ---------------------------------------------------------------------
# https://docs.djangoproject.com/en/5.0/topics/logging/
if os.getenv('DISABLE_LOGGING', False): # только для celery в jenkins ci
LOGGING_CONFIG = None
LOGGING = LOGGING
# REST API
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
#'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
#'PAGE_SIZE': 2,
#ХЗ почему с этими параметрами не работает. Нужно более подробно изучить проблему.
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
"""
# JWT token settings
# По умолчанию был такой пример, узучить более подробно
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=7),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': True,
'UPDATE_LAST_LOGIN': True,
'AUTH_HEADER_TYPES': ('Token',)
}
"""
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': False,
'UPDATE_LAST_LOGIN': False,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'JWK_URL': None,
'LEEWAY': 0,
'AUTH_HEADER_TYPES': ('JWT',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
# https://whitenoise.readthedocs.io/en/stable/
# Радикально упрощенное обслуживание статических файлов для веб-приложений Python.
STORAGES = {
'staticfiles': {
'BACKEND': 'whitenoise.storage.CompressedManifestStaticFilesStorage',
},
}
# приложение для обработки заголовков сервера, необходимых для совместного использования ресурсов между источниками (CORS)
CORS_ORIGIN_ALLOW_ALL=True
CORS_ORIGIN_WHITELIST = CORS_ORIGIN_WHITELIST
CSRF_TRUSTED_ORIGINS = CSRF_TRUSTED_ORIGINS
# При выходе из учётной записи вас направит на данный url
LOGOUT_REDIRECT_URL = "main"

35
config/urls.py Normal file
View File

@@ -0,0 +1,35 @@
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from rest_framework import permissions
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView
# Машиночитаемая [схема] описывает, какие ресурсы доступны через API
schema_view = get_schema_view(
openapi.Info(
title="??? API",
default_version='v1',
description="??? API Documentation",
),
public=True,
permission_classes=(permissions.AllowAny,),
)
# Префикс для понтов, можно без него
api_prefix = 'api'
# Кортеж адресов
urlpatterns = [
path('admin/', admin.site.urls),# Адрес админ панели
path('', include('system.main.urls')), # Адрес главной странице (заглушка)
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-redoc'), # API сборник адресов
path(f'{api_prefix}/', include('system.accounts.urls')), # Адрес управление учётными данными
path(f'{api_prefix}/api-auth/', include('rest_framework.urls')), # API rest, допилить виюшку
path(f'{api_prefix}/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), # Присвоение токена
path(f'{api_prefix}/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), # Перевыдача токена
path(f'{api_prefix}/token/verify/', TokenVerifyView.as_view(), name='token_verify'), # Верифекация токена
]
# Обслуживание файлов, загруженных пользователем во время разработки
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

4
config/wsgi.py Normal file
View File

@@ -0,0 +1,4 @@
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
application = get_wsgi_application()

19
manage.py Normal file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env python
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

31
requirements.txt Normal file
View File

@@ -0,0 +1,31 @@
asgiref==3.7.2
certifi==2023.11.17
charset-normalizer==3.3.2
coreapi==2.3.3
coreschema==0.0.4
Django==4.2.7
django-cors-headers==4.3.1
django-filter==23.5
django-taggit==5.0.1
djangorestframework==3.14.0
djangorestframework-simplejwt==5.3.1
drf-yasg==1.21.7
idna==3.6
inflection==0.5.1
itypes==1.2.0
Jinja2==3.1.3
Markdown==3.5.2
MarkupSafe==2.1.4
packaging==23.2
pillow==10.2.0
psycopg2==2.9.9
PyJWT==2.8.0
pytz==2023.3.post1
PyYAML==6.0.1
requests==2.31.0
sqlparse==0.4.4
typing_extensions==4.9.0
tzdata==2023.4
uritemplate==4.1.1
urllib3==2.1.0
whitenoise==6.6.0

62
system/accounts/admin.py Normal file
View File

@@ -0,0 +1,62 @@
# Системные приложения
import logging
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.admin.models import LogEntry, DELETION
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.urls import reverse
# Внутриние приложения
from system.accounts.models import User
logger = logging.getLogger(__name__)
class ProfileInline(admin.StackedInline):
model = User
can_delete = False
max_num = 1
verbose_name = 'Profile'
verbose_name_plural = 'Profile'
fk_name = 'user'
@admin.register(User)
class UserAdmin(UserAdmin):
ordering = ('email', )
list_display = (
'id', 'email', 'is_staff', 'is_superuser', 'is_active', 'date_joined',
'last_updated', 'last_login'
#'verified_email', 'accepted_terms', 'read_terms',
)
search_fields = ('email',)
list_filter = (
'is_staff', 'is_superuser', 'is_active',
#'verified_email'
)
readonly_fields = (
'last_login', 'last_updated', 'date_joined'
#'email_token',
)
list_display_links = ('id', 'email')
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Permissions', {'fields': ('groups', 'user_permissions')}),
('Roles', {'fields': ('is_staff', 'is_superuser', 'is_active')}),
#('Additional', {'fields': (
#'verified_email', 'email_token', 'accepted_terms', 'read_terms'
#)}),
('Profile', {'fields': (
'first_name','last_name', 'gender', 'bio', 'image',
'address', 'followers'
)}),
('Dates', {'fields': ('last_login', 'last_updated', 'date_joined')})
)
add_fieldsets = (
(None, {
'classes': ('wide', ),
'fields': (
'email', 'password1', 'password2'
)
}),
)
#inlines = (ProfileInline, )

7
system/accounts/apps.py Normal file
View File

@@ -0,0 +1,7 @@
from django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'system.accounts'
verbose_name = 'Управление пользователями'

68
system/accounts/models.py Normal file
View File

@@ -0,0 +1,68 @@
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', 'Женщина'),
)
first_name: str = models.CharField(max_length=60, null=True, blank=True, verbose_name='Имя')
last_name: str = models.CharField(max_length=60, null=True, blank=True, verbose_name='Фамилия')
gender: str = models.CharField(verbose_name='Пол', max_length=10, choices=GENDER_CHOICES, default='n')
email: str = models.EmailField(verbose_name='Email Address', unique=True)
username: str = models.CharField(max_length=60, verbose_name='Ник')
bio: str = models.TextField(blank=True, verbose_name='О себе')
image: str | None = models.URLField(null=True, blank=True, verbose_name='Аватар')
followers = models.ManyToManyField("self", blank=True, symmetrical=False, verbose_name='Подписчики')
address: str = models.CharField(verbose_name='Адрес', max_length=255, blank=True, null=True)
#avatar = models.ImageField(upload_to=avatar_upload_path, null=True, blank=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)
EMAIL_FIELD = "email"
USERNAME_FIELD = "email"
REQUIRED_FIELDS: list[str] = []
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

View File

@@ -0,0 +1,40 @@
from rest_framework import serializers
from system.accounts.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name', 'password', 'bio', 'image')
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', 'first_name', 'last_name', '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

2
system/accounts/tests.py Normal file
View File

@@ -0,0 +1,2 @@
from django.test import TestCase
# Создайте первуый тест

18
system/accounts/urls.py Normal file
View File

@@ -0,0 +1,18 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from django.contrib.auth.views import LogoutView
from django.contrib.auth import views
from django.urls import path
from system.accounts import views
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('signout/', views.signout, name='signout'),
path('logout/', views.signout, name='logout'),
path('', include(profile_router.urls))
]

135
system/accounts/views.py Normal file
View File

@@ -0,0 +1,135 @@
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 django.shortcuts import redirect
from django.contrib.auth import logout
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
from system.accounts.models import User
from system.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', ])
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)
class UserView(views.APIView):
permission_classes = [IsAuthenticated]
def get(self, request, format=None):
user = self.request.user
serializer = UserSerializer(user)
return Response(serializer.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")

10
system/main/admin.py Normal file
View File

@@ -0,0 +1,10 @@
from django.apps import apps
from django.contrib import admin
#
models = apps.get_models()
# код что отображает все модели всех apps
for model in models:
try:
admin.site.register(model)
except admin.sites.AlreadyRegistered:
pass

6
system/main/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class MainConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'system.main'
verbose_name = 'Главное приложение'

2
system/main/models.py Normal file
View File

@@ -0,0 +1,2 @@
from django.db import models
# Создайте первую модель

2
system/main/tests.py Normal file
View File

@@ -0,0 +1,2 @@
from django.test import TestCase
# Создайте первуый тест

0
system/main/urls.py Normal file
View File

8
system/main/views.py Normal file
View File

@@ -0,0 +1,8 @@
from django.shortcuts import render
from django.views.generic import View
# Простое отображение страницы заглушки.
class MainView(View):
template_name = 'index.html'
def get(self, request):
return render(request, self.template_name)