Update docs (views, extra_func, forms, models)

This commit is contained in:
Степаненко Ольга 2021-05-20 16:19:42 +03:00
parent 3443c60a5f
commit 1114c916e9
10 changed files with 136 additions and 242 deletions

View File

@ -11,6 +11,8 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
"""
import os
from pathlib import Path
from dotenv import load_dotenv
# Build paths inside the project like this: BASE_DIR / 'subdir'.

View File

@ -49,3 +49,33 @@ Views
:members:
*****************
Обработка тикетов
*****************
.. automodule:: main.requester
:members:
*********************
Обработка статистики
*********************
.. automodule:: main.statistic_data
:members:
*********************************
Функционал администратора Zendesk
*********************************
.. automodule:: main.zendesk_admin
:members:
********
Тесты
********
.. automodule:: main.tests
:members:

View File

@ -12,10 +12,11 @@ def api_auth() -> dict:
Функция создания пользователя с использованием Zendesk API.
Получает из env Zendesk - email, token, password пользователя.
Если данные валидны и пользователь Zendesk с указанным email и токеном или паролем существует,
создается словарь данных пользователя, полученных через API c Zendesk.
:return: данные пользователя
:return: данные пользователя в виде словаря: id, имя, email, роль, аватар
"""
credentials = {
'subdomain': ACTRL_ZENDESK_SUBDOMAIN

View File

@ -1,9 +1,9 @@
"""
Вспомогательные функции со списками пользователей, статистикой и т.д.
Вспомогательные функции.
"""
import logging
from datetime import timedelta
from typing import Union
from typing import Union, Optional
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
@ -55,7 +55,8 @@ def make_light_agent(user_profile: UserProfile, who_changes: get_user_model()) -
Функция устанавливает пользователю роль легкого агента.
:param user_profile: Профиль пользователя
:return: Вызов функции **update_role** с параметрами: профиль пользователя, роль "light_agent"
:return: Вызов функции **update_role** с параметрами: профиль пользователя, роль "light_agent".
Предварительно снимаем тикеты, находящие в работы у пользователя.
"""
tickets: SearchResultGenerator = get_tickets_list(user_profile.user.email)
ticket: ZenpyTicket
@ -85,7 +86,7 @@ def make_light_agent(user_profile: UserProfile, who_changes: get_user_model()) -
def get_users_list() -> list:
"""
Функция **get_users_list** возвращает список пользователей Zendesk, относящихся к организации SYSTEM.
Функция возвращает список пользователей Zendesk, относящихся к организации SYSTEM.
"""
zendesk = zenpy
@ -95,23 +96,29 @@ def get_users_list() -> list:
return users
def get_tickets_list(email) -> list:
def get_tickets_list(email: str) -> Optional[list]:
"""
Функция возвращает список тикетов пользователя Zendesk
Функция возвращает список тикетов пользователя Zendesk.
:param email: Email пользователя
:return: Список тикетов пользователя
"""
return TicketListRequester().get_tickets_list_for_user(zenpy.get_user(email))
def get_tickets_list_for_group(group_name):
def get_tickets_list_for_group(group_name: str) -> Optional[list]:
"""
Функция возвращает список не назначенных, нерешённых тикетов группы Zendesk
Функция возвращает список не назначенных, не решённых тикетов группы Zendesk.
:param group_name: Название группы пользователя
:return: Список тикетов группы
"""
return TicketListRequester().get_tickets_list_for_group(zenpy.get_group(group_name))
def update_profile(user_profile: UserProfile) -> None:
"""
Функция обновляет профиль пользователя в соответствии с текущим в Zendesk.
Функция обновляет профиль пользователя в БД в соответствии с текущим в Zendesk.
:param user_profile: Профиль пользователя
:return: Обновленный, в соответствие с текущими данными в Zendesk, профиль пользователя
@ -148,6 +155,9 @@ def check_user_auth(email: str, password: str) -> bool:
"""
Функция проверяет, верны ли входные данные.
:param email: Email пользователя
:param password: Пароль пользователя
:return: Существует ли пользователь
:raise: :class:`APIException`: исключение, вызываемое если пользователь не аутентифицирован
"""
creds = {
@ -165,7 +175,7 @@ def check_user_auth(email: str, password: str) -> bool:
def update_user_in_model(profile: UserProfile, zendesk_user: ZenpyUser) -> None:
"""
Функция обновляет профиль пользователя при изменении данных пользователя на Zendesk.
Функция обновляет профиль пользователя в модели при изменении данных пользователя на Zendesk.
:param profile: Профиль пользователя
:param zendesk_user: Данные пользователя в Zendesk
@ -181,7 +191,10 @@ def update_user_in_model(profile: UserProfile, zendesk_user: ZenpyUser) -> None:
def count_users(users: list) -> tuple:
"""
Функция подсчета количества сотрудников с ролями engineer и light_agent
Функция подсчета количества сотрудников с ролями engineer и light_agent.
:param users: Список пользователей
:return: Количество инженеров, количество light_agents
"""
engineers, light_agents = 0, 0
for user in users:
@ -194,7 +207,7 @@ def count_users(users: list) -> tuple:
def update_users_in_model() -> list:
"""
Обновляет пользователей в модели UserProfile по списку пользователей в организации
Обновляет пользователей в модели UserProfile по списку пользователей в организации.
"""
users = get_users_list()
for user in users:
@ -253,7 +266,13 @@ class DatabaseHandler(logging.Handler):
def __init__(self):
logging.Handler.__init__(self)
def emit(self, record):
def emit(self, record: logging.LogRecord) -> None:
"""
Функция записи в базу данных лога с изменением роли пользователя.
:param record: Лог смены роли пользователя
:return: Запись в БД лога по смене роли пользователя с указанием новой и старой роли, а также автора изменения
"""
database = RoleChangeLogs()
users = record.msg
if users[1]:
@ -284,7 +303,7 @@ class CsvFormatter(logging.Formatter):
"""
Функция форматирует запись смены роли пользователя в строку.
:param record: Запись смены роли пользователя.
:param record: Лог смены роли пользователя.
:return: Строка с записью смены пользователя.
"""
users = record.msg
@ -307,7 +326,7 @@ class CsvFormatter(logging.Formatter):
return msg
def log(user, admin=None):
def log(user: get_user_model(), admin: get_user_model() = None) -> None:
"""
Функция осуществляет запись логов в базу данных и csv файл.
@ -335,7 +354,7 @@ def set_session_params_for_work_page(request: WSGIRequest, count: int = None, is
:param request: Получение данных с рабочей страницы пользователя
:param count: Количество запрошенных тикетов
:param is_confirm: Назначение тикетов
:param is_confirm: Назначены ли тикеты
:return: Перезагрузка страницы "Управление правами" соответствующего пользователя
"""
request.session['is_confirm'] = is_confirm

View File

@ -1,5 +1,5 @@
"""
Формы.
Формы, использующиеся в приложении.
"""
from django import forms
from django.contrib.auth.forms import AuthenticationForm

View File

@ -13,7 +13,6 @@ from access_controller.settings import ZENDESK_ROLES
class UserProfile(models.Model):
"""
Модель профиля пользователя.
Профиль создается и изменяется при создании и изменении модель User.
"""
@ -31,11 +30,7 @@ class UserProfile(models.Model):
@property
def zendesk_role(self) -> str:
"""
Функция возвращает роль пользователя в Zendesk.
В формате str, либо UNDEFINED, если пользователь не найден
:return: Роль пользователя в Zendesk
Роль пользователя в Zendesk, либо UNDEFINED, если пользователь не найден.
"""
for role, r_id in ZENDESK_ROLES.items():
if r_id == self.custom_role_id:
@ -44,12 +39,12 @@ class UserProfile(models.Model):
@receiver(post_save, sender=get_user_model())
def create_user_profile(instance, created, **kwargs) -> None:
def create_user_profile(instance: get_user_model(), created: bool, **kwargs) -> None:
"""
Функция создания профиля пользователя (Userprofile) при регистрации пользователя.
:param instance: Экземпляр класса User
:param created: Создание профиля пользователя
:param created: Существует ли пользователь
:param kwargs: Параметры
:return: Обновленный список объектов профилей пользователей
"""
@ -58,7 +53,7 @@ def create_user_profile(instance, created, **kwargs) -> None:
@receiver(post_save, sender=get_user_model())
def save_user_profile(instance, **kwargs) -> None:
def save_user_profile(instance: get_user_model(), **kwargs) -> None:
"""
Функция записи БД профиля пользователя.
@ -84,7 +79,7 @@ class RoleChangeLogs(models.Model):
class UnassignedTicketStatus(models.IntegerChoices):
"""
Класс статусов не распределенных тикетов.
Модель статусов нераспределенных тикетов.
:param UNASSIGNED: Снят с пользователя, перенесён в буферную группу
:param RESTORED: Авторство восстановлено
@ -95,7 +90,7 @@ class UnassignedTicketStatus(models.IntegerChoices):
"""
UNASSIGNED = 0, 'Снят с пользователя, перенесён в буферную группу'
RESTORED = 1, 'Авторство восстановлено'
NOT_FOUND = 2, 'Пока нас не было, тикет испарился из ' \
NOT_FOUND = 2, 'Пока нас не было, тикет был перенесен из ' \
'буферной группы. Дополнительные действия не требуются'
CLOSED = 3, 'Тикет уже был закрыт. Дополнительные действия не требуются'
SOLVED = 4, 'Тикет решён. Записан на пользователя с почтой SOLVED_TICKETS_EMAIL'

View File

@ -1,6 +1,8 @@
"""
Обработка тикетов.
Обработка тикетов, составление списков тикетов для пользователя и группы пользователей.
"""
from typing import Optional
import requests
from zenpy import TicketApi
from zenpy.lib.api_objects import Ticket
@ -11,6 +13,13 @@ from main.zendesk_admin import zenpy
class TicketListRequester:
"""
Класс обработки тикетов.
:param email: Email пользователя
:type display: :class:`str`
:param token_or_password: Токен или пароль
:type display: :class:`str`
:param prefix: Формат строка url страницы Zendesk
:type display: :class:`str`
"""
def __init__(self):
self.email = zenpy.credentials['email']
@ -21,16 +30,22 @@ class TicketListRequester:
self.token_or_password = zenpy.credentials.get('password')
self.prefix = f'https://{zenpy.credentials.get("subdomain")}.zendesk.com/api/v2/'
def get_tickets_list_for_user(self, zendesk_user: zenpy) -> str:
def get_tickets_list_for_user(self, zendesk_user: zenpy) -> Optional[list]:
"""
Функция получения списка тикетов пользователя Zendesk.
:param zendesk_user: Пользователь Zendesk
:return: Список тикетов, назначенных на данного пользователя в Zendesk
"""
url = self.prefix + f'users/{zendesk_user.id}/tickets/assigned'
return self._get_tickets(url)
def get_tickets_list_for_group(self, group: zenpy) -> list():
def get_tickets_list_for_group(self, group: zenpy) -> Optional[list]:
"""
Функция получения списка тикетов группы пользователей Zendesk.
:param group: Название группы
:return: Список тикетов
"""
url = self.prefix + '/tickets'
all_tickets = self._get_tickets(url)
@ -40,9 +55,12 @@ class TicketListRequester:
tickets.append(ticket)
return tickets
def _get_tickets(self, url: str) -> list():
def _get_tickets(self, url: str) -> Optional[list]:
"""
Функция получения полного списка тикетов по url.
:param url: Url Zendesk c указанием тикетов, назначенных на пользователя
:return: Список тикетов
"""
response = requests.get(url, auth=(self.email, self.token_or_password))
tickets = []

View File

@ -1,5 +1,5 @@
"""
Сериализаторы.
Сериализаторы, используемые в приложении.
"""
from django.contrib.auth import get_user_model
from rest_framework import serializers

View File

@ -61,7 +61,7 @@ def setup_context(**kwargs) -> Dict[str, Any]:
class CustomRegistrationView(RegistrationView):
"""
Отображение и логика работы страницы регистрации пользователя.
Класс отображения и логики работы страницы регистрации пользователя.
:param form_class: Форма, которую необходимо заполнить для регистрации
:type form_class: :class:`forms.CustomRegistrationForm`
@ -86,9 +86,12 @@ class CustomRegistrationView(RegistrationView):
def register(self, form: CustomRegistrationForm) -> Optional[get_user_model()]:
"""
Функция регистрации пользователя.
1. Ввод email пользователя, указанный на Zendesk
1. Ввод email пользователя, указанный на Zendesk.
2. В случае если пользователь с данным паролем зарегистрирован на Zendesk и относится к организации SYSTEM,
происходит сброс ссылки с установлением пароля на указанный email
происходит сброс ссылки с установлением пароля на указанный email.
3. Создается пользователь class User, а также его профиль.
:param form: Email пользователя на Zendesk
@ -133,7 +136,7 @@ class CustomRegistrationView(RegistrationView):
"""
Функция дает разрешение на просмотр страница администратора, если пользователь имеет роль admin.
:param user: авторизованный пользователь (получает разрешение, имея роль "admin")
:param user: Авторизованный пользователь (получает разрешение, имея роль "admin")
"""
if user.userprofile.role == 'admin':
content_type = ContentType.objects.get_for_model(UserProfile)
@ -148,8 +151,8 @@ class CustomRegistrationView(RegistrationView):
Функция возвращает url-адрес страницы, куда нужно перейти после успешной/не успешной регистрации.
Используется самой django-registration.
:param user: пользователь, пытающийся зарегистрироваться
:return: адресация на страницу успешной регистрации
:param user: Пользователь, пытающийся зарегистрироваться
:return: Адресация на страницу успешной регистрации
"""
return self.urls[self.redirect_url]
@ -158,8 +161,8 @@ def registration_error(request: WSGIRequest) -> HttpResponse:
"""
Функция отображения страницы ошибки регистрации.
:param request: регистрация
:return: адресация на страницу ошибки
:param request: Регистрация
:return: Адресация на страницу ошибки
"""
return render(request, 'django_registration/registration_error.html')
@ -169,8 +172,8 @@ def profile_page(request: WSGIRequest) -> HttpResponse:
"""
Функция отображения страницы профиля.
:param request: данные пользователя из БД
:return: адресация на страницу пользователя
:param request: Данные пользователя из БД
:return: Адресация на страницу пользователя
"""
user_profile: UserProfile = request.user.userprofile
update_profile(user_profile)
@ -187,9 +190,9 @@ def work_page(request: WSGIRequest, required_id: int) -> HttpResponse:
"""
Функция отображения страницы "Управления правами" для текущего пользователя (login_required).
:param request: объект пользователя
:param request: Объект пользователя
:param id: id пользователя, используется для динамической адресации
:return: адресация на страницу "Управления правами" (либо на страницу "Авторизации", если id и user.id не совпадают
:return: Адресация на страницу "Управления правами" (либо на страницу "Авторизации", если id и user.id не совпадают
"""
users = get_users_list()
if request.user.id == required_id:
@ -227,8 +230,8 @@ def work_hand_over(request: WSGIRequest) -> HttpResponseRedirect:
"""
Функция позволяет текущему пользователю сдать права, а именно сменить в Zendesk роль с "engineer" на "light_agent"
:param request: данные текущего пользователя (login_required)
:return: перезагрузка текущей страницы после выполнения смены роли
:param request: Данные текущего пользователя (login_required)
:return: Перезагрузка текущей страницы после выполнения смены роли
"""
make_light_agent(request.user.userprofile, request.user)
return set_session_params_for_work_page(request)
@ -240,8 +243,8 @@ def work_become_engineer(request: WSGIRequest) -> HttpResponseRedirect:
Функция позволяет текущему пользователю получить права, а именно сменить в Zendesk роль с "light_agent"
на "engineer".
:param request: данные текущего пользователя (login_required)
:return: перезагрузка текущей страницы после выполнения смены роли
:param request: Данные текущего пользователя (login_required)
:return: Перезагрузка текущей страницы после выполнения смены роли
"""
make_engineer(request.user.userprofile, request.user)
return set_session_params_for_work_page(request)
@ -250,9 +253,10 @@ def work_become_engineer(request: WSGIRequest) -> HttpResponseRedirect:
@login_required()
def work_get_tickets(request: WSGIRequest) -> HttpResponse:
"""
Функция получения тикетов в работу.
:param request:
:return:
:param request: Запрос на принятие тикетов в работу
:return: Перезагрузка рабочей страницы
"""
zenpy_user = zenpy.get_user(request.user.email)
@ -289,6 +293,8 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageM
:type form_class: :class:`forms.AdminPageUsersForm`
:param success_url: Адрес страницы администратора
:type success_url: :class:`HttpResponseRedirect`
:param success_message: Уведомление об изменении прав
:type success_url: :class:`str`
"""
permission_required = 'main.has_control_access'
template_name = 'pages/adm_ruleset.html'
@ -333,7 +339,12 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageM
class CustomLoginView(LoginView):
"""
Отображение страницы авторизации пользователя
Класс отображения страницы авторизации пользователя.
:param extra_context: Добавление в контекст статус пользователя "залогинен"
:type extra_context: :class:`dict`
:param form_class: Форма страницы авторизации
:type form_class: :class: forms.CustomAuthenticationForm
"""
extra_context = setup_context(login_lit=True)
form_class = CustomAuthenticationForm
@ -353,7 +364,8 @@ class UsersViewSet(viewsets.ReadOnlyModelViewSet):
def list(self, request: WSGIRequest, *args, **kwargs) -> Response:
"""
Функция возвращает список пользователей, список пользователей Zendesk, количество engineers и light-agents.
Функция возвращает список пользователей Zendesk, количество engineers и light-agents.
:param request: Запрос
:param args: Аргументы
:param kwargs: Параметры
@ -376,6 +388,7 @@ class UsersViewSet(viewsets.ReadOnlyModelViewSet):
def choose_users(zendesk: list, model: list) -> list:
"""
Функция формирует список пользователей, которые не зарегистрированы у нас.
:param zendesk: Список пользователей Zendesk
:param model: Список пользователей (модель Userprofile)
:return: Список
@ -389,7 +402,8 @@ class UsersViewSet(viewsets.ReadOnlyModelViewSet):
@staticmethod
def get_zendesk_users(users: list) -> list:
"""
Получение списка пользователей Zendesk, не являющихся админами.
Функция получения списка пользователей Zendesk, не являющихся админами.
:param users: Список пользователей
:return: Список пользователей, не являющимися администраторами.
"""
@ -406,8 +420,8 @@ def statistic_page(request: WSGIRequest) -> HttpResponse:
"""
Функция отображения страницы статистики (для "superuser").
:param request: данные о пользователе: email, время и интервал работы. Данные получаем через forms.StatisticForm
:return: адресация на страницу статистики
:param request: Данные о пользователе: email, время и интервал работы. Данные получаем через forms.StFatisticForm
:return: Адресация на страницу статистики
"""
# if not request.user.has_perm('main.has_control_access'):

View File

@ -1,185 +0,0 @@
# pyenchant
#
# Copyright (C) 2004-2008, Ryan Kelly
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
#
# In addition, as a special exception, you are
# given permission to link the code of this program with
# non-LGPL Spelling Provider libraries (eg: a MSFT Office
# spell checker backend) and distribute linked combinations including
# the two. You must obey the GNU Lesser General Public License in all
# respects for all of the code used other than said providers. If you modify
# this file, you may extend this exception to your version of the
# file, but you are not obligated to do so. If you do not wish to
# do so, delete this exception statement from your version.
#
"""
enchant.tokenize.en: Tokenizer for the English language
This module implements a PyEnchant text tokenizer for the English
language, based on very simple rules.
"""
import unicodedata
import enchant.tokenize
class tokenize(enchant.tokenize.tokenize): # noqa: N801
"""Iterator splitting text into words, reporting position.
This iterator takes a text string as input, and yields tuples
representing each distinct word found in the text. The tuples
take the form:
(<word>,<pos>)
Where <word> is the word string found and <pos> is the position
of the start of the word within the text.
The optional argument <valid_chars> may be used to specify a
list of additional characters that can form part of a word.
By default, this list contains only the apostrophe ('). Note that
these characters cannot appear at the start or end of a word.
"""
_DOC_ERRORS = ["pos", "pos"]
def __init__(self, text, valid_chars=None):
self._valid_chars = valid_chars
self._text = text
self._offset = 0
# Select proper implementation of self._consume_alpha.
# 'text' isn't necessarily a string (it could be e.g. a mutable array)
# so we can't use isinstance(text, str) to detect unicode.
# Instead we typetest the first character of the text.
# If there's no characters then it doesn't matter what implementation
# we use since it won't be called anyway.
try:
char1 = text[0]
except IndexError:
self._initialize_for_binary()
else:
if isinstance(char1, str):
self._initialize_for_unicode()
else:
self._initialize_for_binary()
def _initialize_for_binary(self):
self._consume_alpha = self._consume_alpha_b
if self._valid_chars is None:
self._valid_chars = ("'",)
def _initialize_for_unicode(self):
self._consume_alpha = self._consume_alpha_u
if self._valid_chars is None:
# XXX TODO: this doesn't seem to work correctly with the
# MySpell provider, disabling for now.
# Allow unicode typographic apostrophe
# self._valid_chars = (u"'",u"\u2019")
self._valid_chars = ("'",)
def _consume_alpha_b(self, text, offset):
"""Consume an alphabetic character from the given bytestring.
Given a bytestring and the current offset, this method returns
the number of characters occupied by the next alphabetic character
in the string. Non-ASCII bytes are interpreted as utf-8 and can
result in multiple characters being consumed.
"""
assert offset < len(text)
if text[offset].isalpha():
return 1
elif text[offset] >= "\x80":
return self._consume_alpha_utf8(text, offset)
return 0
def _consume_alpha_utf8(self, text, offset):
"""Consume a sequence of utf8 bytes forming an alphabetic character."""
incr = 2
u = ""
while not u and incr <= 4:
try:
try:
# In the common case this will be a string
u = text[offset : offset + incr].decode("utf8")
except AttributeError:
# Looks like it was e.g. a mutable char array.
try:
s = text[offset : offset + incr].tostring()
except AttributeError:
s = "".join([c for c in text[offset : offset + incr]])
u = s.decode("utf8")
except UnicodeDecodeError:
incr += 1
if not u:
return 0
if u.isalpha():
return incr
if unicodedata.category(u)[0] == "M":
return incr
return 0
def _consume_alpha_u(self, text, offset):
"""Consume an alphabetic character from the given unicode string.
Given a unicode string and the current offset, this method returns
the number of characters occupied by the next alphabetic character
in the string. Trailing combining characters are consumed as a
single letter.
"""
assert offset < len(text)
incr = 0
if text[offset].isalpha():
incr = 1
while offset + incr < len(text):
if unicodedata.category(text[offset + incr])[0] != "M":
break
incr += 1
return incr
def next(self):
text = self._text
offset = self._offset
while offset < len(text):
# Find start of next word (must be alpha)
while offset < len(text):
incr = self._consume_alpha(text, offset)
if incr:
break
offset += 1
cur_pos = offset
# Find end of word using, allowing valid_chars
while offset < len(text):
incr = self._consume_alpha(text, offset)
if not incr:
if text[offset] in self._valid_chars:
incr = 1
else:
break
offset += incr
# Return if word isn't empty
if cur_pos != offset:
# Make sure word doesn't end with a valid_char
while text[offset - 1] in self._valid_chars:
offset = offset - 1
self._offset = offset
return (text[cur_pos:offset], cur_pos)
self._offset = offset
raise StopIteration()