access-controller/main/extra_func.py
2021-03-09 00:53:11 +03:00

358 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
from datetime import timedelta, datetime, date
from django.contrib.auth.models import User
from zenpy import Zenpy
from zenpy.lib.exception import APIException
from access_controller.settings import ZENDESK_ROLES as ROLES
from main.models import UserProfile, RoleChangeLogs
class ZendeskAdmin:
"""
Класс **ZendeskAdmin** существует, чтобы в каждой фунциии отдельно не проверять аккаунт администратора
:param credentials: Полномочия (первым указывается учетная запись организации в Zendesk)
:type credentials: :class:`dict`
:param email: Email администратора, указанный в env
:type email: :class:`str`
:param token: Токен администратора (формируется в Zendesk, указывается в env)
:type token: :class:`str`
:param password: Пароль администратора, указанный в env
:type password: :class:`str`
"""
credentials: dict = {
'subdomain': 'ngenix1612197338'
}
email: str = os.getenv('ACCESS_CONTROLLER_API_EMAIL')
token: str = os.getenv('ACCESS_CONTROLLER_API_TOKEN')
password: str = os.getenv('ACCESS_CONTROLLER_API_PASSWORD')
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
self.create_admin()
def check_user(self, email: str) -> bool:
"""
Функция **check_user** осуществляет проверку существования пользователя в Zendesk по email
"""
return True if self.admin.search(email, type='user') else False
def get_user_name(self, email: str) -> str:
"""
Функция **get_user_name** возвращает имя пользователя по его email
"""
user = self.admin.users.search(email).values[0]
return user.name
def get_user_role(self, email: str) -> str:
"""
Функция **get_user_role** возвращает роль пользователя по его email
"""
user = self.admin.users.search(email).values[0]
return user.role
def get_user_id(self, email: str) -> str:
"""
Функция **get_user_id** возвращает id пользователя по его email
"""
user = self.admin.users.search(email).values[0]
return user.id
def get_user_image(self, email: str) -> str:
"""
Функция **get_user_image** возвращает url-ссылку на аватар пользователя по его email
"""
user = self.admin.users.search(email).values[0]
return user.photo['content_url'] if user.photo else None
def get_user(self, email: str) -> str:
"""
Функция **get_user** возвращает пользователя (объект) по его email
:param email: email пользователя
:return: email пользователя, найденного в БД
"""
return self.admin.users.search(email).values[0]
def get_user_org(self, email: str) -> str:
"""
Функция **get_user_org** возвращает организацию, к которой относится пользователь по его email
"""
user = self.admin.users.search(email).values[0]
return user.organization.name if user.organization else None
def create_admin(self) -> Zenpy:
"""
Функция **Create_admin()** создает администратора, проверяя наличие вводимых данных в env.
:param credentials: В список полномочий администратора вносятся email, token, password из env
:type credentials: :class:`dict`
:raise: :class:`ValueError`: исключение, вызываемое если email не введен в env
:raise: :class:`APIException`: исключение, вызываемое если пользователя с таким email не существует в Zendesk
"""
if self.email is None:
raise ValueError('access_controller email not in env')
self.credentials['email'] = self.email
if self.token:
self.credentials['token'] = self.token
elif self.password:
self.credentials['password'] = self.password
else:
raise ValueError('access_controller token or password not in env')
self.admin = Zenpy(**self.credentials)
try:
self.admin.search(self.email, type='user')
except APIException:
raise ValueError('invalid access_controller`s login data')
def update_role(user_profile: UserProfile, role: str) -> UserProfile:
"""
Функция **update_role** меняет роль пользователя.
"""
zendesk = ZendeskAdmin()
user = zendesk.get_user(user_profile.user.email)
user.custom_role_id = role
zendesk.admin.users.update(user)
def make_engineer(user_profile: UserProfile) -> UserProfile:
"""
Функция **make_engineer** устанавливапет пользователю роль инженера.
"""
update_role(user_profile, ROLES['engineer'])
def make_light_agent(user_profile: UserProfile) -> UserProfile:
"""
Функция **make_light_agent** устанавливапет пользователю роль легкого агента.
"""
update_role(user_profile, ROLES['light_agent'])
def get_users_list() -> list:
"""
Функция **get_users_list** возвращает список пользователей Zendesk, относящихся к организации.
"""
zendesk = ZendeskAdmin()
admin = zendesk.get_user(zendesk.email)
org = next(zendesk.admin.users.organizations(user=admin))
return zendesk.admin.organizations.users(org)
def update_profile(user_profile: UserProfile) -> UserProfile:
"""
Функция обновляет профиль пользователя в соотвтетствии с текущим в Zendesk
"""
user = ZendeskAdmin().get_user(user_profile.user.email)
user_profile.name = user.name
user_profile.role = user.role
user_profile.image = user.photo['content_url'] if user.photo else None
user_profile.save()
def check_user_exist(email: str) -> bool:
"""
Функция проверяет, существует ли пользователь
"""
return ZendeskAdmin().check_user(email)
def get_user_organization(email: str) -> str:
"""
Функция возвращает организацию пользователя
"""
return ZendeskAdmin().get_user_org(email)
def daterange(start_date, end_date) -> list:
"""
Возвращает список дней с start_date по end_date исключая правую границу
"""
dates = []
for n in range(int((end_date - start_date).days)):
dates.append(start_date + timedelta(n))
return dates
def get_timedelta(log) -> timedelta:
"""
Возвращает объект класса timedelta, который хранит промежуток времени от начала суток до момента,
который находится в log (объект класса RoleChangeLogs)
"""
time = log.change_time.time()
time = timedelta(hours=time.hour, minutes=time.minute, seconds=time.second)
return time
def last_day_of_month(day):
"""
Возвращает последний день любого месяца
"""
next_month = day.replace(day=28) + timedelta(days=4)
return next_month - timedelta(days=next_month.day)
class StatisticData:
def __init__(self, start_date, end_date, user_email):
self.errors = list()
self.data = None
self.statistic = dict()
self.start_date = start_date
self.end_date = end_date
self.email = user_email
self._set_data()
self._set_statistic()
def use_interval(self, interval):
"""
Объединяет ключи и значения в соответствии с интервалом работы
"""
if not self.is_valid_statistic():
return None
if not (interval in ['days', 'months']):
self.errors += ['Интервал работы должен быть в днях или месяцах']
return None
stat = {}
if interval == 'months':
# Переделываем ключи под формат, как в прототипе('началоесяца - конец_месяца')
for key, value in self.get_statistic().items():
current_month_start = max(self.start_date, date(year=key.year, month=key.month, day=1))
current_month_end = min(self.end_date, last_day_of_month(date(year=key.year, month=key.month, day=1)))
index = ' - '.join([str(current_month_start), str(current_month_end)])
if stat.get(index):
stat[index] += value
else:
stat[index] = value
elif interval == 'days':
stat = self.get_statistic()
return stat
def use_display(self, display_format):
"""
Приводит данные к формату отображения, возвращает их
"""
if not self.get_statistic():
return None
if not (display_format in ['hours', 'days']):
self.errors += ['Формат отображения должен быть в часах или днях']
return None
for key, item in self.statistic.items():
if display_format == 'hours':
self.statistic[key] = item / 3600
elif display_format == 'days':
self.statistic[key] = item / 86400
return self.statistic.copy()
def pop_errors(self):
"""
Возвращает все текущие ошибки
"""
errors = self.errors.copy()
self.errors.clear()
return errors
def get_data(self):
"""
Вернуть данные
data - массив объектов RoleChangeLogs, является списком логов пользователя
"""
if self.is_valid_data():
return self.data
else:
return None
def get_statistic(self):
"""
Вернуть словарь statistic или None, если были ошибки при создании
"""
if self.is_valid_statistic():
return self.statistic.copy()
else:
return None
def is_valid_statistic(self):
"""
Были ли ошибки при создании статистики
"""
return not self.errors and self.statistic
def is_valid_data(self):
"""
Были ли ошибки при создании объекта
"""
return not self.errors
def _set_data(self):
"""
Получение списка из лог-ов в диапазоне дат start_date-end_date для пользователя с почтой email
"""
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),
).order_by('change_time')
self.errors.clear()
except User.DoesNotExist:
self.errors += ['Пользователь не найден']
def _set_statistic(self):
"""
Функция заполняет словарь, в котором ключ - дата, значение - кол-во проработанных в этот день секунд
"""
if not self.get_data():
self.errors += ['Не обнаружены изменения роли в данном промежутке']
return None
self.clear_statistic()
first_log, last_log = self.data[0], self.data[len(self.data) - 1]
# Если инженер работал ещё до начала диапазона
if int(first_log.old_role) == ROLES['engineer']:
self.fill_daterange(self.start_date, first_log.change_time.date())
self.statistic[first_log.change_time.date()] += get_timedelta(first_log).total_seconds()
# Если инженер закончил работать после диапазона
if int(last_log.new_role) == ROLES['engineer']:
self.fill_daterange(last_log.change_time.date() + timedelta(days=1), self.end_date + timedelta(days=1))
self.statistic[last_log.change_time.date()] += (timedelta(days=1) - get_timedelta(last_log)).total_seconds()
# Цикл по логам
for log_index in range(len(self.data) - 1):
if int(self.data[log_index].new_role) == ROLES['engineer']:
current_log, next_log = self.data[log_index], self.data[log_index + 1]
# Если сессия закончилась НЕ в тот же день, что и началась
if current_log.change_time.date() != next_log.change_time.date():
self.statistic[current_log.change_time.date()] += (
timedelta(days=1) - get_timedelta(current_log)).total_seconds()
self.statistic[next_log.change_time.date()] += get_timedelta(next_log).total_seconds()
# Если проработал несколько дней подряд, то заполнить эти дни по 24 часа
self.fill_daterange(current_log.change_time.date() + timedelta(days=1), next_log.change_time.date())
# Если сессия закончилась в тот же день, что и началась
else:
elapsed_time = next_log.change_time - current_log.change_time
self.statistic[current_log.change_time.date()] += elapsed_time.total_seconds()
def fill_daterange(self, first, last, val=24 * 3600):
"""
Заполение диапазона дат значением val, по умолчанию val = кол-во секунд в 1 дне
"""
for day in daterange(first, last):
self.statistic[day] = val
def clear_statistic(self):
"""
Обнуление всех дней
"""
self.fill_daterange(self.start_date, self.end_date + timedelta(days=1), 0)