Merge branch 'feature/documentation' into 'develop'
Feature/documentation See merge request 2020-2021/online/s101/group-02/access_controller!41
@ -3,14 +3,17 @@
|
|||||||
|
|
||||||
# You can set these variables from the command line, and also
|
# You can set these variables from the command line, and also
|
||||||
# from the environment for the first two.
|
# from the environment for the first two.
|
||||||
SPHINXOPTS ?=
|
ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
|
||||||
SPHINXBUILD ?= sphinx-build
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build
|
||||||
SOURCEDIR = source
|
SOURCEDIR = source
|
||||||
BUILDDIR = build
|
BUILDDIR = build
|
||||||
|
|
||||||
|
|
||||||
# Put it first so that "make" without argument is like "make help".
|
# Put it first so that "make" without argument is like "make help".
|
||||||
help:
|
help:
|
||||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
@echo " spelling to check for typos in documentation"
|
||||||
|
|
||||||
.PHONY: help Makefile
|
.PHONY: help Makefile
|
||||||
|
|
||||||
@ -19,5 +22,10 @@ help:
|
|||||||
%: Makefile
|
%: Makefile
|
||||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
|
|
||||||
spelling:
|
spelling:
|
||||||
$(SPHINXBUILD) -b spelling -W $(ALLSPHINXOPTS) $(BUILDDIR)/spelling
|
$(SPHINXBUILD) -b spelling -W $(SOURCEDIR) $(BUILDDIR)/spelling
|
||||||
|
@echo
|
||||||
|
@echo "Check finished. Wrong words can be found in " \
|
||||||
|
"build/spelling/output.txt."
|
||||||
|
|
||||||
|
BIN
docs/source/_static/login.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
docs/source/_static/main.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
docs/source/_static/main_logined.png
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
docs/source/_static/main_logined_agent.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
docs/source/_static/permission_management.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
docs/source/_static/permission_request.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
docs/source/_static/profile.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
docs/source/_static/registration.png
Normal file
After Width: | Height: | Size: 25 KiB |
@ -1,10 +1,9 @@
|
|||||||
Документация разработчика
|
Документация разработчика
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
*******
|
||||||
******
|
|
||||||
Models
|
Models
|
||||||
******
|
*******
|
||||||
|
|
||||||
.. automodule:: main.models
|
.. automodule:: main.models
|
||||||
:members:
|
:members:
|
||||||
@ -26,9 +25,27 @@ Extra Functions
|
|||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
***************
|
||||||
|
Serializers
|
||||||
|
***************
|
||||||
|
|
||||||
|
.. automodule:: main.serializers
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
***************
|
||||||
|
API functions
|
||||||
|
***************
|
||||||
|
|
||||||
|
.. automodule:: main.apiauth
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
*****
|
*****
|
||||||
Views
|
Views
|
||||||
*****
|
*****
|
||||||
|
|
||||||
.. automodule:: main.views
|
.. automodule:: main.views
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,6 +14,9 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import importlib
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
|
import enchant
|
||||||
|
from enchant import checker
|
||||||
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath('../../'))
|
sys.path.insert(0, os.path.abspath('../../'))
|
||||||
|
|
||||||
@ -35,10 +38,7 @@ ManagerDescriptor.__get__ = lambda self, *args, **kwargs: self.manager
|
|||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
|
|
||||||
QuerySet.__repr__ = lambda self: self.__class__.__name__
|
QuerySet.__repr__ = lambda self: self.__class__.__name__
|
||||||
try:
|
|
||||||
import enchant # NoQA
|
|
||||||
except ImportError:
|
|
||||||
enchant = None
|
|
||||||
|
|
||||||
django.setup()
|
django.setup()
|
||||||
|
|
||||||
@ -52,8 +52,6 @@ author = 'SHP S101, group 2'
|
|||||||
release = 'v0.01'
|
release = 'v0.01'
|
||||||
|
|
||||||
|
|
||||||
# Django sphinx setup by https://gist.github.com/codingjoe/314bda5a07ff3b41f247
|
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
def process_django_models(app, what, name, obj, options, lines):
|
def process_django_models(app, what, name, obj, options, lines):
|
||||||
@ -91,7 +89,6 @@ def process_django_models(app, what, name, obj, options, lines):
|
|||||||
lines.append(':type %s: %s.%s' % (field.attname, module, field_type.__name__))
|
lines.append(':type %s: %s.%s' % (field.attname, module, field_type.__name__))
|
||||||
if enchant is not None:
|
if enchant is not None:
|
||||||
lines += spelling_white_list
|
lines += spelling_white_list
|
||||||
print('ok')
|
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
@ -119,18 +116,18 @@ def skip_queryset(app, what, name, obj, skip, options):
|
|||||||
return skip
|
return skip
|
||||||
|
|
||||||
|
|
||||||
def setup(app):
|
# def setup(app):
|
||||||
# Register the docstring processor with sphinx
|
# # Register the docstring processor with sphinx
|
||||||
app.connect('autodoc-process-docstring', process_django_models)
|
# app.connect('autodoc-process-docstring', process_django_models)
|
||||||
app.connect('autodoc-skip-member', skip_queryset)
|
# app.connect('autodoc-skip-member', skip_queryset)
|
||||||
if enchant is not None:
|
# app.connect('autodoc-process-docstring', process_modules)
|
||||||
app.connect('autodoc-process-docstring', process_modules)
|
|
||||||
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
# ones.
|
# ones.
|
||||||
extensions = [
|
extensions = {
|
||||||
'sphinx.ext.todo',
|
'sphinx.ext.todo',
|
||||||
'sphinx.ext.autodoc',
|
'sphinx.ext.autodoc',
|
||||||
'sphinx.ext.intersphinx',
|
'sphinx.ext.intersphinx',
|
||||||
@ -138,12 +135,11 @@ extensions = [
|
|||||||
'sphinx_rtd_theme',
|
'sphinx_rtd_theme',
|
||||||
'sphinx.ext.graphviz',
|
'sphinx.ext.graphviz',
|
||||||
'sphinx.ext.inheritance_diagram',
|
'sphinx.ext.inheritance_diagram',
|
||||||
'sphinx_autodoc_typehints'
|
'sphinx_autodoc_typehints',
|
||||||
|
'sphinxcontrib.spelling'
|
||||||
|
|
||||||
]
|
}
|
||||||
|
|
||||||
if enchant is not None:
|
|
||||||
extensions.append('sphinxcontrib.spelling')
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
@ -188,16 +184,23 @@ intersphinx_mapping = {
|
|||||||
autodoc_default_flags = ['members']
|
autodoc_default_flags = ['members']
|
||||||
|
|
||||||
# spell checking
|
# spell checking
|
||||||
spelling_lang = 'en_US'
|
spelling_lang = 'ru_RU'
|
||||||
spelling_word_list_filename = 'spelling_wordlist.txt'
|
tokenizer_lang = 'ru_RU'
|
||||||
|
spelling_exclude_patterns=['ignored_*']
|
||||||
spelling_show_suggestions = True
|
spelling_show_suggestions = True
|
||||||
|
spelling_show_whole_line=True
|
||||||
|
spelling_warning=True
|
||||||
spelling_ignore_pypi_package_names = True
|
spelling_ignore_pypi_package_names = True
|
||||||
|
spelling_ignore_wiki_words=True
|
||||||
|
spelling_ignore_acronyms=True
|
||||||
|
spelling_ignore_python_builtins=True
|
||||||
|
spelling_ignore_importable_modules=True
|
||||||
|
spelling_ignore_contributor_names=True
|
||||||
|
|
||||||
# -- Options for todo extension ----------------------------------------------
|
# -- Options for todo extension ----------------------------------------------
|
||||||
|
|
||||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||||
todo_include_todos = True
|
todo_include_todos = True
|
||||||
|
|
||||||
set_type_checking_flag = True
|
set_type_checking_flag = True
|
||||||
typehints_fully_qualified = True
|
typehints_fully_qualified = True
|
||||||
always_document_param_types = True
|
always_document_param_types = True
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
You can adapt this file completely to your liking, but it should at least
|
You can adapt this file completely to your liking, but it should at least
|
||||||
contain the root `toctree` directive.
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
Welcome to ZenDesk Access Controller's documentation!
|
Документация контроллера прав доступа
|
||||||
=====================================================
|
=====================================================
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
@ -15,7 +15,6 @@ Welcome to ZenDesk Access Controller's documentation!
|
|||||||
todo
|
todo
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
@ -2,17 +2,88 @@
|
|||||||
Документация пользователя
|
Документация пользователя
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
******************************
|
||||||
**Управление правами доступа**
|
**Управление правами доступа**
|
||||||
|
******************************
|
||||||
|
|
||||||
|
|
||||||
**ZenDesk Access Controller** - Web-приложение, для выдачи прав пользователям системы по запросу самого пользователя.
|
**ZenDesk Access Controller** - web-приложение, для выдачи прав пользователям системы по запросу самого пользователя.
|
||||||
|
|
||||||
Например, из 12 человек 3 сейчас работают с правами администратора, по окончании рабочей смены они сдают свои права (освобождают места) и другие пользователи могут запросить эти права в свое пользование.
|
Например, из 12 человек 3 сейчас работают с правами администратора, по окончании рабочей смены они сдают свои права (освобождают места) и другие пользователи могут запросить эти права в свое пользование.
|
||||||
|
|
||||||
Оставшиеся 9 человек получают права легкого агента - без прав редактирования, а только чтение.
|
Оставшиеся 9 человек получают права легкого агента - без прав редактирования, а только чтение.
|
||||||
|
|
||||||
**Интерфейс пользователя:**
|
*****************
|
||||||
|
Главная страница
|
||||||
|
*****************
|
||||||
|
|
||||||
|
Меню главной страницы предоставляет Вам выбор:
|
||||||
|
|
||||||
|
* **"Войти"** - если Вы уже являетесь зарегистрированным пользователем
|
||||||
|
* **"Зарегистрироваться"** - при первом входе
|
||||||
|
|
||||||
|
.. image:: _static/main.png
|
||||||
|
|
||||||
|
Внимание! Для регистрации используется email с сайта Zendesk. Регистрация по каждому email
|
||||||
|
возможна один раз
|
||||||
|
|
||||||
|
**После авторизации пользователь может выбрать из следующих разделов меню:**
|
||||||
|
|
||||||
|
* **"Профиль"** - просмотреть свои данные и запросить права доступа
|
||||||
|
* **"Запрос прав"** - получение прав для работы с тикетами или **"Управление"** - доступно для администратора и предоставляет возможность группового назначения ролей пользователям
|
||||||
|
|
||||||
|
.. image:: _static/main_logined_agent.png
|
||||||
|
|
||||||
|
*************
|
||||||
|
Регистрация
|
||||||
|
*************
|
||||||
|
|
||||||
|
Для регистрации необходимо ввести email, который указан Вами в Zendesk.
|
||||||
|
|
||||||
|
.. image:: _static/registration.png
|
||||||
|
|
||||||
|
На электронную почту придет ссылка, пройдя по которой, Вам необходимо задать пароль.
|
||||||
|
|
||||||
|
***********
|
||||||
|
Авторизация
|
||||||
|
***********
|
||||||
|
|
||||||
|
Для входа необходимо ввести email и пароль
|
||||||
|
|
||||||
|
.. image:: _static/login.png
|
||||||
|
|
||||||
|
Если Вы не помните пароль необходимо пройти по ссылке "Забыли пароль" и указать email.
|
||||||
|
На Вашу почту придет ссылка для установки нового пароля.
|
||||||
|
|
||||||
|
********
|
||||||
|
Профиль
|
||||||
|
********
|
||||||
|
|
||||||
|
Профиль пользователя - это Ваша рабочая страница.
|
||||||
|
|
||||||
|
Здесь Вы можете просмотреть информацию пользователя (Ваши данные с Zendesk) и запросить права доступа для работы с тикетами.
|
||||||
|
|
||||||
|
.. image:: _static/profile.png
|
||||||
|
|
||||||
|
********************
|
||||||
|
Запрос прав доступа
|
||||||
|
********************
|
||||||
|
|
||||||
|
На странице запроса прав Вам доступна информация о количестве и списке работающих над тикетами сотрудников,
|
||||||
|
а также возможность сдать и запросить права.
|
||||||
|
|
||||||
|
.. image:: _static/permission_request.png
|
||||||
|
|
||||||
|
******************************************
|
||||||
|
Управление правами доступа администратором
|
||||||
|
******************************************
|
||||||
|
|
||||||
|
Для администратора существует удобный интерфейс страницы управления, в котором представлены:
|
||||||
|
|
||||||
|
* Количество свободных инженерных мест
|
||||||
|
* Количество и список инженеров и легких агентов
|
||||||
|
* Возможность группового назначения прав с использованием чек-боксов
|
||||||
|
|
||||||
|
.. image:: _static/permission_management.png
|
||||||
|
|
||||||
.. |copy| unicode:: 0xA9 .. Школа программистов S101, группа 2. 2021гю
|
.. |copy| unicode:: 0xA9 .. Школа программистов S101, группа 2. 2021гю
|
||||||
|
86
docs/source/spelling_wordlist.txt
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
тикетами
|
||||||
|
тикета
|
||||||
|
тикетов
|
||||||
|
тикет
|
||||||
|
web
|
||||||
|
Indices
|
||||||
|
and
|
||||||
|
tables
|
||||||
|
Models
|
||||||
|
логирования
|
||||||
|
User
|
||||||
|
user
|
||||||
|
superuser
|
||||||
|
light
|
||||||
|
light_agent
|
||||||
|
admin
|
||||||
|
agent
|
||||||
|
bootstrap
|
||||||
|
form
|
||||||
|
control
|
||||||
|
Zendesk
|
||||||
|
email
|
||||||
|
Extra
|
||||||
|
Functions
|
||||||
|
env
|
||||||
|
ID
|
||||||
|
url
|
||||||
|
None
|
||||||
|
token
|
||||||
|
password
|
||||||
|
engineer
|
||||||
|
SYSTEM
|
||||||
|
start_date
|
||||||
|
end_date
|
||||||
|
timedelta
|
||||||
|
log
|
||||||
|
RoleChangeLogs
|
||||||
|
time(datetime.time)
|
||||||
|
stat
|
||||||
|
statistic
|
||||||
|
True
|
||||||
|
False
|
||||||
|
val
|
||||||
|
start
|
||||||
|
end
|
||||||
|
date
|
||||||
|
Токен
|
||||||
|
токеном
|
||||||
|
аутентифицирован
|
||||||
|
(datetime.time)
|
||||||
|
datetime
|
||||||
|
time
|
||||||
|
serializer
|
||||||
|
валидны
|
||||||
|
html
|
||||||
|
subdomain
|
||||||
|
логгирования
|
||||||
|
логгирование
|
||||||
|
forms
|
||||||
|
StatisticForm
|
||||||
|
Userprofile
|
||||||
|
login
|
||||||
|
login_required
|
||||||
|
required
|
||||||
|
id
|
||||||
|
prom
|
||||||
|
home
|
||||||
|
PycharmProjects
|
||||||
|
Access
|
||||||
|
access
|
||||||
|
controler
|
||||||
|
controller
|
||||||
|
main
|
||||||
|
views
|
||||||
|
py
|
||||||
|
docstring
|
||||||
|
of
|
||||||
|
page
|
||||||
|
API
|
||||||
|
functions
|
||||||
|
Serializer
|
||||||
|
Serializers
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4,7 +4,16 @@ from zenpy import Zenpy
|
|||||||
from zenpy.lib.api_objects import User as ZenpyUser
|
from zenpy.lib.api_objects import User as ZenpyUser
|
||||||
|
|
||||||
|
|
||||||
def api_auth():
|
def api_auth() -> dict:
|
||||||
|
"""
|
||||||
|
Функция создания пользователя с использованием Zendesk API.
|
||||||
|
|
||||||
|
Получает из env Zendesk - email, token, password пользователя.
|
||||||
|
Если данные валидны и пользователь Zendesk с указанным email и токеном или паролем существует,
|
||||||
|
создается словарь данных пользователя, полученных через API c Zendesk.
|
||||||
|
|
||||||
|
:return: данные пользователя
|
||||||
|
"""
|
||||||
credentials = {
|
credentials = {
|
||||||
'subdomain': 'ngenix1612197338'
|
'subdomain': 'ngenix1612197338'
|
||||||
}
|
}
|
||||||
|
@ -13,16 +13,16 @@ from main.models import UserProfile, RoleChangeLogs, UnassignedTicket, Unassigne
|
|||||||
|
|
||||||
class ZendeskAdmin:
|
class ZendeskAdmin:
|
||||||
"""
|
"""
|
||||||
Класс **ZendeskAdmin** существует, чтобы в каждой фунциии отдельно не проверять аккаунт администратора
|
Класс **ZendeskAdmin** существует, чтобы в каждой функции отдельно не проверять аккаунт администратора.
|
||||||
|
|
||||||
:param credentials: Полномочия (первым указывается учетная запись организации в Zendesk)
|
:param credentials: Полномочия (первым указывается учетная запись организации в Zendesk)
|
||||||
:type credentials: :class:`dict`
|
:type credentials: :class:`dict`
|
||||||
:param email: Email администратора, указанный в env
|
:param email: Email администратора, указанный в env
|
||||||
:type email: :class:`str`
|
:type email: :class:`str`
|
||||||
:param token: Токен администратора (формируется в Zendesk, указывается в env)
|
:param token: Токен администратора (формируется в Zendesk, указывается в env)
|
||||||
:type token: :class:`str`
|
:type token: :class:`str`
|
||||||
:param password: Пароль администратора, указанный в env
|
:param password: Пароль администратора, указанный в env
|
||||||
:type password: :class:`str`
|
:type password: :class:`str`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
credentials: dict = {
|
credentials: dict = {
|
||||||
@ -37,7 +37,10 @@ class ZendeskAdmin:
|
|||||||
|
|
||||||
def check_user(self, email: str) -> bool:
|
def check_user(self, email: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Функция **check_user** осуществляет проверку существования пользователя в Zendesk по email
|
Функция осуществляет проверку существования пользователя в Zendesk по email.
|
||||||
|
|
||||||
|
:param email: Email пользователя
|
||||||
|
:return: Является ли зарегистрированным
|
||||||
"""
|
"""
|
||||||
return True if self.admin.search(email, type='user') else False
|
return True if self.admin.search(email, type='user') else False
|
||||||
|
|
||||||
@ -50,35 +53,50 @@ class ZendeskAdmin:
|
|||||||
|
|
||||||
def get_user_role(self, email: str) -> str:
|
def get_user_role(self, email: str) -> str:
|
||||||
"""
|
"""
|
||||||
Функция **get_user_role** возвращает роль пользователя по его email
|
Функция возвращает роль пользователя по его email.
|
||||||
|
|
||||||
|
:param email: Email пользователя
|
||||||
|
:return: Роль пользователя
|
||||||
"""
|
"""
|
||||||
user = self.admin.users.search(email).values[0]
|
user = self.admin.users.search(email).values[0]
|
||||||
return user.role
|
return user.role
|
||||||
|
|
||||||
def get_user_id(self, email: str) -> str:
|
def get_user_id(self, email: str) -> str:
|
||||||
"""
|
"""
|
||||||
Функция **get_user_id** возвращает id пользователя по его email
|
Функция возвращает id пользователя по его email
|
||||||
|
|
||||||
|
:param email: Email пользователя
|
||||||
|
:return: ID пользователя
|
||||||
"""
|
"""
|
||||||
user = self.admin.users.search(email).values[0]
|
user = self.admin.users.search(email).values[0]
|
||||||
return user.id
|
return user.id
|
||||||
|
|
||||||
def get_user_image(self, email: str) -> str:
|
def get_user_image(self, email: str) -> str:
|
||||||
"""
|
"""
|
||||||
Функция **get_user_image** возвращает url-ссылку на аватар пользователя по его email
|
Функция возвращает url-ссылку на аватар пользователя по его email.
|
||||||
|
|
||||||
|
:param email: Email пользователя
|
||||||
|
:return: Аватар пользователя
|
||||||
"""
|
"""
|
||||||
user = self.admin.users.search(email).values[0]
|
user = self.admin.users.search(email).values[0]
|
||||||
return user.photo['content_url'] if user.photo else None
|
return user.photo['content_url'] if user.photo else None
|
||||||
|
|
||||||
def get_user(self, email: str):
|
def get_user(self, email: str):
|
||||||
"""
|
"""
|
||||||
Функция **get_user** возвращает пользователя (объект) по его email
|
Функция возвращает пользователя (объект) по его email.
|
||||||
|
|
||||||
:param email: email пользователя
|
:param email: Email пользователя
|
||||||
:return: email пользователя, найденного в БД
|
:return: Объект пользователя, найденного в БД
|
||||||
"""
|
"""
|
||||||
return self.admin.users.search(email).values[0]
|
return self.admin.users.search(email).values[0]
|
||||||
|
|
||||||
def get_group(self, name):
|
def get_group(self, name: str) -> str:
|
||||||
|
"""
|
||||||
|
Функция возвращает группы, к которым принадлежит пользователь.
|
||||||
|
|
||||||
|
:param name: Имя пользователя
|
||||||
|
:return: Группы пользователя (в случае отсутствия None)
|
||||||
|
"""
|
||||||
groups = self.admin.search(name)
|
groups = self.admin.search(name)
|
||||||
for group in groups:
|
for group in groups:
|
||||||
return group
|
return group
|
||||||
@ -86,19 +104,22 @@ class ZendeskAdmin:
|
|||||||
|
|
||||||
def get_user_org(self, email: str) -> str:
|
def get_user_org(self, email: str) -> str:
|
||||||
"""
|
"""
|
||||||
Функция **get_user_org** возвращает организацию, к которой относится пользователь по его email
|
Функция возвращает организацию, к которой относится пользователь по его email.
|
||||||
|
|
||||||
|
:param email: Email пользователя
|
||||||
|
:return: Организация пользователя
|
||||||
"""
|
"""
|
||||||
user = self.admin.users.search(email).values[0]
|
user = self.admin.users.search(email).values[0]
|
||||||
return user.organization.name if user.organization else None
|
return user.organization.name if user.organization else None
|
||||||
|
|
||||||
def create_admin(self) -> Zenpy:
|
def create_admin(self) -> Zenpy:
|
||||||
"""
|
"""
|
||||||
Функция **Create_admin()** создает администратора, проверяя наличие вводимых данных в env.
|
Функция создает администратора, проверяя наличие вводимых данных в env.
|
||||||
|
|
||||||
:param credentials: В список полномочий администратора вносятся email, token, password из env
|
:param credentials: В список полномочий администратора вносятся email, token, password из env
|
||||||
:type credentials: :class:`dict`
|
:type credentials: :class:`dict`
|
||||||
:raise: :class:`ValueError`: исключение, вызываемое если email не введен в env
|
:raise: :class:`ValueError`: исключение, вызываемое если email не введен в env
|
||||||
:raise: :class:`APIException`: исключение, вызываемое если пользователя с таким email не существует в Zendesk
|
:raise: :class:`APIException`: исключение, вызываемое если пользователя с таким email не существует в Zendesk
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.email is None:
|
if self.email is None:
|
||||||
@ -120,7 +141,11 @@ class ZendeskAdmin:
|
|||||||
|
|
||||||
def update_role(user_profile: UserProfile, role: str) -> UserProfile:
|
def update_role(user_profile: UserProfile, role: str) -> UserProfile:
|
||||||
"""
|
"""
|
||||||
Функция **update_role** меняет роль пользователя.
|
Функция меняет роль пользователя.
|
||||||
|
|
||||||
|
:param user_profile: Профиль пользователя
|
||||||
|
:param role: Новая роль
|
||||||
|
:return: Пользователь с обновленной ролью
|
||||||
"""
|
"""
|
||||||
zendesk = ZendeskAdmin()
|
zendesk = ZendeskAdmin()
|
||||||
user = zendesk.get_user(user_profile.user.email)
|
user = zendesk.get_user(user_profile.user.email)
|
||||||
@ -130,7 +155,10 @@ def update_role(user_profile: UserProfile, role: str) -> UserProfile:
|
|||||||
|
|
||||||
def make_engineer(user_profile: UserProfile, who_changes: User) -> UserProfile:
|
def make_engineer(user_profile: UserProfile, who_changes: User) -> UserProfile:
|
||||||
"""
|
"""
|
||||||
Функция **make_engineer** устанавливает пользователю роль инженера.
|
Функция устанавливает пользователю роль инженера.
|
||||||
|
|
||||||
|
:param user_profile: Профиль пользователя
|
||||||
|
:return: Вызов функции **update_role** с параметрами: профиль пользователя, роль "engineer"
|
||||||
"""
|
"""
|
||||||
RoleChangeLogs.objects.create(
|
RoleChangeLogs.objects.create(
|
||||||
user=user_profile.user,
|
user=user_profile.user,
|
||||||
@ -143,7 +171,10 @@ def make_engineer(user_profile: UserProfile, who_changes: User) -> UserProfile:
|
|||||||
|
|
||||||
def make_light_agent(user_profile: UserProfile, who_changes: User) -> UserProfile:
|
def make_light_agent(user_profile: UserProfile, who_changes: User) -> UserProfile:
|
||||||
"""
|
"""
|
||||||
Функция **make_light_agent** устанавливапет пользователю роль легкого агента.
|
Функция устанавливает пользователю роль легкого агента.
|
||||||
|
|
||||||
|
:param user_profile: Профиль пользователя
|
||||||
|
:return: Вызов функции **update_role** с параметрами: профиль пользователя, роль "light_agent"
|
||||||
"""
|
"""
|
||||||
tickets = get_tickets_list(user_profile.user.email)
|
tickets = get_tickets_list(user_profile.user.email)
|
||||||
for ticket in tickets:
|
for ticket in tickets:
|
||||||
@ -170,7 +201,7 @@ def make_light_agent(user_profile: UserProfile, who_changes: User) -> UserProfil
|
|||||||
|
|
||||||
def get_users_list() -> list:
|
def get_users_list() -> list:
|
||||||
"""
|
"""
|
||||||
Функция **get_users_list** возвращает список пользователей Zendesk, относящихся к организации.
|
Функция **get_users_list** возвращает список пользователей Zendesk, относящихся к организации SYSTEM.
|
||||||
"""
|
"""
|
||||||
zendesk = ZendeskAdmin()
|
zendesk = ZendeskAdmin()
|
||||||
|
|
||||||
@ -189,7 +220,10 @@ def get_tickets_list(email):
|
|||||||
|
|
||||||
def update_profile(user_profile: UserProfile) -> UserProfile:
|
def update_profile(user_profile: UserProfile) -> UserProfile:
|
||||||
"""
|
"""
|
||||||
Функция обновляет профиль пользователя в соотвтетствии с текущим в Zendesk
|
Функция обновляет профиль пользователя в соответствии с текущим в Zendesk.
|
||||||
|
|
||||||
|
:param user_profile: Профиль пользователя
|
||||||
|
:return: Обновленный, в соответствие с текущими данными в Zendesk, профиль пользователя
|
||||||
"""
|
"""
|
||||||
user = ZendeskAdmin().get_user(user_profile.user.email)
|
user = ZendeskAdmin().get_user(user_profile.user.email)
|
||||||
user_profile.name = user.name
|
user_profile.name = user.name
|
||||||
@ -201,21 +235,27 @@ def update_profile(user_profile: UserProfile) -> UserProfile:
|
|||||||
|
|
||||||
def check_user_exist(email: str) -> bool:
|
def check_user_exist(email: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Функция проверяет, существует ли пользователь
|
Функция проверяет, существует ли пользователь.
|
||||||
|
|
||||||
|
:param email: Email пользователя
|
||||||
|
:return: Зарегистрирован ли пользователь в Zendesk
|
||||||
"""
|
"""
|
||||||
return ZendeskAdmin().check_user(email)
|
return ZendeskAdmin().check_user(email)
|
||||||
|
|
||||||
|
|
||||||
def get_user_organization(email: str) -> str:
|
def get_user_organization(email: str) -> str:
|
||||||
"""
|
"""
|
||||||
Функция возвращает организацию пользователя
|
Функция возвращает организацию пользователя.
|
||||||
|
|
||||||
|
:param email: Email пользователя
|
||||||
|
:return: Организация пользователя
|
||||||
"""
|
"""
|
||||||
return ZendeskAdmin().get_user_org(email)
|
return ZendeskAdmin().get_user_org(email)
|
||||||
|
|
||||||
|
|
||||||
def check_user_auth(email: str, password: str) -> bool:
|
def check_user_auth(email: str, password: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Функция проверяет, верны ли входные данные
|
Функция проверяет, верны ли входные данные.
|
||||||
|
|
||||||
:raise: :class:`APIException`: исключение, вызываемое если пользователь не аутентифицирован
|
:raise: :class:`APIException`: исключение, вызываемое если пользователь не аутентифицирован
|
||||||
"""
|
"""
|
||||||
@ -232,7 +272,14 @@ def check_user_auth(email: str, password: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def update_user_in_model(profile, zendesk_user):
|
def update_user_in_model(profile: UserProfile, zendesk_user: User) -> UserProfile:
|
||||||
|
"""
|
||||||
|
Функция обновляет профиль пользователя при изменении данных пользователя на Zendesk.
|
||||||
|
|
||||||
|
:param profile: Профиль пользователя
|
||||||
|
:param zendesk_user: Данные пользователя в Zendesk
|
||||||
|
:return: Обновленный профиль пользователя
|
||||||
|
"""
|
||||||
profile.name = zendesk_user.name
|
profile.name = zendesk_user.name
|
||||||
profile.role = zendesk_user.role
|
profile.role = zendesk_user.role
|
||||||
profile.image = zendesk_user.photo['content_url'] if zendesk_user.photo else None
|
profile.image = zendesk_user.photo['content_url'] if zendesk_user.photo else None
|
||||||
@ -243,7 +290,7 @@ def update_user_in_model(profile, zendesk_user):
|
|||||||
|
|
||||||
def count_users(users) -> tuple:
|
def count_users(users) -> tuple:
|
||||||
"""
|
"""
|
||||||
Функция подсчета количества сотрудников с ролями engineer и light_a
|
Функция подсчета количества сотрудников с ролями engineer и light_agent
|
||||||
"""
|
"""
|
||||||
engineers, light_agents = 0, 0
|
engineers, light_agents = 0, 0
|
||||||
for user in users:
|
for user in users:
|
||||||
@ -270,7 +317,11 @@ def update_users_in_model():
|
|||||||
|
|
||||||
def daterange(start_date, end_date) -> list:
|
def daterange(start_date, end_date) -> list:
|
||||||
"""
|
"""
|
||||||
Возвращает список дней с start_date по end_date исключая правую границу
|
Функция возвращает список дней с start_date по end_date, исключая правую границу.
|
||||||
|
|
||||||
|
:param start_date: Начальная дата
|
||||||
|
:param end_date: Конечная дата
|
||||||
|
:return: Список дней, не включая конечную дату
|
||||||
"""
|
"""
|
||||||
dates = []
|
dates = []
|
||||||
for n in range(int((end_date - start_date).days)):
|
for n in range(int((end_date - start_date).days)):
|
||||||
@ -280,8 +331,12 @@ def daterange(start_date, end_date) -> list:
|
|||||||
|
|
||||||
def get_timedelta(log, time=None) -> timedelta:
|
def get_timedelta(log, time=None) -> timedelta:
|
||||||
"""
|
"""
|
||||||
Возвращает объект класса timedelta, который хранит промежуток времени от начала суток до момента,
|
Функция возвращает объект класса timedelta, который хранит промежуток времени от начала суток до момента,
|
||||||
который находится в log (объект класса RoleChangeLogs) или в time(datetime.time), если введён
|
который находится в log (объект класса RoleChangeLogs) или в time(datetime.time), если введён.
|
||||||
|
|
||||||
|
:param log: Лог
|
||||||
|
:param time: Время
|
||||||
|
:return: Сколько времени прошло от начала суток до события
|
||||||
"""
|
"""
|
||||||
if time is None:
|
if time is None:
|
||||||
time = log.change_time.time()
|
time = log.change_time.time()
|
||||||
@ -289,15 +344,41 @@ def get_timedelta(log, time=None) -> timedelta:
|
|||||||
return time
|
return time
|
||||||
|
|
||||||
|
|
||||||
def last_day_of_month(day):
|
def last_day_of_month(day: int) -> int:
|
||||||
"""
|
"""
|
||||||
Возвращает последний день любого месяца
|
Функция возвращает последний день текущего месяца.
|
||||||
|
|
||||||
|
:param day: Текущий день
|
||||||
|
:return: Последний день месяца
|
||||||
"""
|
"""
|
||||||
next_month = day.replace(day=28) + timedelta(days=4)
|
next_month = day.replace(day=28) + timedelta(days=4)
|
||||||
return next_month - timedelta(days=next_month.day)
|
return next_month - timedelta(days=next_month.day)
|
||||||
|
|
||||||
|
|
||||||
class StatisticData:
|
class StatisticData:
|
||||||
|
"""
|
||||||
|
Класс для учета статистики интервалов работы пользователей.
|
||||||
|
Передаваемые параметры: start_date, end_date, email, stat.
|
||||||
|
|
||||||
|
:param display: Формат отображения времени (часы, минуты)
|
||||||
|
:type display: :class:`list`
|
||||||
|
:param interval: Интервал времени в часах и минутах
|
||||||
|
:type interval: :class:`list`
|
||||||
|
:param start_date: Дата начала работы
|
||||||
|
:type start_date: :class:`date`
|
||||||
|
:param end_date: Дата окончания работы
|
||||||
|
:type end_date: :class:`date`
|
||||||
|
:param email: Email пользователя
|
||||||
|
:type email: :class:`str`
|
||||||
|
:param errors: Список ошибок
|
||||||
|
:type errors: :class:`list`
|
||||||
|
:param warnings: Список предупреждений
|
||||||
|
:type warnings: :class:`list`
|
||||||
|
:param data: Ретроспектива смены ролей пользователя
|
||||||
|
:type data: :class:`dict`
|
||||||
|
:param statistic: Интервалы работы пользователя
|
||||||
|
:type statistic: :class:`dict`
|
||||||
|
"""
|
||||||
def __init__(self, start_date, end_date, user_email, stat=None):
|
def __init__(self, start_date, end_date, user_email, stat=None):
|
||||||
self.display = None
|
self.display = None
|
||||||
self.interval = None
|
self.interval = None
|
||||||
@ -314,10 +395,11 @@ class StatisticData:
|
|||||||
else:
|
else:
|
||||||
self.statistic = stat
|
self.statistic = stat
|
||||||
|
|
||||||
def get_statistic(self):
|
def get_statistic(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Вернуть словарь statistic с применением формата отображения и интеравала работы(если они есть)
|
Функция возвращает статистику работы пользователя.
|
||||||
None, если были ошибки при создании
|
|
||||||
|
:return: Словарь statistic с применением формата отображения и интервала работы(если они есть). None, если были ошибки при создании.
|
||||||
"""
|
"""
|
||||||
if self.is_valid_statistic():
|
if self.is_valid_statistic():
|
||||||
stat = self.statistic
|
stat = self.statistic
|
||||||
@ -327,15 +409,20 @@ class StatisticData:
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def is_valid_statistic(self):
|
def is_valid_statistic(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Были ли ошибки при создании статистики
|
Функция проверяет были ли ошибки при создании статистики.
|
||||||
|
|
||||||
|
:return: True, при отсутствии ошибок
|
||||||
"""
|
"""
|
||||||
return not self.errors and self.statistic
|
return not self.errors and self.statistic
|
||||||
|
|
||||||
def set_interval(self, interval):
|
def set_interval(self, interval: list) -> bool:
|
||||||
"""
|
"""
|
||||||
Устанавливает интервал работы
|
Функция проверяет корректность представления интервала работы.
|
||||||
|
|
||||||
|
:param interval: Интервал должен быть указан в днях или месяцах.
|
||||||
|
:return: True, если указан верно
|
||||||
"""
|
"""
|
||||||
if interval not in ['months', 'days']:
|
if interval not in ['months', 'days']:
|
||||||
self.errors += ['Интервал работы должен быть в днях или месяцах']
|
self.errors += ['Интервал работы должен быть в днях или месяцах']
|
||||||
@ -343,9 +430,12 @@ class StatisticData:
|
|||||||
self.interval = interval
|
self.interval = interval
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_display(self, display_format):
|
def set_display(self, display_format: list) -> bool:
|
||||||
"""
|
"""
|
||||||
Устанавливает формат отображения
|
Функция проверяет корректность формата отображения интервала.
|
||||||
|
|
||||||
|
:param display_format: Формат отображения должен быть указан в днях или месяцах.
|
||||||
|
:return: True, если указан верно
|
||||||
"""
|
"""
|
||||||
if display_format not in ['days', 'hours']:
|
if display_format not in ['days', 'hours']:
|
||||||
self.errors += ['Формат отображения должен быть в часах или днях']
|
self.errors += ['Формат отображения должен быть в часах или днях']
|
||||||
@ -353,26 +443,29 @@ class StatisticData:
|
|||||||
self.display = display_format
|
self.display = display_format
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self) -> list:
|
||||||
"""
|
"""
|
||||||
Вернуть данные
|
Функция возвращает данные - список объектов RoleChangeLogs.
|
||||||
data - массив объектов RoleChangeLogs, является списком логов пользователя
|
|
||||||
data может быть пустым списком
|
|
||||||
"""
|
"""
|
||||||
if self.is_valid_data():
|
if self.is_valid_data():
|
||||||
return self.data
|
return self.data
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def is_valid_data(self):
|
def is_valid_data(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Были ли ошибки при получении логов
|
Функция определяет были ли ошибки при получении логов.
|
||||||
|
|
||||||
|
:return: True, если ошибок нет
|
||||||
"""
|
"""
|
||||||
return not self.errors
|
return not self.errors
|
||||||
|
|
||||||
def _use_display(self, stat):
|
def _use_display(self, stat: list) -> list:
|
||||||
"""
|
"""
|
||||||
Приводит данные к формату отображения
|
Функция приводит данные к формату отображения.
|
||||||
|
|
||||||
|
:param stat: Список данных статистики пользователя
|
||||||
|
:return: Обновленный список
|
||||||
"""
|
"""
|
||||||
if not self.is_valid_statistic() or not self.display:
|
if not self.is_valid_statistic() or not self.display:
|
||||||
return stat
|
return stat
|
||||||
@ -384,9 +477,12 @@ class StatisticData:
|
|||||||
new_stat[key] = item / (ONE_DAY * 3600)
|
new_stat[key] = item / (ONE_DAY * 3600)
|
||||||
return new_stat
|
return new_stat
|
||||||
|
|
||||||
def _use_interval(self, stat):
|
def _use_interval(self, stat: dict) -> dict:
|
||||||
"""
|
"""
|
||||||
Объединяет ключи и значения в соответствии с интервалом работы
|
Функция объединяет ключи и значения в соответствии с интервалом работы.
|
||||||
|
|
||||||
|
:param stat: Статистика работы пользователя
|
||||||
|
:return: Обновленная статистика
|
||||||
"""
|
"""
|
||||||
if not self.is_valid_statistic() or not self.interval:
|
if not self.is_valid_statistic() or not self.interval:
|
||||||
return stat
|
return stat
|
||||||
@ -405,9 +501,11 @@ class StatisticData:
|
|||||||
new_stat = stat # статистика изначально в днях
|
new_stat = stat # статистика изначально в днях
|
||||||
return new_stat
|
return new_stat
|
||||||
|
|
||||||
def check_time(self):
|
def check_time(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Проверка на правильность введенного времени
|
Функция проверяет корректность введенного времени.
|
||||||
|
|
||||||
|
:return: True, если время указано корректно. Иначе, False
|
||||||
"""
|
"""
|
||||||
if self.end_date < self.start_date or self.end_date > datetime.now().date():
|
if self.end_date < self.start_date or self.end_date > datetime.now().date():
|
||||||
return False
|
return False
|
||||||
@ -415,7 +513,9 @@ class StatisticData:
|
|||||||
|
|
||||||
def _init_data(self):
|
def _init_data(self):
|
||||||
"""
|
"""
|
||||||
Получение логов в диапазоне дат start_date-end_date для пользователя с почтой email
|
Функция возвращает логи в диапазоне дат start_date - end_date для пользователя с указанным email.
|
||||||
|
|
||||||
|
:return: Данные о смене статусов пользователя. Если пользователь не найден или интервал времени некорректен - ошибку.
|
||||||
"""
|
"""
|
||||||
if not self.check_time():
|
if not self.check_time():
|
||||||
self.errors += ['Конец диапазона должен быть позже начала диапазона и раньше текущего времени']
|
self.errors += ['Конец диапазона должен быть позже начала диапазона и раньше текущего времени']
|
||||||
@ -428,9 +528,11 @@ class StatisticData:
|
|||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
self.errors += ['Пользователь не найден']
|
self.errors += ['Пользователь не найден']
|
||||||
|
|
||||||
def _init_statistic(self):
|
def _init_statistic(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Функция заполняет словарь, в котором ключ - дата, значение - кол-во проработанных в этот день секунд
|
Функция заполняет словарь, в котором ключ - дата, значение - кол-во проработанных в этот день секунд.
|
||||||
|
|
||||||
|
:return: Статистика работы пользователя (statistic)
|
||||||
"""
|
"""
|
||||||
self.clear_statistic()
|
self.clear_statistic()
|
||||||
if not self.get_data():
|
if not self.get_data():
|
||||||
@ -465,17 +567,23 @@ class StatisticData:
|
|||||||
elapsed_time = next_log.change_time - current_log.change_time
|
elapsed_time = next_log.change_time - current_log.change_time
|
||||||
self.statistic[current_log.change_time.date()] += elapsed_time.total_seconds()
|
self.statistic[current_log.change_time.date()] += elapsed_time.total_seconds()
|
||||||
|
|
||||||
def fill_daterange(self, first, last, val=24 * 3600):
|
def fill_daterange(self, first: date, last: date, val: int = 24 * 3600) -> dict:
|
||||||
"""
|
"""
|
||||||
Заполение диапазона дат значением val
|
Функция заполняет диапазон дат значением val (по умолчанию val = кол-во секунд в 1 дне).
|
||||||
по умолчанию val = кол-во секунд в 1 дне
|
|
||||||
|
:param first: Начальная дата интервала
|
||||||
|
:param last: Последняя дата интервала
|
||||||
|
:param val: Количество секунд в одном дне
|
||||||
|
:return: Статистику пользователя с указанным количеством секунд в заданных днях
|
||||||
"""
|
"""
|
||||||
for day in daterange(first, last):
|
for day in daterange(first, last):
|
||||||
self.statistic[day] = val
|
self.statistic[day] = val
|
||||||
|
|
||||||
def clear_statistic(self):
|
def clear_statistic(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Обнуление всех дней
|
Функция осуществляет обновление всех дней.
|
||||||
|
|
||||||
|
:return: Статистику пользователя с количеством рабочих секунд = 0
|
||||||
"""
|
"""
|
||||||
self.statistic.clear()
|
self.statistic.clear()
|
||||||
self.fill_daterange(self.start_date, self.end_date + timedelta(days=1), 0)
|
self.fill_daterange(self.start_date, self.end_date + timedelta(days=1), 0)
|
||||||
|
@ -8,12 +8,11 @@ from main.models import UserProfile
|
|||||||
|
|
||||||
class CustomRegistrationForm(RegistrationFormUniqueEmail):
|
class CustomRegistrationForm(RegistrationFormUniqueEmail):
|
||||||
"""
|
"""
|
||||||
Форма для регистрации :class:`django_registration.forms.RegistrationFormUniqueEmail`
|
Форма для регистрации :class:`django_registration.forms.RegistrationFormUniqueEmail`
|
||||||
|
с добавлением bootstrap-класса "form-control".
|
||||||
|
|
||||||
с добавлением bootstrap-класса "form-control"
|
:param visible_fields.email: Поле для ввода email, зарегистрированного на Zendesk
|
||||||
|
:type visible_fields.email: :class:`django_registration.forms.RegistrationFormUniqueEmail`
|
||||||
:param visible_fields.email: Поле для ввода email, зарегистирированного на Zendesk
|
|
||||||
:type visible_fields.email: :class:`django_registration.forms.RegistrationFormUniqueEmail`
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs) -> RegistrationFormUniqueEmail:
|
def __init__(self, *args, **kwargs) -> RegistrationFormUniqueEmail:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -32,10 +31,10 @@ class CustomRegistrationForm(RegistrationFormUniqueEmail):
|
|||||||
|
|
||||||
class AdminPageUsers(forms.Form):
|
class AdminPageUsers(forms.Form):
|
||||||
"""
|
"""
|
||||||
Форма для установки статусов engineer или light_agent пользователям
|
Форма для установки статусов engineer или light_agent пользователям.
|
||||||
|
|
||||||
:param users: Поле для установки статуса
|
:param users: Поле для установки статуса
|
||||||
:type users: :class:`ModelMultipleChoiceField`
|
:type users: :class:`ModelMultipleChoiceField`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
users = forms.ModelMultipleChoiceField(
|
users = forms.ModelMultipleChoiceField(
|
||||||
@ -52,8 +51,11 @@ class AdminPageUsers(forms.Form):
|
|||||||
|
|
||||||
class CustomAuthenticationForm(AuthenticationForm):
|
class CustomAuthenticationForm(AuthenticationForm):
|
||||||
"""
|
"""
|
||||||
Форма для авторизации :class:`django.contrib.auth.forms.AuthenticationForm`
|
Форма для авторизации :class:`django.contrib.auth.forms.AuthenticationForm`
|
||||||
с изменением поля username на email
|
с изменением поля username на email.
|
||||||
|
|
||||||
|
:param username: Поле для ввода email пользователя
|
||||||
|
:type username: :class:`django.forms.fields.CharField`
|
||||||
"""
|
"""
|
||||||
username = forms.CharField(
|
username = forms.CharField(
|
||||||
label="Электронная почта",
|
label="Электронная почта",
|
||||||
@ -69,6 +71,20 @@ class CustomAuthenticationForm(AuthenticationForm):
|
|||||||
|
|
||||||
|
|
||||||
class StatisticForm(forms.Form):
|
class StatisticForm(forms.Form):
|
||||||
|
"""
|
||||||
|
Форма отображения интервалов работы пользователя.
|
||||||
|
|
||||||
|
:param email: Поле для ввода email пользователя
|
||||||
|
:type email: :class:`django.forms.fields.EmailField`
|
||||||
|
:param interval: Расчет интервала рабочего времени
|
||||||
|
:type interval: :class:`django.forms.fields.CharField`
|
||||||
|
:param display_format: Формат отображения данных
|
||||||
|
:type display_format: :class:`django.forms.fields.CharField`
|
||||||
|
:param range_start: Дата и время начала работы
|
||||||
|
:type range_start: :class:`django.forms.fields.DateField`
|
||||||
|
:param range_end: Дата и время окончания работы
|
||||||
|
:type range_end: :class:`django.forms.fields.DateField`
|
||||||
|
"""
|
||||||
email = forms.EmailField(
|
email = forms.EmailField(
|
||||||
label='Электроная почта',
|
label='Электроная почта',
|
||||||
)
|
)
|
||||||
|
@ -6,7 +6,11 @@ from django.utils import timezone
|
|||||||
|
|
||||||
|
|
||||||
class UserProfile(models.Model):
|
class UserProfile(models.Model):
|
||||||
"""Модель профиля пользователя"""
|
"""
|
||||||
|
Модель профиля пользователя.
|
||||||
|
|
||||||
|
Профиль создается и изменяется при создании и изменении модель User.
|
||||||
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
@ -32,17 +36,27 @@ def save_user_profile(sender, instance, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
class RoleChangeLogs(models.Model):
|
class RoleChangeLogs(models.Model):
|
||||||
"""Модель для логирования изменений ролей пользователя"""
|
"""
|
||||||
user = models.ForeignKey(to=User, on_delete=models.CASCADE,
|
Модель для логирования изменений ролей пользователя.
|
||||||
help_text='Пользователь, которому присвоили другую роль')
|
"""
|
||||||
|
|
||||||
|
user = models.ForeignKey(to=User, on_delete=models.CASCADE, help_text='Пользователь, которому присвоили другую роль')
|
||||||
old_role = models.IntegerField(default=0, help_text='Старая роль')
|
old_role = models.IntegerField(default=0, help_text='Старая роль')
|
||||||
new_role = models.IntegerField(default=0, help_text='Присвоенная роль')
|
new_role = models.IntegerField(default=0, help_text='Присвоенная роль')
|
||||||
change_time = models.DateTimeField(help_text='Дата и время изменения роли', default=timezone.now)
|
change_time = models.DateTimeField(default=timezone.now, help_text='Дата и время изменения роли')
|
||||||
changed_by = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='changed_by',
|
changed_by = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='changed_by', help_text='Кем была изменена роль')
|
||||||
help_text='Кем была изменена роль')
|
|
||||||
|
|
||||||
|
|
||||||
class UnassignedTicketStatus(models.IntegerChoices):
|
class UnassignedTicketStatus(models.IntegerChoices):
|
||||||
|
"""
|
||||||
|
Класс статусов не распределенных тикетов.
|
||||||
|
|
||||||
|
:param UNASSIGNED: Снят с пользователя, перенесён в буферную группу
|
||||||
|
:param RESTORED: Авторство восстановлено
|
||||||
|
:param NOT_FOUND: Пока нас не было, тикет испарился из буферной группы. Дополнительные действия не требуются
|
||||||
|
:param CLOSED: Тикет уже был закрыт. Дополнительные действия не требуются
|
||||||
|
:param SOLVED: Тикет решён. Записан на пользователя с почтой SOLVED_TICKETS_EMAIL
|
||||||
|
"""
|
||||||
UNASSIGNED = 0, 'Снят с пользователя, перенесён в буферную группу'
|
UNASSIGNED = 0, 'Снят с пользователя, перенесён в буферную группу'
|
||||||
RESTORED = 1, 'Авторство восстановлено'
|
RESTORED = 1, 'Авторство восстановлено'
|
||||||
NOT_FOUND = 2, 'Пока нас не было, тикет испарился из буферной группы. Дополнительные действия не требуются'
|
NOT_FOUND = 2, 'Пока нас не было, тикет испарился из буферной группы. Дополнительные действия не требуются'
|
||||||
@ -51,6 +65,9 @@ class UnassignedTicketStatus(models.IntegerChoices):
|
|||||||
|
|
||||||
|
|
||||||
class UnassignedTicket(models.Model):
|
class UnassignedTicket(models.Model):
|
||||||
assignee = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='tickets')
|
"""
|
||||||
|
Модель не распределенного тикета.
|
||||||
|
"""
|
||||||
|
assignee = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='tickets', help_text='Пользователь, с которого снят тикет')
|
||||||
ticket_id = models.IntegerField(help_text='Номер тикера, для которого сняли ответственного')
|
ticket_id = models.IntegerField(help_text='Номер тикера, для которого сняли ответственного')
|
||||||
status = models.IntegerField(choices=UnassignedTicketStatus.choices, default=UnassignedTicketStatus.UNASSIGNED)
|
status = models.IntegerField(choices=UnassignedTicketStatus.choices, default=UnassignedTicketStatus.UNASSIGNED, help_text='Статус тикета')
|
||||||
|
@ -4,12 +4,18 @@ from main.models import UserProfile
|
|||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
"""
|
||||||
|
Класс serializer для модели User.
|
||||||
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['email']
|
fields = ['email']
|
||||||
|
|
||||||
|
|
||||||
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
|
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
"""
|
||||||
|
Класс serializer для модель профиля пользователя.
|
||||||
|
"""
|
||||||
user = UserSerializer()
|
user = UserSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
135
main/views.py
@ -1,4 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.forms import PasswordResetForm
|
from django.contrib.auth.forms import PasswordResetForm
|
||||||
@ -35,11 +37,16 @@ from .models import UserProfile
|
|||||||
|
|
||||||
class CustomRegistrationView(RegistrationView):
|
class CustomRegistrationView(RegistrationView):
|
||||||
"""
|
"""
|
||||||
Отображение и логика работы страницы регистрации пользователя
|
Отображение и логика работы страницы регистрации пользователя.
|
||||||
|
|
||||||
1. Ввод email пользователя, указанный на Zendesk
|
:param form_class: Форма, которую необходимо заполнить для регистрации
|
||||||
2. В случае если пользователь с данным паролем зарегистрирован на Zendesk и относится к определенной организации, происходит сброс ссылки с установлением пароля на указанный email
|
:type form_class: :class:`forms.CustomRegistrationForm`
|
||||||
3. Создается пользователь class User, а также его профиль
|
:param template_name: Указание пути к html-странице django регистрации
|
||||||
|
: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
|
||||||
|
:type is_allowed: :class:`bool`
|
||||||
"""
|
"""
|
||||||
form_class = CustomRegistrationForm
|
form_class = CustomRegistrationForm
|
||||||
template_name = 'django_registration/registration_form.html'
|
template_name = 'django_registration/registration_form.html'
|
||||||
@ -47,6 +54,16 @@ class CustomRegistrationView(RegistrationView):
|
|||||||
is_allowed = True
|
is_allowed = True
|
||||||
|
|
||||||
def register(self, form: CustomRegistrationForm) -> User:
|
def register(self, form: CustomRegistrationForm) -> User:
|
||||||
|
"""
|
||||||
|
Функция регистрации пользователя.
|
||||||
|
1. Ввод email пользователя, указанный на Zendesk
|
||||||
|
2. В случае если пользователь с данным паролем зарегистрирован на Zendesk и относится к организации SYSTEM,
|
||||||
|
происходит сброс ссылки с установлением пароля на указанный email
|
||||||
|
3. Создается пользователь class User, а также его профиль.
|
||||||
|
|
||||||
|
:param form: Email пользователя на Zendesk
|
||||||
|
:return: user
|
||||||
|
"""
|
||||||
self.is_allowed = True
|
self.is_allowed = True
|
||||||
if check_user_exist(form.data['email']) and get_user_organization(form.data['email']) == 'SYSTEM':
|
if check_user_exist(form.data['email']) and get_user_organization(form.data['email']) == 'SYSTEM':
|
||||||
forms = PasswordResetForm(self.request.POST)
|
forms = PasswordResetForm(self.request.POST)
|
||||||
@ -76,9 +93,11 @@ class CustomRegistrationView(RegistrationView):
|
|||||||
self.is_allowed = False
|
self.is_allowed = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_permission(user) -> None:
|
def set_permission(user: User) -> None:
|
||||||
"""
|
"""
|
||||||
Дает разрешение на просмотр страница администратора, если пользователь имеет роль admin
|
Функция дает разрешение на просмотр страница администратора, если пользователь имеет роль admin.
|
||||||
|
|
||||||
|
:param user: авторизованный пользователь (получает разрешение, имея роль "admin")
|
||||||
"""
|
"""
|
||||||
if user.userprofile.role == 'admin':
|
if user.userprofile.role == 'admin':
|
||||||
content_type = ContentType.objects.get_for_model(UserProfile)
|
content_type = ContentType.objects.get_for_model(UserProfile)
|
||||||
@ -90,8 +109,11 @@ class CustomRegistrationView(RegistrationView):
|
|||||||
|
|
||||||
def get_success_url(self, user: User = None) -> success_url:
|
def get_success_url(self, user: User = None) -> success_url:
|
||||||
"""
|
"""
|
||||||
Возвращает url-адрес страницы, куда нужно перейти после успешной/неуспешной регистрации
|
Функция возвращает url-адрес страницы, куда нужно перейти после успешной/не успешной регистрации.
|
||||||
Используется самой django-registration
|
Используется самой django-registration.
|
||||||
|
|
||||||
|
:param user: пользователь, пытающийся зарегистрироваться
|
||||||
|
:return: адресация на страницу успешной регистрации
|
||||||
"""
|
"""
|
||||||
if self.is_allowed:
|
if self.is_allowed:
|
||||||
return reverse_lazy('password_reset_done')
|
return reverse_lazy('password_reset_done')
|
||||||
@ -102,7 +124,10 @@ class CustomRegistrationView(RegistrationView):
|
|||||||
@login_required()
|
@login_required()
|
||||||
def profile_page(request: WSGIRequest) -> HttpResponse:
|
def profile_page(request: WSGIRequest) -> HttpResponse:
|
||||||
"""
|
"""
|
||||||
Отображение страницы профиля
|
Функция отображения страницы профиля.
|
||||||
|
|
||||||
|
:param request: данные пользователя из БД
|
||||||
|
:return: адресация на страницу пользователя
|
||||||
"""
|
"""
|
||||||
user_profile: UserProfile = request.user.userprofile
|
user_profile: UserProfile = request.user.userprofile
|
||||||
update_profile(user_profile)
|
update_profile(user_profile)
|
||||||
@ -113,14 +138,27 @@ def profile_page(request: WSGIRequest) -> HttpResponse:
|
|||||||
return render(request, 'pages/profile.html', context)
|
return render(request, 'pages/profile.html', context)
|
||||||
|
|
||||||
|
|
||||||
def auth_user(request):
|
def auth_user(request: WSGIRequest) -> ZenpyUser:
|
||||||
|
"""
|
||||||
|
Функция возвращает профиль пользователя на Zendesk.
|
||||||
|
|
||||||
|
:param request: email, subdomain и token пользователя
|
||||||
|
:return: объект пользователя Zendesk
|
||||||
|
"""
|
||||||
admin = ZendeskAdmin().admin
|
admin = ZendeskAdmin().admin
|
||||||
zenpy_user: ZenpyUser = admin.users.search(request.user.email).values[0]
|
zenpy_user: ZenpyUser = admin.users.search(request.user.email).values[0]
|
||||||
return zenpy_user, admin
|
return zenpy_user, admin
|
||||||
|
|
||||||
|
|
||||||
@login_required()
|
@login_required()
|
||||||
def work_page(request, id):
|
def work_page(request: WSGIRequest, id: int) -> HttpResponse:
|
||||||
|
"""
|
||||||
|
Функция отображения страницы "Управления правами" для текущего пользователя (login_required).
|
||||||
|
|
||||||
|
:param request: объект пользователя
|
||||||
|
:param id: id пользователя, используется для динамической адресации
|
||||||
|
:return: адресация на страницу "Управления правами" (либо на страницу "Авторизации", если id и user.id не совпадают
|
||||||
|
"""
|
||||||
users = get_users_list()
|
users = get_users_list()
|
||||||
if request.user.id == id:
|
if request.user.id == id:
|
||||||
engineers = []
|
engineers = []
|
||||||
@ -143,7 +181,16 @@ def work_page(request, id):
|
|||||||
return redirect("login")
|
return redirect("login")
|
||||||
|
|
||||||
|
|
||||||
def user_update(zenpy_user, admin, request):
|
def user_update(zenpy_user: User, admin: User, request: WSGIRequest) -> UserProfile:
|
||||||
|
"""
|
||||||
|
Функция устанавливает пользователю роль "agent" (изменяет профиль).
|
||||||
|
|
||||||
|
:param zenpy_user: Пользователь Zendesk
|
||||||
|
:param admin: Пользователь
|
||||||
|
:param request: Запрос установки роли "agent" в Userprofile
|
||||||
|
:return: Обновленный профиль пользователя
|
||||||
|
"""
|
||||||
|
|
||||||
admin.users.update(zenpy_user)
|
admin.users.update(zenpy_user)
|
||||||
request.user.userprofile.role = "agent"
|
request.user.userprofile.role = "agent"
|
||||||
request.user.userprofile.save()
|
request.user.userprofile.save()
|
||||||
@ -151,7 +198,14 @@ def user_update(zenpy_user, admin, request):
|
|||||||
|
|
||||||
|
|
||||||
@login_required()
|
@login_required()
|
||||||
def work_hand_over(request):
|
def work_hand_over(request: WSGIRequest) -> HttpResponseRedirect:
|
||||||
|
"""
|
||||||
|
Функция позволяет текущему пользователю (login_required) сдать права, а именно сменить в Zendesk роль с "engineer" на "light agent"
|
||||||
|
и установить роль "agent" в БД. Действия выполняются, если исходная роль пользователя "engineer".
|
||||||
|
|
||||||
|
:param request: данные текущего пользователя (login_required)
|
||||||
|
:return: перезагрузка текущей страницы после выполнения смены роли
|
||||||
|
"""
|
||||||
zenpy_user, admin = auth_user(request)
|
zenpy_user, admin = auth_user(request)
|
||||||
if zenpy_user.custom_role_id == ZENDESK_ROLES['engineer']:
|
if zenpy_user.custom_role_id == ZENDESK_ROLES['engineer']:
|
||||||
zenpy_user.custom_role_id = ZENDESK_ROLES['light_agent']
|
zenpy_user.custom_role_id = ZENDESK_ROLES['light_agent']
|
||||||
@ -160,7 +214,13 @@ def work_hand_over(request):
|
|||||||
|
|
||||||
|
|
||||||
@login_required()
|
@login_required()
|
||||||
def work_become_engineer(request):
|
def work_become_engineer(request: WSGIRequest) -> HttpResponseRedirect:
|
||||||
|
"""
|
||||||
|
Функция меняет роль пользователя в Zendesk на "engineer" и присваивает роль "agent" в БД (в случае, если исходная роль пользователя была "light_agent").
|
||||||
|
|
||||||
|
:param request: данные текущего пользователя (login_required)
|
||||||
|
:return: перезагрузка текущей страницы после выполнения смены роли
|
||||||
|
"""
|
||||||
zenpy_user, admin = auth_user(request)
|
zenpy_user, admin = auth_user(request)
|
||||||
if zenpy_user.custom_role_id == ZENDESK_ROLES['light_agent']:
|
if zenpy_user.custom_role_id == ZENDESK_ROLES['light_agent']:
|
||||||
zenpy_user.custom_role_id = ZENDESK_ROLES['engineer']
|
zenpy_user.custom_role_id = ZENDESK_ROLES['engineer']
|
||||||
@ -168,16 +228,34 @@ def work_become_engineer(request):
|
|||||||
return HttpResponseRedirect(reverse('work', args=(request.user.id,)))
|
return HttpResponseRedirect(reverse('work', args=(request.user.id,)))
|
||||||
|
|
||||||
|
|
||||||
def main_page(request):
|
def main_page(request: WSGIRequest) -> HttpResponse:
|
||||||
"""
|
"""
|
||||||
Отображение логгирования на главной странице
|
Функция отображения логгирования на главной странице.
|
||||||
|
|
||||||
|
.. todo::
|
||||||
|
Дописать параметры в документацию:
|
||||||
|
|
||||||
|
:param request:
|
||||||
|
:return:
|
||||||
"""
|
"""
|
||||||
logger = logging.getLogger('main.index')
|
logger = logging.getLogger('main.index')
|
||||||
logger.info('Index page opened')
|
logger.info('Index page opened')
|
||||||
return render(request, 'pages/index.html')
|
return render(request, 'pages/index.html')
|
||||||
|
|
||||||
|
|
||||||
class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, FormView):
|
class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
||||||
|
"""
|
||||||
|
Класс отображения страницы администратора.
|
||||||
|
|
||||||
|
:param permission_required: Права доступа к странице администратора
|
||||||
|
:type permission_required: :class:`str`
|
||||||
|
:param template_name: HTML-шаблон страницы администратора
|
||||||
|
:type template_name: :class:`str`
|
||||||
|
:param form_class: Форма страницы администратора
|
||||||
|
:type form_class: :class:`forms.AdminPageUsersForm`
|
||||||
|
:param success_url: Адрес страницы администратора
|
||||||
|
:type success_url: :class:`HttpResponseRedirect`
|
||||||
|
"""
|
||||||
permission_required = 'main.has_control_access'
|
permission_required = 'main.has_control_access'
|
||||||
template_name = 'pages/adm_ruleset.html'
|
template_name = 'pages/adm_ruleset.html'
|
||||||
form_class = AdminPageUsers
|
form_class = AdminPageUsers
|
||||||
@ -186,7 +264,10 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageM
|
|||||||
|
|
||||||
def form_valid(self, form: AdminPageUsers) -> AdminPageUsers:
|
def form_valid(self, form: AdminPageUsers) -> AdminPageUsers:
|
||||||
"""
|
"""
|
||||||
Функция установки ролей пользователям
|
Функция обновления страницы AdminPageUsers.
|
||||||
|
|
||||||
|
:param form: Форма страницы администратора
|
||||||
|
:return: Обновленная страница (пользователям проставлены новые статусы)
|
||||||
"""
|
"""
|
||||||
users = form.cleaned_data['users']
|
users = form.cleaned_data['users']
|
||||||
if 'engineer' in self.request.POST:
|
if 'engineer' in self.request.POST:
|
||||||
@ -196,6 +277,12 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageM
|
|||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
def make_engineers(self, users):
|
def make_engineers(self, users):
|
||||||
|
"""
|
||||||
|
Функция проходит по списку пользователей, проставляя статус "engineer".
|
||||||
|
|
||||||
|
:param users: Список пользователей
|
||||||
|
:return: Обновленный список пользователей
|
||||||
|
"""
|
||||||
for user in users:
|
for user in users:
|
||||||
make_engineer(user, self.request.user)
|
make_engineer(user, self.request.user)
|
||||||
|
|
||||||
@ -244,9 +331,15 @@ class UsersViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
@login_required()
|
@login_required()
|
||||||
def statistic_page(request):
|
def statistic_page(request: WSGIRequest) -> HttpResponse:
|
||||||
if not request.user.has_perm('main.has_control_access'):
|
"""
|
||||||
raise PermissionDenied
|
Функция отображения страницы статистики (для "superuser").
|
||||||
|
|
||||||
|
:param request: данные о пользователе: email, время и интервал работы. Данные получаем через forms.StatisticForm
|
||||||
|
:return: адресация на страницу статистики
|
||||||
|
"""
|
||||||
|
if not request.user.is_superuser:
|
||||||
|
return redirect('index')
|
||||||
context = {
|
context = {
|
||||||
'pagename': 'страница статистики',
|
'pagename': 'страница статистики',
|
||||||
'errors': list(),
|
'errors': list(),
|
||||||
|