Merge branch 'develop' into feature/react_test
# Conflicts: # access_controller/settings.py # main/templates/pages/adm_ruleset.html # static/main/js/control.js
This commit is contained in:
commit
0a85ddacd2
@ -186,13 +186,13 @@ autopep8 --in-place filename
|
||||
##Для проверки орфографии:
|
||||
cd docs
|
||||
|
||||
(set -a && source ../.env && make spelling)
|
||||
make spelling
|
||||
##Для обновления документации:
|
||||
m2r README.md
|
||||
|
||||
cd docs
|
||||
|
||||
(set -a && source ../.env && make html)
|
||||
make html
|
||||
|
||||
|
||||
## Read more
|
||||
|
17
README.rst
17
README.rst
@ -57,9 +57,9 @@ Quickstart
|
||||
sudo apt install make
|
||||
pip install --upgrade pip
|
||||
pip install -r requirements/dev.txt
|
||||
(set -a && source .env && ./manage.py migrate)
|
||||
(set -a && source .env && ./manage.py loaddata data.json)
|
||||
(set -a && source .env && ./manage.py runserver)
|
||||
./manage.py migrate
|
||||
./manage.py loaddata data.json
|
||||
./manage.py runserver
|
||||
|
||||
Перед запуском для тестирования:
|
||||
--------------------------------
|
||||
@ -76,7 +76,7 @@ Quickstart
|
||||
* Перейти в папку приложения
|
||||
* Активировать виртуальное окружение
|
||||
* Выполнить команду ``pip install -r requirements/dev.txt``
|
||||
* В виртуальное окружение добавить следующие переменные:
|
||||
* В файл ``.env`` добавить следующие переменные:
|
||||
|
||||
.. code-block::
|
||||
|
||||
@ -170,10 +170,7 @@ Quickstart
|
||||
Для проверки pylint используем:
|
||||
-------------------------------
|
||||
|
||||
pylint ../access_controller_new
|
||||
|
||||
Вместо "access_controller_new" необходимо указать папку проекта.
|
||||
|
||||
pylint ../access_controller (каталог, где лежит проект)
|
||||
|
||||
Для приведения файлов к стандарту PEP8 используем:
|
||||
--------------------------------------------------
|
||||
@ -185,7 +182,7 @@ autopep8 --in-place filename
|
||||
|
||||
cd docs
|
||||
|
||||
(set -a && source ../.env && make spelling)
|
||||
make spelling
|
||||
|
||||
Для обновления документации:
|
||||
----------------------------
|
||||
@ -194,7 +191,7 @@ m2r README.md
|
||||
|
||||
cd docs
|
||||
|
||||
(set -a && source ../.env && make html)
|
||||
make html
|
||||
|
||||
Read more
|
||||
---------
|
||||
|
@ -10,6 +10,7 @@ For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||
"""
|
||||
import os
|
||||
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
@ -151,7 +152,6 @@ LOGIN_REDIRECT_URL = '/'
|
||||
LOGOUT_REDIRECT_URL = '/'
|
||||
|
||||
|
||||
|
||||
# Название_приложения.Название_файла.Название_класса_обработчика
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'access_controller.auth.EmailAuthBackend',
|
||||
@ -190,5 +190,5 @@ ACTRL_API_EMAIL = os.getenv('ACTRL_API_EMAIL') or os.getenv('ACCESS_CONTROLLER_A
|
||||
ACTRL_API_TOKEN = os.getenv('ACTRL_API_TOKEN') or os.getenv('ACCESS_CONTROLLER_API_TOKEN')
|
||||
ACTRL_API_PASSWORD = os.getenv('ACTRL_API_PASSWORD') or os.getenv('ACCESS_CONTROLLER_API_PASSWORD')
|
||||
|
||||
NODE_PACKAGE_JSON = BASE_DIR / 'static/main/js/control_page_js_modules/package.json'
|
||||
NODE_MODULES_ROOT = BASE_DIR / 'static/main/js/control_page_js_modules/node_modules'
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
@ -22,8 +22,9 @@
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "ZendeskAdmin",
|
||||
"user": 1,
|
||||
"role": "admin"
|
||||
"user": 3,
|
||||
"role": "admin",
|
||||
"user_id": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
|
BIN
docs/source/_static/statistic.png
Normal file
BIN
docs/source/_static/statistic.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
BIN
docs/source/_static/take_tickets.png
Normal file
BIN
docs/source/_static/take_tickets.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
@ -41,3 +41,33 @@ Views
|
||||
:members:
|
||||
|
||||
|
||||
*****************
|
||||
Обработка тикетов
|
||||
*****************
|
||||
|
||||
.. automodule:: main.requester
|
||||
:members:
|
||||
|
||||
|
||||
*********************
|
||||
Обработка статистики
|
||||
*********************
|
||||
|
||||
.. automodule:: main.statistic_data
|
||||
:members:
|
||||
|
||||
|
||||
*********************************
|
||||
Функционал администратора Zendesk
|
||||
*********************************
|
||||
|
||||
.. automodule:: main.zendesk_admin
|
||||
:members:
|
||||
|
||||
|
||||
********
|
||||
Тесты
|
||||
********
|
||||
|
||||
.. automodule:: main.tests
|
||||
:members:
|
||||
|
@ -81,6 +81,12 @@
|
||||
|
||||
.. image:: _static/role_change.png
|
||||
|
||||
Являясь инженером, Вы можете запросить в работу необходимое количество тикетов.
|
||||
|
||||
.. image:: _static/take_tickets.png
|
||||
|
||||
Назначенные тикеты будут доступны в Zendesk.
|
||||
|
||||
******************************************
|
||||
Управление правами доступа администратором
|
||||
******************************************
|
||||
@ -97,4 +103,13 @@
|
||||
|
||||
.. image:: _static/admin_manage_done.png
|
||||
|
||||
.. |copy| unicode:: 0xA9 .. Школа программистов S101, группа 2. 2021гю
|
||||
Вы можете смотреть статистику работы пользователя.
|
||||
Для этого на странице статистика необходимо указать:
|
||||
|
||||
* email пользователя
|
||||
* период, за который необходима статистика
|
||||
* формат отображения данных
|
||||
|
||||
.. image:: _static/statistic.png
|
||||
|
||||
.. |copy| unicode:: 0xA9 .. Школа программистов S101, группа 2. 2021г.
|
||||
|
@ -191,4 +191,58 @@ docs
|
||||
a
|
||||
Аватарка
|
||||
filename
|
||||
|
||||
work
|
||||
form
|
||||
work_get_tickets
|
||||
get
|
||||
tickets
|
||||
Do
|
||||
takes
|
||||
whatever
|
||||
it
|
||||
to
|
||||
actually
|
||||
log
|
||||
the
|
||||
specified
|
||||
logging
|
||||
record
|
||||
This
|
||||
version
|
||||
is
|
||||
intended
|
||||
be
|
||||
implemented
|
||||
by
|
||||
subclasses
|
||||
so
|
||||
new
|
||||
тикеты
|
||||
StatisticForm
|
||||
patch
|
||||
zenpy
|
||||
Mock
|
||||
редирект
|
||||
редиректа
|
||||
предустановки
|
||||
TicketListRequester
|
||||
get_tickets_list_for_user
|
||||
side
|
||||
effect
|
||||
for
|
||||
залогиненный
|
||||
предустанавливает
|
||||
переадресация
|
||||
фикстуры
|
||||
profile
|
||||
json
|
||||
аватарки
|
||||
аватарке
|
||||
locmem
|
||||
бэкенд
|
||||
has
|
||||
control
|
||||
disallowed
|
||||
test
|
||||
users
|
||||
Contents
|
||||
|
@ -1,9 +1,9 @@
|
||||
"""
|
||||
Вспомогательные функции со списками пользователей, статистикой и т.д.
|
||||
Вспомогательные функции.
|
||||
"""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from typing import Union
|
||||
from datetime import timedelta, date
|
||||
from typing import Union, Optional
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
@ -29,7 +29,6 @@ def update_role(user_profile: UserProfile, role: int, who_changes: get_user_mode
|
||||
:param user_profile: Профиль пользователя
|
||||
:param role: Новая роль
|
||||
:param who_changes: Пользователь, меняющий роль
|
||||
:return: Пользователь с обновленной ролью
|
||||
"""
|
||||
zendesk = zenpy
|
||||
user = zendesk.get_user(user_profile.user.email)
|
||||
@ -55,7 +54,8 @@ def make_light_agent(user_profile: UserProfile, who_changes: get_user_model()) -
|
||||
Функция устанавливает пользователю роль легкого агента.
|
||||
|
||||
:param user_profile: Профиль пользователя
|
||||
:return: Вызов функции **update_role** с параметрами: профиль пользователя, роль "light_agent"
|
||||
:return: Вызов функции **update_role** с параметрами: профиль пользователя, роль "light_agent".
|
||||
Предварительно снимаем тикеты, находящие в работы у пользователя.
|
||||
"""
|
||||
tickets: SearchResultGenerator = get_tickets_list(user_profile.user.email)
|
||||
ticket: ZenpyTicket
|
||||
@ -85,7 +85,7 @@ def make_light_agent(user_profile: UserProfile, who_changes: get_user_model()) -
|
||||
|
||||
def get_users_list() -> list:
|
||||
"""
|
||||
Функция **get_users_list** возвращает список пользователей Zendesk, относящихся к организации SYSTEM.
|
||||
Функция возвращает список пользователей Zendesk, относящихся к организации SYSTEM.
|
||||
"""
|
||||
zendesk = zenpy
|
||||
|
||||
@ -95,23 +95,29 @@ def get_users_list() -> list:
|
||||
return users
|
||||
|
||||
|
||||
def get_tickets_list(email) -> list:
|
||||
def get_tickets_list(email: str) -> Optional[list]:
|
||||
"""
|
||||
Функция возвращает список тикетов пользователя Zendesk
|
||||
Функция возвращает список тикетов пользователя Zendesk.
|
||||
|
||||
:param email: Email пользователя
|
||||
:return: Список тикетов пользователя
|
||||
"""
|
||||
return TicketListRequester().get_tickets_list_for_user(zenpy.get_user(email))
|
||||
|
||||
|
||||
def get_tickets_list_for_group(group_name):
|
||||
def get_tickets_list_for_group(group_name: str) -> Optional[list]:
|
||||
"""
|
||||
Функция возвращает список неназначенных, нерешённых тикетов группы Zendesk
|
||||
Функция возвращает список не назначенных, не решённых тикетов группы Zendesk.
|
||||
|
||||
:param group_name: Название группы пользователя
|
||||
:return: Список тикетов группы
|
||||
"""
|
||||
return TicketListRequester().get_tickets_list_for_group(zenpy.get_group(group_name))
|
||||
|
||||
|
||||
def update_profile(user_profile: UserProfile) -> None:
|
||||
"""
|
||||
Функция обновляет профиль пользователя в соответствии с текущим в Zendesk.
|
||||
Функция обновляет профиль пользователя в БД в соответствии с текущим в Zendesk.
|
||||
|
||||
:param user_profile: Профиль пользователя
|
||||
:return: Обновленный, в соответствие с текущими данными в Zendesk, профиль пользователя
|
||||
@ -148,6 +154,9 @@ def check_user_auth(email: str, password: str) -> bool:
|
||||
"""
|
||||
Функция проверяет, верны ли входные данные.
|
||||
|
||||
:param email: Email пользователя
|
||||
:param password: Пароль пользователя
|
||||
:return: Существует ли пользователь
|
||||
:raise: :class:`APIException`: исключение, вызываемое если пользователь не аутентифицирован
|
||||
"""
|
||||
creds = {
|
||||
@ -165,7 +174,7 @@ def check_user_auth(email: str, password: str) -> bool:
|
||||
|
||||
def update_user_in_model(profile: UserProfile, zendesk_user: ZenpyUser) -> None:
|
||||
"""
|
||||
Функция обновляет профиль пользователя при изменении данных пользователя на Zendesk.
|
||||
Функция обновляет профиль пользователя в модели при изменении данных пользователя на Zendesk.
|
||||
|
||||
:param profile: Профиль пользователя
|
||||
:param zendesk_user: Данные пользователя в Zendesk
|
||||
@ -181,7 +190,10 @@ def update_user_in_model(profile: UserProfile, zendesk_user: ZenpyUser) -> None:
|
||||
|
||||
def count_users(users: list) -> tuple:
|
||||
"""
|
||||
Функция подсчета количества сотрудников с ролями engineer и light_agent
|
||||
Функция подсчета количества сотрудников с ролями engineer и light_agent.
|
||||
|
||||
:param users: Список пользователей
|
||||
:return: Количество инженеров, количество light_agents
|
||||
"""
|
||||
engineers, light_agents = 0, 0
|
||||
for user in users:
|
||||
@ -194,7 +206,7 @@ def count_users(users: list) -> tuple:
|
||||
|
||||
def update_users_in_model() -> list:
|
||||
"""
|
||||
Обновляет пользователей в модели UserProfile по списку пользователей в организации
|
||||
Обновляет пользователей в модели UserProfile по списку пользователей в организации.
|
||||
"""
|
||||
users = get_users_list()
|
||||
for user in users:
|
||||
@ -206,7 +218,7 @@ def update_users_in_model() -> list:
|
||||
return users
|
||||
|
||||
|
||||
def daterange(start_date: timedelta, end_date: timedelta) -> list:
|
||||
def daterange(start_date: date, end_date: date) -> list:
|
||||
"""
|
||||
Функция возвращает список дней с start_date по end_date, исключая правую границу.
|
||||
|
||||
@ -253,7 +265,13 @@ class DatabaseHandler(logging.Handler):
|
||||
def __init__(self):
|
||||
logging.Handler.__init__(self)
|
||||
|
||||
def emit(self, record):
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
"""
|
||||
Функция записи в базу данных лога с изменением роли пользователя.
|
||||
|
||||
:param record: Лог смены роли пользователя
|
||||
:return: Запись в БД лога по смене роли пользователя с указанием новой и старой роли, а также автора изменения
|
||||
"""
|
||||
database = RoleChangeLogs()
|
||||
users = record.msg
|
||||
if users[1]:
|
||||
@ -284,7 +302,7 @@ class CsvFormatter(logging.Formatter):
|
||||
"""
|
||||
Функция форматирует запись смены роли пользователя в строку.
|
||||
|
||||
:param record: Запись смены роли пользователя.
|
||||
:param record: Лог смены роли пользователя.
|
||||
:return: Строка с записью смены пользователя.
|
||||
"""
|
||||
users = record.msg
|
||||
@ -307,7 +325,7 @@ class CsvFormatter(logging.Formatter):
|
||||
return msg
|
||||
|
||||
|
||||
def log(user, admin=None):
|
||||
def log(user: get_user_model(), admin: get_user_model() = None) -> None:
|
||||
"""
|
||||
Функция осуществляет запись логов в базу данных и csv файл.
|
||||
|
||||
@ -335,7 +353,7 @@ def set_session_params_for_work_page(request: WSGIRequest, count: int = None, is
|
||||
|
||||
:param request: Получение данных с рабочей страницы пользователя
|
||||
:param count: Количество запрошенных тикетов
|
||||
:param is_confirm: Назначение тикетов
|
||||
:param is_confirm: Назначены ли тикеты
|
||||
:return: Перезагрузка страницы "Управление правами" соответствующего пользователя
|
||||
"""
|
||||
request.session['is_confirm'] = is_confirm
|
||||
|
@ -1,5 +1,5 @@
|
||||
"""
|
||||
Формы.
|
||||
Формы, использующиеся в приложении.
|
||||
"""
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
|
@ -13,7 +13,6 @@ from access_controller.settings import ZENDESK_ROLES
|
||||
class UserProfile(models.Model):
|
||||
"""
|
||||
Модель профиля пользователя.
|
||||
|
||||
Профиль создается и изменяется при создании и изменении модель User.
|
||||
"""
|
||||
|
||||
@ -24,18 +23,14 @@ class UserProfile(models.Model):
|
||||
|
||||
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='Код роли пользователя')
|
||||
custom_role_id = models.BigIntegerField(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) -> str:
|
||||
"""
|
||||
Функция возвращает роль пользователя в Zendesk.
|
||||
|
||||
В формате str, либо UNDEFINED, если пользователь не найден
|
||||
|
||||
:return: Роль пользователя в Zendesk
|
||||
Роль пользователя в Zendesk, либо UNDEFINED, если пользователь не найден.
|
||||
"""
|
||||
for role, r_id in ZENDESK_ROLES.items():
|
||||
if r_id == self.custom_role_id:
|
||||
@ -44,12 +39,12 @@ class UserProfile(models.Model):
|
||||
|
||||
|
||||
@receiver(post_save, sender=get_user_model())
|
||||
def create_user_profile(instance, created, **kwargs) -> None:
|
||||
def create_user_profile(instance: get_user_model(), created: bool, **kwargs) -> None:
|
||||
"""
|
||||
Функция создания профиля пользователя (Userprofile) при регистрации пользователя.
|
||||
|
||||
:param instance: Экземпляр класса User
|
||||
:param created: Создание профиля пользователя
|
||||
:param created: Существует ли пользователь
|
||||
:param kwargs: Параметры
|
||||
:return: Обновленный список объектов профилей пользователей
|
||||
"""
|
||||
@ -58,7 +53,7 @@ def create_user_profile(instance, created, **kwargs) -> None:
|
||||
|
||||
|
||||
@receiver(post_save, sender=get_user_model())
|
||||
def save_user_profile(instance, **kwargs) -> None:
|
||||
def save_user_profile(instance: get_user_model(), **kwargs) -> None:
|
||||
"""
|
||||
Функция записи БД профиля пользователя.
|
||||
|
||||
@ -75,8 +70,8 @@ class RoleChangeLogs(models.Model):
|
||||
"""
|
||||
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='Присвоенная роль')
|
||||
old_role = models.BigIntegerField(default=0, help_text='Старая роль')
|
||||
new_role = models.BigIntegerField(default=0, help_text='Присвоенная роль')
|
||||
change_time = models.DateTimeField(default=timezone.now, help_text='Дата и время изменения роли')
|
||||
changed_by = models.ForeignKey(to=get_user_model(), on_delete=models.CASCADE, related_name='changed_by',
|
||||
help_text='Кем была изменена роль')
|
||||
@ -84,7 +79,7 @@ class RoleChangeLogs(models.Model):
|
||||
|
||||
class UnassignedTicketStatus(models.IntegerChoices):
|
||||
"""
|
||||
Класс статусов не распределенных тикетов.
|
||||
Модель статусов не распределенных тикетов.
|
||||
|
||||
:param UNASSIGNED: Снят с пользователя, перенесён в буферную группу
|
||||
:param RESTORED: Авторство восстановлено
|
||||
@ -95,7 +90,7 @@ class UnassignedTicketStatus(models.IntegerChoices):
|
||||
"""
|
||||
UNASSIGNED = 0, 'Снят с пользователя, перенесён в буферную группу'
|
||||
RESTORED = 1, 'Авторство восстановлено'
|
||||
NOT_FOUND = 2, 'Пока нас не было, тикет испарился из ' \
|
||||
NOT_FOUND = 2, 'Пока нас не было, тикет был перенесен из ' \
|
||||
'буферной группы. Дополнительные действия не требуются'
|
||||
CLOSED = 3, 'Тикет уже был закрыт. Дополнительные действия не требуются'
|
||||
SOLVED = 4, 'Тикет решён. Записан на пользователя с почтой SOLVED_TICKETS_EMAIL'
|
||||
|
@ -1,6 +1,8 @@
|
||||
"""
|
||||
Обработка тикетов.
|
||||
Обработка тикетов, составление списков тикетов для пользователя и группы пользователей.
|
||||
"""
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
from zenpy import TicketApi
|
||||
from zenpy.lib.api_objects import Ticket
|
||||
@ -11,6 +13,13 @@ from main.zendesk_admin import zenpy
|
||||
class TicketListRequester:
|
||||
"""
|
||||
Класс обработки тикетов.
|
||||
|
||||
:param email: Email пользователя
|
||||
:type display: :class:`str`
|
||||
:param token_or_password: Токен или пароль
|
||||
:type display: :class:`str`
|
||||
:param prefix: Формат строка url страницы Zendesk
|
||||
:type display: :class:`str`
|
||||
"""
|
||||
def __init__(self):
|
||||
self.email = zenpy.credentials['email']
|
||||
@ -21,16 +30,22 @@ 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: zenpy) -> str:
|
||||
def get_tickets_list_for_user(self, zendesk_user: zenpy) -> Optional[list]:
|
||||
"""
|
||||
Функция получения списка тикетов пользователя Zendesk.
|
||||
|
||||
:param zendesk_user: Пользователь Zendesk
|
||||
:return: Список тикетов, назначенных на данного пользователя в Zendesk
|
||||
"""
|
||||
url = self.prefix + f'users/{zendesk_user.id}/tickets/assigned'
|
||||
return self._get_tickets(url)
|
||||
|
||||
def get_tickets_list_for_group(self, group: zenpy) -> list():
|
||||
def get_tickets_list_for_group(self, group: zenpy) -> Optional[list]:
|
||||
"""
|
||||
Функция получения списка тикетов группы пользователей Zendesk.
|
||||
|
||||
:param group: Название группы
|
||||
:return: Список тикетов
|
||||
"""
|
||||
url = self.prefix + '/tickets'
|
||||
all_tickets = self._get_tickets(url)
|
||||
@ -40,9 +55,12 @@ class TicketListRequester:
|
||||
tickets.append(ticket)
|
||||
return tickets
|
||||
|
||||
def _get_tickets(self, url: str) -> list():
|
||||
def _get_tickets(self, url: str) -> Optional[list]:
|
||||
"""
|
||||
Функция получения полного списка тикетов по url.
|
||||
|
||||
:param url: Url Zendesk c указанием тикетов, назначенных на пользователя
|
||||
:return: Список тикетов
|
||||
"""
|
||||
response = requests.get(url, auth=(self.email, self.token_or_password))
|
||||
tickets = []
|
||||
|
@ -1,5 +1,5 @@
|
||||
"""
|
||||
Сериализаторы.
|
||||
Сериализаторы, используемые в приложении.
|
||||
"""
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import serializers
|
||||
|
@ -1,6 +1,9 @@
|
||||
"""
|
||||
Обработка статистики.
|
||||
|
||||
Обнаруживает факт изменения роли пользователя и вычисляет отработанное на смене время.
|
||||
"""
|
||||
|
||||
from datetime import date, datetime, timedelta
|
||||
from typing import Optional
|
||||
|
||||
@ -14,7 +17,7 @@ from main.models import RoleChangeLogs
|
||||
|
||||
class StatisticData:
|
||||
"""
|
||||
Класс для учета статистики интервалов работы пользователей.
|
||||
Класс для учета статистики времени работы пользователей.
|
||||
Передаваемые параметры: start_date, end_date, email, stat.
|
||||
|
||||
:param display: Формат отображения времени (часы, минуты)
|
||||
@ -37,7 +40,7 @@ class StatisticData:
|
||||
:type statistic: :class:`dict`
|
||||
"""
|
||||
|
||||
def __init__(self, start_date, end_date, user_email, stat=None):
|
||||
def __init__(self, start_date, end_date, user_email: str, stat=None):
|
||||
self.display = None
|
||||
self.interval = None
|
||||
self.start_date = start_date
|
||||
@ -57,7 +60,8 @@ class StatisticData:
|
||||
"""
|
||||
Функция возвращает статистику работы пользователя.
|
||||
|
||||
:return: Словарь statistic с применением формата отображения и интервала работы(если они есть).
|
||||
:return: Словарь statistic с применением формата отображения
|
||||
и интервала работы (если они есть).
|
||||
None, если были ошибки при создании.
|
||||
"""
|
||||
if self.is_valid_statistic():
|
||||
@ -117,7 +121,7 @@ class StatisticData:
|
||||
"""
|
||||
return not self.errors
|
||||
|
||||
def _use_display(self, stat: list) -> list:
|
||||
def _use_display(self, stat: dict) -> dict:
|
||||
"""
|
||||
Функция приводит данные к формату отображения.
|
||||
|
||||
@ -136,7 +140,9 @@ class StatisticData:
|
||||
|
||||
def _use_interval(self, stat: dict) -> dict:
|
||||
"""
|
||||
Функция объединяет ключи и значения в соответствии с интервалом работы.
|
||||
Переупаковка результата в соответствии с указанным временным диапазоном
|
||||
|
||||
Сжимает набор дней в месяцы, если указан режим работы "по месяцам"
|
||||
|
||||
:param stat: Статистика работы пользователя
|
||||
:return: Обновленная статистика
|
||||
@ -172,8 +178,8 @@ class StatisticData:
|
||||
"""
|
||||
Функция возвращает логи в диапазоне дат start_date - end_date для пользователя с указанным email.
|
||||
|
||||
:return: Данные о смене статусов пользователя. Если пользователь не найден или
|
||||
интервал времени некорректен - ошибку.
|
||||
:return: Данные о смене статусов пользователя.
|
||||
Если пользователь не найден или интервал времени некорректен - ошибку.
|
||||
"""
|
||||
if not self.check_time():
|
||||
self.errors += ['Конец диапазона должен быть позже начала диапазона и раньше текущего времени']
|
||||
@ -208,9 +214,12 @@ class StatisticData:
|
||||
if self.data[log_index].new_role == ROLES['engineer']:
|
||||
self.engineer_logic(log_index)
|
||||
|
||||
def engineer_logic(self, log_index):
|
||||
def engineer_logic(self, log_index: int) -> None:
|
||||
"""
|
||||
Функция обрабатывает основную часть работы инженера
|
||||
Функция вычисляет время работы инженера.
|
||||
|
||||
:param log_index: Индекс текущего лога
|
||||
:return: Дополняет статистику работы инженера временем между текущим и последующим логом
|
||||
"""
|
||||
current_log, next_log = self.data[log_index], self.data[log_index + 1]
|
||||
if current_log.change_time.date() != next_log.change_time.date():
|
||||
@ -222,9 +231,14 @@ class StatisticData:
|
||||
elapsed_time = next_log.change_time - current_log.change_time
|
||||
self.statistic[current_log.change_time.date()] += elapsed_time.total_seconds()
|
||||
|
||||
def post_engineer_logic(self, last_log):
|
||||
def post_engineer_logic(self, last_log: RoleChangeLogs) -> None:
|
||||
"""
|
||||
Функция обрабатывает случай, когда нам изветсно что инженер работал и после диапазона
|
||||
Обработка случая, в котором инженер не закрыл смену.
|
||||
|
||||
В таком случае считается всё время от момента открытия смены до текущего момента.
|
||||
|
||||
:param last_log: Последний лог изменения роли, в результате которого пользователь назначен инженером.
|
||||
:return: Дополняет статистику работы
|
||||
"""
|
||||
self.fill_daterange(last_log.change_time.date() + timedelta(days=1), self.end_date + timedelta(days=1))
|
||||
if last_log.change_time.date() == timezone.now().date():
|
||||
@ -237,15 +251,25 @@ class StatisticData:
|
||||
if self.end_date == timezone.now().date():
|
||||
self.statistic[self.end_date] = get_timedelta(None, timezone.now().time()).total_seconds()
|
||||
|
||||
def prev_engineer_logic(self, first_log):
|
||||
def prev_engineer_logic(self, first_log: RoleChangeLogs) -> None:
|
||||
"""
|
||||
Функция обрабатывает случай, когда нам изветсно что инженер начал работу до диапазона
|
||||
Обработка случая, в котором инженер закрыл смену в отражаемом периоде, а открыл её до этого периода.
|
||||
|
||||
В таком случае должен быть учтён только период от начала отображаемого диапазона до закрытия смены.
|
||||
|
||||
:param first_log_log: Первый лог в диапазоне, в результате которого пользователь назначен легким агентом.
|
||||
:return: Дополняет статистику работы
|
||||
"""
|
||||
self.fill_daterange(max(get_user_model().objects.get(email=self.email).date_joined.date(), self.start_date),
|
||||
first_log.change_time.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()
|
||||
|
||||
def fill_daterange(self, first: date, last: date, val: int = 24 * 3600) -> dict:
|
||||
def fill_daterange(self, first: date, last: date, val: int = 24 * 3600) -> None:
|
||||
"""
|
||||
Функция заполняет диапазон дат значением val (по умолчанию val = кол-во секунд в 1 дне).
|
||||
|
||||
@ -256,9 +280,11 @@ class StatisticData:
|
||||
for day in daterange(first, last):
|
||||
self.statistic[day] = val
|
||||
|
||||
def clear_statistic(self) -> dict:
|
||||
def clear_statistic(self) -> None:
|
||||
"""
|
||||
Функция осуществляет обновление всех дней.
|
||||
Чистка статистики и установка времени по умолчанию.
|
||||
|
||||
Устанавливает время смены в 0
|
||||
"""
|
||||
self.statistic.clear()
|
||||
self.fill_daterange(self.start_date, self.end_date + timedelta(days=1), 0)
|
||||
|
@ -19,6 +19,8 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.bd-placeholder-img-lg {
|
||||
font-size: 3.5rem;
|
||||
|
@ -24,7 +24,7 @@
|
||||
{% csrf_token %}
|
||||
<div class="row justify-content-center new-section">
|
||||
<div class="col-10">
|
||||
<h6 class="table-title">Список сотрудников</h6>
|
||||
<h3 class="py-4 text-center font-weight-bold">Список сотрудников</h3>
|
||||
|
||||
{% block table %}
|
||||
<div id="table"></div>
|
||||
|
@ -23,7 +23,7 @@
|
||||
{% block content %}
|
||||
<br>
|
||||
<div class="row px-4 py-4">
|
||||
<div class="col-auto">
|
||||
<div class="col-auto px-4 py-4">
|
||||
<div class="container">
|
||||
<img
|
||||
src="{% if profile.image %}{{ profile.image }}{% else %}{% static 'no_avatar.png' %}{% endif %}"
|
||||
@ -31,7 +31,9 @@
|
||||
alt="Нет изображения"
|
||||
>
|
||||
</div>
|
||||
<a href="{%url 'password_change' %}">Сменить пароль</a>
|
||||
<div class="px-3 py-5">
|
||||
<a href="{%url 'password_change' %}" class="btn btn-info">Сменить пароль</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h4><span class="badge bg-secondary text-light">Имя пользователя</span></h4> <h5><strong>{{ profile.name }}</strong></h5>
|
||||
@ -44,7 +46,7 @@
|
||||
{% elif profile.custom_role_id == ZENDESK_ROLES.light_agent %}
|
||||
<h5><strong>light_agent</strong></h5>
|
||||
{% else %}
|
||||
<h5><strong><small class="text-muted">None</small></strong></h5>
|
||||
<h5><strong><small class="text-muted">Без роли</small></strong></h5>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
@ -52,7 +54,7 @@
|
||||
<br>
|
||||
<div align="center" >
|
||||
<form action="">
|
||||
<a href="{% url 'work' profile.user.id %}" class="btn btn-primary btn-lg">Запросить права доступа</a>
|
||||
<a href="{% url 'work' profile.user.id %}" type="submit" class="btn default-button btn-success btn-lg px-4 py-3">Запросить права доступа</a>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -7,21 +7,21 @@
|
||||
{% block heading %} Страницы просмотра статистики{% endblock %}
|
||||
|
||||
{% block content%}
|
||||
<div class="mt-5">
|
||||
<div class="mt-5 py-4">
|
||||
<div class="container-fluid" style="font-size:2rem">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="row g-3">
|
||||
<div class="col-auto">
|
||||
{{ form.email.label }}
|
||||
<div class="col-auto p-2">
|
||||
<h4 class="py-1">{{ form.email.label }}</h4>
|
||||
</div>
|
||||
<div class="col-auto mt-4">
|
||||
{{ form.email }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 mt-4">
|
||||
<div class="col-auto">
|
||||
{{ form.interval.label }}
|
||||
<div class="col-auto p-2">
|
||||
<h4 class="py-1">{{ form.interval.label }}</h4>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
{% for radio in form.interval%}
|
||||
@ -33,8 +33,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 mt-4">
|
||||
<div class="col-auto">
|
||||
{{ form.display_format.label }}
|
||||
<div class="col-auto p-2">
|
||||
<h4 class="py-1">{{ form.display_format.label }}</h4>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
{% for radio in form.display_format%}
|
||||
@ -46,8 +46,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 mt-4">
|
||||
<div class="col-auto">
|
||||
{{ form.range_start.label}}
|
||||
<div class="col-auto p-2">
|
||||
<h4 class="py-1">{{ form.range_start.label}}</h4>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class='col-sm-7'>
|
||||
@ -56,8 +56,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 mt-4">
|
||||
<div class="col-auto">
|
||||
{{ form.range_end.label}}
|
||||
<div class="col-auto p-2">
|
||||
<h4 class="py-1">{{ form.range_end.label}}</h4>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class='col-sm-7'>
|
||||
@ -65,9 +65,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row text-center">
|
||||
<div class="form-row text-center py-5">
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-outline-primary">Посмотреть статистику</button>
|
||||
<button type="submit" class="btn default-button btn-info py-3 px-5">Посмотреть статистику</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -16,66 +16,73 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-md">
|
||||
|
||||
<div class="new-section">
|
||||
<p class="row page-description">Свободных Мест: {{ licences_remaining }}</p>
|
||||
</div>
|
||||
<p class="row page-description">Свободных Мест: {{ licences_remaining }}</p>
|
||||
|
||||
<div class="row justify-content-center new-section">
|
||||
<div class="col-10">
|
||||
<h6 class="table-title">Список сотрудников с правами инженера</h6>
|
||||
<table class="light-table">
|
||||
<thead>
|
||||
<th>Email</th>
|
||||
<th>Name</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for engineer in engineers %}
|
||||
<tr>
|
||||
<td>{{ engineer.email }}</td>
|
||||
<td>{{ engineer.name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center new-section">
|
||||
<div class="col-5">
|
||||
<div class="info">
|
||||
<div class="info-row">
|
||||
<div class="info-target">инженеров: </div>
|
||||
<div class="info-quantity">
|
||||
<div class="status-circle-small light-green"></div>
|
||||
<span class="info-quantity-value">{{ engineers|length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-target">легких агентов:</div>
|
||||
<div class="info-quantity">
|
||||
<div class="status-circle-small light-yellow"></div>
|
||||
<span class="info-quantity-value">{{ agents|length }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<a href="/work/become_engineer" class="request-acess-button default-button">Получить права инженера</a>
|
||||
<a href="/work/hand_over" class="hand-over-acess-button default-button">Сдать права инженера</a>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<form method="post" action="{% url 'work_get_tickets' %}">
|
||||
{% csrf_token %}
|
||||
{{ get_tickets_form.count_tickets }}
|
||||
<button type="submit" class="default-button">Взять тикеты в работу</button>
|
||||
</form>
|
||||
</div>
|
||||
{% for message in messages %}
|
||||
<script>create_notification('{{message}}','','{{message.tags}}',2000)</script>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="row justify-content-center new-section">
|
||||
<div class="col-10 py-3">
|
||||
<h3 class="py-4 text-center font-weight-bold">Список сотрудников с правами инженера</h3>
|
||||
<table class="table table-dark">
|
||||
<thead>
|
||||
<th>Email</th>
|
||||
<th>Name</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for engineer in engineers %}
|
||||
<tr>
|
||||
<td>{{ engineer.email }}</td>
|
||||
<td>{{ engineer.name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center new-section">
|
||||
<div class="col-5">
|
||||
<div class="info">
|
||||
<div class="info-row">
|
||||
<div class="info-target px-4"><h6>инженеров:</h6></div>
|
||||
<div class="info-quantity">
|
||||
<div class="status-circle-small light-green"></div>
|
||||
<span class="info-quantity-value">{{ engineers|length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-target px-4"><h6>легких агентов:</h6></div>
|
||||
<div class="info-quantity">
|
||||
<div class="status-circle-small light-yellow"></div>
|
||||
<span class="info-quantity-value">{{ agents|length }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-5 mb-3 py-1">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<a href="/work/become_engineer" class="btn btn-success btn-block py-3">Получить права инженера</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2 py-1">
|
||||
<div class="col">
|
||||
<a href="/work/hand_over" class="btn btn-danger btn-block py-3">Сдать права инженера</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="row g-3 align-items-center" method="GET" action="/work/get_tickets">
|
||||
<div class="col-2">
|
||||
<input class="form-control" type="number" min="1" value="1" name="count_tickets">
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<button type="submit" class="btn default-button btn-warning btn-block btn-sm py-3">Взять тикеты в работу</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for message in messages %}
|
||||
<script>create_notification('{{message}}','','{{message.tags}}',5000)</script>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
|
467
main/tests.py
467
main/tests.py
@ -1,5 +1,5 @@
|
||||
"""
|
||||
Тесты.
|
||||
Тестирование работы программы.
|
||||
"""
|
||||
|
||||
|
||||
@ -22,14 +22,35 @@ from main.extra_func import log
|
||||
|
||||
|
||||
class UsersBaseTestCase(TestCase):
|
||||
"""Базовый класс загружения данных для тестов с пользователями"""
|
||||
"""
|
||||
Базовый класс загрузки данных для тестов с пользователями.
|
||||
|
||||
Для тестов используются фикстуры тестовых пользователей (test_users.json).
|
||||
"""
|
||||
fixtures = ['fixtures/test_users.json']
|
||||
|
||||
def setUp(self):
|
||||
"""Добавление в переменные почт и клиентов для пользователей"""
|
||||
def setUp(self) -> None:
|
||||
"""
|
||||
Функция предустановки значений переменных.
|
||||
|
||||
Добавляем email тестовых пользователей и создаем клиентов для тестов.
|
||||
|
||||
:param light_agent: email тестового пользователя с правами light_agent
|
||||
:type light_agent: :class:`str`
|
||||
:param engineer: email тестового пользователя с правами engineer
|
||||
:type engineer: :class:`str`
|
||||
:param admin: email тестового пользователя с правами admin
|
||||
:type admin: :class:`str`
|
||||
:param agent_client: клиент, залогиненный как пользователь с email light_agent
|
||||
:type agent_client: :class:`django.test.client.Client`
|
||||
:param engineer_client: клиент, залогиненный как пользователь с email engineer
|
||||
:type engineer_client: :class:`django.test.client.Client`
|
||||
:param admin_client: клиент, залогиненный как пользователь с email admin
|
||||
:type admin_client: :class:`django.test.client.Client`
|
||||
"""
|
||||
self.light_agent = '123@test.ru'
|
||||
self.admin = 'admin@gmail.com'
|
||||
self.engineer = 'customer@example.com'
|
||||
self.engineer = 'customer@example.com'
|
||||
self.agent_client = Client()
|
||||
self.agent_client.force_login(get_user_model().objects.get(email=self.light_agent))
|
||||
self.admin_client = Client()
|
||||
@ -40,13 +61,27 @@ class UsersBaseTestCase(TestCase):
|
||||
|
||||
class RegistrationTestCase(TestCase):
|
||||
"""
|
||||
Класс тестирования регистрации пользователя.
|
||||
Класс тестирования регистрации.
|
||||
|
||||
Для тестов используются фикстуры с данными пользователей engineer и light_agent (data.json).
|
||||
"""
|
||||
fixtures = ['fixtures/data.json']
|
||||
|
||||
def setUp(self) -> None:
|
||||
"""
|
||||
Функция предтестовых настроек.
|
||||
Функция предустановки значений переменных.
|
||||
|
||||
Добавляем email тестовых пользователей и создаем клиентов для тестов.
|
||||
|
||||
:param email_backend: locmem бэкенд со списком отправленных писем
|
||||
:type email_backend: :class:`str`
|
||||
:param any_zendesk_user_email: email пользователя, зарегистрированного на Zendesk
|
||||
:type any_zendesk_user_email: :class:`str`
|
||||
:param zendesk_admin_email: email администратора
|
||||
:type zendesk_admin_email: :class:`str`
|
||||
:param client: новый клиент
|
||||
:type client: :class:`django.test.client.Client`
|
||||
|
||||
"""
|
||||
self.email_backend = 'django.core.mail.backends.locmem.EmailBackend'
|
||||
self.any_zendesk_user_email = 'idar.sokurov.05@mail.ru'
|
||||
@ -55,31 +90,43 @@ class RegistrationTestCase(TestCase):
|
||||
|
||||
def test_registration_complete_redirect(self) -> None:
|
||||
"""
|
||||
Функция тестирования успешно завершенной регистрации.
|
||||
Функция тестирования успешной регистрации пользователя.
|
||||
|
||||
Проверяет, что в случае если email пользователя зарегистрирован на Zendesk, была заполнена форма регистрации
|
||||
и направлено письмо со ссылкой для завершения регистрации, происходит редирект на страницу завершения
|
||||
регистрации.
|
||||
"""
|
||||
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):
|
||||
def test_registration_fail_redirect(self) -> None:
|
||||
"""
|
||||
Функция тестирования неуспешной регистрации.
|
||||
Функция тестирования не успешной регистрации пользователя (введен email, не зарегистрированный на Zendesk).
|
||||
|
||||
Проверяет, что происходит редирект на страницу "registration disallowed"
|
||||
"""
|
||||
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):
|
||||
def test_registration_user_already_exist(self) -> None:
|
||||
"""
|
||||
Функция тестирования попытки регистрации уже зарегистрированного пользователя.
|
||||
Функция тестирования попытки зарегистрироваться, используя email уже зарегистрированного в приложении
|
||||
пользователя ("123@test.ru").
|
||||
|
||||
Проверяет, что пользователь получает сообщение "Этот адрес электронной почты уже используется"
|
||||
"""
|
||||
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):
|
||||
def test_registration_send_email(self) -> None:
|
||||
"""
|
||||
Функция тестирования отправки email.
|
||||
Функция тестирования отправки email пользователю при регистрации.
|
||||
|
||||
Проверяет отправку уведомления на указанный пользователем адрес, а также содержание письма (заголовка и тела)
|
||||
через email locmem backend.
|
||||
"""
|
||||
with self.settings(EMAIL_BACKEND=self.email_backend):
|
||||
response: HttpResponseRedirect = \
|
||||
@ -95,9 +142,11 @@ class RegistrationTestCase(TestCase):
|
||||
correct_body = render_to_string('registration/password_reset_email.html', email_context, response.request)
|
||||
self.assertEqual(mail.outbox[0].body, correct_body)
|
||||
|
||||
def test_registration_user_creating(self):
|
||||
def test_registration_user_creating(self) -> None:
|
||||
"""
|
||||
Функция тестирования регистрации пользователя (сверяем имя с именем в Zendesk.
|
||||
Функция тестирования создания пользователя приложения при регистрации.
|
||||
|
||||
Проверяет соответствие имени созданного пользователя с именем пользователя в Zendesk
|
||||
"""
|
||||
with self.settings(EMAIL_BACKEND=self.email_backend):
|
||||
self.client.post(reverse('registration'), data={'email': self.any_zendesk_user_email})
|
||||
@ -105,9 +154,11 @@ class RegistrationTestCase(TestCase):
|
||||
zendesk_user = zenpy.get_user(self.any_zendesk_user_email)
|
||||
self.assertEqual(user.userprofile.name, zendesk_user.name)
|
||||
|
||||
def test_permissions_applying(self):
|
||||
def test_permissions_applying(self) -> None:
|
||||
"""
|
||||
Функция тестирования проверке присвоения роли admin.
|
||||
Функция тестирования создания администратора и присвоения ему соответствующих прав.
|
||||
|
||||
Проверяет, что у созданного пользователя роль "admin" и права "has_control_access".
|
||||
"""
|
||||
with self.settings(EMAIL_BACKEND=self.email_backend):
|
||||
self.client.post(reverse('registration'), data={'email': self.zendesk_admin_email})
|
||||
@ -118,47 +169,67 @@ class RegistrationTestCase(TestCase):
|
||||
|
||||
class MakeEngineerTestCase(UsersBaseTestCase):
|
||||
"""
|
||||
Класс тестов для проверки функции назначения роли engineer.
|
||||
Класс тестирования присвоения пользователю роли engineer.
|
||||
|
||||
В тестах используется @patch('main.extra_func.zenpy') Mock для работы с API Zendesk.
|
||||
"""
|
||||
|
||||
@patch('main.extra_func.zenpy')
|
||||
def test_become_engineer_redirect(self, _zenpy_mock):
|
||||
def test_become_engineer_redirect(self, _zenpy_mock: Mock) -> None:
|
||||
"""
|
||||
Функция проверки переадресации пользователя на рабочую страницу после назначения роли engineer.
|
||||
Функция тестирования редиректа на рабочую страницу тестового пользователя при назначении его инженером.
|
||||
|
||||
:param _zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
"""
|
||||
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):
|
||||
def test_light_agent_make_engineer(self, zenpy_mock: Mock) -> None:
|
||||
"""
|
||||
Функция проверки назначения light_agent на роль engineer.
|
||||
Функция тестирования назначения легкого агента на роль инженера.
|
||||
|
||||
Проверяет установку роли "engineer" в Zendesk.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
"""
|
||||
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):
|
||||
def test_admin_make_engineer(self, zenpy_mock: Mock) -> None:
|
||||
"""
|
||||
Функция проверки назначения admin на роль engineer.
|
||||
Функция тестирования назначения администратора на роль инженера.
|
||||
|
||||
Проверяет установку роли "engineer" в Zendesk.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
"""
|
||||
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):
|
||||
def test_engineer_make_engineer(self, zenpy_mock: Mock) -> None:
|
||||
"""
|
||||
Функция проверки назначения engineer на роль engineer.
|
||||
Функция тестирования назначения инженера на роль инженера.
|
||||
|
||||
Проверяет установку роли "engineer" в Zendesk.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
"""
|
||||
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):
|
||||
def test_control_page_make_engineer_one(self, zenpy_mock: Mock) -> None:
|
||||
"""
|
||||
Функция проверки назначения администратором на роль engineer одного пользователя.
|
||||
Функция тестирования назначения администратором одного инженера на странице "Управление".
|
||||
|
||||
Проверяет обновление администратором роли пользователя с light_agent на engineer.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
"""
|
||||
self.admin_client.post(
|
||||
reverse_lazy('control'),
|
||||
@ -171,9 +242,13 @@ class MakeEngineerTestCase(UsersBaseTestCase):
|
||||
self.assertEqual(mock_object.custom_role_id, sets.ZENDESK_ROLES['engineer'])
|
||||
|
||||
@patch('main.extra_func.zenpy')
|
||||
def test_control_page_make_engineer_many(self, zenpy_mock):
|
||||
def test_control_page_make_engineer_many(self, zenpy_mock: Mock) -> None:
|
||||
"""
|
||||
Функция проверки назначения администратором на роль engineer нескольких пользователей.
|
||||
Функция тестирования назначения администратором нескольких инженеров на странице "Управление".
|
||||
|
||||
Проверяет обновление администратором ролей двух пользователей с light_agent на engineer.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
"""
|
||||
self.admin_client.post(
|
||||
reverse_lazy('control'),
|
||||
@ -193,10 +268,23 @@ class MakeEngineerTestCase(UsersBaseTestCase):
|
||||
|
||||
|
||||
class MakeLightAgentTestCase(UsersBaseTestCase):
|
||||
"""
|
||||
Класс тестирования присвоения пользователю роли light_agent.
|
||||
|
||||
В тестах используется @patch('main.extra_func.zenpy') Mock для работы API Zendesk, а также
|
||||
@patch('main.requester.TicketListRequester.get_tickets_list_for_user', side_effect=[[]]), предоставляющий пустой
|
||||
список в качестве списка тикетов пользователя.
|
||||
"""
|
||||
|
||||
@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):
|
||||
def test_hand_over_redirect(self, _zenpy_mock: Mock, _user_tickets_mock: Mock) -> None:
|
||||
"""
|
||||
Функция тестирования переадресации инженера на рабочую страницу, после сдачи прав.
|
||||
|
||||
:param _zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
:param _user_tickets_mock: Mock, заменяющий список тикетов пользователя на пустой список.
|
||||
"""
|
||||
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]))
|
||||
@ -204,7 +292,15 @@ class MakeLightAgentTestCase(UsersBaseTestCase):
|
||||
|
||||
@patch('main.requester.TicketListRequester.get_tickets_list_for_user', side_effect=[[]])
|
||||
@patch('main.extra_func.zenpy')
|
||||
def test_engineer_make_light_agent_no_tickets(self, zenpy_mock, _user_tickets_mock):
|
||||
def test_engineer_make_light_agent_no_tickets(self, zenpy_mock: Mock, _user_tickets_mock: Mock) -> None:
|
||||
"""
|
||||
Функция тестирования назначения инженера легким агентом, в случае, когда у него в работе нет тикетов.
|
||||
|
||||
Проверяет назначение роли light_agent в Zendesk.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
:param _user_tickets_mock: Mock, заменяющий список тикетов пользователя на пустой список.
|
||||
"""
|
||||
self.engineer_client.post(reverse_lazy('work_hand_over'))
|
||||
self.assertEqual(zenpy_mock.update_user.call_args[0][0].custom_role_id, sets.ZENDESK_ROLES['light_agent'])
|
||||
|
||||
@ -212,7 +308,18 @@ class MakeLightAgentTestCase(UsersBaseTestCase):
|
||||
[Mock(id=1, status='solved'), Mock(id=2, status='open'), Mock(id=3, status='open')]
|
||||
])
|
||||
@patch('main.extra_func.zenpy')
|
||||
def test_engineer_make_light_agent_with_tickets(self, zenpy_mock, _user_tickets_mock):
|
||||
def test_engineer_make_light_agent_with_tickets(self, zenpy_mock: Mock, _user_tickets_mock: Mock) -> None:
|
||||
"""
|
||||
Функция тестирования назначения инженера легким агентом, в случае, когда у него в работе есть тикеты.
|
||||
|
||||
Для тестирования принимается, что в работе у инженера находится 3 тикета, один в состоянии: решен,
|
||||
два в состоянии: открыт.
|
||||
Проверяет распределение тикетов (поместить в решенные или назначить нового ответственного),
|
||||
а также назначение роли light_agent в Zendesk.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
:param _user_tickets_mock: Mock, заменяющий список тикетов пользователя на пустой список.
|
||||
"""
|
||||
zenpy_mock.solved_tickets_user_id = Mock()
|
||||
self.engineer_client.post(reverse_lazy('work_hand_over'))
|
||||
|
||||
@ -224,7 +331,15 @@ class MakeLightAgentTestCase(UsersBaseTestCase):
|
||||
|
||||
@patch('main.requester.TicketListRequester.get_tickets_list_for_user', side_effect=[[]])
|
||||
@patch('main.extra_func.zenpy')
|
||||
def test_admin_make_light_agent_no_tickets(self, zenpy_mock, _user_tickets_mock):
|
||||
def test_admin_make_light_agent_no_tickets(self, zenpy_mock: Mock, _user_tickets_mock: Mock) -> None:
|
||||
"""
|
||||
Функция тестирования назначения администратора на роль легкого агента.
|
||||
|
||||
Проверяет назначение роли light_agent в Zendesk.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
:param _user_tickets_mock: Mock, заменяющий список тикетов пользователя на пустой список.
|
||||
"""
|
||||
self.admin_client.post(reverse_lazy('work_hand_over'))
|
||||
self.assertEqual(zenpy_mock.update_user.call_args[0][0].custom_role_id, sets.ZENDESK_ROLES['light_agent'])
|
||||
|
||||
@ -232,7 +347,18 @@ class MakeLightAgentTestCase(UsersBaseTestCase):
|
||||
[Mock(id=1, status='solved'), Mock(id=2, status='open'), Mock(id=3, status='open')]
|
||||
])
|
||||
@patch('main.extra_func.zenpy')
|
||||
def test_admin_make_light_agent_with_tickets(self, zenpy_mock, _user_tickets_mock):
|
||||
def test_admin_make_light_agent_with_tickets(self, zenpy_mock: zenpy, _user_tickets_mock: list) -> None:
|
||||
"""
|
||||
Функция тестирования назначения администратора легким агентом, в случае, когда у него в работе есть тикеты.
|
||||
|
||||
Для тестирования принимается, что в работе находится 3 тикета, один в состоянии: решен,
|
||||
два в состоянии: открыт.
|
||||
Проверяет распределение тикетов (поместить в решенные или назначить нового ответственного),
|
||||
а также назначение роли light_agent в Zendesk.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
:param _user_tickets_mock: Mock, заменяющий список тикетов пользователя на пустой список.
|
||||
"""
|
||||
zenpy_mock.solved_tickets_user_id = Mock()
|
||||
self.admin_client.post(reverse_lazy('work_hand_over'))
|
||||
|
||||
@ -244,19 +370,33 @@ class MakeLightAgentTestCase(UsersBaseTestCase):
|
||||
|
||||
@patch('main.requester.TicketListRequester.get_tickets_list_for_user', side_effect=[[]])
|
||||
@patch('main.extra_func.zenpy')
|
||||
def test_light_agent_make_light_agent(self, zenpy_mock, _user_tickets_mock):
|
||||
def test_light_agent_make_light_agent(self, zenpy_mock: Mock, _user_tickets_mock: Mock) -> None:
|
||||
"""
|
||||
Функция тестирования назначения легкого агента на роль легкого агента.
|
||||
|
||||
Проверяет назначение роли light_agent в Zendesk.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
:param _user_tickets_mock: Mock, заменяющий список тикетов пользователя на пустой список.
|
||||
"""
|
||||
self.agent_client.post(reverse_lazy('work_hand_over'))
|
||||
self.assertEqual(zenpy_mock.update_user.call_args[0][0].custom_role_id, sets.ZENDESK_ROLES['light_agent'])
|
||||
|
||||
@patch('main.requester.TicketListRequester.get_tickets_list_for_user', side_effect=[[]])
|
||||
@patch('main.extra_func.zenpy')
|
||||
def test_control_page_make_light_agent_one(self, zenpy_mock, _user_tickets_mock):
|
||||
def test_control_page_make_light_agent_one(self, zenpy_mock: Mock, _user_tickets_mock: Mock) -> None:
|
||||
"""
|
||||
Функция тестирования назначения администратором одного легкого агента на странице "Управление".
|
||||
|
||||
Проверяет обновление администратором роли пользователя с engineer на light_agent.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
:param _user_tickets_mock: Mock, заменяющий список тикетов пользователя на пустой список.
|
||||
"""
|
||||
self.admin_client.post(
|
||||
reverse_lazy('control'),
|
||||
data={
|
||||
'users': [get_user_model().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]
|
||||
@ -265,7 +405,16 @@ class MakeLightAgentTestCase(UsersBaseTestCase):
|
||||
|
||||
@patch('main.requester.TicketListRequester.get_tickets_list_for_user', side_effect=[[], []])
|
||||
@patch('main.extra_func.zenpy')
|
||||
def test_control_page_make_light_agent_many(self, zenpy_mock, _user_tickets_mock):
|
||||
def test_control_page_make_light_agent_many(self, zenpy_mock: Mock, _user_tickets_mock: Mock) -> None:
|
||||
"""
|
||||
Функция тестирования назначения администратором нескольких легких агентов на странице "Управление".
|
||||
|
||||
Проверяет обновление администратором ролей двух пользователей с engineer на light_agent.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
:param _user_tickets_mock: Mock, заменяющий список тикетов пользователя на пустой список.
|
||||
"""
|
||||
|
||||
self.admin_client.post(
|
||||
reverse_lazy('control'),
|
||||
data={
|
||||
@ -285,27 +434,30 @@ class MakeLightAgentTestCase(UsersBaseTestCase):
|
||||
|
||||
class PasswordResetTestCase(UsersBaseTestCase):
|
||||
"""
|
||||
Класс тестов сброса пароля.
|
||||
Класс тестирования сброса пароля.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Предустановленные значения для проведения тестов.
|
||||
"""
|
||||
super().setUp()
|
||||
self.email_backend = 'django.core.mail.backends.locmem.EmailBackend'
|
||||
|
||||
def test_redirect(self):
|
||||
def test_redirect(self) -> None:
|
||||
"""
|
||||
Функция проверки переадресации на страницу уведомления о сбросе пароля на email.
|
||||
Функция тестирования успешной смены пароля.
|
||||
|
||||
Проверяется переадресация на страницу завершения смены пароля, в случае, когда пользователь существует и на его
|
||||
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):
|
||||
def test_send_email(self) -> None:
|
||||
"""
|
||||
Функция проверки содержания и отправки письма для установки пароля.
|
||||
Функция тестирования отправки email для сброса пароля.
|
||||
|
||||
Проверяет наличие отправленного письма, и его содержание, сверяет email адресата с email пользователя.
|
||||
"""
|
||||
with self.settings(EMAIL_BACKEND=self.email_backend):
|
||||
response: HttpResponseRedirect = \
|
||||
@ -321,25 +473,25 @@ class PasswordResetTestCase(UsersBaseTestCase):
|
||||
correct_body = render_to_string('registration/password_reset_email.html', email_context, response.request)
|
||||
self.assertEqual(mail.outbox[0].body, correct_body)
|
||||
|
||||
def test_email_invalid(self):
|
||||
def test_email_invalid(self) -> None:
|
||||
"""
|
||||
Функция проверки уведомления клиента о некорректности введенного email.
|
||||
Функция тестирования попытки смены пароля с некорректным 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):
|
||||
def test_user_does_not_exist(self) -> None:
|
||||
"""
|
||||
Функция корректности отработки неверно введенного email.
|
||||
Функция тестирования попытки смены пароля с 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)
|
||||
@ -349,25 +501,27 @@ class PasswordChangeTestCase(UsersBaseTestCase):
|
||||
"""
|
||||
Класс тестирования смены пароля.
|
||||
"""
|
||||
def setUp(self):
|
||||
"""
|
||||
Предустановленные значения для проведения тестов.
|
||||
"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.set_password()
|
||||
|
||||
def set_password(self):
|
||||
def set_password(self) -> None:
|
||||
"""
|
||||
Пароль, сформированный для тестирования.
|
||||
Функция предустанавливает тестовому пользователю с ролью light_agent пароль 'ImpossiblyHardPassword' и создает
|
||||
клиента с соответствующими данным для тестирования.
|
||||
"""
|
||||
user: get_user_model() = get_user_model().objects.get(email=self.light_agent)
|
||||
user = get_user_model().objects.get(email=self.light_agent)
|
||||
user.set_password('ImpossiblyHardPassword')
|
||||
user.save()
|
||||
self.agent_client.force_login(get_user_model().objects.get(email=self.light_agent))
|
||||
|
||||
def test_change_successful(self):
|
||||
def test_change_successful(self) -> None:
|
||||
"""
|
||||
Функция тестирования успешного изменения пароля.
|
||||
Функция тестирования успешной смены пароля.
|
||||
|
||||
Проверяет установку нового пароля пользователю при вводе корректных данных: старый пароль, новый пароль
|
||||
(2 раза).
|
||||
"""
|
||||
self.agent_client.post(
|
||||
reverse_lazy('password_change'),
|
||||
@ -380,9 +534,11 @@ class PasswordChangeTestCase(UsersBaseTestCase):
|
||||
user = get_user_model().objects.get(email=self.light_agent)
|
||||
self.assertTrue(user.check_password('EasyPassword'))
|
||||
|
||||
def test_invalid_old_password(self):
|
||||
def test_invalid_old_password(self) -> None:
|
||||
"""
|
||||
Функция тестирования отработки неверно введенного старого пароля при смене.
|
||||
Функция тестирования смены пароля, при неверном вводе старого пароля.
|
||||
|
||||
Проверяет текст уведомления пользователя 'Ваш старый пароль введен неправильно'.
|
||||
"""
|
||||
with translation.override('ru'):
|
||||
resp = self.agent_client.post(
|
||||
@ -395,9 +551,11 @@ class PasswordChangeTestCase(UsersBaseTestCase):
|
||||
)
|
||||
self.assertContains(resp, 'Ваш старый пароль введен неправильно', count=1, status_code=200)
|
||||
|
||||
def test_different_new_passwords(self):
|
||||
def test_different_new_passwords(self) -> None:
|
||||
"""
|
||||
Функция тестирования случая с вводом двух разных новых паролей.
|
||||
Функция тестирования смены пароля, при вводе не совпадающих новых паролей.
|
||||
|
||||
Проверяет текст уведомления пользователя 'Введенные пароли не совпадают'.
|
||||
"""
|
||||
with translation.override('ru'):
|
||||
resp = self.agent_client.post(
|
||||
@ -412,7 +570,9 @@ class PasswordChangeTestCase(UsersBaseTestCase):
|
||||
|
||||
def test_invalid_new_password1(self):
|
||||
"""
|
||||
Функция тестирования случая с неправильно подобранным новым паролем (слишком короткий).
|
||||
Функция тестирования попытки смены пароля, когда новый пароль не соответствует требованиям: слишком короткий.
|
||||
|
||||
Проверяет текст уведомления пользователя 'Введённый пароль слишком короткий'.
|
||||
"""
|
||||
with translation.override('ru'):
|
||||
resp = self.agent_client.post(
|
||||
@ -425,9 +585,12 @@ class PasswordChangeTestCase(UsersBaseTestCase):
|
||||
)
|
||||
self.assertContains(resp, 'Введённый пароль слишком короткий', count=1, status_code=200)
|
||||
|
||||
def test_invalid_new_password2(self):
|
||||
def test_invalid_new_password2(self) -> None:
|
||||
"""
|
||||
Функция тестирования случая с неправильно подобранным новым паролем (употребляются только цифры).
|
||||
Функция тестирования попытки смены пароля, когда новый пароль не соответствует требованиям: состоит
|
||||
только из цифр.
|
||||
|
||||
Проверяет текст уведомления пользователя 'Введённый пароль состоит только из цифр'.
|
||||
"""
|
||||
with translation.override('ru'):
|
||||
resp = self.agent_client.post(
|
||||
@ -442,7 +605,10 @@ class PasswordChangeTestCase(UsersBaseTestCase):
|
||||
|
||||
def test_invalid_new_password3(self):
|
||||
"""
|
||||
Функция тестирования случая с неправильно подобранным новым паролем (совпадает с именем пользователя).
|
||||
Функция тестирования попытки смены пароля, когда новый пароль не соответствует требованиям: аналогичен имени
|
||||
пользователя.
|
||||
|
||||
Проверяет текст уведомления пользователя 'Введённый пароль слишком похож на имя пользователя'.
|
||||
"""
|
||||
with translation.override('ru'):
|
||||
resp = self.agent_client.post(
|
||||
@ -459,26 +625,40 @@ class PasswordChangeTestCase(UsersBaseTestCase):
|
||||
class GetTicketsTestCase(UsersBaseTestCase):
|
||||
"""
|
||||
Класс тестов для проверки функции получения тикетов.
|
||||
|
||||
В тестах используются @patch('main.views.zenpy.get_user') и @patch('main.views.zenpy.get_user')
|
||||
для работы с API Zendesk.
|
||||
"""
|
||||
|
||||
@patch('main.views.zenpy.get_user')
|
||||
@patch('main.extra_func.zenpy')
|
||||
def test_redirect(self, _zenpy_mock, get_user_mock):
|
||||
def test_redirect(self, _zenpy_mock: Mock, get_user_mock: Mock) -> None:
|
||||
"""
|
||||
Функция проверки переадресации пользователя на рабочую страницу.
|
||||
|
||||
Проверяет редирект на рабочую страницу, в случае, когда пользователь с правами инженера заполняет форму
|
||||
принятия тикетов в работу.
|
||||
|
||||
:param _zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
:param get_user_mock: Mock объекта zenpy_user.
|
||||
"""
|
||||
get_user_mock.return_value = Mock()
|
||||
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')
|
||||
def test_take_one_ticket(self, group_tickets_mock, zenpy_mock):
|
||||
def test_take_one_ticket(self, group_tickets_mock: Mock, zenpy_mock: Mock) -> None:
|
||||
"""
|
||||
Функция проверки назначения одного тикета на engineer.
|
||||
|
||||
Проверяет соответствие ответственного за тикет объекта tickets и тестового клиента правами инженера,
|
||||
направившего запрос на назначение одного тикета.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
:param group_tickets_mock: Mock списка не назначенных и нерешенных тикетов группы.
|
||||
"""
|
||||
group_tickets_mock.return_value = [Mock()]
|
||||
zenpy_mock.get_user.return_value = Mock(role='agent', custom_role_id=sets.ZENDESK_ROLES['engineer'])
|
||||
@ -488,9 +668,15 @@ class GetTicketsTestCase(UsersBaseTestCase):
|
||||
|
||||
@patch('main.views.get_tickets_list_for_group')
|
||||
@patch('main.views.zenpy')
|
||||
def test_take_many_tickets(self, zenpy_mock, group_tickets_mock):
|
||||
def test_take_many_tickets(self, zenpy_mock: Mock, group_tickets_mock: Mock) -> None:
|
||||
"""
|
||||
Функция проверки назначения нескольких тикетов на engineer.
|
||||
|
||||
Проверяет соответствие ответственного за тикеты объекта tickets и тестового клиента правами инженера,
|
||||
направившего запрос на назначение трех тикетов.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
:param group_tickets_mock: Mock списка не назначенных и нерешенных тикетов группы.
|
||||
"""
|
||||
group_tickets_mock.return_value = [Mock()] * 3
|
||||
zenpy_mock.get_user.return_value = Mock(role='agent', custom_role_id=sets.ZENDESK_ROLES['engineer'])
|
||||
@ -501,9 +687,12 @@ class GetTicketsTestCase(UsersBaseTestCase):
|
||||
|
||||
@patch('main.views.zenpy.get_user')
|
||||
@patch('main.views.zenpy')
|
||||
def test_light_agent_take_ticket(self, zenpy_mock, get_user_mock):
|
||||
def test_light_agent_take_ticket(self, zenpy_mock: Mock, get_user_mock: Mock) -> None:
|
||||
"""
|
||||
Функция проверки попытки назначения тикета на light_agent.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
:param get_user_mock: Mock объекта zenpy_user.
|
||||
"""
|
||||
get_user_mock.return_value = Mock(role='agent', custom_role_id=sets.ZENDESK_ROLES['light_agent'])
|
||||
self.agent_client.post(reverse('work_get_tickets'), data={'count_tickets': 3})
|
||||
@ -512,9 +701,14 @@ class GetTicketsTestCase(UsersBaseTestCase):
|
||||
|
||||
@patch('main.views.zenpy')
|
||||
@patch('main.views.get_tickets_list_for_group')
|
||||
def test_take_zero_tickets(self, tickets_mock, zenpy_mock):
|
||||
def test_take_zero_tickets(self, tickets_mock: Mock, zenpy_mock: Mock) -> None:
|
||||
"""
|
||||
Функция проверки попытки назначения нуля тикета на engineer.
|
||||
Функция проверки попытки назначения нулевого количества тикетов.
|
||||
|
||||
Проверяет, что список тикетов остался пустым.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
:param tickets_mock: Mock списка тикетов - возвращает пустой список.
|
||||
"""
|
||||
tickets_mock.return_value = [Mock()] * 3
|
||||
zenpy_mock.get_user.return_value = Mock(role='agent', custom_role_id=sets.ZENDESK_ROLES['engineer'])
|
||||
@ -524,9 +718,15 @@ class GetTicketsTestCase(UsersBaseTestCase):
|
||||
|
||||
@patch('main.views.get_tickets_list_for_group')
|
||||
@patch('main.views.zenpy')
|
||||
def test_take_invalid_count_tickets(self, zenpy_mock, group_tickets_mock):
|
||||
def test_take_invalid_count_tickets(self, zenpy_mock: Mock, group_tickets_mock: Mock) -> None:
|
||||
"""
|
||||
Функция проверки попытки назначения нуля тикетов на engineer.
|
||||
Функция проверки попытки назначения некорректного количества тикетов (введении в форму назначения тикетов
|
||||
не числового значения, а строки).
|
||||
|
||||
Проверяет, отсутствие списка тикетов.
|
||||
|
||||
:param zenpy_mock: Mock объекта zenpy для функций, работающих с API Zendesk.
|
||||
:param group_tickets_mock: Mock списка не назначенных и нерешенных тикетов группы.
|
||||
"""
|
||||
group_tickets_mock.return_value = [Mock()] * 3
|
||||
zenpy_mock.get_user.return_value = Mock(role='agent', custom_role_id=sets.ZENDESK_ROLES['engineer'])
|
||||
@ -538,12 +738,25 @@ class GetTicketsTestCase(UsersBaseTestCase):
|
||||
class ProfileTestCase(TestCase):
|
||||
"""
|
||||
Класс тестов для проверки синхронизации профиля пользователя.
|
||||
|
||||
Для тестов используются фикстуры тестовых пользователей (profile.json).
|
||||
"""
|
||||
fixtures = ['fixtures/profile.json']
|
||||
|
||||
def setUp(self):
|
||||
def setUp(self) -> None:
|
||||
"""
|
||||
Предустановленные значения для проведения тестов.
|
||||
Функция предустановки значений переменных.
|
||||
|
||||
Добавляем email тестовых пользователей Zendesk и создаем клиентов для тестов.
|
||||
|
||||
:param zendesk_agent_email: email тестового пользователя с правами light_agent
|
||||
:type zendesk_agent_email: :class:`str`
|
||||
:param zendesk_admin_email: email тестового пользователя с правами admin
|
||||
:type zendesk_admin_email: :class:`str`
|
||||
:param client: клиент, залогиненный как пользователь с email zendesk_agent_email
|
||||
:type client: :class:`django.test.client.Client`
|
||||
:param admin_client: клиент, залогиненный как пользователь с zendesk_admin_email
|
||||
:type admin_client: :class:`django.test.client.Client`
|
||||
"""
|
||||
self.zendesk_agent_email = 'krav-88@mail.ru'
|
||||
self.zendesk_admin_email = 'idar.sokurov.05@mail.ru'
|
||||
@ -552,32 +765,42 @@ class ProfileTestCase(TestCase):
|
||||
self.admin_client = Client()
|
||||
self.admin_client.force_login(get_user_model().objects.get(email=self.zendesk_admin_email))
|
||||
|
||||
def test_correct_username(self):
|
||||
def test_correct_username(self) -> None:
|
||||
"""
|
||||
Функция проверки синхронизации имени пользователя.
|
||||
|
||||
Проверяет соответствие имени пользователя из контекста страницы профиля имени пользователя в Zendesk.
|
||||
"""
|
||||
resp = self.client.get(reverse('profile'))
|
||||
self.assertEqual(resp.context['profile'].name, zenpy.get_user(self.zendesk_agent_email).name)
|
||||
|
||||
def test_correct_email(self):
|
||||
def test_correct_email(self) -> None:
|
||||
"""
|
||||
Функция проверки синхронизации почты пользователя.
|
||||
|
||||
Проверяет соответствие email пользователя из контекста страницы профиля email пользователя в Zendesk.
|
||||
"""
|
||||
resp = self.client.get(reverse('profile'))
|
||||
self.assertEqual(resp.context['profile'].user.email, zenpy.get_user(self.zendesk_agent_email).email)
|
||||
|
||||
def test_correct_role(self):
|
||||
def test_correct_role(self) -> None:
|
||||
"""
|
||||
Функция проверки синхронизации роли пользователя.
|
||||
|
||||
Проверяет соответствие роли пользователя из контекста страницы профиля роли пользователя в Zendesk. Проверка
|
||||
осуществляется на примере администратора и агента.
|
||||
"""
|
||||
resp = self.client.get(reverse('profile'))
|
||||
self.assertEqual(resp.context['profile'].role, zenpy.get_user(self.zendesk_agent_email).role)
|
||||
resp = self.admin_client.get(reverse('profile'))
|
||||
self.assertEqual(resp.context['profile'].role, zenpy.get_user(self.zendesk_admin_email).role)
|
||||
|
||||
def test_correct_custom_role_id(self):
|
||||
def test_correct_custom_role_id(self) -> None:
|
||||
"""
|
||||
Функция проверки синхронизации рабочей роли пользователя.
|
||||
|
||||
Проверяет соответствие id рабочей роли пользователя из контекста страницы профиля id
|
||||
роли пользователя в Zendesk. Проверка осуществляется на примере администратора и агента.
|
||||
"""
|
||||
resp = self.client.get(reverse('profile'))
|
||||
user = zenpy.get_user(self.zendesk_agent_email)
|
||||
@ -586,9 +809,11 @@ class ProfileTestCase(TestCase):
|
||||
user = zenpy.get_user(self.zendesk_admin_email)
|
||||
self.assertEqual(resp.context['profile'].custom_role_id, user.custom_role_id if user.custom_role_id else 0)
|
||||
|
||||
def test_correct_image(self):
|
||||
def test_correct_image(self) -> None:
|
||||
"""
|
||||
Функция проверки синхронизации изображения пользователя.
|
||||
|
||||
Проверяет соответствие аватарки пользователя из контекста страницы профиля аватарке пользователя в Zendesk.
|
||||
"""
|
||||
resp = self.client.get(reverse('profile'))
|
||||
user = zenpy.get_user(self.zendesk_agent_email)
|
||||
@ -596,38 +821,78 @@ class ProfileTestCase(TestCase):
|
||||
|
||||
|
||||
class LoggingTestCase(UsersBaseTestCase):
|
||||
"""
|
||||
Класс тестирования процесса логгирования.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
def setUp(self) -> None:
|
||||
"""
|
||||
Функция предустановки значений переменных.
|
||||
|
||||
Определяем профили пользователей с разными ролями.
|
||||
|
||||
:param admin_profile: профиль тестового пользователя с правами admin
|
||||
:type admin_profile: :class:`Userprofile`
|
||||
:param agent_profile: профиль тестового пользователя с правами light_agent
|
||||
:type agent_profile: :class:`Userprofile`
|
||||
:param engineer_profile: профиль тестового пользователя с правами engineer
|
||||
:type engineer_profile: :class:`Userprofile`
|
||||
"""
|
||||
super().setUp()
|
||||
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():
|
||||
def get_file_output() -> str:
|
||||
"""
|
||||
Получение данных из файла логов.
|
||||
"""
|
||||
with open('logs/logs.csv', 'r') as file:
|
||||
file_output = file.readlines()[-1]
|
||||
return file_output
|
||||
|
||||
def test_engineer_with_admin(self):
|
||||
def test_engineer_with_admin(self) -> None:
|
||||
"""
|
||||
Функция проверки корректной записи лога по смене роли инженера в файл.
|
||||
|
||||
Сравнивает запись в файле и созданный лог с переданными значениями профилей инженера и администратора
|
||||
для смены прав.
|
||||
"""
|
||||
log(self.engineer_profile, self.admin_profile)
|
||||
file_output = self.get_file_output()
|
||||
self.assertEqual(file_output, f'UserForAccessTest,engineer,'
|
||||
f'{str(timezone.now().today())[:16]},ZendeskAdmin\n')
|
||||
|
||||
def test_engineer_without_admin(self):
|
||||
def test_engineer_without_admin(self) -> None:
|
||||
"""
|
||||
Функция проверки корректной записи лога по смене роли инженера в файл без указания администратора.
|
||||
|
||||
Сравнивает запись в файле и созданный лог с переданным значением профиля инженера для смены прав.
|
||||
"""
|
||||
log(self.engineer_profile)
|
||||
file_output = self.get_file_output()
|
||||
self.assertEqual(file_output, f'UserForAccessTest,engineer,'
|
||||
f'{str(timezone.now().today())[:16]},UserForAccessTest\n')
|
||||
|
||||
def test_light_agent_with_admin(self):
|
||||
def test_light_agent_with_admin(self) -> None:
|
||||
"""
|
||||
Функция проверки корректной записи лога по смене роли агента в файл.
|
||||
|
||||
Сравнивает запись в файле и созданный лог с переданными значениями профилей агента и администратора
|
||||
для смены прав.
|
||||
"""
|
||||
log(self.agent_profile, self.admin_profile)
|
||||
file_output = self.get_file_output()
|
||||
self.assertEqual(file_output, f'UserForAccessTest,light_agent,'
|
||||
f'{str(timezone.now().today())[:16]},ZendeskAdmin\n')
|
||||
|
||||
def test_light_agent_without_admin(self):
|
||||
def test_light_agent_without_admin(self) -> None:
|
||||
"""
|
||||
Функция проверки корректной записи лога по смене роли агента в файл без указания администратора.
|
||||
|
||||
Сравнивает запись в файле и созданный лог с переданным значением профиля агента для смены прав.
|
||||
"""
|
||||
log(self.agent_profile)
|
||||
file_output = self.get_file_output()
|
||||
self.assertEqual(file_output, f'UserForAccessTest,light_agent,'
|
||||
|
@ -61,7 +61,7 @@ def setup_context(**kwargs) -> Dict[str, Any]:
|
||||
|
||||
class CustomRegistrationView(RegistrationView):
|
||||
"""
|
||||
Отображение и логика работы страницы регистрации пользователя.
|
||||
Класс отображения и логики работы страницы регистрации пользователя.
|
||||
|
||||
:param form_class: Форма, которую необходимо заполнить для регистрации
|
||||
:type form_class: :class:`forms.CustomRegistrationForm`
|
||||
@ -86,9 +86,12 @@ class CustomRegistrationView(RegistrationView):
|
||||
def register(self, form: CustomRegistrationForm) -> Optional[get_user_model()]:
|
||||
"""
|
||||
Функция регистрации пользователя.
|
||||
1. Ввод email пользователя, указанный на Zendesk
|
||||
|
||||
1. Ввод email пользователя, указанный на Zendesk.
|
||||
|
||||
2. В случае если пользователь с данным паролем зарегистрирован на Zendesk и относится к организации SYSTEM,
|
||||
происходит сброс ссылки с установлением пароля на указанный email
|
||||
происходит сброс ссылки с установлением пароля на указанный email.
|
||||
|
||||
3. Создается пользователь class User, а также его профиль.
|
||||
|
||||
:param form: Email пользователя на Zendesk
|
||||
@ -133,7 +136,7 @@ class CustomRegistrationView(RegistrationView):
|
||||
"""
|
||||
Функция дает разрешение на просмотр страница администратора, если пользователь имеет роль admin.
|
||||
|
||||
:param user: авторизованный пользователь (получает разрешение, имея роль "admin")
|
||||
:param user: Авторизованный пользователь (получает разрешение, имея роль "admin")
|
||||
"""
|
||||
if user.userprofile.role == 'admin':
|
||||
content_type = ContentType.objects.get_for_model(UserProfile)
|
||||
@ -148,8 +151,8 @@ class CustomRegistrationView(RegistrationView):
|
||||
Функция возвращает url-адрес страницы, куда нужно перейти после успешной/не успешной регистрации.
|
||||
Используется самой django-registration.
|
||||
|
||||
:param user: пользователь, пытающийся зарегистрироваться
|
||||
:return: адресация на страницу успешной регистрации
|
||||
:param user: Пользователь, пытающийся зарегистрироваться
|
||||
:return: Адресация на страницу успешной регистрации
|
||||
"""
|
||||
return self.urls[self.redirect_url]
|
||||
|
||||
@ -158,8 +161,8 @@ def registration_error(request: WSGIRequest) -> HttpResponse:
|
||||
"""
|
||||
Функция отображения страницы ошибки регистрации.
|
||||
|
||||
:param request: регистрация
|
||||
:return: адресация на страницу ошибки
|
||||
:param request: Регистрация
|
||||
:return: Адресация на страницу ошибки
|
||||
"""
|
||||
return render(request, 'django_registration/registration_error.html')
|
||||
|
||||
@ -169,8 +172,8 @@ def profile_page(request: WSGIRequest) -> HttpResponse:
|
||||
"""
|
||||
Функция отображения страницы профиля.
|
||||
|
||||
:param request: данные пользователя из БД
|
||||
:return: адресация на страницу пользователя
|
||||
:param request: Данные пользователя из БД
|
||||
:return: Адресация на страницу пользователя
|
||||
"""
|
||||
user_profile: UserProfile = request.user.userprofile
|
||||
update_profile(user_profile)
|
||||
@ -187,9 +190,9 @@ def work_page(request: WSGIRequest, required_id: int) -> HttpResponse:
|
||||
"""
|
||||
Функция отображения страницы "Управления правами" для текущего пользователя (login_required).
|
||||
|
||||
:param request: объект пользователя
|
||||
:param request: Объект пользователя
|
||||
:param id: id пользователя, используется для динамической адресации
|
||||
:return: адресация на страницу "Управления правами" (либо на страницу "Авторизации", если id и user.id не совпадают
|
||||
:return: Адресация на страницу "Управления правами" (либо на страницу "Авторизации", если id и user.id не совпадают
|
||||
"""
|
||||
users = get_users_list()
|
||||
if request.user.id == required_id:
|
||||
@ -227,8 +230,8 @@ def work_hand_over(request: WSGIRequest) -> HttpResponseRedirect:
|
||||
"""
|
||||
Функция позволяет текущему пользователю сдать права, а именно сменить в Zendesk роль с "engineer" на "light_agent"
|
||||
|
||||
:param request: данные текущего пользователя (login_required)
|
||||
:return: перезагрузка текущей страницы после выполнения смены роли
|
||||
:param request: Данные текущего пользователя (login_required)
|
||||
:return: Перезагрузка текущей страницы после выполнения смены роли
|
||||
"""
|
||||
make_light_agent(request.user.userprofile, request.user)
|
||||
return set_session_params_for_work_page(request)
|
||||
@ -240,8 +243,8 @@ def work_become_engineer(request: WSGIRequest) -> HttpResponseRedirect:
|
||||
Функция позволяет текущему пользователю получить права, а именно сменить в Zendesk роль с "light_agent"
|
||||
на "engineer".
|
||||
|
||||
:param request: данные текущего пользователя (login_required)
|
||||
:return: перезагрузка текущей страницы после выполнения смены роли
|
||||
:param request: Данные текущего пользователя (login_required)
|
||||
:return: Перезагрузка текущей страницы после выполнения смены роли
|
||||
"""
|
||||
make_engineer(request.user.userprofile, request.user)
|
||||
return set_session_params_for_work_page(request)
|
||||
@ -250,9 +253,10 @@ def work_become_engineer(request: WSGIRequest) -> HttpResponseRedirect:
|
||||
@login_required()
|
||||
def work_get_tickets(request: WSGIRequest) -> HttpResponse:
|
||||
"""
|
||||
Функция получения тикетов в работу.
|
||||
|
||||
:param request:
|
||||
:return:
|
||||
:param request: Запрос на принятие тикетов в работу
|
||||
:return: Перезагрузка рабочей страницы
|
||||
"""
|
||||
zenpy_user = zenpy.get_user(request.user.email)
|
||||
|
||||
@ -289,6 +293,8 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageM
|
||||
:type form_class: :class:`forms.AdminPageUsersForm`
|
||||
:param success_url: Адрес страницы администратора
|
||||
:type success_url: :class:`HttpResponseRedirect`
|
||||
:param success_message: Уведомление об изменении прав
|
||||
:type success_url: :class:`str`
|
||||
"""
|
||||
permission_required = 'main.has_control_access'
|
||||
template_name = 'pages/adm_ruleset.html'
|
||||
@ -333,7 +339,12 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageM
|
||||
|
||||
class CustomLoginView(LoginView):
|
||||
"""
|
||||
Отображение страницы авторизации пользователя
|
||||
Класс отображения страницы авторизации пользователя.
|
||||
|
||||
:param extra_context: Добавление в контекст статус пользователя "залогинен"
|
||||
:type extra_context: :class:`dict`
|
||||
:param form_class: Форма страницы авторизации
|
||||
:type form_class: :class: forms.CustomAuthenticationForm
|
||||
"""
|
||||
extra_context = setup_context(login_lit=True)
|
||||
form_class = CustomAuthenticationForm
|
||||
@ -353,7 +364,8 @@ class UsersViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
|
||||
def list(self, request: WSGIRequest, *args, **kwargs) -> Response:
|
||||
"""
|
||||
Функция возвращает список пользователей, список пользователей Zendesk, количество engineers и light-agents.
|
||||
Функция возвращает список пользователей Zendesk, количество engineers и light-agents.
|
||||
|
||||
:param request: Запрос
|
||||
:param args: Аргументы
|
||||
:param kwargs: Параметры
|
||||
@ -376,6 +388,7 @@ class UsersViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
def choose_users(zendesk: list, model: list) -> list:
|
||||
"""
|
||||
Функция формирует список пользователей, которые не зарегистрированы у нас.
|
||||
|
||||
:param zendesk: Список пользователей Zendesk
|
||||
:param model: Список пользователей (модель Userprofile)
|
||||
:return: Список
|
||||
@ -389,7 +402,8 @@ class UsersViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
@staticmethod
|
||||
def get_zendesk_users(users: list) -> list:
|
||||
"""
|
||||
Получение списка пользователей Zendesk, не являющихся админами.
|
||||
Функция получения списка пользователей Zendesk, не являющихся админами.
|
||||
|
||||
:param users: Список пользователей
|
||||
:return: Список пользователей, не являющимися администраторами.
|
||||
"""
|
||||
@ -406,8 +420,8 @@ def statistic_page(request: WSGIRequest) -> HttpResponse:
|
||||
"""
|
||||
Функция отображения страницы статистики (для "superuser").
|
||||
|
||||
:param request: данные о пользователе: email, время и интервал работы. Данные получаем через forms.StatisticForm
|
||||
:return: адресация на страницу статистики
|
||||
:param request: Данные о пользователе: email, время и интервал работы. Данные получаем через forms.StatisticForm
|
||||
:return: Адресация на страницу статистики
|
||||
"""
|
||||
|
||||
# if not request.user.has_perm('main.has_control_access'):
|
||||
@ -439,5 +453,8 @@ def statistic_page(request: WSGIRequest) -> HttpResponse:
|
||||
context['form'] = form
|
||||
return render(request, 'pages/statistic.html', context)
|
||||
|
||||
def registration_failed(request):
|
||||
def registration_failed(request: WSGIRequest) -> HttpResponse:
|
||||
"""
|
||||
Функция отображения страницы "Регистрация закрыта".
|
||||
"""
|
||||
return render(request, 'pages/registration_failed.html')
|
||||
|
@ -32,7 +32,7 @@ class ZendeskAdmin:
|
||||
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:
|
||||
def update_user(self, user: ZenpyUser) -> None:
|
||||
"""
|
||||
Функция сохраняет изменение пользователя в Zendesk.
|
||||
|
||||
@ -40,7 +40,7 @@ class ZendeskAdmin:
|
||||
"""
|
||||
self.admin.users.update(user)
|
||||
|
||||
def update_tickets(self, tickets: List[ZenpyTicket]):
|
||||
def update_tickets(self, tickets: List[ZenpyTicket]) -> None:
|
||||
"""
|
||||
Функция сохраняет изменение тикетов в Zendesk.
|
||||
|
||||
@ -79,7 +79,7 @@ class ZendeskAdmin:
|
||||
return group
|
||||
return None
|
||||
|
||||
def get_user_org(self, email: str) -> str:
|
||||
def get_user_org(self, email: str) -> Optional[str]:
|
||||
"""
|
||||
Функция возвращает организацию, к которой относится пользователь по его email.
|
||||
|
||||
@ -96,7 +96,6 @@ class ZendeskAdmin:
|
||||
:raise: :class:`ValueError`: исключение, вызываемое если email не введен в env
|
||||
:raise: :class:`APIException`: исключение, вызываемое если пользователя с таким email не существует в Zendesk
|
||||
"""
|
||||
|
||||
if self.credentials.get('email') is None:
|
||||
raise ValueError('access_controller email not in env')
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user