diff --git a/main/extra_func.py b/main/extra_func.py index e6a2a97..90ec414 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -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 diff --git a/main/forms.py b/main/forms.py index 81b2e8a..8ca6f13 100644 --- a/main/forms.py +++ b/main/forms.py @@ -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 + } + ), + ) diff --git a/main/templates/pages/work.html b/main/templates/pages/work.html index cb07b6d..bd46341 100644 --- a/main/templates/pages/work.html +++ b/main/templates/pages/work.html @@ -66,10 +66,11 @@ Сдать права инженера
-
- - -
+
+ {% csrf_token %} + {{ get_tickets_form.count_tickets }} + +
{% for message in messages %} diff --git a/main/tests.py b/main/tests.py index 5f71955..59df965 100644 --- a/main/tests.py +++ b/main/tests.py @@ -1,5 +1,5 @@ import random -from unittest.mock import patch +from unittest.mock import patch, Mock from django.contrib.auth.models import User from django.core import mail @@ -43,7 +43,7 @@ class RegistrationTestCase(TestCase): 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].to, [self.any_zendesk_user_email]) # context that the email template was rendered with email_context = response.context[0].dicts[1] @@ -262,3 +262,93 @@ class PasswordChangeTestCase(TestCase): } ) 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) diff --git a/main/views.py b/main/views.py index 22978c2..4602bcf 100644 --- a/main/views.py +++ b/main/views.py @@ -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,13 +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 +from .statistic_data import StatisticData class CustomRegistrationView(RegistrationView): @@ -178,6 +177,7 @@ def work_page(request: WSGIRequest, id: int) -> HttpResponse: '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") @@ -211,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) diff --git a/main/zendesk_admin.py b/main/zendesk_admin.py index 2a689ce..627d900 100644 --- a/main/zendesk_admin.py +++ b/main/zendesk_admin.py @@ -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.