From b92e6d043523951f902e22d0ab1ec710f28e850c Mon Sep 17 00:00:00 2001 From: Dmitriy Shesterkin Date: Thu, 1 Mar 2018 00:40:15 +0300 Subject: [PATCH] add method for generate new password to students --- access/serializers.py | 26 +++++++++++++++++++++++ access/urls.py | 7 ++++++- access/views.py | 39 ++++++++++++++++++++++++++++++++++- api_v1/urls.py | 2 +- lms/settings.py | 10 +++++++++ pytest.ini | 3 ++- requirements.txt | 2 ++ tests/conftest.py | 28 +++++++++++++++++++++++++ tests/fixtures/__init__.py | 0 tests/fixtures/users.py | 24 ++++++++++++++++++++-- tests/test_user.py | 42 ++++++++++++++++++++++++++++++++++++++ 11 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 tests/fixtures/__init__.py diff --git a/access/serializers.py b/access/serializers.py index e59a1c1..99a9091 100644 --- a/access/serializers.py +++ b/access/serializers.py @@ -1,5 +1,6 @@ from django.contrib.auth import get_user_model from rest_framework import serializers +from rest_framework.generics import get_object_or_404 from access.models.other import Account from achievements.serialers import DiplomaSerializer, AchievementsSerializer @@ -101,3 +102,28 @@ class UserSearchSerializer(serializers.ModelSerializer): @staticmethod def get_last_request(self): return self.useractivity.last_request + + +class UserEmailSerializer(serializers.Serializer): + """ + Serializer for set new password to the student in admin area by manager. + """ + email = serializers.EmailField() + + def __init__(self, *args, **kwargs): + super(UserEmailSerializer, self).__init__(*args, **kwargs) + self.user = None + self.password = None + + def validate_email(self, email): + self.user = get_object_or_404(get_user_model(), email=email) + if not self.user.is_active: + raise serializers.ValidationError('Учетная запись еще не активирована.') + if not self.user: + raise serializers.ValidationError('Email не зарегистрирован.') + return email + + def save(self): + self.password = get_user_model().objects.make_random_password() + self.user.set_password(self.password) + self.user.save() diff --git a/access/urls.py b/access/urls.py index 75ef29a..35b1cc6 100644 --- a/access/urls.py +++ b/access/urls.py @@ -18,4 +18,9 @@ urlpatterns = [ url(r'logout/$', views.LogoutView.as_view()), url(r'reset/$', views.ResetPasswordView.as_view()), url(r'progress_detail/upload/(?P[0-9A-Fa-f-]+)/$', progress.views.UploadCourseProgressUserView.as_view()), -] \ No newline at end of file + url( + r'management/password/$', + views.ManagementPassword.as_view(), + name='management-password' + ) +] diff --git a/access/views.py b/access/views.py index b782575..fb047a1 100644 --- a/access/views.py +++ b/access/views.py @@ -1,6 +1,7 @@ import datetime import random import string +import logging from django.contrib import auth from django.conf import settings @@ -8,15 +9,21 @@ from django.contrib.auth import get_user_model from django.core.mail import send_mail from django.db.models import Q from django.shortcuts import redirect + from rest_framework.renderers import JSONRenderer from rest_framework.response import Response from rest_framework.views import APIView +from rest_framework import permissions, generics, status from access.models.other import Invite, ResetPassword, Account -from access.serializers import UserSelfSerializer, UserSearchSerializer +from access.serializers import (UserSelfSerializer, UserSearchSerializer, + UserEmailSerializer) from lms.tools import decode_base64 +logger = logging.getLogger(__name__) + + class TeacherListView(APIView): renderer_classes = (JSONRenderer,) status_code = 200 @@ -283,3 +290,33 @@ class MinUserView(APIView): return Response(UserSearchSerializer(get_user_model().objects.get(out_key=out_key)).data, status=200) except get_user_model().DoesNotExist: return Response("User not found", status=404) + + +class ManagementPassword(generics.GenericAPIView): + permission_classes = (permissions.IsAuthenticated, permissions.IsAdminUser) + serializer_class = UserEmailSerializer + + def post(self, request): + """ + Set password to the student in admin area by manager + --- + Generate new password to the student and send email with new password + """ + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + logger.info(f'set password: {serializer.password} to the ' + f'student with email: {serializer.user.email}') + send_mail( + subject='Установлен новый пароль', + message=f'Ваш новый пароль {serializer.password} ' + f'(в последствии вы сможите сменить его в личном кабинете).', + from_email='robo@skillbox.ru', + recipient_list=[serializer.user.email], + ) + logger.info(f'send email to {serializer.user.email} ' + f'with new password') + return Response( + data={'message': 'Письмо с новым паролем отправлено на email студента.'}, + status=status.HTTP_201_CREATED + ) diff --git a/api_v1/urls.py b/api_v1/urls.py index 69fcec5..d4b257b 100644 --- a/api_v1/urls.py +++ b/api_v1/urls.py @@ -5,7 +5,7 @@ schema_view = get_swagger_view(title='Skillbox LMS API') urlpatterns = [ url(r'courses/', include('courses.urls')), - url(r'users/', include('access.urls')), + url(r'users/', include('access.urls', namespace='users')), url(r'library/', include('library.urls')), url(r'finance/', include('finance.urls')), url(r'storage/', include('storage.urls')), diff --git a/lms/settings.py b/lms/settings.py index 6576e6f..4c8081b 100644 --- a/lms/settings.py +++ b/lms/settings.py @@ -232,6 +232,16 @@ LOGGING = { }, } +# Configure loggers for all local apps +LOCAL_APPS_LOGGERS = {} +for app in apps: + LOCAL_APPS_LOGGERS[app] = { + 'handlers': ['console'], + 'level': 'DEBUG', + 'propagate': True, + } +LOGGING['loggers'].update(LOCAL_APPS_LOGGERS) + SWAGGER_SETTINGS = { 'USE_SESSION_AUTH': True, 'LOGIN_URL': 'admin:login', diff --git a/pytest.ini b/pytest.ini index 6311a6e..2127292 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,7 +2,8 @@ DJANGO_SETTINGS_MODULE = lms.settings norecursedirs = env/* docs/* misc/* static/* -addopts = --flake8 -vvs +;addopts = --flake8 -vvs +addopts = -vvs python_files = test_*.py diff --git a/requirements.txt b/requirements.txt index 52861f8..57e3a0d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,6 +50,8 @@ pytest-sugar==0.9.1 pytest-django==3.1.2 coverage==4.5.1 pytest-cov==2.5.1 +mock==2.0.0 +pytest-mock==1.7.0 # factories Faker==0.8.11 factory-boy==2.10.0 diff --git a/tests/conftest.py b/tests/conftest.py index e69de29..e48266c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -0,0 +1,28 @@ +import pytest +from tests.client import BetterAPIClient + +pytest_plugins = [ + 'tests.fixtures.users', +] + +@pytest.fixture +def api_client(): + """Anonymous client for REST API.""" + client = BetterAPIClient() + return client + + +@pytest.fixture +def staff_client(user_staff): + """Authorized as staff client for REST API.""" + client = BetterAPIClient() + client.force_authenticate(user=user_staff) + return client + + +@pytest.fixture +def student_client(user_student): + """Authorized as staff client for REST API.""" + client = BetterAPIClient() + client.force_authenticate(user=user_student) + return client diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/users.py b/tests/fixtures/users.py index fbc9f8b..4b6a9b4 100644 --- a/tests/fixtures/users.py +++ b/tests/fixtures/users.py @@ -4,11 +4,12 @@ from factories.users import UserFactory @pytest.fixture -def user_admin(): +def user_staff(): """ - Create user as administration with data: + Create user as staff with data: email = 'admin@example.com' password = 'test' + is_staff=True is_active = True is_superuser = True """ @@ -16,7 +17,26 @@ def user_admin(): last_name='Иванов', first_name='Иван', email='admin@example.com', + is_staff=True, is_active=True, is_superuser=True ) return admin + + +@pytest.fixture +def user_student(): + """ + Create user as student with data: + email = 'student@example.com' + password = 'test' + is_active = True + """ + student = UserFactory( + last_name='Иванов', + first_name='Иван', + email='student@example.com', + is_staff=False, + is_active=True, + ) + return student diff --git a/tests/test_user.py b/tests/test_user.py index e69de29..522910a 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -0,0 +1,42 @@ +import mock +import pytest + +from django.contrib.auth import get_user_model +from django.urls import reverse + +from rest_framework import status +from rest_framework.generics import get_object_or_404 + + +@pytest.mark.django_db +@mock.patch('django.core.mail.send_mail') +def test_generate_password_by_manager(mocked_send_mail, staff_client, + student_client, user_student): + """ + Test generate new password from admin area by manager + """ + assert staff_client.get( + reverse('users:management-password'), + status=status.HTTP_405_METHOD_NOT_ALLOWED + ) + assert student_client.get( + reverse('users:management-password'), + status=status.HTTP_403_FORBIDDEN + ) + data = { + 'email': user_student.email, + } + assert staff_client.post( + reverse('users:management-password'), + data=data, + status=status.HTTP_201_CREATED + ) + test_user = get_object_or_404(get_user_model(), email=user_student.email) + assert not test_user.check_password('test') + assert mocked_send_mail.call_count == 1 + assert student_client.post( + reverse('users:management-password'), + data=data, + status=status.HTTP_403_FORBIDDEN + ) +