Merge branch 'develop' into feature/profile_page_lookfix
This commit is contained in:
@@ -3,6 +3,8 @@ import os
|
||||
from zenpy import Zenpy
|
||||
from zenpy.lib.api_objects import User as ZenpyUser
|
||||
|
||||
from access_controller.settings import ACTRL_ZENDESK_SUBDOMAIN, ACTRL_API_EMAIL, ACTRL_API_TOKEN, ACTRL_API_PASSWORD
|
||||
|
||||
|
||||
def api_auth() -> dict:
|
||||
"""
|
||||
@@ -15,15 +17,15 @@ def api_auth() -> dict:
|
||||
:return: данные пользователя
|
||||
"""
|
||||
credentials = {
|
||||
'subdomain': 'ngenix1612197338'
|
||||
'subdomain': ACTRL_ZENDESK_SUBDOMAIN
|
||||
}
|
||||
email = os.getenv('ACCESS_CONTROLLER_API_EMAIL')
|
||||
token = os.getenv('ACCESS_CONTROLLER_API_TOKEN')
|
||||
password = os.getenv('ACCESS_CONTROLLER_API_PASSWORD')
|
||||
email = ACTRL_API_EMAIL
|
||||
token = ACTRL_API_TOKEN
|
||||
password = ACTRL_API_PASSWORD
|
||||
|
||||
if email is None:
|
||||
raise ValueError('access_controller email not in env')
|
||||
credentials['email'] = os.getenv('ACCESS_CONTROLLER_API_EMAIL')
|
||||
credentials['email'] = email
|
||||
|
||||
# prefer token, use password if token not provided
|
||||
if token:
|
||||
|
||||
@@ -1,144 +1,19 @@
|
||||
import logging
|
||||
import os
|
||||
from datetime import timedelta, datetime, date
|
||||
from typing import Optional
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.shortcuts import redirect
|
||||
from django.utils import timezone
|
||||
from zenpy import Zenpy
|
||||
from zenpy.lib.exception import APIException
|
||||
from zenpy.lib.api_objects import User as ZenpyUser, Ticket as ZenpyTicket
|
||||
from zenpy.lib.generator import SearchResultGenerator
|
||||
|
||||
|
||||
from access_controller.settings import ZENDESK_ROLES as ROLES, ONE_DAY, ZENDESK_GROUPS, SOLVED_TICKETS_EMAIL
|
||||
from access_controller.settings import ZENDESK_ROLES as ROLES, ONE_DAY, ACTRL_ZENDESK_SUBDOMAIN
|
||||
from main.models import UserProfile, RoleChangeLogs, UnassignedTicket, UnassignedTicketStatus
|
||||
|
||||
|
||||
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')
|
||||
|
||||
def __init__(self):
|
||||
self.create_admin()
|
||||
|
||||
def check_user(self, email: str) -> bool:
|
||||
"""
|
||||
Функция осуществляет проверку существования пользователя в Zendesk по email.
|
||||
|
||||
:param email: Email пользователя
|
||||
:return: Является ли зарегистрированным
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
Функция возвращает роль пользователя по его email.
|
||||
|
||||
:param email: Email пользователя
|
||||
:return: Роль пользователя
|
||||
"""
|
||||
user = self.admin.users.search(email).values[0]
|
||||
return user.role
|
||||
|
||||
def get_user_id(self, email: str) -> str:
|
||||
"""
|
||||
Функция возвращает id пользователя по его email
|
||||
|
||||
:param email: Email пользователя
|
||||
:return: ID пользователя
|
||||
"""
|
||||
user = self.admin.users.search(email).values[0]
|
||||
return user.id
|
||||
|
||||
def get_user_image(self, email: str) -> str:
|
||||
"""
|
||||
Функция возвращает url-ссылку на аватар пользователя по его email.
|
||||
|
||||
:param email: Email пользователя
|
||||
:return: Аватар пользователя
|
||||
"""
|
||||
user = self.admin.users.search(email).values[0]
|
||||
return user.photo['content_url'] if user.photo else None
|
||||
|
||||
def get_user(self, email: str):
|
||||
"""
|
||||
Функция возвращает пользователя (объект) по его email.
|
||||
|
||||
:param email: Email пользователя
|
||||
:return: Объект пользователя, найденного в БД
|
||||
"""
|
||||
return self.admin.users.search(email).values[0]
|
||||
|
||||
def get_group(self, name: str) -> str:
|
||||
"""
|
||||
Функция возвращает группу по названию
|
||||
|
||||
:param name: Имя пользователя
|
||||
:return: Группы пользователя (в случае отсутствия None)
|
||||
"""
|
||||
groups = self.admin.search(name, type='group')
|
||||
for group in groups:
|
||||
return group
|
||||
return None
|
||||
|
||||
def get_user_org(self, email: str) -> str:
|
||||
"""
|
||||
Функция возвращает организацию, к которой относится пользователь по его email.
|
||||
|
||||
:param email: Email пользователя
|
||||
:return: Организация пользователя
|
||||
"""
|
||||
user = self.admin.users.search(email).values[0]
|
||||
return user.organization.name if user.organization else None
|
||||
|
||||
def create_admin(self) -> None:
|
||||
"""
|
||||
Функция создает администратора, проверяя наличие вводимых данных в 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')
|
||||
from main.zendesk_admin import zenpy
|
||||
|
||||
|
||||
def update_role(user_profile: UserProfile, role: int) -> None:
|
||||
@@ -149,7 +24,7 @@ def update_role(user_profile: UserProfile, role: int) -> None:
|
||||
:param role: Новая роль
|
||||
:return: Пользователь с обновленной ролью
|
||||
"""
|
||||
zendesk = ZendeskAdmin()
|
||||
zendesk = zenpy
|
||||
user = zendesk.get_user(user_profile.user.email)
|
||||
user.custom_role_id = role
|
||||
user_profile.custom_role_id = role
|
||||
@@ -174,7 +49,8 @@ def make_light_agent(user_profile: UserProfile, who_changes: User) -> None:
|
||||
:param user_profile: Профиль пользователя
|
||||
:return: Вызов функции **update_role** с параметрами: профиль пользователя, роль "light_agent"
|
||||
"""
|
||||
tickets = get_tickets_list(user_profile.user.email)
|
||||
tickets: SearchResultGenerator = get_tickets_list(user_profile.user.email)
|
||||
ticket: ZenpyTicket
|
||||
for ticket in tickets:
|
||||
UnassignedTicket.objects.create(
|
||||
assignee=user_profile.user,
|
||||
@@ -182,19 +58,30 @@ def make_light_agent(user_profile: UserProfile, who_changes: User) -> None:
|
||||
status=UnassignedTicketStatus.SOLVED if ticket.status == 'solved' else UnassignedTicketStatus.UNASSIGNED
|
||||
)
|
||||
if ticket.status == 'solved':
|
||||
ticket.assignee = ZendeskAdmin().get_user(SOLVED_TICKETS_EMAIL)
|
||||
ticket.assignee_id = zenpy.solved_tickets_user_id
|
||||
else:
|
||||
ticket.assignee = None
|
||||
ticket.group = ZendeskAdmin().get_group(ZENDESK_GROUPS['buffer'])
|
||||
ZendeskAdmin().admin.tickets.update(ticket)
|
||||
update_role(user_profile, ROLES['light_agent'])
|
||||
ticket.group_id = zenpy.buffer_group_id
|
||||
|
||||
if tickets.count:
|
||||
zenpy.admin.tickets.update(tickets.values)
|
||||
|
||||
attempts, success = 5, False
|
||||
while not success and attempts != 0:
|
||||
try:
|
||||
update_role(user_profile, ROLES['light_agent'])
|
||||
success = True
|
||||
except APIException as e:
|
||||
attempts -= 1
|
||||
if attempts == 0:
|
||||
raise e
|
||||
|
||||
|
||||
def get_users_list() -> list:
|
||||
"""
|
||||
Функция **get_users_list** возвращает список пользователей Zendesk, относящихся к организации SYSTEM.
|
||||
"""
|
||||
zendesk = ZendeskAdmin()
|
||||
zendesk = zenpy
|
||||
|
||||
# У пользователей должна быть организация SYSTEM
|
||||
org = next(zendesk.admin.search(type='organization', name='SYSTEM'))
|
||||
@@ -206,17 +93,17 @@ def get_tickets_list(email):
|
||||
"""
|
||||
Функция возвращает список тикетов пользователя Zendesk
|
||||
"""
|
||||
return ZendeskAdmin().admin.search(assignee=email, type='ticket')
|
||||
return zenpy.admin.search(assignee=email, type='ticket')
|
||||
|
||||
|
||||
def update_profile(user_profile: UserProfile) -> UserProfile:
|
||||
def update_profile(user_profile: UserProfile):
|
||||
"""
|
||||
Функция обновляет профиль пользователя в соответствии с текущим в Zendesk.
|
||||
|
||||
:param user_profile: Профиль пользователя
|
||||
:return: Обновленный, в соответствие с текущими данными в Zendesk, профиль пользователя
|
||||
"""
|
||||
user = ZendeskAdmin().get_user(user_profile.user.email)
|
||||
user = zenpy.get_user(user_profile.user.email)
|
||||
user_profile.name = user.name
|
||||
user_profile.role = user.role
|
||||
user_profile.custom_role_id = user.custom_role_id if user.custom_role_id else 0
|
||||
@@ -231,7 +118,7 @@ def check_user_exist(email: str) -> bool:
|
||||
:param email: Email пользователя
|
||||
:return: Зарегистрирован ли пользователь в Zendesk
|
||||
"""
|
||||
return ZendeskAdmin().check_user(email)
|
||||
return zenpy.check_user(email)
|
||||
|
||||
|
||||
def get_user_organization(email: str) -> str:
|
||||
@@ -241,7 +128,7 @@ def get_user_organization(email: str) -> str:
|
||||
:param email: Email пользователя
|
||||
:return: Организация пользователя
|
||||
"""
|
||||
return ZendeskAdmin().get_user_org(email)
|
||||
return zenpy.get_user_org(email)
|
||||
|
||||
|
||||
def check_user_auth(email: str, password: str) -> bool:
|
||||
@@ -253,7 +140,7 @@ def check_user_auth(email: str, password: str) -> bool:
|
||||
creds = {
|
||||
'email': email,
|
||||
'password': password,
|
||||
'subdomain': 'ngenix1612197338',
|
||||
'subdomain': ACTRL_ZENDESK_SUBDOMAIN,
|
||||
}
|
||||
try:
|
||||
user = Zenpy(**creds)
|
||||
@@ -263,7 +150,7 @@ def check_user_auth(email: str, password: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def update_user_in_model(profile: UserProfile, zendesk_user: User):
|
||||
def update_user_in_model(profile: UserProfile, zendesk_user: ZenpyUser):
|
||||
"""
|
||||
Функция обновляет профиль пользователя при изменении данных пользователя на Zendesk.
|
||||
|
||||
@@ -435,7 +322,7 @@ class StatisticData:
|
||||
self.display = display_format
|
||||
return True
|
||||
|
||||
def get_data(self) -> list:
|
||||
def get_data(self) -> Optional[dict]:
|
||||
"""
|
||||
Функция возвращает данные - список объектов RoleChangeLogs.
|
||||
"""
|
||||
@@ -665,3 +552,13 @@ def log(user, admin=0):
|
||||
logger.addHandler(csvhandler)
|
||||
logger.setLevel('INFO')
|
||||
logger.info(users)
|
||||
|
||||
|
||||
def set_session_params_for_work_page(request, count=None, is_confirm=True):
|
||||
"""
|
||||
Функция для страницы получения прав
|
||||
Устанавливает данные сессии о успешности запроса и количестве назначенных тикетов
|
||||
"""
|
||||
request.session['is_confirm'] = is_confirm
|
||||
request.session['count_tickets'] = count
|
||||
return redirect('work', request.user.id)
|
||||
|
||||
@@ -96,12 +96,11 @@ class StatisticForm(forms.Form):
|
||||
:type range_end: :class:`django.forms.fields.DateField`
|
||||
"""
|
||||
email = forms.EmailField(
|
||||
label='Электроная почта',
|
||||
label='Электронная почта',
|
||||
widget=forms.EmailInput(
|
||||
attrs={
|
||||
'placeholder': 'example@ngenix.ru',
|
||||
'class': 'form-control',
|
||||
'style': 'background-color:#f2f2f2;'
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
@@ -81,3 +81,4 @@ 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='Статус тикета')
|
||||
|
||||
|
||||
@@ -15,21 +15,27 @@
|
||||
{% else %}
|
||||
class="btn btn-secondary"
|
||||
{% endif %}
|
||||
href="{% url 'profile' %}">Профиль</a>
|
||||
href="{% url 'profile' %}">Профиль</a>
|
||||
{% if perms.main.has_control_access %}
|
||||
<a {% if control_lit %}
|
||||
class="btn btn-primary"
|
||||
{% else %}
|
||||
class="btn btn-secondary"
|
||||
{% endif %}
|
||||
href="{% url 'control' %}">Управление</a>
|
||||
href="{% url 'control' %}">Управление</a>
|
||||
<a {% if stats_lit %}
|
||||
class="btn btn-primary"
|
||||
{% else %}
|
||||
class="btn btn-secondary"
|
||||
{% endif %}
|
||||
href="{% url 'statistic' %}">Статистика</a>
|
||||
{% else %}
|
||||
<a {% if work_lit %}
|
||||
class="btn btn-primary"
|
||||
{% else %}
|
||||
class="btn btn-secondary"
|
||||
{% endif %}
|
||||
href="{% url 'work' request.user.id %}">Запрос прав</a>
|
||||
href="{% url 'work' request.user.id %}">Запрос прав</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-secondary" href="{% url 'logout' %}">Выйти</a>
|
||||
</div>
|
||||
@@ -40,13 +46,13 @@
|
||||
{% else %}
|
||||
class="btn btn-secondary"
|
||||
{% endif %}
|
||||
href="/accounts/login">Войти</a>
|
||||
href="/accounts/login">Войти</a>
|
||||
<a {% if registration_lit %}
|
||||
class="btn btn-primary"
|
||||
{% else %}
|
||||
class="btn btn-secondary"
|
||||
{% endif %}
|
||||
href="/accounts/register">Зарегистрироваться</a>
|
||||
href="/accounts/register">Зарегистрироваться</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</nav>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<div class="mt-5">
|
||||
{% for message in messages %}
|
||||
<div
|
||||
class="alert alert-{{ message.tags }} alert-dismissible fade show p-2"
|
||||
role="alert"
|
||||
style="display: flex; align-items: center; justify-content: space-between;"
|
||||
>
|
||||
{{ message }}
|
||||
<div>
|
||||
<button type="button" class="btn btn-light p-2" data-bs-dismiss="alert" aria-label="Close">X</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -7,7 +7,8 @@
|
||||
{% block heading %}Управление{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{% static 'main/css/work.css' %}"/>
|
||||
<link rel="stylesheet" href="{% static 'main/css/work.css' %}" xmlns="http://www.w3.org/1999/html">
|
||||
<link rel="stylesheet" href="{% static 'modules/notifications/dist/notifications.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
@@ -15,15 +16,22 @@
|
||||
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
{% endblock%}
|
||||
|
||||
<script src="{% static 'modules/notifications/dist/notifications.js' %}"></script>
|
||||
<script src="{% static 'main/js/control.js'%}" type="text/babel"></script>
|
||||
<script src="{% static 'main/js/notifications.js' %}"></script>
|
||||
{% endblock%}
|
||||
{% block content %}
|
||||
<div class="container-md">
|
||||
|
||||
<div class="new-section">
|
||||
<p class="row page-description">Свободных Мест: {{ licences_remaining }}</p>
|
||||
<p class="row page-description" id="licences_remaining">Свободных Мест:</p>
|
||||
</div>
|
||||
|
||||
{% for message in messages %}
|
||||
<script>create_notification('{{message}}','','{{message.tags}}',2000)</script>
|
||||
{% endfor %}
|
||||
|
||||
{% block form %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
@@ -37,10 +45,16 @@
|
||||
<table class="table table-dark light-table">
|
||||
|
||||
<thead>
|
||||
<th>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="head-checkbox"
|
||||
/>
|
||||
</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Checked</th>
|
||||
</thead>
|
||||
<tbody id="tbody"></tbody>
|
||||
|
||||
@@ -93,9 +107,6 @@
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% include 'base/success_messages.html' %}
|
||||
</div>
|
||||
|
||||
<script src="{% static 'main/js/control.js'%}" type="text/babel"></script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
alt="Нет изображения"
|
||||
>
|
||||
</div>
|
||||
<a href="{%url 'password_change' %}">Сменить пароль</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h4><span class="badge bg-secondary text-light">Имя пользователя</span></h4> <h5><strong>{{ profile.name }}</strong></h5>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<div class="col-auto">
|
||||
{% for radio in form.interval%}
|
||||
{{ radio.tag }}
|
||||
<label class="btn btn-secondary text-primary bg-white" for="{{ radio.id_for_label }}">
|
||||
<label class="btn btn-outline-secondary" for="{{ radio.id_for_label }}">
|
||||
{{ radio.choice_label }}
|
||||
</label>
|
||||
{% endfor %}
|
||||
@@ -39,7 +39,7 @@
|
||||
<div class="col-auto">
|
||||
{% for radio in form.display_format%}
|
||||
{{ radio.tag }}
|
||||
<label class="btn btn-secondary text-primary bg-white" for="{{ radio.id_for_label }}">
|
||||
<label class="btn btn-outline-secondary" for="{{ radio.id_for_label }}">
|
||||
{{ radio.choice_label }}
|
||||
</label>
|
||||
{% endfor %}
|
||||
@@ -67,7 +67,7 @@
|
||||
</div>
|
||||
<div class="form-row text-center">
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary bg-white text-primary">Посмотреть статистику</button>
|
||||
<button type="submit" class="btn btn-outline-primary">Посмотреть статистику</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -86,9 +86,9 @@
|
||||
<table class="table table-bordered text-center text-secondary mt-5" style="background-color:#f2f2f2;">
|
||||
<thead>
|
||||
<tr>
|
||||
<td scope="col">Пользователи/Даты</td>
|
||||
<td scope="col"> </td>
|
||||
{% for date in log_stats.keys %}
|
||||
<td scope="col">{{date}}</td>
|
||||
<td scope="col">{{ date | date:'d.m' }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -96,7 +96,7 @@
|
||||
<tr>
|
||||
<td>{{ form.email.value }}</td>
|
||||
{% for time in log_stats.values %}
|
||||
<td>{{time}}</td>
|
||||
<td>{{ time | floatformat:2 }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
{% block heading %}Управление правами{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{% static 'main/css/work.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'main/css/work.css' %}" xmlns="http://www.w3.org/1999/html">
|
||||
<link rel="stylesheet" href="{% static 'modules/notifications/dist/notifications.css' %}">
|
||||
{% endblock %}
|
||||
{% block extra_scripts %}
|
||||
<script src="{% static 'modules/notifications/dist/notifications.js' %}"></script>
|
||||
<script src="{% static 'main/js/notifications.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -66,8 +71,10 @@
|
||||
<button type="submit" class="default-button">Взять тикеты в работу</button>
|
||||
</form>
|
||||
</div>
|
||||
{% for message in messages %}
|
||||
<script>create_notification('{{message}}','','{{message.tags}}',2000)</script>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% include 'base/success_messages.html' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
12
main/templates/registration/password_change_done.html
Normal file
12
main/templates/registration/password_change_done.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{% extends "base/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Пароль успешно изменен{% endblock title %}
|
||||
|
||||
{% block heading %}Пароль успешно изменен{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<h4>Ваш пароль был изменен.</h4>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
14
main/templates/registration/password_change_form.html
Normal file
14
main/templates/registration/password_change_form.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% extends "base/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Изменение пароля{% endblock title %}
|
||||
|
||||
{% block heading %}Сменить пароль{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="Сменить" class="btn btn-success">
|
||||
</form>
|
||||
{% endblock content %}
|
||||
@@ -1,3 +1,2 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
from django.test import TestCase, Client
|
||||
import access_controller.settings as sets
|
||||
|
||||
125
main/views.py
125
main/views.py
@@ -1,4 +1,5 @@
|
||||
from smtplib import SMTPException
|
||||
from typing import Dict, Any
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
@@ -11,32 +12,34 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from django.http import HttpResponseRedirect, HttpResponse
|
||||
from django.shortcuts import render, redirect, get_list_or_404
|
||||
from django.shortcuts import render, redirect
|
||||
from django.urls import reverse_lazy, reverse
|
||||
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
|
||||
from zenpy.lib.api_objects import User as ZenpyUser
|
||||
|
||||
from access_controller.settings import DEFAULT_FROM_EMAIL, ZENDESK_ROLES, ZENDESK_MAX_AGENTS
|
||||
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, \
|
||||
StatisticData, log, ZendeskAdmin
|
||||
StatisticData, log, set_session_params_for_work_page
|
||||
from main.zendesk_admin import zenpy
|
||||
from main.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, StatisticForm
|
||||
from main.serializers import ProfileSerializer, ZendeskUserSerializer
|
||||
from .models import UserProfile
|
||||
|
||||
|
||||
def setup_context(profile_lit: bool = False, control_lit: bool = False, work_lit: bool = False,
|
||||
registration_lit: bool = False, login_lit: bool = False):
|
||||
registration_lit: bool = False, login_lit: bool = False, stats_lit: bool = False) -> Dict[str, Any]:
|
||||
|
||||
context = {
|
||||
'profile_lit': profile_lit,
|
||||
'control_lit': control_lit,
|
||||
'work_lit': work_lit,
|
||||
'registration_lit': registration_lit,
|
||||
'login_lit': login_lit,
|
||||
'stats_lit': stats_lit,
|
||||
}
|
||||
return context
|
||||
|
||||
@@ -155,18 +158,6 @@ def profile_page(request: WSGIRequest) -> HttpResponse:
|
||||
return render(request, 'pages/profile.html', context)
|
||||
|
||||
|
||||
def auth_user(request: WSGIRequest) -> ZenpyUser:
|
||||
"""
|
||||
Функция возвращает профиль пользователя на Zendesk.
|
||||
|
||||
:param request: email, subdomain и token пользователя
|
||||
:return: объект пользователя Zendesk
|
||||
"""
|
||||
admin = ZendeskAdmin().admin
|
||||
zenpy_user: ZenpyUser = admin.users.search(request.user.email).values[0]
|
||||
return zenpy_user, admin
|
||||
|
||||
|
||||
@login_required()
|
||||
def work_page(request: WSGIRequest, id: int) -> HttpResponse:
|
||||
"""
|
||||
@@ -178,64 +169,75 @@ def work_page(request: WSGIRequest, id: int) -> HttpResponse:
|
||||
"""
|
||||
users = get_users_list()
|
||||
if request.user.id == id:
|
||||
if request.session.get('is_confirm', None):
|
||||
messages.success(request, 'Изменения были применены')
|
||||
elif request.session.get('is_confirm', None) is not None:
|
||||
messages.error(request, 'Изменения не были применены')
|
||||
count = request.session.get('count_tickets', None)
|
||||
if count is not None:
|
||||
messages.success(request, f'{count} тикетов назначено')
|
||||
request.session['is_confirm'] = None
|
||||
request.session['count_tickets'] = None
|
||||
|
||||
engineers = []
|
||||
light_agents = []
|
||||
for user in users:
|
||||
|
||||
if user.custom_role_id == ZENDESK_ROLES['engineer']:
|
||||
engineers.append(user)
|
||||
elif user.custom_role_id == ZENDESK_ROLES['light_agent']:
|
||||
light_agents.append(user)
|
||||
|
||||
context = setup_context(work_lit=True)
|
||||
context.update({
|
||||
'engineers': engineers,
|
||||
'agents': light_agents,
|
||||
'messages': messages.get_messages(request),
|
||||
'licences_remaining': max(0, ZENDESK_MAX_AGENTS - len(engineers)),
|
||||
'pagename': 'Управление правами'
|
||||
'pagename': 'Управление правами',
|
||||
})
|
||||
return render(request, 'pages/work.html', context)
|
||||
return redirect("login")
|
||||
|
||||
|
||||
@login_required()
|
||||
def work_hand_over(request: WSGIRequest) -> HttpResponseRedirect:
|
||||
def work_hand_over(request: WSGIRequest):
|
||||
"""
|
||||
Функция позволяет текущему пользователю (login_required) сдать права, а именно сменить в Zendesk роль с "engineer" на "light agent"
|
||||
и установить роль "agent" в БД. Действия выполняются, если исходная роль пользователя "engineer".
|
||||
Функция позволяет текущему пользователю сдать права, а именно сменить в Zendesk роль с "engineer" на "light_agent"
|
||||
|
||||
:param request: данные текущего пользователя (login_required)
|
||||
:return: перезагрузка текущей страницы после выполнения смены роли
|
||||
"""
|
||||
make_light_agent(request.user.userprofile,request.user)
|
||||
return HttpResponseRedirect(reverse('work', args=(request.user.id,)))
|
||||
make_light_agent(request.user.userprofile, request.user)
|
||||
return set_session_params_for_work_page(request)
|
||||
|
||||
|
||||
@login_required()
|
||||
def work_become_engineer(request: WSGIRequest) -> HttpResponseRedirect:
|
||||
"""
|
||||
Функция меняет роль пользователя в Zendesk на "engineer" и присваивает роль "agent" в БД (в случае, если исходная роль пользователя была "light_agent").
|
||||
Функция позволяет текущему пользователю получить права, а именно сменить в Zendesk роль с "light_agent" на "engineer"
|
||||
|
||||
:param request: данные текущего пользователя (login_required)
|
||||
:return: перезагрузка текущей страницы после выполнения смены роли
|
||||
"""
|
||||
zenpy_user, admin = auth_user(request)
|
||||
|
||||
make_engineer(request.user.userprofile,request.user)
|
||||
return HttpResponseRedirect(reverse('work', args=(request.user.id,)))
|
||||
make_engineer(request.user.userprofile, request.user)
|
||||
return set_session_params_for_work_page(request)
|
||||
|
||||
|
||||
@login_required()
|
||||
def work_get_tickets(request):
|
||||
zenpy_user, admin = auth_user(request)
|
||||
count_tickets = int(request.GET["count_tickets"])
|
||||
tickets = [ticket for ticket in admin.search(type="ticket") if ticket.group.name == 'Сменная группа' and ticket.assignee is None]
|
||||
for i in range(len(tickets)):
|
||||
if i == count_tickets:
|
||||
return HttpResponseRedirect(reverse('work', args=(request.user.id,)))
|
||||
tickets[i].assignee = zenpy_user
|
||||
admin.tickets.update(tickets[i])
|
||||
return HttpResponseRedirect(reverse('work', args=(request.user.id,)))
|
||||
zenpy_user = zenpy.get_user(request.user.email)
|
||||
if zenpy_user.role == 'admin' or zenpy_user.custom_role_id == ZENDESK_ROLES['engineer']:
|
||||
tickets = [ticket for ticket in zenpy.admin.search(type="ticket") if
|
||||
ticket.group.name == 'Сменная группа' and ticket.assignee is None]
|
||||
count = 0
|
||||
for i in range(len(tickets)):
|
||||
if i == int(request.GET.get('count_tickets')):
|
||||
return set_session_params_for_work_page(request, count)
|
||||
tickets[i].assignee = zenpy_user
|
||||
zenpy.admin.tickets.update(tickets[i])
|
||||
count += 1
|
||||
return set_session_params_for_work_page(request, count)
|
||||
return set_session_params_for_work_page(request, is_confirm=False)
|
||||
|
||||
|
||||
def main_page(request: WSGIRequest) -> HttpResponse:
|
||||
@@ -300,30 +302,6 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageM
|
||||
make_light_agent(user, self.request.user)
|
||||
log(user, self.request.user.userprofile)
|
||||
|
||||
def get_context_data(self, **kwargs) -> dict:
|
||||
"""
|
||||
Функция формирования контента страницы администратора (с проверкой прав доступа)
|
||||
"""
|
||||
|
||||
# context = super().get_context_data(**kwargs)
|
||||
|
||||
# context['licences_remaining'] = max(0, ZENDESK_MAX_AGENTS - context['engineers'])
|
||||
# return context
|
||||
|
||||
context = setup_context(control_lit=True)
|
||||
context.update(super().get_context_data(**kwargs))
|
||||
users = get_list_or_404(
|
||||
UserProfile, role='agent')
|
||||
context['engineers'], context['light_agents'] = count_users(get_users_list())
|
||||
context.update({
|
||||
'users': users,
|
||||
'ZENDESK_ROLES': ZENDESK_ROLES,
|
||||
'engineers': context['engineers'],
|
||||
'light_agents': context['light_agents'],
|
||||
'licences_remaining': max(0, ZENDESK_MAX_AGENTS - context['engineers']),
|
||||
})
|
||||
return context # TODO: need to get profile page url
|
||||
|
||||
|
||||
class CustomLoginView(LoginView):
|
||||
"""
|
||||
@@ -349,7 +327,8 @@ class UsersViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
'users': serializer.data,
|
||||
'engineers': count[0],
|
||||
'light_agents': count[1],
|
||||
"zendesk_users": self.get_zendesk_users(self.choose_users(users.values, profiles))
|
||||
'zendesk_users': self.get_zendesk_users(self.choose_users(users.values, profiles)),
|
||||
'max_agents': ZENDESK_MAX_AGENTS
|
||||
}
|
||||
return Response(res)
|
||||
|
||||
@@ -384,9 +363,9 @@ def statistic_page(request: WSGIRequest) -> HttpResponse:
|
||||
# raise PermissionDenied
|
||||
# context = {
|
||||
|
||||
if not request.user.is_superuser:
|
||||
if not request.user.has_perm("main.has_control_access"):
|
||||
return redirect('index')
|
||||
context = setup_context()
|
||||
context = setup_context(stats_lit=True)
|
||||
context.update({
|
||||
'pagename': 'страница статистики',
|
||||
'errors': list(),
|
||||
@@ -396,16 +375,16 @@ def statistic_page(request: WSGIRequest) -> HttpResponse:
|
||||
if form.is_valid():
|
||||
start_date, end_date = form.cleaned_data['range_start'], form.cleaned_data['range_end']
|
||||
interval, show = form.cleaned_data['interval'], form.cleaned_data['display_format']
|
||||
Data = StatisticData(start_date, end_date, form.cleaned_data['email'])
|
||||
Data.set_display(show)
|
||||
Data.set_interval(interval)
|
||||
stats = Data.get_statistic()
|
||||
if Data.errors:
|
||||
context['errors'] = Data.errors
|
||||
if Data.warnings:
|
||||
context['warnings'] = Data.warnings
|
||||
data = StatisticData(start_date, end_date, form.cleaned_data['email'])
|
||||
data.set_display(show)
|
||||
data.set_interval(interval)
|
||||
stats = data.get_statistic()
|
||||
if data.errors:
|
||||
context['errors'] = data.errors
|
||||
if data.warnings:
|
||||
context['warnings'] = data.warnings
|
||||
context['log_stats'] = stats if not context['errors'] else None
|
||||
if request.method == 'GET':
|
||||
elif request.method == 'GET':
|
||||
form = StatisticForm()
|
||||
context['form'] = form
|
||||
return render(request, 'pages/statistic.html', context)
|
||||
|
||||
93
main/zendesk_admin.py
Normal file
93
main/zendesk_admin.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from typing import Optional, Dict
|
||||
|
||||
from zenpy import Zenpy
|
||||
from zenpy.lib.api_objects import User as ZenpyUser, Group as ZenpyGroup
|
||||
from zenpy.lib.exception import APIException
|
||||
|
||||
from access_controller.settings import ACTRL_ZENDESK_SUBDOMAIN, ACTRL_API_EMAIL, ACTRL_API_TOKEN, ACTRL_API_PASSWORD, \
|
||||
ZENDESK_GROUPS, SOLVED_TICKETS_EMAIL
|
||||
|
||||
|
||||
class ZendeskAdmin:
|
||||
"""
|
||||
Класс **ZendeskAdmin** существует, чтобы в каждой функции отдельно не проверять аккаунт администратора.
|
||||
|
||||
:param credentials: Полномочия (первым указывается учетная запись организации в Zendesk)
|
||||
:type credentials: :class:`Dict[str, str]`
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
def check_user(self, email: str) -> bool:
|
||||
"""
|
||||
Функция осуществляет проверку существования пользователя в Zendesk по email.
|
||||
|
||||
:param email: Email пользователя
|
||||
:return: Является ли зарегистрированным
|
||||
"""
|
||||
return True if self.admin.search(email, type='user') else False
|
||||
|
||||
def get_user(self, email: str) -> ZenpyUser:
|
||||
"""
|
||||
Функция возвращает пользователя (объект) по его email.
|
||||
|
||||
:param email: Email пользователя
|
||||
:return: Объект пользователя, найденного в БД
|
||||
"""
|
||||
return self.admin.users.search(email).values[0]
|
||||
|
||||
def get_group(self, name: str) -> Optional[ZenpyGroup]:
|
||||
"""
|
||||
Функция возвращает группу по названию
|
||||
|
||||
:param name: Имя пользователя
|
||||
:return: Группы пользователя (в случае отсутствия None)
|
||||
"""
|
||||
groups = self.admin.search(name, type='group')
|
||||
for group in groups:
|
||||
return group
|
||||
return None
|
||||
|
||||
def get_user_org(self, email: str) -> str:
|
||||
"""
|
||||
Функция возвращает организацию, к которой относится пользователь по его email.
|
||||
|
||||
:param email: Email пользователя
|
||||
:return: Организация пользователя
|
||||
"""
|
||||
user = self.admin.users.search(email).values[0]
|
||||
return user.organization.name if user.organization else None
|
||||
|
||||
def create_admin(self) -> Zenpy:
|
||||
"""
|
||||
Функция создает администратора, проверяя наличие вводимых данных в env.
|
||||
|
||||
: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')
|
||||
|
||||
if self.credentials.get('token') is None and self.credentials.get('password') is None:
|
||||
raise ValueError('access_controller token or password not in env')
|
||||
|
||||
admin = Zenpy(**self.credentials)
|
||||
try:
|
||||
admin.search(self.credentials['email'], type='user')
|
||||
except APIException:
|
||||
raise ValueError('invalid access_controller`s login data')
|
||||
|
||||
return admin
|
||||
|
||||
|
||||
zenpy = ZendeskAdmin({
|
||||
'subdomain': ACTRL_ZENDESK_SUBDOMAIN,
|
||||
'email': ACTRL_API_EMAIL,
|
||||
'token': ACTRL_API_TOKEN,
|
||||
'password': ACTRL_API_PASSWORD,
|
||||
})
|
||||
Reference in New Issue
Block a user