Merge branch 'develop' into feature/react_test
# Conflicts: # README.md # main/tests.py
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
from django.contrib import admin
|
||||
"""
|
||||
Встроенный файл
|
||||
"""
|
||||
|
||||
|
||||
# from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
|
||||
0
main/apiauth.py
Normal file
0
main/apiauth.py
Normal file
@@ -1,5 +1,11 @@
|
||||
"""
|
||||
Стандартный файл Django конфигурации приложения.
|
||||
"""
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MainConfig(AppConfig):
|
||||
"""
|
||||
Старт приложения
|
||||
"""
|
||||
name = 'main'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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': "Аккаунт не активен.",
|
||||
}
|
||||
|
||||
|
||||
18
main/migrations/0018_alter_unassignedticket_ticket_id.py
Normal file
18
main/migrations/0018_alter_unassignedticket_ticket_id.py
Normal 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='Номер тикета, для которого сняли ответственного'),
|
||||
),
|
||||
]
|
||||
@@ -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='Статус тикета')
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
169
main/tests.py
169
main/tests.py
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
116
main/views.py
116
main/views.py
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user