diff --git a/server/proj/account/__init__.py b/server/proj/account/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/proj/account/admin.py b/server/proj/account/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/server/proj/account/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/server/proj/account/apps.py b/server/proj/account/apps.py new file mode 100644 index 0000000..2b08f1a --- /dev/null +++ b/server/proj/account/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'account' diff --git a/server/proj/account/forms.py b/server/proj/account/forms.py new file mode 100644 index 0000000..c65f721 --- /dev/null +++ b/server/proj/account/forms.py @@ -0,0 +1,60 @@ +from django import forms +from django.contrib.auth.forms import ReadOnlyPasswordHashField +from django.contrib.auth.forms import ReadOnlyPasswordHashField +from django.core.exceptions import ValidationError + +from account.models import MyUser + + +class UserCreationForm(forms.ModelForm): + """A form for creating new users. Includes all the required + fields, plus a repeated password.""" + password1 = forms.CharField(label='Пароль', widget=forms.PasswordInput) + password2 = forms.CharField(label='Пароль ещё раз', widget=forms.PasswordInput) + + class Meta: + model = MyUser + fields = ('email',) + + def clean_password2(self): + # Check that the two password entries match + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: + raise ValidationError("Пароли не совпадают") + return password2 + + def save(self, commit=True): + # Save the provided password in hashed format + user = super().save(commit=False) + user.set_password(self.cleaned_data["password1"]) + if commit: + user.save() + return user + + +class UserChangeForm(forms.ModelForm): + """A form for updating users. Includes all the fields on + the user, but replaces the password field with admin's + disabled password hash display field. + """ + password = ReadOnlyPasswordHashField( + label='Пароль', + help_text='Пароли не хранятся в открытом виде, поэтому нет возможности увидеть пароль этого пользователя, ' + 'но вы можете изменить пароль, воспользовавшись ' + 'этой формой.', + ) + + class Meta: + model = MyUser + fields = ('email', 'password', 'is_active', 'is_staff', 'is_superuser') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + password = self.fields.get('password') + if password: + password.help_text = password.help_text.format('../password/') + user_permissions = self.fields.get('user_permissions') + if user_permissions: + user_permissions.queryset = user_permissions.queryset.select_related('content_type') + diff --git a/server/proj/account/migrations/__init__.py b/server/proj/account/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/proj/account/models.py b/server/proj/account/models.py new file mode 100644 index 0000000..002554f --- /dev/null +++ b/server/proj/account/models.py @@ -0,0 +1,89 @@ +from django.db import models +from django.utils import timezone +from django.core.mail import send_mail +from django.contrib.auth.models import ( + BaseUserManager, AbstractBaseUser, PermissionsMixin +) + + +class MyUserManager(BaseUserManager): + def create_user(self, email, phone, password=None): + """ + Creates and saves a User with the given email, date of + birth and password. + """ + if not email: + raise ValueError('Users must have an email address') + + user = self.model( + email=self.normalize_email(email), + phone=phone + ) + + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, email, password=None): + """ + Creates and saves a superuser with the given email, date of + birth and password. + """ + user = self.create_user( + email, + password=password, + ) + user.is_staff = True + user.is_superuser = True + user.set_password(password) + user.save(using=self._db) + return user + + +class MyUser(AbstractBaseUser, PermissionsMixin): + email = models.EmailField( + verbose_name='Емайл', + max_length=255, + unique=True, + ) + first_name = models.CharField('Имя', max_length=150, blank=True) + last_name = models.CharField('Фамилия', max_length=150, blank=True) + phone = models.CharField('Телефон', max_length=15, blank=True, null=True, unique=True) + is_active = models.BooleanField('Активный', help_text='Отметьте, если пользователь должен считаться активным. ' + 'Уберите эту отметку вместо удаления учётной записи.', default=True) + is_staff = models.BooleanField('Статус персонала', help_text='Отметьте, если пользователь может входить в ' + 'административную часть сайта.', default=False) + date_joined = models.DateTimeField('Дата регистрации', default=timezone.now) + organization = models.CharField('Название организаци', max_length=200, null=True) + INN_us = models.CharField('ИНН', max_length=12, null=True) + urid_adress = models.CharField('Юридический адрес', max_length=200, null=True) + number = models.CharField('Номер телефона', max_length=20, null=True) + + objects = MyUserManager() + + USERNAME_FIELD = 'email' + + def __str__(self): + return self.email + + def full_name(self): + return f'{self.first_name} {self.last_name}' if self.first_name and self.last_name else self.email + + def has_perm(self, perm, obj=None): + """Does the user have a specific permission?""" + # Simplest possible answer: Yes, always + return True + + def has_module_perms(self, app_label): + """Does the user have permissions to view the app `app_label`?""" + # Simplest possible answer: Yes, always + return True + + def email_user(self, subject, message, from_email=None, **kwargs): + """Send an email to this user.""" + send_mail(subject, message, from_email, [self.email], **kwargs) + + class Meta: + verbose_name = 'Пользователь' + verbose_name_plural = 'Пользователи' + diff --git a/server/proj/account/serializers.py b/server/proj/account/serializers.py new file mode 100644 index 0000000..932e20b --- /dev/null +++ b/server/proj/account/serializers.py @@ -0,0 +1,35 @@ +from rest_framework.serializers import ModelSerializer, CharField, SerializerMethodField, ListField, IntegerField +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer +from account.models import MyUser + + +class MyUserSerializer(ModelSerializer): + + def create(self, validated_data): + user = MyUser.objects.create_user(**validated_data) + return user + + class Meta: + model = MyUser + fields = ('id', 'email', 'password', + 'first_name', 'last_name', + 'phone', 'date_joined', + 'last_login', 'INN_us', + 'urid_adress', 'organization' + ) + extra_kwargs = { + 'password': {'write_only': True}, + } + + + +class MyTokenObtainPairSerializer(TokenObtainPairSerializer): + + @classmethod + def get_token(cls, user): + token = super().get_token(user) + token['email'] = user.email + token['first_name'] = user.first_name + token['last_name'] = user.last_name + + return token diff --git a/server/proj/account/tests.py b/server/proj/account/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/server/proj/account/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/server/proj/account/urls.py b/server/proj/account/urls.py new file mode 100644 index 0000000..441c761 --- /dev/null +++ b/server/proj/account/urls.py @@ -0,0 +1,12 @@ +from rest_framework import routers +from account import views + +router = routers.DefaultRouter() + +router.register(r'', views.MyUserViewSet, basename='users') + +app_name = 'account' +urlpatterns = router.urls + + + diff --git a/server/proj/account/views.py b/server/proj/account/views.py new file mode 100644 index 0000000..2f20787 --- /dev/null +++ b/server/proj/account/views.py @@ -0,0 +1,101 @@ +from rest_framework.viewsets import ViewSet +from rest_framework.response import Response +from rest_framework.permissions import IsAdminUser, IsAuthenticated, AllowAny +from rest_framework_simplejwt.views import TokenObtainPairView +from rest_framework.decorators import action +from rest_framework import status +from django.core.mail import send_mail +from django.contrib.auth.hashers import make_password +from conf import settings +from account.serializers import MyUserSerializer, MyTokenObtainPairSerializer +from account.models import MyUser +PermissionClass = IsAuthenticated if not settings.DEBUG else AllowAny + +class MyTokenObtainPairView(TokenObtainPairView): + permission_classes = [AllowAny] + serializer_class = MyTokenObtainPairSerializer + + +class MyUserViewSet(ViewSet): + """ + list: + Авторизированный пользователь + create_user: + Создание пользователя + update_user: + Обновление пользователей + """ + + def list(self, request): + serializer = MyUserSerializer(request.user, many=False) + return Response(serializer.data, status=status.HTTP_200_OK) + + @action(detail=False, methods=['post']) + def create_user(self, request): + serializer = MyUserSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + else: + text_error = '' + error_dict = {} + for error in serializer.errors: + elm_error = serializer.errors.get(error) + if len(elm_error) > 0: + text_error += "{} \n".format(elm_error[0]) + error_dict.update({error: elm_error[0]}) + return Response({"detail": text_error, "error": error_dict}, status=status.HTTP_400_BAD_REQUEST) + + token_data = { + "email": request.data["email"], + "password": request.data["password"] + } + token_serializer = MyTokenObtainPairSerializer(data=token_data) + token_serializer.is_valid(raise_exception=True) + return Response(token_serializer.validated_data, status=status.HTTP_201_CREATED) + + @action(detail=False, methods=['post']) + def update_user(self, request): + if 'email' in request.data: + del request.data['email'] + if 'password' in request.data: + request.data['password'] = make_password(request.data['password']) + serializer = MyUserSerializer(request.user, data=request.data, partial=True) + serializer.is_valid(raise_exception=True) + serializer.save() + + return Response(serializer.data) + + @action(detail=False, methods=['post']) + def password_reset_user(self, request): + if 'email' in request.data: + email = request.data.get('email') + try: + user = MyUser.objects.get(email=email) + password = MyUser.objects.make_random_password() + user.set_password(password) + user.save(update_fields=['password']) + message = "Запрос на восстановление пароля \nНовый пароль сгенерирован: {password}".format(password=password) + send_mail('Смена пароля', message, settings.EMAIL_HOST_USER, [email]) + return Response({'detail': 'Новый пароль отправлен на емайл: {}'.format(email)}, status=status.HTTP_200_OK) + except MyUser.DoesNotExist: + text = 'Пользователь с емайлом {}, не найден'.format(email) + return Response({'detail': text, 'error': {'email': text}}, status=status.HTTP_400_BAD_REQUEST) + except: + return Response( + {'detail': 'Произошла неизвестная ошибка', 'error': {'email': 'Произошла неизвестная ошибка'}}, + status=status.HTTP_400_BAD_REQUEST) + return Response({'detail': 'Не передан емайл', 'error': {'email': 'Не передан емайл'}}, + status=status.HTTP_400_BAD_REQUEST) + + def get_permissions(self): + if self.action == "list": + permission_classes = [IsAuthenticated] + elif self.action == "create_user": + permission_classes = [AllowAny] + elif self.action == "update_user": + permission_classes = [IsAuthenticated] + elif self.action == "password_reset_user": + permission_classes = [AllowAny] + else: + permission_classes = [IsAdminUser] + return [permission() for permission in permission_classes] diff --git a/server/proj/conf/settings/__pycache__/base.cpython-310.pyc b/server/proj/conf/settings/__pycache__/base.cpython-310.pyc index ed22f82..1d0b78f 100644 Binary files a/server/proj/conf/settings/__pycache__/base.cpython-310.pyc and b/server/proj/conf/settings/__pycache__/base.cpython-310.pyc differ diff --git a/server/proj/conf/settings/__pycache__/development.cpython-310.pyc b/server/proj/conf/settings/__pycache__/development.cpython-310.pyc index 3778883..5a9aa36 100644 Binary files a/server/proj/conf/settings/__pycache__/development.cpython-310.pyc and b/server/proj/conf/settings/__pycache__/development.cpython-310.pyc differ diff --git a/server/proj/conf/settings/base.py b/server/proj/conf/settings/base.py index aa8bb97..887a404 100644 --- a/server/proj/conf/settings/base.py +++ b/server/proj/conf/settings/base.py @@ -110,7 +110,7 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' ROTATE_LOG_SIZE = 15 * 1024 * 1024 ROTATE_LOG_COUNT = 10 -LOGGING = { +""" LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { @@ -159,7 +159,7 @@ LOGGING = { 'propagate': False, }, }, -} +} """ REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ diff --git a/server/proj/conf/urls.py b/server/proj/conf/urls.py index cee2d02..d9b9c7d 100644 --- a/server/proj/conf/urls.py +++ b/server/proj/conf/urls.py @@ -31,7 +31,7 @@ urlpatterns = [ path('api/', include_docs_urls(title='API docs')), path('api/radio/', include(router.urls)), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), - + path('api/user/', include('account.urls', namespace='user')), # path('webhook/', AzuraNowPlayingWebhookView.as_view(), name='webhook-receiver'), path('api/login/', login, name='login'), path('api/register/', register, name='register'), diff --git a/server/proj/loginApi/__pycache__/admin.cpython-310.pyc b/server/proj/loginApi/__pycache__/admin.cpython-310.pyc index 7893a02..32e226a 100644 Binary files a/server/proj/loginApi/__pycache__/admin.cpython-310.pyc and b/server/proj/loginApi/__pycache__/admin.cpython-310.pyc differ diff --git a/server/proj/loginApi/__pycache__/apps.cpython-310.pyc b/server/proj/loginApi/__pycache__/apps.cpython-310.pyc index 73a28cd..d47564d 100644 Binary files a/server/proj/loginApi/__pycache__/apps.cpython-310.pyc and b/server/proj/loginApi/__pycache__/apps.cpython-310.pyc differ diff --git a/server/proj/loginApi/__pycache__/models.cpython-310.pyc b/server/proj/loginApi/__pycache__/models.cpython-310.pyc index d01aefd..247b231 100644 Binary files a/server/proj/loginApi/__pycache__/models.cpython-310.pyc and b/server/proj/loginApi/__pycache__/models.cpython-310.pyc differ diff --git a/server/proj/news/__pycache__/__init__.cpython-310.pyc b/server/proj/news/__pycache__/__init__.cpython-310.pyc index fa4d7cd..2df7509 100644 Binary files a/server/proj/news/__pycache__/__init__.cpython-310.pyc and b/server/proj/news/__pycache__/__init__.cpython-310.pyc differ diff --git a/server/proj/news/__pycache__/admin.cpython-310.pyc b/server/proj/news/__pycache__/admin.cpython-310.pyc index 167794f..04b0f23 100644 Binary files a/server/proj/news/__pycache__/admin.cpython-310.pyc and b/server/proj/news/__pycache__/admin.cpython-310.pyc differ diff --git a/server/proj/news/__pycache__/apps.cpython-310.pyc b/server/proj/news/__pycache__/apps.cpython-310.pyc index bea16dc..b5a95af 100644 Binary files a/server/proj/news/__pycache__/apps.cpython-310.pyc and b/server/proj/news/__pycache__/apps.cpython-310.pyc differ diff --git a/server/proj/news/__pycache__/models.cpython-310.pyc b/server/proj/news/__pycache__/models.cpython-310.pyc index 3a9a4bf..d090dbf 100644 Binary files a/server/proj/news/__pycache__/models.cpython-310.pyc and b/server/proj/news/__pycache__/models.cpython-310.pyc differ diff --git a/server/proj/rubricks/__pycache__/__init__.cpython-310.pyc b/server/proj/rubricks/__pycache__/__init__.cpython-310.pyc index 2b559db..7af52fd 100644 Binary files a/server/proj/rubricks/__pycache__/__init__.cpython-310.pyc and b/server/proj/rubricks/__pycache__/__init__.cpython-310.pyc differ diff --git a/server/proj/rubricks/__pycache__/admin.cpython-310.pyc b/server/proj/rubricks/__pycache__/admin.cpython-310.pyc index ef10922..69d33a2 100644 Binary files a/server/proj/rubricks/__pycache__/admin.cpython-310.pyc and b/server/proj/rubricks/__pycache__/admin.cpython-310.pyc differ diff --git a/server/proj/rubricks/__pycache__/apps.cpython-310.pyc b/server/proj/rubricks/__pycache__/apps.cpython-310.pyc index 87c88f0..ed74ba4 100644 Binary files a/server/proj/rubricks/__pycache__/apps.cpython-310.pyc and b/server/proj/rubricks/__pycache__/apps.cpython-310.pyc differ diff --git a/server/proj/rubricks/__pycache__/models.cpython-310.pyc b/server/proj/rubricks/__pycache__/models.cpython-310.pyc index db1fc39..afd0449 100644 Binary files a/server/proj/rubricks/__pycache__/models.cpython-310.pyc and b/server/proj/rubricks/__pycache__/models.cpython-310.pyc differ diff --git a/server/proj/userProfile/__pycache__/__init__.cpython-310.pyc b/server/proj/userProfile/__pycache__/__init__.cpython-310.pyc index f30d03c..115d566 100644 Binary files a/server/proj/userProfile/__pycache__/__init__.cpython-310.pyc and b/server/proj/userProfile/__pycache__/__init__.cpython-310.pyc differ diff --git a/server/proj/userProfile/__pycache__/admin.cpython-310.pyc b/server/proj/userProfile/__pycache__/admin.cpython-310.pyc index 354d896..0e94c1a 100644 Binary files a/server/proj/userProfile/__pycache__/admin.cpython-310.pyc and b/server/proj/userProfile/__pycache__/admin.cpython-310.pyc differ diff --git a/server/proj/userProfile/__pycache__/apps.cpython-310.pyc b/server/proj/userProfile/__pycache__/apps.cpython-310.pyc index 29d0cf0..cbf7dbe 100644 Binary files a/server/proj/userProfile/__pycache__/apps.cpython-310.pyc and b/server/proj/userProfile/__pycache__/apps.cpython-310.pyc differ diff --git a/server/proj/userProfile/__pycache__/models.cpython-310.pyc b/server/proj/userProfile/__pycache__/models.cpython-310.pyc index 5fbb65d..6b27745 100644 Binary files a/server/proj/userProfile/__pycache__/models.cpython-310.pyc and b/server/proj/userProfile/__pycache__/models.cpython-310.pyc differ