This commit is contained in:
Dmitriy Andreev 2021-05-13 20:16:26 +03:00
commit 381ae363e3
11 changed files with 452 additions and 106 deletions

View File

@ -48,9 +48,9 @@ cp .env.example .env
sudo apt install make
pip install --upgrade pip
pip install -r requirements/dev.txt
(set -a && source .env && ./manage.py migrate)
(set -a && source .env && ./manage.py loaddata data.json)
(set -a && source .env && ./manage.py runserver)
./manage.py migrate
./manage.py loaddata data.json
./manage.py runserver
```
## Перед запуском для тестирования:
@ -65,7 +65,7 @@ pip install -r requirements/dev.txt
- Перейти в папку приложения
- Активировать виртуальное окружение
- Выполнить команду `pip install -r requirements/dev.txt`
- В виртуальное окружение добавить следующие переменные:
- В файл `.env` добавить следующие переменные:
```

View File

@ -11,6 +11,7 @@ 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'.
BASE_DIR = Path(__file__).resolve().parent.parent
@ -18,6 +19,9 @@ BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
# Load environment variables from .env
load_dotenv()
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv('ACTRL_SECRET_KEY', 'empty')

59
fixtures/profile.json Normal file
View File

@ -0,0 +1,59 @@
[
{
"model": "auth.user",
"pk": 1,
"fields": {
"password": "pbkdf2_sha256$216000$gHBBCr1jBELf$ZkEDW3IEd8Wij7u8vkv+0Eze32CS01bcaYWhcD9OIC4=",
"last_login": null,
"is_superuser": true,
"username": "idar.sokurov.05@mail.ru",
"first_name": "",
"last_name": "",
"email": "idar.sokurov.05@mail.ru",
"is_staff": true,
"is_active": true,
"date_joined": "2021-03-10T16:38:56.303Z",
"groups": [],
"user_permissions": [
33
]
}
},
{
"model": "main.userprofile",
"pk": 1,
"fields": {
"name": "ZendeskAdmin",
"user": 1,
"role": "admin"
}
},
{
"model": "auth.user",
"pk": 2,
"fields": {
"password": "pbkdf2_sha256$216000$5qLJgrm2Quq9$KDBNNymVZXkUx0HKBPFst2m83kLe0egPBnkW7KnkORU=",
"last_login": null,
"is_superuser": false,
"username": "krav-88@mail.ru",
"first_name": "",
"last_name": "",
"email": "krav-88@mail.ru",
"is_staff": false,
"is_active": true,
"date_joined": "2021-03-10T16:38:56.303Z",
"groups": [],
"user_permissions": []
}
},
{
"model": "main.userprofile",
"pk": 2,
"fields": {
"name": "UserForAccessTest",
"user": 2,
"role": "agent",
"custom_role_id": "360005209000"
}
}
]

View File

@ -6,8 +6,8 @@ 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.exception import APIException
from zenpy.lib.generator import SearchResultGenerator
from access_controller.settings import ZENDESK_ROLES as ROLES, ACTRL_ZENDESK_SUBDOMAIN

View File

@ -140,3 +140,23 @@ class StatisticForm(forms.Form):
}
),
)
class WorkGetTicketsForm(forms.Form):
"""
Форма получения количества тикетов для страницы work и work_get_tickets.
:param count_tickets: Поле для ввода количества тикетов
:type count_tickets: :class:`django.forms.fields.IntegerField`
"""
count_tickets = forms.IntegerField(
min_value=0,
max_value=100,
required=True,
widget=forms.NumberInput(
attrs={
'class': 'form-control mb-3',
'value': 1
}
),
)

View File

@ -3,56 +3,62 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<nav class="navbar navbar-light" style="background-color: #113A60;">
<nav class="navbar navbar-light py-3" style="background-color: #113A60;">
<a class="navbar-brand" href="{% url 'index' %}">
<img src="{% static 'main/img/logo_real.png' %}" width="107" height="22" class="d-inline-block align-top" style="margin-left: 15px" alt="" loading="lazy">
<t style="color:#FFFFFF">Access Controller</t>
<t class="px-2" style="color:#FFFFFF">Access Controller</t>
</a>
{% if request.user.is_authenticated %}
<div class="btn-group" role="group" aria-label="Basic example" style="margin-right: 9px">
<a {% if profile_lit %}
{% url 'profile' as profile_url %}
<a {% if request.path == profile_url %}
class="btn btn-primary"
{% else %}
class="btn btn-secondary"
{% endif %}
href="{% url 'profile' %}">Профиль</a>
href="{{ profile_url }}">Профиль</a>
{% if perms.main.has_control_access %}
<a {% if control_lit %}
{% url 'control' as control_url %}
<a {% if request.path == control_url %}
class="btn btn-primary"
{% else %}
class="btn btn-secondary"
{% endif %}
href="{% url 'control' %}">Управление</a>
<a {% if stats_lit %}
href="{{ control_url }}">Управление</a>
{% url 'statistic' as statistic_url %}
<a {% if request.path == statistic_url %}
class="btn btn-primary"
{% else %}
class="btn btn-secondary"
{% endif %}
href="{% url 'statistic' %}">Статистика</a>
href="{{ statistic_url }}">Статистика</a>
{% else %}
<a {% if work_lit %}
{% url 'work' request.user.id as work_url %}
<a {% if request.path == work_url %}
class="btn btn-primary"
{% else %}
class="btn btn-secondary"
{% endif %}
href="{% url 'work' request.user.id %}">Запрос прав</a>
href="{{ work_url }}">Запрос прав</a>
{% endif %}
<a class="btn btn-secondary" href="{% url 'logout' %}">Выйти</a>
</div>
{% else %}
<div class="btn-group" role="group" aria-label="Basic example" style="margin-right: 9px">
<a {% if login_lit %}
{% url 'login' as login_url %}
<a {% if request.path == login_url %}
class="btn btn-primary"
{% else %}
class="btn btn-secondary"
{% endif %}
href="/accounts/login">Войти</a>
<a {% if registration_lit %}
href="{{ login_url }}">Войти</a>
{% url 'registration' as registration_url %}
<a {% if request.path == registration_url %}
class="btn btn-primary"
{% else %}
class="btn btn-secondary"
{% endif %}
href="/accounts/register">Зарегистрироваться</a>
href="{{ registration_url }}">Зарегистрироваться</a>
</div>
{% endif %}
</nav>

View File

@ -66,10 +66,11 @@
<a href="/work/hand_over" class="hand-over-acess-button default-button">Сдать права инженера</a>
</div>
<div class="col-10">
<form method="GET" action="/work/get_tickets">
<input class="form-control mb-3" type="number" min="1" value="1" name="count_tickets">
<button type="submit" class="default-button">Взять тикеты в работу</button>
</form>
<form method="post" action="{% url 'work_get_tickets' %}">
{% csrf_token %}
{{ get_tickets_form.count_tickets }}
<button type="submit" class="default-button">Взять тикеты в работу</button>
</form>
</div>
{% for message in messages %}
<script>create_notification('{{message}}','','{{message.tags}}',2000)</script>

View File

@ -1,8 +1,10 @@
from unittest.mock import patch
from urllib.parse import urlparse
import random
from unittest.mock import patch, Mock
from django.contrib.auth.models import User
from django.core import mail
from django.http import HttpResponseRedirect
from django.template.loader import render_to_string
from django.test import TestCase, Client
from django.urls import reverse, reverse_lazy
from django.utils import translation
@ -35,39 +37,20 @@ class RegistrationTestCase(TestCase):
resp = self.client.post(reverse('registration'), data={'email': '123@test.ru'})
self.assertContains(resp, 'Этот адрес электронной почты уже используется', count=1, status_code=200)
def test_registration_email_sending(self):
# TODO: Найти способ лучше проверять сообщения
email_template = [
'',
'Вы получили это письмо, потому что вы (или кто-то другой) запросили восстановление пароля '
'от учётной записи на сайте testserver, которая связана с этим адресом электронной почты.',
'',
'Пожалуйста, перейдите на эту страницу и введите новый пароль:',
'',
'url',
'',
f'Ваше имя пользователя (на случай, если вы его забыли): {self.any_zendesk_user_email}',
'',
'Спасибо, что используете наш сайт!',
'',
'Команда сайта testserver',
'',
'',
'',
]
with self.settings(EMAIL_BACKEND=self.email_backend) and translation.override('ru'):
self.client.post(reverse('registration'), data={'email': self.any_zendesk_user_email})
def test_registration_send_email(self):
with self.settings(EMAIL_BACKEND=self.email_backend):
response: HttpResponseRedirect = \
self.client.post(reverse('registration'), data={'email': self.any_zendesk_user_email})
self.assertEqual(response.status_code, 302)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].to, [self.zendesk_admin_email])
self.assertEqual(mail.outbox[0].from_email, sets.DEFAULT_FROM_EMAIL)
self.assertEqual(mail.outbox[0].to, [self.any_zendesk_user_email])
message = mail.outbox[0].body.split('\n')
for i in range(len(message)):
if email_template[i] != 'url':
self.assertEqual(message[i], email_template[i])
else:
self.assertTrue(urlparse(message[i]).scheme)
# context that the email template was rendered with
email_context = response.context[0].dicts[1]
correct_subject = render_to_string('registration/password_reset_subject.txt', email_context, response.request)
self.assertEqual(mail.outbox[0].subject, correct_subject.strip())
correct_body = render_to_string('registration/password_reset_email.html', email_context, response.request)
self.assertEqual(mail.outbox[0].body, correct_body)
def test_registration_user_creating(self):
with self.settings(EMAIL_BACKEND=self.email_backend):
@ -148,3 +131,284 @@ class MakeEngineerTestCase(TestCase):
self.assertEqual(len(call_list), 2)
for obj in mock_objects:
self.assertEqual(obj[0][0].custom_role_id, sets.ZENDESK_ROLES['engineer'])
class PasswordResetTestCase(TestCase):
fixtures = ['fixtures/test_make_engineer.json']
def setUp(self):
self.user = '123@test.ru'
self.email_backend = 'django.core.mail.backends.locmem.EmailBackend'
self.client = Client()
self.client.force_login(User.objects.get(email=self.user))
def test_redirect(self):
with self.settings(EMAIL_BACKEND=self.email_backend):
resp = self.client.post(reverse_lazy('password_reset'), data={'email': self.user})
self.assertRedirects(resp, reverse('password_reset_done'))
self.assertEqual(resp.status_code, 302)
def test_send_email(self):
with self.settings(EMAIL_BACKEND=self.email_backend):
response: HttpResponseRedirect = \
self.client.post(reverse_lazy('password_reset'), data={'email': self.user})
self.assertEqual(response.status_code, 302)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].to, [self.user])
# context that the email template was rendered with
email_context = response.context[0].dicts[1]
correct_subject = render_to_string('registration/password_reset_subject.txt', email_context, response.request)
self.assertEqual(mail.outbox[0].subject, correct_subject.strip())
correct_body = render_to_string('registration/password_reset_email.html', email_context, response.request)
self.assertEqual(mail.outbox[0].body, correct_body)
def test_email_invalid(self):
with self.settings(EMAIL_BACKEND=self.email_backend) and translation.override('ru'):
resp = self.client.post(reverse_lazy('password_reset'), data={'email': 1})
self.assertContains(resp, 'Введите правильный адрес электронной почты.', count=1, status_code=200)
def test_user_does_not_exist(self):
with self.settings(EMAIL_BACKEND=self.email_backend):
resp = self.client.post(reverse_lazy('password_reset'), data={'email': self.user + str(random.random())})
self.assertRedirects(resp, reverse('password_reset_done'))
self.assertEqual(resp.status_code, 302)
self.assertEqual(len(mail.outbox), 0)
class PasswordChangeTestCase(TestCase):
fixtures = ['fixtures/test_make_engineer.json']
def setUp(self):
self.user = '123@test.ru'
self.client = Client()
self.client.force_login(User.objects.get(email=self.user))
self.set_password()
def set_password(self):
user: User = User.objects.get(email=self.user)
user.set_password('ImpossiblyHardPassword')
user.save()
self.client.force_login(User.objects.get(email=self.user))
def test_change_successful(self):
self.client.post(
reverse_lazy('password_change'),
data={
'old_password': 'ImpossiblyHardPassword',
'new_password1': 'EasyPassword',
'new_password2': 'EasyPassword',
}
)
user = User.objects.get(email=self.user)
self.assertTrue(user.check_password('EasyPassword'))
def test_invalid_old_password(self):
with translation.override('ru'):
resp = self.client.post(
reverse_lazy('password_change'),
data={
'old_password': 'EasyPassword',
'new_password1': 'EasyPassword',
'new_password2': 'EasyPassword',
}
)
self.assertContains(resp, 'Ваш старый пароль введен неправильно', count=1, status_code=200)
def test_different_new_passwords(self):
with translation.override('ru'):
resp = self.client.post(
reverse_lazy('password_change'),
data={
'old_password': 'ImpossiblyHardPassword',
'new_password1': 'EasyPassword',
'new_password2': 'EasyPassword1',
}
)
self.assertContains(resp, 'Введенные пароли не совпадают', count=1, status_code=200)
def test_invalid_new_password1(self):
with translation.override('ru'):
resp = self.client.post(
reverse_lazy('password_change'),
data={
'old_password': 'ImpossiblyHardPassword',
'new_password1': 'short',
'new_password2': 'short',
}
)
self.assertContains(resp, 'Введённый пароль слишком короткий', count=1, status_code=200)
def test_invalid_new_password2(self):
with translation.override('ru'):
resp = self.client.post(
reverse_lazy('password_change'),
data={
'old_password': 'ImpossiblyHardPassword',
'new_password1': '123123123123123123132123123',
'new_password2': '123123123123123123132123123',
}
)
self.assertContains(resp, 'Введённый пароль состоит только из цифр', count=1, status_code=200)
def test_invalid_new_password3(self):
with translation.override('ru'):
resp = self.client.post(
reverse_lazy('password_change'),
data={
'old_password': 'ImpossiblyHardPassword',
'new_password1': self.user,
'new_password2': self.user,
}
)
self.assertContains(resp, 'Введённый пароль слишком похож на имя пользователя', count=1, status_code=200)
class GetTicketsTestCase(TestCase):
"""
Класс тестов для проверки функции получения тикетов.
"""
fixtures = ['fixtures/test_make_engineer.json']
def setUp(self):
"""
Предустановленные значения для проведения тестов.
"""
self.light_agent = '123@test.ru'
self.engineer = 'customer@example.com'
self.client = Client()
self.client.force_login(User.objects.get(email=self.engineer))
self.light_agent_client = Client()
self.light_agent_client.force_login(User.objects.get(email=self.light_agent))
@patch('main.views.zenpy.get_user')
@patch('main.extra_func.zenpy')
def test_redirect(self, ZenpyMock, GetUserMock):
"""
Функция проверки переадресации пользователя на рабочую страницу.
"""
GetUserMock.return_value = Mock()
user = User.objects.get(email=self.engineer)
resp = self.client.post(reverse('work_get_tickets'))
self.assertRedirects(resp, reverse('work', args=[user.id]))
self.assertEqual(resp.status_code, 302)
@patch('main.views.zenpy')
@patch('main.views.get_tickets_list_for_group')
def test_take_one_ticket(self, TicketsMock, ZenpyViewsMock):
"""
Функция проверки назначения одного тикета на engineer.
"""
TicketsMock.return_value = [Mock()]
ZenpyViewsMock.get_user.return_value = Mock(role='agent', custom_role_id=sets.ZENDESK_ROLES['engineer'])
self.client.post(reverse('work_get_tickets'), data={'count_tickets': 1})
tickets = ZenpyViewsMock.update_tickets.call_args
self.assertEqual(tickets[0][0][0].assignee, ZenpyViewsMock.get_user.return_value)
@patch('main.views.get_tickets_list_for_group')
@patch('main.views.zenpy')
def test_take_many_tickets(self, ZenpyMock, TicketsMock):
"""
Функция проверки назначения нескольких тикетов на engineer.
"""
TicketsMock.return_value = [Mock()] * 3
ZenpyMock.get_user.return_value = Mock(role='agent', custom_role_id=sets.ZENDESK_ROLES['engineer'])
self.client.post(reverse('work_get_tickets'), data={'count_tickets': 3})
tickets = ZenpyMock.update_tickets.call_args
for ticket in tickets[0][0]:
self.assertEqual(ticket.assignee, ZenpyMock.get_user.return_value)
@patch('main.views.zenpy.get_user')
@patch('main.views.zenpy')
def test_light_agent_take_ticket(self, ZenpyMock, GetUserMock):
"""
Функция проверки попытки назначения тикета на light_agent.
"""
GetUserMock.return_value = Mock(role='agent', custom_role_id=sets.ZENDESK_ROLES['light_agent'])
self.light_agent_client.post(reverse('work_get_tickets'), data={'count_tickets': 3})
tickets = ZenpyMock.update_tickets.call_args
self.assertIsNone(tickets)
@patch('main.views.zenpy')
@patch('main.views.get_tickets_list_for_group')
def test_take_zero_tickets(self, TicketsMock, ZenpyMock):
"""
Функция проверки попытки назначения нуля тикета на engineer.
"""
TicketsMock.return_value = [Mock()] * 3
ZenpyMock.get_user.return_value = Mock(role='agent', custom_role_id=sets.ZENDESK_ROLES['engineer'])
self.client.post(reverse('work_get_tickets'), data={'count_tickets': 0})
tickets = ZenpyMock.update_tickets.call_args[0][0]
self.assertListEqual(tickets, [])
@patch('main.views.get_tickets_list_for_group')
@patch('main.views.zenpy')
def test_take_invalid_count_tickets(self, ZenpyMock, TicketsMock, ):
"""
Функция проверки попытки назначения нуля тикетов на engineer.
"""
TicketsMock.return_value = [Mock()] * 3
ZenpyMock.get_user.return_value = Mock(role='agent', custom_role_id=sets.ZENDESK_ROLES['engineer'])
self.client.post(reverse('work_get_tickets'), data={'count_tickets': 'asd'})
tickets = ZenpyMock.update_tickets.call_args
self.assertIsNone(tickets)
class ProfileTestCase(TestCase):
"""
Класс тестов для проверки синхронизации профиля пользователя.
"""
fixtures = ['fixtures/profile.json']
def setUp(self):
"""
Предустановленные значения для проведения тестов.
"""
self.zendesk_agent_email = 'krav-88@mail.ru'
self.zendesk_admin_email = 'idar.sokurov.05@mail.ru'
self.client = Client()
self.client.force_login(User.objects.get(email=self.zendesk_agent_email))
self.admin_client = Client()
self.admin_client.force_login(User.objects.get(email=self.zendesk_admin_email))
def test_correct_username(self):
"""
Функция проверки синхронизации имени пользователя.
"""
resp = self.client.get(reverse('profile'))
self.assertEqual(resp.context['profile'].name, zenpy.get_user(self.zendesk_agent_email).name)
def test_correct_email(self):
"""
Функция проверки синхронизации почты пользователя.
"""
resp = self.client.get(reverse('profile'))
self.assertEqual(resp.context['profile'].user.email, zenpy.get_user(self.zendesk_agent_email).email)
def test_correct_role(self):
"""
Функция проверки синхронизации роли пользователя.
"""
resp = self.client.get(reverse('profile'))
self.assertEqual(resp.context['profile'].role, zenpy.get_user(self.zendesk_agent_email).role)
resp = self.admin_client.get(reverse('profile'))
self.assertEqual(resp.context['profile'].role, zenpy.get_user(self.zendesk_admin_email).role)
def test_correct_custom_role_id(self):
"""
Функция проверки синхронизации рабочей роли пользователя.
"""
resp = self.client.get(reverse('profile'))
user = zenpy.get_user(self.zendesk_agent_email)
self.assertEqual(resp.context['profile'].custom_role_id, user.custom_role_id if user.custom_role_id else 0)
resp = self.admin_client.get(reverse('profile'))
user = zenpy.get_user(self.zendesk_admin_email)
self.assertEqual(resp.context['profile'].custom_role_id, user.custom_role_id if user.custom_role_id else 0)
def test_correct_image(self):
"""
Функция проверки синхронизации изображения пользователя.
"""
resp = self.client.get(reverse('profile'))
user = zenpy.get_user(self.zendesk_agent_email)
self.assertEqual(resp.context['profile'].image, user.photo['content_url'] if user.photo else None)

View File

@ -1,19 +1,18 @@
from smtplib import SMTPException
from typing import Dict, Any
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.contrib.auth.models import User, Permission
from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.views import LoginView
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
from django.urls import reverse_lazy, reverse
from django.urls import reverse_lazy
from django.views.generic import FormView
from django_registration.views import RegistrationView
# Django REST
@ -23,27 +22,13 @@ from rest_framework.response import Response
from access_controller.settings import DEFAULT_FROM_EMAIL, ZENDESK_ROLES, ZENDESK_MAX_AGENTS, ZENDESK_GROUPS
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, \
log, set_session_params_for_work_page, get_tickets_list_for_group
from .statistic_data import StatisticData
from main.zendesk_admin import zenpy
from main.requester import TicketListRequester
from main.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, StatisticForm
set_session_params_for_work_page, get_tickets_list_for_group
from main.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, StatisticForm, \
WorkGetTicketsForm
from main.serializers import ProfileSerializer, ZendeskUserSerializer
from main.zendesk_admin import zenpy
from .models import UserProfile
def setup_context(profile_lit: bool = False, control_lit: bool = False, work_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
from .statistic_data import StatisticData
class CustomRegistrationView(RegistrationView):
@ -59,7 +44,6 @@ class CustomRegistrationView(RegistrationView):
:param is_allowed: Определение зарегистрирован ли пользователь с введенным email на Zendesk и принадлежит ли он к организации SYSTEM
:type is_allowed: :class:`bool`
"""
extra_context = setup_context(registration_lit=True)
form_class = CustomRegistrationForm
template_name = 'django_registration/registration_form.html'
urls = {
@ -151,12 +135,11 @@ def profile_page(request: WSGIRequest) -> HttpResponse:
"""
user_profile: UserProfile = request.user.userprofile
update_profile(user_profile)
context = setup_context(profile_lit=True)
context.update({
context = {
'profile': user_profile,
'pagename': 'Страница профиля',
'ZENDESK_ROLES': ZENDESK_ROLES,
})
}
return render(request, 'pages/profile.html', context)
@ -188,14 +171,14 @@ def work_page(request: WSGIRequest, id: int) -> HttpResponse:
engineers.append(user)
elif user.custom_role_id == ZENDESK_ROLES['light_agent']:
light_agents.append(user)
context = setup_context(work_lit=True)
context.update({
context = {
'engineers': engineers,
'agents': light_agents,
'messages': messages.get_messages(request),
'licences_remaining': max(0, ZENDESK_MAX_AGENTS - len(engineers)),
'pagename': 'Управление правами',
})
'get_tickets_form': WorkGetTicketsForm()
}
return render(request, 'pages/work.html', context)
return redirect("login")
@ -228,21 +211,18 @@ def work_become_engineer(request: WSGIRequest) -> HttpResponseRedirect:
@login_required()
def work_get_tickets(request):
zenpy_user = zenpy.get_user(request.user.email)
if zenpy_user.role == 'admin' or zenpy_user.custom_role_id == ZENDESK_ROLES['engineer']:
tickets = get_tickets_list_for_group(ZENDESK_GROUPS['buffer'])
assigned_tickets = []
count = 0
for i in range(len(tickets)):
if i == int(request.GET.get('count_tickets')):
if assigned_tickets:
zenpy.admin.tickets.update(assigned_tickets)
return set_session_params_for_work_page(request, count)
tickets[i].assignee = zenpy_user
assigned_tickets.append(tickets[i])
count += 1
if assigned_tickets:
zenpy.admin.tickets.update(assigned_tickets)
return set_session_params_for_work_page(request, count)
if request.method == 'POST':
if zenpy_user.role == 'admin' or zenpy_user.custom_role_id == ZENDESK_ROLES['engineer']:
form = WorkGetTicketsForm(request.POST)
if form.is_valid():
tickets = get_tickets_list_for_group(ZENDESK_GROUPS['buffer'])
assigned_tickets = []
for i in range(min(form.cleaned_data['count_tickets'], len(tickets))):
tickets[i].assignee = zenpy_user
assigned_tickets.append(tickets[i])
zenpy.update_tickets(assigned_tickets)
return set_session_params_for_work_page(request, len(assigned_tickets))
return set_session_params_for_work_page(request, is_confirm=False)
@ -311,7 +291,6 @@ class CustomLoginView(LoginView):
"""
Отображение страницы авторизации пользователя
"""
extra_context = setup_context(login_lit=True)
form_class = CustomAuthenticationForm
@ -369,11 +348,10 @@ def statistic_page(request: WSGIRequest) -> HttpResponse:
if not request.user.has_perm("main.has_control_access"):
return redirect('index')
context = setup_context(stats_lit=True)
context.update({
context = {
'pagename': 'страница статистики',
'errors': list(),
})
}
if request.method == "POST":
form = StatisticForm(request.POST)
if form.is_valid():

View File

@ -1,7 +1,9 @@
from typing import Optional, Dict
from typing import Optional, Dict, List
from zenpy import Zenpy
from zenpy.lib.api_objects import User as ZenpyUser, Group as ZenpyGroup
from zenpy.lib.api_objects import User as ZenpyUser, Group as ZenpyGroup, Ticket as ZenpyTicket
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
@ -28,6 +30,15 @@ class ZendeskAdmin:
"""
self.admin.users.update(user)
def update_tickets(self, tickets: List[ZenpyTicket]):
"""
Функция сохраняет изменение тикетов в Zendesk.
:param tickets: Тикеты с изменёнными данными
"""
if tickets:
self.admin.tickets.update(tickets)
def check_user(self, email: str) -> bool:
"""
Функция осуществляет проверку существования пользователя в Zendesk по email.

View File

@ -14,3 +14,6 @@ sphinx-rtd-theme==0.5.1
sphinx-autodoc-typehints==1.11.1
pyenchant==3.2.0
sphinxcontrib-spelling==7.1.0
# Misc
python-dotenv==0.17.1