Merge branch 'develop' into feature/react_test

# Conflicts:
#	README.md
#	main/tests.py
This commit is contained in:
Yuriy Kulakov
2021-05-25 19:55:07 +03:00
31 changed files with 1504 additions and 206 deletions

View File

@@ -1,3 +1,8 @@
from django.contrib import admin
"""
Встроенный файл
"""
# from django.contrib import admin
# Register your models here.

0
main/apiauth.py Normal file
View File

View File

@@ -1,5 +1,11 @@
"""
Стандартный файл Django конфигурации приложения.
"""
from django.apps import AppConfig
class MainConfig(AppConfig):
"""
Старт приложения
"""
name = 'main'

View File

@@ -1,8 +1,14 @@
"""
Вспомогательные функции со списками пользователей, статистикой и т.д.
"""
import logging
from datetime import timedelta
from typing import Union
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
from django.shortcuts import redirect
from django.utils import timezone
from zenpy import Zenpy
@@ -16,7 +22,7 @@ from main.requester import TicketListRequester
from main.zendesk_admin import zenpy
def update_role(user_profile: UserProfile, role: int, who_changes: User) -> None:
def update_role(user_profile: UserProfile, role: int, who_changes: get_user_model()) -> None:
"""
Функция меняет роль пользователя.
@@ -34,7 +40,7 @@ def update_role(user_profile: UserProfile, role: int, who_changes: User) -> None
zendesk.update_user(user)
def make_engineer(user_profile: UserProfile, who_changes: User) -> None:
def make_engineer(user_profile: UserProfile, who_changes: get_user_model()) -> None:
"""
Функция устанавливает пользователю роль инженера.
@@ -44,7 +50,7 @@ def make_engineer(user_profile: UserProfile, who_changes: User) -> None:
update_role(user_profile, ROLES['engineer'], who_changes)
def make_light_agent(user_profile: UserProfile, who_changes: User) -> None:
def make_light_agent(user_profile: UserProfile, who_changes: get_user_model()) -> None:
"""
Функция устанавливает пользователю роль легкого агента.
@@ -89,7 +95,7 @@ def get_users_list() -> list:
return users
def get_tickets_list(email):
def get_tickets_list(email) -> list:
"""
Функция возвращает список тикетов пользователя Zendesk
"""
@@ -103,7 +109,7 @@ def get_tickets_list_for_group(group_name):
return TicketListRequester().get_tickets_list_for_group(zenpy.get_group(group_name))
def update_profile(user_profile: UserProfile):
def update_profile(user_profile: UserProfile) -> None:
"""
Функция обновляет профиль пользователя в соответствии с текущим в Zendesk.
@@ -157,7 +163,7 @@ def check_user_auth(email: str, password: str) -> bool:
return True
def update_user_in_model(profile: UserProfile, zendesk_user: ZenpyUser):
def update_user_in_model(profile: UserProfile, zendesk_user: ZenpyUser) -> None:
"""
Функция обновляет профиль пользователя при изменении данных пользователя на Zendesk.
@@ -173,7 +179,7 @@ def update_user_in_model(profile: UserProfile, zendesk_user: ZenpyUser):
profile.save()
def count_users(users) -> tuple:
def count_users(users: list) -> tuple:
"""
Функция подсчета количества сотрудников с ролями engineer и light_agent
"""
@@ -186,21 +192,21 @@ def count_users(users) -> tuple:
return engineers, light_agents
def update_users_in_model():
def update_users_in_model() -> list:
"""
Обновляет пользователей в модели UserProfile по списку пользователей в организации
"""
users = get_users_list()
for user in users:
try:
profile = User.objects.get(email=user.email).userprofile
profile = get_user_model().objects.get(email=user.email).userprofile
update_user_in_model(profile, user)
except ObjectDoesNotExist:
pass
return users
def daterange(start_date, end_date) -> list:
def daterange(start_date: timedelta, end_date: timedelta) -> list:
"""
Функция возвращает список дней с start_date по end_date, исключая правую границу.
@@ -214,17 +220,17 @@ def daterange(start_date, end_date) -> list:
return dates
def get_timedelta(log, time=None) -> timedelta:
def get_timedelta(current_log: RoleChangeLogs, time: timedelta = None) -> timedelta:
"""
Функция возвращает объект класса timedelta, который хранит промежуток времени от начала суток до момента,
который находится в log (объект класса RoleChangeLogs) или в time(datetime.time), если введён.
:param log: Лог
:param current_log: Лог
:param time: Время
:return: Сколько времени прошло от начала суток до события
"""
if time is None:
time = log.change_time.time()
time = current_log.change_time.time()
time = timedelta(hours=time.hour, minutes=time.minute, seconds=time.second)
return time
@@ -241,6 +247,9 @@ def last_day_of_month(day: int) -> int:
class DatabaseHandler(logging.Handler):
"""
Класс записи изменений ролей в базу данных.
"""
def __init__(self):
logging.Handler.__init__(self)
@@ -265,10 +274,19 @@ class DatabaseHandler(logging.Handler):
class CsvFormatter(logging.Formatter):
"""
Класс преобразования смены ролей пользователей в строковый формат.
"""
def __init__(self):
logging.Formatter.__init__(self)
def format(self, record):
def format(self, record: logging.LogRecord) -> str:
"""
Функция форматирует запись смены роли пользователя в строку.
:param record: Запись смены роли пользователя.
:return: Строка с записью смены пользователя.
"""
users = record.msg
if users[1]:
user = users[0]
@@ -291,10 +309,10 @@ class CsvFormatter(logging.Formatter):
def log(user, admin=None):
"""
Осуществляет запись логов в базу данных и csv файл
:param admin:
:param user:
:return:
Функция осуществляет запись логов в базу данных и csv файл.
:param admin: Админ, который меняет роль
:param user: Пользователь, которому изменена роль
"""
users = [user, admin]
logger = logging.getLogger('MY_LOGGER')
@@ -309,10 +327,16 @@ def log(user, admin=None):
logger.info(users)
def set_session_params_for_work_page(request, count=None, is_confirm=True):
def set_session_params_for_work_page(request: WSGIRequest, count: int = None, is_confirm: bool = True) -> \
Union[HttpResponsePermanentRedirect, HttpResponseRedirect]:
"""
Функция для страницы получения прав
Устанавливает данные сессии о успешности запроса и количестве назначенных тикетов
Функция для страницы получения прав, устанавливает данные сессии о успешности запроса и количестве
назначенных тикетов.
:param request: Получение данных с рабочей страницы пользователя
:param count: Количество запрошенных тикетов
:param is_confirm: Назначение тикетов
:return: Перезагрузка страницы "Управление правами" соответствующего пользователя
"""
request.session['is_confirm'] = is_confirm
request.session['count_tickets'] = count

View File

@@ -1,3 +1,6 @@
"""
Формы.
"""
from django import forms
from django.contrib.auth.forms import AuthenticationForm
from django_registration.forms import RegistrationFormUniqueEmail
@@ -56,6 +59,8 @@ class CustomAuthenticationForm(AuthenticationForm):
:param username: Поле для ввода email пользователя
:type username: :class:`django.forms.fields.CharField`
:param error_messages: Список ошибок авторизации
:type error_messages: :class:`dict`
"""
username = forms.CharField(
label="Электронная почта",
@@ -64,8 +69,7 @@ class CustomAuthenticationForm(AuthenticationForm):
error_messages = {
'invalid_login':
"Пожалуйста, введите правильные электронную почту и пароль. Оба поля "
"могут быть чувствительны к регистру."
,
"могут быть чувствительны к регистру.",
'inactive': "Аккаунт не активен.",
}

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.3 on 2021-05-20 17:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0017_auto_20210408_1943'),
]
operations = [
migrations.AlterField(
model_name='unassignedticket',
name='ticket_id',
field=models.IntegerField(help_text='Номер тикета, для которого сняли ответственного'),
),
]

View File

@@ -1,5 +1,8 @@
"""
Модели, использующиеся в приложении.
"""
from django.contrib.auth import get_user_model
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone
@@ -19,42 +22,64 @@ class UserProfile(models.Model):
('has_control_access', 'Can view admin page'),
)
user = models.OneToOneField(to=User, on_delete=models.CASCADE, help_text='Пользователь')
user = models.OneToOneField(to=get_user_model(), on_delete=models.CASCADE, help_text='Пользователь')
role = models.CharField(default='None', max_length=100, help_text='Глобальное имя роли пользователя')
custom_role_id = models.IntegerField(default=0, help_text='Код роли пользователя')
image = models.URLField(null=True, blank=True, help_text='Аватарка')
name = models.CharField(default='None', max_length=100, help_text='Имя пользователя на нашем сайте')
@property
def zendesk_role(self):
id = self.custom_role_id
def zendesk_role(self) -> str:
"""
Функция возвращает роль пользователя в Zendesk.
В формате str, либо UNDEFINED, если пользователь не найден
:return: Роль пользователя в Zendesk
"""
for role, r_id in ZENDESK_ROLES.items():
if r_id == id:
if r_id == self.custom_role_id:
return role
return 'UNDEFINED'
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
@receiver(post_save, sender=get_user_model())
def create_user_profile(instance, created, **kwargs) -> None:
"""
Функция создания профиля пользователя (Userprofile) при регистрации пользователя.
:param instance: Экземпляр класса User
:param created: Создание профиля пользователя
:param kwargs: Параметры
:return: Обновленный список объектов профилей пользователей
"""
if created:
UserProfile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
@receiver(post_save, sender=get_user_model())
def save_user_profile(instance, **kwargs) -> None:
"""
Функция записи БД профиля пользователя.
:param instance: Экземпляр класса User
:param kwargs: Параметры
:return: Запись профиля пользователя
"""
instance.userprofile.save()
class RoleChangeLogs(models.Model):
"""
Модель для логирования изменений ролей пользователя.
Модель для логгирования изменений ролей пользователя
"""
user = models.ForeignKey(to=User, on_delete=models.CASCADE, help_text='Пользователь, которому присвоили другую роль')
user = models.ForeignKey(to=get_user_model(), on_delete=models.CASCADE,
help_text='Пользователь, которому присвоили другую роль')
old_role = models.IntegerField(default=0, help_text='Старая роль')
new_role = models.IntegerField(default=0, help_text='Присвоенная роль')
change_time = models.DateTimeField(default=timezone.now, help_text='Дата и время изменения роли')
changed_by = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='changed_by', help_text='Кем была изменена роль')
changed_by = models.ForeignKey(to=get_user_model(), on_delete=models.CASCADE, related_name='changed_by',
help_text='Кем была изменена роль')
class UnassignedTicketStatus(models.IntegerChoices):
@@ -63,13 +88,15 @@ class UnassignedTicketStatus(models.IntegerChoices):
:param UNASSIGNED: Снят с пользователя, перенесён в буферную группу
:param RESTORED: Авторство восстановлено
:param NOT_FOUND: Пока нас не было, тикет испарился из буферной группы. Дополнительные действия не требуются
:param NOT_FOUND: Пока нас не было, тикет испарился из буферной группы.
Дополнительные действия не требуются
:param CLOSED: Тикет уже был закрыт. Дополнительные действия не требуются
:param SOLVED: Тикет решён. Записан на пользователя с почтой SOLVED_TICKETS_EMAIL
"""
UNASSIGNED = 0, 'Снят с пользователя, перенесён в буферную группу'
RESTORED = 1, 'Авторство восстановлено'
NOT_FOUND = 2, 'Пока нас не было, тикет испарился из буферной группы. Дополнительные действия не требуются'
NOT_FOUND = 2, 'Пока нас не было, тикет испарился из ' \
'буферной группы. Дополнительные действия не требуются'
CLOSED = 3, 'Тикет уже был закрыт. Дополнительные действия не требуются'
SOLVED = 4, 'Тикет решён. Записан на пользователя с почтой SOLVED_TICKETS_EMAIL'
@@ -78,7 +105,8 @@ class UnassignedTicket(models.Model):
"""
Модель не распределенного тикета.
"""
assignee = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='tickets', help_text='Пользователь, с которого снят тикет')
ticket_id = models.IntegerField(help_text='Номер тикера, для которого сняли ответственного')
status = models.IntegerField(choices=UnassignedTicketStatus.choices, default=UnassignedTicketStatus.UNASSIGNED, help_text='Статус тикета')
assignee = models.ForeignKey(to=get_user_model(), on_delete=models.CASCADE, related_name='tickets',
help_text='Пользователь, с которого снят тикет')
ticket_id = models.IntegerField(help_text='Номер тикета, для которого сняли ответственного')
status = models.IntegerField(choices=UnassignedTicketStatus.choices, default=UnassignedTicketStatus.UNASSIGNED,
help_text='Статус тикета')

View File

@@ -1,3 +1,6 @@
"""
Обработка тикетов.
"""
import requests
from zenpy import TicketApi
from zenpy.lib.api_objects import Ticket
@@ -6,6 +9,9 @@ from main.zendesk_admin import zenpy
class TicketListRequester:
"""
Класс обработки тикетов.
"""
def __init__(self):
self.email = zenpy.credentials['email']
if zenpy.credentials.get('token'):
@@ -15,11 +21,17 @@ class TicketListRequester:
self.token_or_password = zenpy.credentials.get('password')
self.prefix = f'https://{zenpy.credentials.get("subdomain")}.zendesk.com/api/v2/'
def get_tickets_list_for_user(self, zendesk_user):
def get_tickets_list_for_user(self, zendesk_user: zenpy) -> str:
"""
Функция получения списка тикетов пользователя Zendesk.
"""
url = self.prefix + f'users/{zendesk_user.id}/tickets/assigned'
return self._get_tickets(url)
def get_tickets_list_for_group(self, group):
def get_tickets_list_for_group(self, group: zenpy) -> list():
"""
Функция получения списка тикетов группы пользователей Zendesk.
"""
url = self.prefix + '/tickets'
all_tickets = self._get_tickets(url)
tickets = list()
@@ -28,7 +40,10 @@ class TicketListRequester:
tickets.append(ticket)
return tickets
def _get_tickets(self, url):
def _get_tickets(self, url: str) -> list():
"""
Функция получения полного списка тикетов по url.
"""
response = requests.get(url, auth=(self.email, self.token_or_password))
tickets = []
if response.status_code != 200:

View File

@@ -1,4 +1,7 @@
from django.contrib.auth.models import User
"""
Сериализаторы.
"""
from django.contrib.auth import get_user_model
from rest_framework import serializers
from main.models import UserProfile
from access_controller.settings import ZENDESK_ROLES
@@ -7,14 +10,28 @@ from access_controller.settings import ZENDESK_ROLES
class UserSerializer(serializers.HyperlinkedModelSerializer):
"""
Класс serializer для модели User.
:param model: Модель, на основании которой создается сериализатор
:type model: :class:`django.contrib.auth.Models`
:param fields: Передаваемые поля
:type email: :class:`list`
"""
class Meta:
model = User
model = get_user_model()
fields = ['email']
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
"""Класс serializer для модели профиля пользователя"""
"""
Класс serializer для модели профиля пользователя.
:param user: Вложенный сериализатор
:type user: :class:`UserSerializer`
:param model: Модель, на основании которой создается сериализатор
:type model: :class:`django.contrib.auth.Models`
:param fields: Передаваемые поля
:type email: :class:`list`
"""
user = UserSerializer()
class Meta:
@@ -23,16 +40,36 @@ class ProfileSerializer(serializers.HyperlinkedModelSerializer):
class ZendeskUserSerializer(serializers.Serializer):
"""Класс serializer для объектов пользователей из zenpy"""
"""
Класс serializer для объектов пользователей из Zenpy.
:param name: Имя пользователя
:type name: :class:`str`
:param zendesk_role: Роль из Zendesk
:type zendesk_role: :class:`str`
:param email: Email пользователя
:type email: :class:`str`
"""
name = serializers.CharField()
zendesk_role = serializers.SerializerMethodField('get_zendesk_role')
email = serializers.EmailField()
@staticmethod
def get_zendesk_role(obj):
def get_zendesk_role(obj) -> str:
"""
Функция строкового заполнения поля сериализатора zendesk_role.
:param obj: объект пользователя Zendesk
:return: роль engineer либо light_agent
"""
if obj.custom_role_id == ZENDESK_ROLES['engineer']:
return 'engineer'
elif obj.custom_role_id == ZENDESK_ROLES['light_agent']:
if obj.custom_role_id == ZENDESK_ROLES['light_agent']:
return 'light_agent'
else:
return "empty"
return "empty"
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass

View File

@@ -1,7 +1,10 @@
"""
Обработка статистики.
"""
from datetime import date, datetime, timedelta
from typing import Optional
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.utils import timezone
from access_controller.settings import ONE_DAY, ZENDESK_ROLES as ROLES
@@ -50,19 +53,19 @@ class StatisticData:
else:
self.statistic = stat
def get_statistic(self) -> dict:
def get_statistic(self) -> Optional[dict]:
"""
Функция возвращает статистику работы пользователя.
:return: Словарь statistic с применением формата отображения и интервала работы(если они есть). None, если были ошибки при создании.
:return: Словарь statistic с применением формата отображения и интервала работы(если они есть).
None, если были ошибки при создании.
"""
if self.is_valid_statistic():
stat = self.statistic
stat = self._use_display(stat)
stat = self._use_interval(stat)
return stat
else:
return None
return None
def is_valid_statistic(self) -> bool:
"""
@@ -104,8 +107,7 @@ class StatisticData:
"""
if self.is_valid_data():
return self.data
else:
return None
return None
def is_valid_data(self) -> bool:
"""
@@ -170,7 +172,8 @@ class StatisticData:
"""
Функция возвращает логи в диапазоне дат start_date - end_date для пользователя с указанным email.
:return: Данные о смене статусов пользователя. Если пользователь не найден или интервал времени некорректен - ошибку.
:return: Данные о смене статусов пользователя. Если пользователь не найден или
интервал времени некорректен - ошибку.
"""
if not self.check_time():
self.errors += ['Конец диапазона должен быть позже начала диапазона и раньше текущего времени']
@@ -178,12 +181,12 @@ class StatisticData:
try:
self.data = RoleChangeLogs.objects.filter(
change_time__range=[self.start_date, self.end_date + timedelta(days=1)],
user=User.objects.get(email=self.email),
user=get_user_model().objects.get(email=self.email),
).order_by('change_time')
except User.DoesNotExist:
except get_user_model().DoesNotExist:
self.errors += ['Пользователь не найден']
def _init_statistic(self) -> dict:
def _init_statistic(self) -> None:
"""
Функция заполняет словарь, в котором ключ - дата, значение - кол-во проработанных в этот день секунд.
@@ -192,18 +195,18 @@ class StatisticData:
self.clear_statistic()
if not self.get_data():
self.warnings += ['Не обнаружены изменения роли в данном промежутке']
return None
first_log, last_log = self.data[0], self.data[len(self.data) - 1]
else:
first_log, last_log = self.data[0], self.data[len(self.data) - 1]
if first_log.old_role == ROLES['engineer']:
self.prev_engineer_logic(first_log)
if first_log.old_role == ROLES['engineer']:
self.prev_engineer_logic(first_log)
if last_log.new_role == ROLES['engineer']:
self.post_engineer_logic(last_log)
if last_log.new_role == ROLES['engineer']:
self.post_engineer_logic(last_log)
for log_index in range(len(self.data) - 1):
if self.data[log_index].new_role == ROLES['engineer']:
self.engineer_logic(log_index)
for log_index in range(len(self.data) - 1):
if self.data[log_index].new_role == ROLES['engineer']:
self.engineer_logic(log_index)
def engineer_logic(self, log_index):
"""
@@ -238,7 +241,7 @@ class StatisticData:
"""
Функция обрабатывает случай, когда нам изветсно что инженер начал работу до диапазона
"""
self.fill_daterange(max(User.objects.get(email=self.email).date_joined.date(), self.start_date),
self.fill_daterange(max(get_user_model().objects.get(email=self.email).date_joined.date(), self.start_date),
first_log.change_time.date())
self.statistic[first_log.change_time.date()] += get_timedelta(first_log).total_seconds()

View File

@@ -12,7 +12,7 @@
<p>Введте свой e-mail адрес для восстановления пароля.</p>
<form action="." method="post">
{{ form.as_p }}
<p><input class="btn btn-success" type="submit" value="Отпрваить e-mail"></p>
<p><input class="btn btn-success" type="submit" value="Отправить e-mail"></p>
{% csrf_token %}
</form>
{% endblock %}

View File

@@ -1,7 +1,11 @@
"""
Тесты.
"""
import random
from unittest.mock import patch, Mock
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.core import mail
from django.http import HttpResponseRedirect
@@ -27,38 +31,56 @@ class UsersBaseTestCase(TestCase):
self.admin = 'admin@gmail.com'
self.engineer = 'customer@example.com'
self.agent_client = Client()
self.agent_client.force_login(User.objects.get(email=self.light_agent))
self.agent_client.force_login(get_user_model().objects.get(email=self.light_agent))
self.admin_client = Client()
self.admin_client.force_login(User.objects.get(email=self.admin))
self.admin_client.force_login(get_user_model().objects.get(email=self.admin))
self.engineer_client = Client()
self.engineer_client.force_login(User.objects.get(email=self.engineer))
self.engineer_client.force_login(get_user_model().objects.get(email=self.engineer))
class RegistrationTestCase(TestCase):
"""
Класс тестирования регистрации пользователя.
"""
fixtures = ['fixtures/data.json']
def setUp(self):
def setUp(self) -> None:
"""
Функция предтестовых настроек.
"""
self.email_backend = 'django.core.mail.backends.locmem.EmailBackend'
self.any_zendesk_user_email = 'idar.sokurov.05@mail.ru'
self.zendesk_admin_email = 'idar.sokurov.05@mail.ru'
self.client = Client()
def test_registration_complete_redirect(self):
def test_registration_complete_redirect(self) -> None:
"""
Функция тестирования успешно завершенной регистрации.
"""
with self.settings(EMAIL_BACKEND=self.email_backend):
resp = self.client.post(reverse('registration'), data={'email': self.any_zendesk_user_email})
self.assertRedirects(resp, reverse('password_reset_done'))
def test_registration_fail_redirect(self):
"""
Функция тестирования неуспешной регистрации.
"""
with self.settings(EMAIL_BACKEND=self.email_backend):
resp = self.client.post(reverse('registration'), data={'email': self.any_zendesk_user_email + 'asd'})
self.assertRedirects(resp, reverse('django_registration_disallowed'))
def test_registration_user_already_exist(self):
"""
Функция тестирования попытки регистрации уже зарегистрированного пользователя.
"""
with self.settings(EMAIL_BACKEND=self.email_backend) and translation.override('ru'):
resp = self.client.post(reverse('registration'), data={'email': '123@test.ru'})
self.assertContains(resp, 'Этот адрес электронной почты уже используется', count=1, status_code=200)
def test_registration_send_email(self):
"""
Функция тестирования отправки email.
"""
with self.settings(EMAIL_BACKEND=self.email_backend):
response: HttpResponseRedirect = \
self.client.post(reverse('registration'), data={'email': self.any_zendesk_user_email})
@@ -74,49 +96,74 @@ class RegistrationTestCase(TestCase):
self.assertEqual(mail.outbox[0].body, correct_body)
def test_registration_user_creating(self):
"""
Функция тестирования регистрации пользователя (сверяем имя с именем в Zendesk.
"""
with self.settings(EMAIL_BACKEND=self.email_backend):
self.client.post(reverse('registration'), data={'email': self.any_zendesk_user_email})
user = User.objects.get(email=self.any_zendesk_user_email)
user = get_user_model().objects.get(email=self.any_zendesk_user_email)
zendesk_user = zenpy.get_user(self.any_zendesk_user_email)
self.assertEqual(user.userprofile.name, zendesk_user.name)
def test_permissions_applying(self):
"""
Функция тестирования проверке присвоения роли admin.
"""
with self.settings(EMAIL_BACKEND=self.email_backend):
self.client.post(reverse('registration'), data={'email': self.zendesk_admin_email})
user = User.objects.get(email=self.zendesk_admin_email)
user = get_user_model().objects.get(email=self.zendesk_admin_email)
self.assertEqual(user.userprofile.role, 'admin')
self.assertTrue(user.has_perm('main.has_control_access'))
class MakeEngineerTestCase(UsersBaseTestCase):
"""
Класс тестов для проверки функции назначения роли engineer.
"""
@patch('main.extra_func.zenpy')
def test_become_engineer_redirect(self, _zenpy_mock):
user = User.objects.get(email=self.light_agent)
"""
Функция проверки переадресации пользователя на рабочую страницу после назначения роли engineer.
"""
user = get_user_model().objects.get(email=self.light_agent)
resp = self.agent_client.post(reverse_lazy('work_become_engineer'))
self.assertRedirects(resp, reverse('work', args=[user.id]))
self.assertEqual(resp.status_code, 302)
self.assertFalse(_zenpy_mock.called)
@patch('main.extra_func.zenpy')
def test_light_agent_make_engineer(self, zenpy_mock):
"""
Функция проверки назначения light_agent на роль engineer.
"""
self.agent_client.post(reverse_lazy('work_become_engineer'))
self.assertEqual(zenpy_mock.update_user.call_args[0][0].custom_role_id, sets.ZENDESK_ROLES['engineer'])
@patch('main.extra_func.zenpy')
def test_admin_make_engineer(self, zenpy_mock):
"""
Функция проверки назначения admin на роль engineer.
"""
self.admin_client.post(reverse_lazy('work_become_engineer'))
self.assertEqual(zenpy_mock.update_user.call_args[0][0].custom_role_id, sets.ZENDESK_ROLES['engineer'])
@patch('main.extra_func.zenpy')
def test_engineer_make_engineer(self, zenpy_mock):
"""
Функция проверки назначения engineer на роль engineer.
"""
self.engineer_client.post(reverse_lazy('work_become_engineer'))
self.assertEqual(zenpy_mock.update_user.call_args[0][0].custom_role_id, sets.ZENDESK_ROLES['engineer'])
@patch('main.extra_func.zenpy')
def test_control_page_make_engineer_one(self, zenpy_mock):
"""
Функция проверки назначения администратором на роль engineer одного пользователя.
"""
self.admin_client.post(
reverse_lazy('control'),
data={'users': [User.objects.get(email=self.light_agent).userprofile.id], 'engineer': 'engineer'}
data={'users': [get_user_model().objects.get(email=self.light_agent).userprofile.id],
'engineer': 'engineer'}
)
call_list = zenpy_mock.update_user.call_args_list
mock_object = call_list[0][0][0]
@@ -125,12 +172,15 @@ class MakeEngineerTestCase(UsersBaseTestCase):
@patch('main.extra_func.zenpy')
def test_control_page_make_engineer_many(self, zenpy_mock):
"""
Функция проверки назначения администратором на роль engineer нескольких пользователей.
"""
self.admin_client.post(
reverse_lazy('control'),
data={
'users': [
User.objects.get(email=self.light_agent).userprofile.id,
User.objects.get(email=self.engineer).userprofile.id,
get_user_model().objects.get(email=self.light_agent).userprofile.id,
get_user_model().objects.get(email=self.engineer).userprofile.id,
],
'engineer': 'engineer'
}
@@ -147,7 +197,7 @@ class MakeLightAgentTestCase(UsersBaseTestCase):
@patch('main.requester.TicketListRequester.get_tickets_list_for_user', side_effect=[[]])
@patch('main.extra_func.zenpy')
def test_hand_over_redirect(self, _zenpy_mock, _user_tickets_mock):
user = User.objects.get(email=self.engineer)
user = get_user_model().objects.get(email=self.engineer)
resp = self.engineer_client.post(reverse_lazy('work_hand_over'))
self.assertRedirects(resp, reverse('work', args=[user.id]))
self.assertEqual(resp.status_code, 302)
@@ -203,7 +253,10 @@ class MakeLightAgentTestCase(UsersBaseTestCase):
def test_control_page_make_light_agent_one(self, zenpy_mock, _user_tickets_mock):
self.admin_client.post(
reverse_lazy('control'),
data={'users': [User.objects.get(email=self.engineer).userprofile.id], 'light_agent': 'light_agent'}
data={
'users': [get_user_model().objects.get(email=self.engineer).userprofile.id],
'light_agent': 'light_agent'
}
)
call_list = zenpy_mock.update_user.call_args_list
mock_object = call_list[0][0][0]
@@ -217,8 +270,8 @@ class MakeLightAgentTestCase(UsersBaseTestCase):
reverse_lazy('control'),
data={
'users': [
User.objects.get(email=self.light_agent).userprofile.id,
User.objects.get(email=self.engineer).userprofile.id,
get_user_model().objects.get(email=self.light_agent).userprofile.id,
get_user_model().objects.get(email=self.engineer).userprofile.id,
],
'light_agent': 'light_agent'
}
@@ -231,18 +284,29 @@ class MakeLightAgentTestCase(UsersBaseTestCase):
class PasswordResetTestCase(UsersBaseTestCase):
"""
Класс тестов сброса пароля.
"""
def setUp(self):
"""
Предустановленные значения для проведения тестов.
"""
super().setUp()
self.email_backend = 'django.core.mail.backends.locmem.EmailBackend'
def test_redirect(self):
"""
Функция проверки переадресации на страницу уведомления о сбросе пароля на email.
"""
with self.settings(EMAIL_BACKEND=self.email_backend):
resp = self.agent_client.post(reverse_lazy('password_reset'), data={'email': self.light_agent})
self.assertRedirects(resp, reverse('password_reset_done'))
self.assertEqual(resp.status_code, 302)
def test_send_email(self):
"""
Функция проверки содержания и отправки письма для установки пароля.
"""
with self.settings(EMAIL_BACKEND=self.email_backend):
response: HttpResponseRedirect = \
self.agent_client.post(reverse_lazy('password_reset'), data={'email': self.light_agent})
@@ -258,31 +322,53 @@ class PasswordResetTestCase(UsersBaseTestCase):
self.assertEqual(mail.outbox[0].body, correct_body)
def test_email_invalid(self):
"""
Функция проверки уведомления клиента о некорректности введенного email.
"""
with self.settings(EMAIL_BACKEND=self.email_backend) and translation.override('ru'):
resp = self.agent_client.post(reverse_lazy('password_reset'), data={'email': 1})
self.assertContains(resp, 'Введите правильный адрес электронной почты.', count=1, status_code=200)
def test_user_does_not_exist(self):
"""
Функция корректности отработки неверно введенного email.
"""
with self.settings(EMAIL_BACKEND=self.email_backend):
resp = self.agent_client.post(reverse_lazy('password_reset'), data={'email': self.light_agent + str(random.random())})
resp = self.agent_client.post(
reverse_lazy('password_reset'),
data={
'email': self.light_agent + str(random.random())
}
)
self.assertRedirects(resp, reverse('password_reset_done'))
self.assertEqual(resp.status_code, 302)
self.assertEqual(len(mail.outbox), 0)
class PasswordChangeTestCase(UsersBaseTestCase):
"""
Класс тестирования смены пароля.
"""
def setUp(self):
"""
Предустановленные значения для проведения тестов.
"""
super().setUp()
self.set_password()
def set_password(self):
user: User = User.objects.get(email=self.light_agent)
"""
Пароль, сформированный для тестирования.
"""
user: get_user_model() = get_user_model().objects.get(email=self.light_agent)
user.set_password('ImpossiblyHardPassword')
user.save()
self.agent_client.force_login(User.objects.get(email=self.light_agent))
self.agent_client.force_login(get_user_model().objects.get(email=self.light_agent))
def test_change_successful(self):
"""
Функция тестирования успешного изменения пароля.
"""
self.agent_client.post(
reverse_lazy('password_change'),
data={
@@ -291,10 +377,13 @@ class PasswordChangeTestCase(UsersBaseTestCase):
'new_password2': 'EasyPassword',
}
)
user = User.objects.get(email=self.light_agent)
user = get_user_model().objects.get(email=self.light_agent)
self.assertTrue(user.check_password('EasyPassword'))
def test_invalid_old_password(self):
"""
Функция тестирования отработки неверно введенного старого пароля при смене.
"""
with translation.override('ru'):
resp = self.agent_client.post(
reverse_lazy('password_change'),
@@ -307,6 +396,9 @@ class PasswordChangeTestCase(UsersBaseTestCase):
self.assertContains(resp, 'Ваш старый пароль введен неправильно', count=1, status_code=200)
def test_different_new_passwords(self):
"""
Функция тестирования случая с вводом двух разных новых паролей.
"""
with translation.override('ru'):
resp = self.agent_client.post(
reverse_lazy('password_change'),
@@ -319,6 +411,9 @@ class PasswordChangeTestCase(UsersBaseTestCase):
self.assertContains(resp, 'Введенные пароли не совпадают', count=1, status_code=200)
def test_invalid_new_password1(self):
"""
Функция тестирования случая с неправильно подобранным новым паролем (слишком короткий).
"""
with translation.override('ru'):
resp = self.agent_client.post(
reverse_lazy('password_change'),
@@ -331,6 +426,9 @@ class PasswordChangeTestCase(UsersBaseTestCase):
self.assertContains(resp, 'Введённый пароль слишком короткий', count=1, status_code=200)
def test_invalid_new_password2(self):
"""
Функция тестирования случая с неправильно подобранным новым паролем (употребляются только цифры).
"""
with translation.override('ru'):
resp = self.agent_client.post(
reverse_lazy('password_change'),
@@ -343,6 +441,9 @@ class PasswordChangeTestCase(UsersBaseTestCase):
self.assertContains(resp, 'Введённый пароль состоит только из цифр', count=1, status_code=200)
def test_invalid_new_password3(self):
"""
Функция тестирования случая с неправильно подобранным новым паролем (совпадает с именем пользователя).
"""
with translation.override('ru'):
resp = self.agent_client.post(
reverse_lazy('password_change'),
@@ -367,10 +468,11 @@ class GetTicketsTestCase(UsersBaseTestCase):
Функция проверки переадресации пользователя на рабочую страницу.
"""
get_user_mock.return_value = Mock()
user = User.objects.get(email=self.engineer)
user = get_user_model().objects.get(email=self.engineer)
resp = self.engineer_client.post(reverse('work_get_tickets'))
self.assertRedirects(resp, reverse('work', args=[user.id]))
self.assertEqual(resp.status_code, 302)
self.assertFalse(_zenpy_mock.called)
@patch('main.views.zenpy')
@patch('main.views.get_tickets_list_for_group')
@@ -410,11 +512,11 @@ class GetTicketsTestCase(UsersBaseTestCase):
@patch('main.views.zenpy')
@patch('main.views.get_tickets_list_for_group')
def test_take_zero_tickets(self, TicketsMock, zenpy_mock):
def test_take_zero_tickets(self, tickets_mock, zenpy_mock):
"""
Функция проверки попытки назначения нуля тикета на engineer.
"""
TicketsMock.return_value = [Mock()] * 3
tickets_mock.return_value = [Mock()] * 3
zenpy_mock.get_user.return_value = Mock(role='agent', custom_role_id=sets.ZENDESK_ROLES['engineer'])
self.engineer_client.post(reverse('work_get_tickets'), data={'count_tickets': 0})
tickets = zenpy_mock.update_tickets.call_args[0][0]
@@ -446,9 +548,9 @@ class ProfileTestCase(TestCase):
self.zendesk_agent_email = 'krav-88@mail.ru'
self.zendesk_admin_email = 'idar.sokurov.05@mail.ru'
self.client = Client()
self.client.force_login(User.objects.get(email=self.zendesk_agent_email))
self.client.force_login(get_user_model().objects.get(email=self.zendesk_agent_email))
self.admin_client = Client()
self.admin_client.force_login(User.objects.get(email=self.zendesk_admin_email))
self.admin_client.force_login(get_user_model().objects.get(email=self.zendesk_admin_email))
def test_correct_username(self):
"""
@@ -497,15 +599,14 @@ class LoggingTestCase(UsersBaseTestCase):
def setUp(self):
super().setUp()
self.admin_profile = User.objects.get(email=self.admin).userprofile
self.agent_profile = User.objects.get(email=self.light_agent).userprofile
self.engineer_profile = User.objects.get(email=self.engineer).userprofile
self.admin_profile = get_user_model().objects.get(email=self.admin).userprofile
self.agent_profile = get_user_model().objects.get(email=self.light_agent).userprofile
self.engineer_profile = get_user_model().objects.get(email=self.engineer).userprofile
@staticmethod
def get_file_output():
file = open('logs/logs.csv', 'r')
file_output = file.readlines()[-1]
file.close()
with open('logs/logs.csv', 'r') as file:
file_output = file.readlines()[-1]
return file_output
def test_engineer_with_admin(self):

View File

@@ -1,3 +1,7 @@
"""
REST framework adds support for automatic URL routing to Django.
"""
from rest_framework.routers import DefaultRouter
from main.views import UsersViewSet

View File

@@ -1,10 +1,17 @@
"""
View функции.
"""
from smtplib import SMTPException
from typing import Dict, Any, Optional
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.contrib.auth.models import User, Permission
from django.contrib.auth.models import Permission
from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth.views import LoginView
from django.contrib.contenttypes.models import ContentType
@@ -15,7 +22,7 @@ from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views.generic import FormView
from django_registration.views import RegistrationView
# Django REST
from rest_framework import viewsets
from rest_framework.response import Response
@@ -23,14 +30,35 @@ from access_controller.settings import DEFAULT_FROM_EMAIL, ZENDESK_ROLES, ZENDES
from main.extra_func import check_user_exist, update_profile, get_user_organization, \
make_engineer, make_light_agent, get_users_list, update_users_in_model, count_users, \
set_session_params_for_work_page, get_tickets_list_for_group
from main.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, StatisticForm, \
WorkGetTicketsForm
from main.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, \
StatisticForm, WorkGetTicketsForm
from main.serializers import ProfileSerializer, ZendeskUserSerializer
from main.zendesk_admin import zenpy
from .models import UserProfile
from .statistic_data import StatisticData
def setup_context(**kwargs) -> Dict[str, Any]:
"""
Функция добавления в контекст статуса пользователя.
:param profile_lit: True, при создании профиля пользователя, иначе False
:param control_lit: False
:param work_lit: True, при установке пользователю рабочей роли, иначе False
:param registration_lit: True, при регистрации пользователя, иначе False
:param login_lit: True, если пользователь залогинен, иначе False
:param stats_lit: True, при получении пользователем прав администратора (просмотр статистики), иначе False
:return: Контекст (context)
"""
context = {}
for key in ('profile_lit', 'control_lit', 'work_lit', 'registration_lit', 'login_lit', 'stats_lit'):
if key in kwargs:
context.update({key: True})
else:
context.update({key: False})
return context
class CustomRegistrationView(RegistrationView):
"""
Отображение и логика работы страницы регистрации пользователя.
@@ -41,9 +69,11 @@ class CustomRegistrationView(RegistrationView):
:type template_name: :class:`str`
:param success_url: Указание пути к html-странице завершения регистрации
:type success_url: :class:`django.utils.functional.lazy.<locals>.__proxy__`
:param is_allowed: Определение зарегистрирован ли пользователь с введенным email на Zendesk и принадлежит ли он к организации SYSTEM
:param is_allowed: Определение зарегистрирован ли пользователь с введенным email на Zendesk и
принадлежит ли он к организации SYSTEM
:type is_allowed: :class:`bool`
"""
extra_context = setup_context(registration_lit=True)
form_class = CustomRegistrationForm
template_name = 'django_registration/registration_form.html'
urls = {
@@ -53,7 +83,7 @@ class CustomRegistrationView(RegistrationView):
}
redirect_url = 'done'
def register(self, form: CustomRegistrationForm) -> User:
def register(self, form: CustomRegistrationForm) -> Optional[get_user_model()]:
"""
Функция регистрации пользователя.
1. Ввод email пользователя, указанный на Zendesk
@@ -62,7 +92,7 @@ class CustomRegistrationView(RegistrationView):
3. Создается пользователь class User, а также его профиль.
:param form: Email пользователя на Zendesk
:return: user
:return: User
"""
self.redirect_url = 'done'
if check_user_exist(form.data['email']) and get_user_organization(form.data['email']) == 'SYSTEM':
@@ -78,10 +108,10 @@ class CustomRegistrationView(RegistrationView):
'html_email_template_name': None,
'extra_email_context': None,
}
user = User.objects.create_user(
user = get_user_model().objects.create_user(
username=form.data['email'],
email=form.data['email'],
password=User.objects.make_random_password(length=50)
password=get_user_model().objects.make_random_password(length=50)
)
try:
update_profile(user.userprofile)
@@ -90,13 +120,16 @@ class CustomRegistrationView(RegistrationView):
return user
except SMTPException:
self.redirect_url = 'email_sending_error'
return None
else:
self.redirect_url = 'email_sending_error'
return None
else:
self.redirect_url = 'invalid_zendesk_email'
return None
@staticmethod
def set_permission(user: User) -> None:
def set_permission(user: get_user_model()) -> None:
"""
Функция дает разрешение на просмотр страница администратора, если пользователь имеет роль admin.
@@ -110,7 +143,7 @@ class CustomRegistrationView(RegistrationView):
)
user.user_permissions.add(permission)
def get_success_url(self, user: User = None):
def get_success_url(self, user: get_user_model() = None) -> Dict:
"""
Функция возвращает url-адрес страницы, куда нужно перейти после успешной/не успешной регистрации.
Используется самой django-registration.
@@ -121,7 +154,13 @@ class CustomRegistrationView(RegistrationView):
return self.urls[self.redirect_url]
def registration_error(request):
def registration_error(request: WSGIRequest) -> HttpResponse:
"""
Функция отображения страницы ошибки регистрации.
:param request: регистрация
:return: адресация на страницу ошибки
"""
return render(request, 'django_registration/registration_error.html')
@@ -144,7 +183,7 @@ def profile_page(request: WSGIRequest) -> HttpResponse:
@login_required()
def work_page(request: WSGIRequest, id: int) -> HttpResponse:
def work_page(request: WSGIRequest, required_id: int) -> HttpResponse:
"""
Функция отображения страницы "Управления правами" для текущего пользователя (login_required).
@@ -153,7 +192,7 @@ def work_page(request: WSGIRequest, id: int) -> HttpResponse:
:return: адресация на страницу "Управления правами" (либо на страницу "Авторизации", если id и user.id не совпадают
"""
users = get_users_list()
if request.user.id == id:
if request.user.id == required_id:
if request.session.get('is_confirm', None):
messages.success(request, 'Изменения были применены')
elif request.session.get('is_confirm', None) is not None:
@@ -184,7 +223,7 @@ def work_page(request: WSGIRequest, id: int) -> HttpResponse:
@login_required()
def work_hand_over(request: WSGIRequest):
def work_hand_over(request: WSGIRequest) -> HttpResponseRedirect:
"""
Функция позволяет текущему пользователю сдать права, а именно сменить в Zendesk роль с "engineer" на "light_agent"
@@ -198,18 +237,23 @@ def work_hand_over(request: WSGIRequest):
@login_required()
def work_become_engineer(request: WSGIRequest) -> HttpResponseRedirect:
"""
Функция позволяет текущему пользователю получить права, а именно сменить в Zendesk роль с "light_agent" на "engineer"
Функция позволяет текущему пользователю получить права, а именно сменить в Zendesk роль с "light_agent"
на "engineer".
:param request: данные текущего пользователя (login_required)
:return: перезагрузка текущей страницы после выполнения смены роли
"""
make_engineer(request.user.userprofile, request.user)
return set_session_params_for_work_page(request)
@login_required()
def work_get_tickets(request):
def work_get_tickets(request: WSGIRequest) -> HttpResponse:
"""
:param request:
:return:
"""
zenpy_user = zenpy.get_user(request.user.email)
if request.method == 'POST':
@@ -266,7 +310,7 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageM
self.make_light_agents(users)
return super().form_valid(form)
def make_engineers(self, users):
def make_engineers(self, users: list) -> None:
"""
Функция проходит по списку пользователей, проставляя статус "engineer".
@@ -276,7 +320,7 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageM
for user in users:
make_engineer(user, self.request.user)
def make_light_agents(self, users):
def make_light_agents(self, users: list) -> None:
"""
Функция проходит по списку пользователей, проставляя статус "light agent".
@@ -291,17 +335,30 @@ class CustomLoginView(LoginView):
"""
Отображение страницы авторизации пользователя
"""
extra_context = setup_context(login_lit=True)
form_class = CustomAuthenticationForm
class UsersViewSet(viewsets.ReadOnlyModelViewSet):
"""
Класс для получения пользователей с помощью api
Класс для получения пользователей с помощью api.
:param queryset: Список пользователей с ролью 'agent'
:type queryset: :class:`str`
:param serializer_class: Класс сериализатор для модели профиля пользователя
:type serializer_class: :class:`ProfileSerializer`
"""
queryset = UserProfile.objects.filter(role='agent')
serializer_class = ProfileSerializer
def list(self, request, *args, **kwargs):
def list(self, request: WSGIRequest, *args, **kwargs) -> Response:
"""
Функция возвращает список пользователей, список пользователей Zendesk, количество engineers и light-agents.
:param request: Запрос
:param args: Аргументы
:param kwargs: Параметры
:return: Список пользователей
"""
users = update_users_in_model()
count = count_users(users.values)
profiles = UserProfile.objects.filter(role='agent')
@@ -316,7 +373,13 @@ class UsersViewSet(viewsets.ReadOnlyModelViewSet):
return Response(res)
@staticmethod
def choose_users(zendesk, model):
def choose_users(zendesk: list, model: list) -> list:
"""
Функция формирует список пользователей, которые не зарегистрированы у нас.
:param zendesk: Список пользователей Zendesk
:param model: Список пользователей (модель Userprofile)
:return: Список
"""
users = []
for zendesk_user in zendesk:
if zendesk_user.name not in [user.name for user in model]:
@@ -324,7 +387,12 @@ class UsersViewSet(viewsets.ReadOnlyModelViewSet):
return users
@staticmethod
def get_zendesk_users(users):
def get_zendesk_users(users: list) -> list:
"""
Получение списка пользователей Zendesk, не являющихся админами.
:param users: Список пользователей
:return: Список пользователей, не являющимися администраторами.
"""
zendesk_users = ZendeskUserSerializer(
data=[user for user in users if user.role != 'admin'],
many=True

View File

@@ -1,3 +1,7 @@
"""
Функционал работы администратора Zendesk.
"""
from typing import Optional, Dict, List
from zenpy import Zenpy
@@ -10,17 +14,23 @@ from access_controller.settings import ACTRL_ZENDESK_SUBDOMAIN, ACTRL_API_EMAIL,
class ZendeskAdmin:
"""
Класс **ZendeskAdmin** существует, чтобы в каждой функции отдельно не проверять аккаунт администратора.
Класс **ZendeskAdmin** содержит описание всего функционала администратора.
:param credentials: Полномочия (первым указывается учетная запись организации в Zendesk)
:type credentials: :class:`Dict[str, str]`
:param admin: Администратор
:type admin: :class:`Zenpy`
:param buffer_group_id: ID буферной группы
:type buffer_group_id: :class:`int`
:param solved_tickets_user_id: ID пользователя, который решил тикет
:type solved_tickets_user_id: :class:`int`
"""
def __init__(self, credentials: Dict[str, str]):
self.credentials = credentials
self.admin = self.create_admin()
self.buffer_group_id: int = self.get_group(ZENDESK_GROUPS['buffer']).id
self.solved_tickets_user_id: int = self.get_user(SOLVED_TICKETS_EMAIL).id
self.buffer_group_id= self.get_group(ZENDESK_GROUPS['buffer']).id
self.solved_tickets_user_id = self.get_user(SOLVED_TICKETS_EMAIL).id
def update_user(self, user: ZenpyUser) -> bool:
"""
@@ -46,7 +56,7 @@ class ZendeskAdmin:
:param email: Email пользователя
:return: Является ли зарегистрированным
"""
return True if self.admin.search(email, type='user') else False
return bool(self.admin.search(email, type='user'))
def get_user(self, email: str) -> ZenpyUser:
"""
@@ -96,9 +106,8 @@ class ZendeskAdmin:
admin = Zenpy(**self.credentials)
try:
admin.search(self.credentials['email'], type='user')
except APIException:
raise ValueError('invalid access_controller`s login data')
except APIException as invalid_data:
raise ValueError('invalid access_controller`s login data') from invalid_data
return admin