From 8488ea88c29d980a2c706f27aa394ac6b1294b3c Mon Sep 17 00:00:00 2001 From: Yuriy Kulakov Date: Sun, 7 Mar 2021 21:30:02 +0300 Subject: [PATCH 01/17] Added Django REST but have some bugs --- access_controller/settings.py | 9 ++++ access_controller/urls.py | 12 +++-- main/extra_func.py | 45 ++++++++++++++++--- main/serializers.py | 17 ++++++++ main/templates/base/base.html | 1 + main/templates/pages/adm_ruleset.html | 18 +++++--- main/urls.py | 6 +++ main/views.py | 43 ++++++++++-------- requirements.txt | 2 + static/main/js/control.js | 63 ++++++++++++++++++++++++--- 10 files changed, 176 insertions(+), 40 deletions(-) create mode 100644 main/serializers.py create mode 100644 main/urls.py diff --git a/access_controller/settings.py b/access_controller/settings.py index 96703b6..a26931d 100644 --- a/access_controller/settings.py +++ b/access_controller/settings.py @@ -36,6 +36,7 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'django_registration', + 'rest_framework', 'main', ] @@ -183,3 +184,11 @@ ZENDESK_ROLES = { 'engineer': 360005209000, 'light_agent': 360005208980, } + +REST_FRAMEWORK = { + # Use Django's standard `django.contrib.auth` permissions, + # or allow read-only access for unauthenticated users. + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' + ] +} diff --git a/access_controller/urls.py b/access_controller/urls.py index 3595e4f..92edfe1 100644 --- a/access_controller/urls.py +++ b/access_controller/urls.py @@ -17,15 +17,16 @@ from django.contrib import admin from django.contrib.auth import views as auth_views from django.urls import path, include from main.views import work_page, work_hand_over, work_become_engineer, AdminPageView - from main.views import main_page, profile_page, CustomRegistrationView, CustomLoginView +from main.urls import router + urlpatterns = [ path('admin/', admin.site.urls, name='admin'), path('', main_page, name='index'), path('accounts/profile/', profile_page, name='profile'), path('accounts/register/', CustomRegistrationView.as_view(), name='registration'), - path('accounts/login/', CustomLoginView.as_view(extra_context={}), name='login',), # TODO add extra context + path('accounts/login/', CustomLoginView.as_view(extra_context={}), name='login', ), # TODO add extra context path('accounts/', include('django.contrib.auth.urls')), path('accounts/', include('django_registration.backends.one_step.urls')), path('work/', work_page, name="work"), @@ -34,7 +35,7 @@ urlpatterns = [ path('accounts/', include('django_registration.backends.activation.urls')), path('accounts/login/', include('django.contrib.auth.urls')), path('control/', AdminPageView.as_view(), name='control') - ] +] urlpatterns += [ path( @@ -58,3 +59,8 @@ urlpatterns += [ name='password_reset_complete' ), ] + +# Django REST +urlpatterns += [ + path('api/', include(router.urls)) +] diff --git a/main/extra_func.py b/main/extra_func.py index 691bd37..5cfb400 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -3,9 +3,9 @@ import os from zenpy import Zenpy from zenpy.lib.exception import APIException -from main.models import UserProfile +from main.models import UserProfile, User -from access_controller.settings import ZENDESK_ROLES as ROLES +from access_controller.settings import ZENDESK_ROLES as ROLES, ZENDESK_ROLES class ZendeskAdmin: @@ -28,7 +28,7 @@ class ZendeskAdmin: email: str = os.getenv('ACCESS_CONTROLLER_API_EMAIL') token: str = os.getenv('ACCESS_CONTROLLER_API_TOKEN') password: str = os.getenv('ACCESS_CONTROLLER_API_PASSWORD') - _instance=None + _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: @@ -144,8 +144,9 @@ def get_users_list() -> list: Функция **get_users_list** возвращает список пользователей Zendesk, относящихся к организации. """ zendesk = ZendeskAdmin() - admin = zendesk.get_user(zendesk.email) - org = next(zendesk.admin.users.organizations(user=admin)) + + # У пользователей должна быть организация SYSTEM + org = next(zendesk.admin.search(type='organization', name='SYSTEM')) return zendesk.admin.organizations.users(org) @@ -191,3 +192,37 @@ def check_user_auth(email: str, password: str) -> bool: except APIException: return False return True + + +def update_user_in_model(profile, zendesk_user): + profile.name = zendesk_user.name + profile.role = zendesk_user.role + profile.image = zendesk_user.photo['content_url'] if zendesk_user.photo else None + profile.save() + + +def count_users(users) -> tuple: + """ + Функция подсчета количества сотрудников с ролями engineer и light_a + + .. todo:: + this func counts users from all zendesk instead of just from a model: + """ + engineers, light_agents = 0, 0 + for user in users: + if user.custom_role_id == ZENDESK_ROLES['engineer']: + engineers += 1 + elif user.custom_role_id == ZENDESK_ROLES['light_agent']: + light_agents += 1 + return engineers, light_agents + + +def update_users_in_model(): + """ + Обновляет пользователей в модели UserProfile по списку пользователей в организации + """ + users = get_users_list() + for user in users: + profile = User.objects.get(email=user.email).userprofile + update_user_in_model(profile, user) + return users diff --git a/main/serializers.py b/main/serializers.py new file mode 100644 index 0000000..26d08c2 --- /dev/null +++ b/main/serializers.py @@ -0,0 +1,17 @@ +from django.contrib.auth.models import User +from rest_framework import serializers +from main.models import UserProfile + + +class UserSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = User + fields = ['email'] + + +class ProfileSerializer(serializers.HyperlinkedModelSerializer): + user = UserSerializer() + + class Meta: + model = UserProfile + fields = ['user', 'role', 'name'] diff --git a/main/templates/base/base.html b/main/templates/base/base.html index 2aebfe0..166195d 100644 --- a/main/templates/base/base.html +++ b/main/templates/base/base.html @@ -27,6 +27,7 @@ {% block extra_css %}{% endblock %} + {% block extra_scripts %}{% endblock %} diff --git a/main/templates/pages/adm_ruleset.html b/main/templates/pages/adm_ruleset.html index 387cd73..dd9d614 100644 --- a/main/templates/pages/adm_ruleset.html +++ b/main/templates/pages/adm_ruleset.html @@ -10,6 +10,13 @@ {% endblock %} +{% block extra_scripts %} + + + + +{% endblock%} + {% block content %}
@@ -37,25 +44,22 @@ - + - - + {% for user in users %} - + - {% endfor %} -
IDName Email RoleName(link to profile) Checked
{{ user.id }}{{ user.name }} {{ user.user.email }} {{ user.role }}{{ user.name }}
{% endblock%} @@ -103,6 +107,6 @@ {% endblock %}
- + {% endblock %} diff --git a/main/urls.py b/main/urls.py new file mode 100644 index 0000000..fffe11d --- /dev/null +++ b/main/urls.py @@ -0,0 +1,6 @@ +from rest_framework.routers import DefaultRouter +from main.views import UsersViewSet + + +router = DefaultRouter() +router.register(r'users', UsersViewSet) diff --git a/main/views.py b/main/views.py index ca8c9c2..36f82da 100644 --- a/main/views.py +++ b/main/views.py @@ -14,7 +14,7 @@ from zenpy import Zenpy from access_controller.settings import EMAIL_HOST_USER from main.extra_func import check_user_exist, update_profile, get_user_organization, \ - make_engineer, make_light_agent, get_users_list + make_engineer, make_light_agent, get_users_list, update_users_in_model, count_users from django.contrib.auth.models import User, Permission from main.models import UserProfile @@ -27,6 +27,11 @@ from django.core.exceptions import PermissionDenied from access_controller.settings import ZENDESK_ROLES from zenpy.lib.api_objects import User as ZenpyUser +# Django REST +from rest_framework import viewsets, status +from main.serializers import ProfileSerializer +from rest_framework.response import Response +from rest_framework.decorators import action content_type_temp = ContentType.objects.get_for_model(UserProfile) permission_temp, created = Permission.objects.get_or_create( @@ -193,22 +198,6 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView): def make_light_agents(users): [make_light_agent(user) for user in users] - @staticmethod - def count_users(users) -> tuple: - """ - Функция подсчета количества сотрудников с ролями engineer и light_a - - .. todo:: - this func counts users from all zendesk instead of just from a model: - """ - engineers, light_agents = 0, 0 - for user in users: - if user.custom_role_id == ZENDESK_ROLES['engineer']: - engineers += 1 - elif user.custom_role_id == ZENDESK_ROLES['light_agent']: - light_agents += 1 - return engineers, light_agents - def get_context_data(self, **kwargs) -> dict: """ Функция формирования контента страницы администратора (с проверкой прав доступа) @@ -216,9 +205,10 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView): if self.request.user.userprofile.role != 'admin': raise PermissionDenied context = super().get_context_data(**kwargs) - context['users'] = get_list_or_404( + users = get_list_or_404( UserProfile, role='agent') - context['engineers'], context['light_agents'] = self.count_users(get_users_list()) + context['users'] = users + context['engineers'], context['light_agents'] = count_users(users) return context # TODO: need to get profile page url @@ -227,3 +217,18 @@ class CustomLoginView(LoginView): Отображение страницы авторизации пользователя """ form_class = CustomAuthenticationForm + + +class UsersViewSet(viewsets.ReadOnlyModelViewSet): + """ + Класс для получения пользователей с помощью api + """ + queryset = UserProfile.objects.filter(role='agent') + serializer_class = ProfileSerializer + + def list(self, request, *args, **kwargs): + users = update_users_in_model() + profiles = UserProfile.objects.filter(role='agent') + count = count_users(users) + serializer = self.get_serializer(data=profiles, many=True) + return Response(serializer.data + {'engineers': count[0], 'light_agents': count[1]}) diff --git a/requirements.txt b/requirements.txt index 7a4f941..b32a382 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,8 @@ Django==3.1.6 Pillow==8.1.0 zenpy~=2.0.24 django_registration==3.1.1 +djangorestframework==3.12.2 + # Documentation Sphinx==3.4.3 diff --git a/static/main/js/control.js b/static/main/js/control.js index 1fd4f9c..6404741 100644 --- a/static/main/js/control.js +++ b/static/main/js/control.js @@ -1,9 +1,60 @@ "use strict"; -let checkboxes = document.getElementsByName("users"); -let fields = document.querySelectorAll(".checkbox_field"); -if (checkboxes.length == fields.length) { - for (let i = 0; i < fields.length; ++i) { - let el = checkboxes[i].cloneNode(true); - fields[i].appendChild(el); + +function move_checkboxes() { + let checkboxes = document.getElementsByName("users"); + let fields = document.querySelectorAll(".checkbox_field"); + if (checkboxes.length == fields.length) { + for (let i = 0; i < fields.length; ++i) { + let el = checkboxes[i].cloneNode(true); + fields[i].appendChild(el); + } } } + +class TableRow extends React.Component { + render() { + return ( + + + {this.props.user.name} + + {this.props.user.user.email} + {this.props.user.role} + + + ); + } +} + +class TableBody extends React.Component { + constructor(props) { + super(props); + this.state = { users: [] }; + } + + get_users() { + axios.get("/api/users").then((response) => { + this.setState({ users: response.data }); + }); + } + + componentDidMount() { + this.interval = setInterval(() => { + this.get_users(); + move_checkboxes(); + }, 1000); + } + + componentWillUnmount() { + clearInterval(this.interval); + } + + render() { + return this.state.users.map((user, key) => ( + + )); + } +} + +move_checkboxes(); +ReactDOM.render(, document.getElementById("table")); From 72b70cc585f3e2e46ff53e520e3a19326949cd91 Mon Sep 17 00:00:00 2001 From: Yuriy Kulakov Date: Thu, 11 Mar 2021 19:29:16 +0300 Subject: [PATCH 02/17] Fixed bug with api response --- main/extra_func.py | 12 +++++++++--- main/serializers.py | 2 +- main/views.py | 13 +++++++++---- static/main/js/control.js | 31 +++++++++++++++++++++++++------ 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/main/extra_func.py b/main/extra_func.py index 5cfb400..295a677 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -1,9 +1,12 @@ import os +from django.contrib.auth.models import User from zenpy import Zenpy from zenpy.lib.exception import APIException -from main.models import UserProfile, User +from main.models import UserProfile +from django.core.exceptions import ObjectDoesNotExist + from access_controller.settings import ZENDESK_ROLES as ROLES, ZENDESK_ROLES @@ -223,6 +226,9 @@ def update_users_in_model(): """ users = get_users_list() for user in users: - profile = User.objects.get(email=user.email).userprofile - update_user_in_model(profile, user) + try: + profile = User.objects.get(email=user.email).userprofile + update_user_in_model(profile, user) + except ObjectDoesNotExist: + pass return users diff --git a/main/serializers.py b/main/serializers.py index 26d08c2..f72fc86 100644 --- a/main/serializers.py +++ b/main/serializers.py @@ -14,4 +14,4 @@ class ProfileSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = UserProfile - fields = ['user', 'role', 'name'] + fields = ['user', 'id', 'role', 'name'] diff --git a/main/views.py b/main/views.py index 36f82da..e831d34 100644 --- a/main/views.py +++ b/main/views.py @@ -208,7 +208,7 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView): users = get_list_or_404( UserProfile, role='agent') context['users'] = users - context['engineers'], context['light_agents'] = count_users(users) + context['engineers'], context['light_agents'] = count_users(get_users_list()) return context # TODO: need to get profile page url @@ -228,7 +228,12 @@ class UsersViewSet(viewsets.ReadOnlyModelViewSet): def list(self, request, *args, **kwargs): users = update_users_in_model() - profiles = UserProfile.objects.filter(role='agent') count = count_users(users) - serializer = self.get_serializer(data=profiles, many=True) - return Response(serializer.data + {'engineers': count[0], 'light_agents': count[1]}) + profiles = UserProfile.objects.filter(role='agent') + serializer = self.get_serializer(profiles, many=True) + return Response({ + 'users': serializer.data, + 'engineers': count[0], + 'light_agents': count[1] + }) + diff --git a/static/main/js/control.js b/static/main/js/control.js index 6404741..e645196 100644 --- a/static/main/js/control.js +++ b/static/main/js/control.js @@ -10,7 +10,9 @@ function move_checkboxes() { } } } +move_checkboxes(); +// React class TableRow extends React.Component { render() { return ( @@ -20,7 +22,13 @@ class TableRow extends React.Component { {this.props.user.user.email} {this.props.user.role} - + + + ); } @@ -29,20 +37,31 @@ class TableRow extends React.Component { class TableBody extends React.Component { constructor(props) { super(props); - this.state = { users: [] }; + this.state = { + users: [], + engineers: 0, + light_agents: 0, + }; } get_users() { axios.get("/api/users").then((response) => { - this.setState({ users: response.data }); + this.setState({ + users: response.data.users, + engineers: response.data.engineers, + light_agents: response.data.light_agents, + }); + let elements = document.querySelectorAll(".info-quantity-value"); + console.log(elements) + elements[0].innerHTML = this.state.engineers; + elements[1].innerHTML = this.state.light_agents; }); } componentDidMount() { this.interval = setInterval(() => { this.get_users(); - move_checkboxes(); - }, 1000); + }, 10000); } componentWillUnmount() { @@ -56,5 +75,5 @@ class TableBody extends React.Component { } } -move_checkboxes(); ReactDOM.render(, document.getElementById("table")); + From 674df0e66b1f5139c9058596bbf7fbc4bbe0206f Mon Sep 17 00:00:00 2001 From: Andrew Smirnov Date: Thu, 11 Mar 2021 20:28:04 +0300 Subject: [PATCH 03/17] Add model for tracking unassigned tickets --- main/migrations/0012_auto_20210311_2027.py | 29 ++++++++++++++++++++++ main/models.py | 13 +++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 main/migrations/0012_auto_20210311_2027.py diff --git a/main/migrations/0012_auto_20210311_2027.py b/main/migrations/0012_auto_20210311_2027.py new file mode 100644 index 0000000..113e51e --- /dev/null +++ b/main/migrations/0012_auto_20210311_2027.py @@ -0,0 +1,29 @@ +# Generated by Django 3.1.6 on 2021-03-11 17:27 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('main', '0011_auto_20210311_1734'), + ] + + operations = [ + migrations.RemoveField( + model_name='rolechangelogs', + name='name', + ), + migrations.CreateModel( + name='UnassignedTicket', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ticket_id', models.IntegerField(help_text='Номер тикера, для которого сняли ответственного')), + ('status', models.IntegerField(choices=[(0, 'Снят с пользователя, перенесён в буферную группу'), (1, 'Авторство восстановлено'), (2, 'Пока нас не было, тикет испарился из буферной группы. Дополнительные действия не требуются')], default=0)), + ('assignee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tickets', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/main/models.py b/main/models.py index 95d38f2..4811e8d 100644 --- a/main/models.py +++ b/main/models.py @@ -33,9 +33,20 @@ class RoleChangeLogs(models.Model): """Модель для логирования изменений ролей пользователя""" user = models.ForeignKey(to=User, on_delete=models.CASCADE, help_text='Пользователь, которому присвоили другую роль') - name = models.TextField(help_text='Имя пользователя') old_role = models.IntegerField(default=0, help_text='Старая роль') new_role = models.IntegerField(default=0, help_text='Присвоенная роль') change_time = models.DateTimeField(help_text='Дата и время изменения роли') changed_by = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='changed_by', help_text='Кем была изменена роль') + + +class UnassignedTicketStatus(models.IntegerChoices): + UNASSIGNED = 0, 'Снят с пользователя, перенесён в буферную группу' + RESTORED = 1, 'Авторство восстановлено' + NOT_FOUND = 2, 'Пока нас не было, тикет испарился из буферной группы. Дополнительные действия не требуются' + + +class UnassignedTicket(models.Model): + assignee = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='tickets') + ticket_id = models.IntegerField(help_text='Номер тикера, для которого сняли ответственного') + status = models.IntegerField(choices=UnassignedTicketStatus.choices, default=UnassignedTicketStatus.UNASSIGNED) From d2e76fdbd96e6b84980d1b11de85fd923cb43129 Mon Sep 17 00:00:00 2001 From: Andrew Smirnov Date: Thu, 11 Mar 2021 20:54:56 +0300 Subject: [PATCH 04/17] Update model, add documentation todos, add sampla code for logging. --- main/extra_func.py | 33 +++++++++++++++++++--- main/migrations/0013_auto_20210311_2040.py | 18 ++++++++++++ main/models.py | 6 ++-- main/views.py | 7 +++-- 4 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 main/migrations/0013_auto_20210311_2040.py diff --git a/main/extra_func.py b/main/extra_func.py index b76eb55..d2f1810 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -6,7 +6,7 @@ from zenpy import Zenpy from zenpy.lib.exception import APIException from access_controller.settings import ZENDESK_ROLES as ROLES, ONE_DAY -from main.models import UserProfile, RoleChangeLogs +from main.models import UserProfile, RoleChangeLogs, UnassignedTicket, UnassignedTicketStatus class ZendeskAdmin: @@ -128,16 +128,41 @@ def update_role(user_profile: UserProfile, role: str) -> UserProfile: def make_engineer(user_profile: UserProfile) -> UserProfile: """ - Функция **make_engineer** устанавливапет пользователю роль инженера. + Функция **make_engineer** устанавливает пользователю роль инженера. """ + update_role(user_profile, ROLES['engineer']) -def make_light_agent(user_profile: UserProfile) -> UserProfile: +def make_light_agent(user_profile: UserProfile, who_changes: User) -> UserProfile: """ - Функция **make_light_agent** устанавливапет пользователю роль легкого агента. + Функция **make_light_agent** устанавливапет пользователю роль легкого агента. + + .. todo:: + Решить проблему с ошибкой при выполнении этой функции из-за неотвязанных тикетов. А именно: + + - найти все тикеты, ответственным которых является снимаемый аккаунт + - для всех этих тикетов - перенести ответственность на буферную группу. + - [PARTIALY DONE] создать записи о снятых тикетах и их прошлом авторстве. Если тикет уже был закрыт - выставить в логе CLOSED. Иначе UNASSIGNED + - [DONE] после этого снять права c инженера + - [DONE] создать запись в логе о снятии прав инженера """ + + # tickets = [] + # # TODO: set ticket fields correct + # for ticket in tickets: + # UnassignedTicket.create( + # assignee=user_profile.user, + # ticket_id=ticket.number, + # status=UnassignedTicketStatus.UNASSIGNED if ticket.status=='opened' else UnassignedTicketStatus.CLOSED + # ) update_role(user_profile, ROLES['light_agent']) + RoleChangeLogs.create( + user=user_profile.user, + old_role=ROLES['engineer'], + new_role=ROLES['light_agent'], + changed_by=who_changes + ) def get_users_list() -> list: diff --git a/main/migrations/0013_auto_20210311_2040.py b/main/migrations/0013_auto_20210311_2040.py new file mode 100644 index 0000000..5648813 --- /dev/null +++ b/main/migrations/0013_auto_20210311_2040.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.6 on 2021-03-11 17:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0012_auto_20210311_2027'), + ] + + operations = [ + migrations.AlterField( + model_name='unassignedticket', + name='status', + field=models.IntegerField(choices=[(0, 'Снят с пользователя, перенесён в буферную группу'), (1, 'Авторство восстановлено'), (2, 'Пока нас не было, тикет испарился из буферной группы. Дополнительные действия не требуются'), (3, 'Тикет уже был закрыт. Дополнительные действия не требуются')], default=0), + ), + ] diff --git a/main/models.py b/main/models.py index 4811e8d..087784c 100644 --- a/main/models.py +++ b/main/models.py @@ -2,6 +2,7 @@ from django.db import models from django.contrib.auth.models import User from django.db.models.signals import post_save from django.dispatch import receiver +from django.utils import timezone class UserProfile(models.Model): @@ -12,7 +13,7 @@ class UserProfile(models.Model): ('has_control_access', 'Can view admin page'), ) - user = models.OneToOneField(to=User, on_delete=models.CASCADE, help_text='Пользователь') + user = models.OneToOneField(to=User, on_delete=models.CASCADE, help_text='Пользователь', related_name='user') role = models.IntegerField(default=0, help_text='Код роли пользователя') image = models.URLField(null=True, blank=True, help_text='Аватарка') name = models.CharField(default='None', max_length=100, help_text='Имя пользователя на нашем сайте') @@ -35,7 +36,7 @@ class RoleChangeLogs(models.Model): help_text='Пользователь, которому присвоили другую роль') old_role = models.IntegerField(default=0, help_text='Старая роль') new_role = models.IntegerField(default=0, help_text='Присвоенная роль') - change_time = models.DateTimeField(help_text='Дата и время изменения роли') + change_time = models.DateTimeField(help_text='Дата и время изменения роли', default=timezone.now) changed_by = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='changed_by', help_text='Кем была изменена роль') @@ -44,6 +45,7 @@ class UnassignedTicketStatus(models.IntegerChoices): UNASSIGNED = 0, 'Снят с пользователя, перенесён в буферную группу' RESTORED = 1, 'Авторство восстановлено' NOT_FOUND = 2, 'Пока нас не было, тикет испарился из буферной группы. Дополнительные действия не требуются' + CLOSED = 3, 'Тикет уже был закрыт. Дополнительные действия не требуются' class UnassignedTicket(models.Model): diff --git a/main/views.py b/main/views.py index 36365f2..4c596ad 100644 --- a/main/views.py +++ b/main/views.py @@ -190,9 +190,10 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView): def make_engineers(users): [make_engineer(user) for user in users] - @staticmethod - def make_light_agents(users): - [make_light_agent(user) for user in users] + def make_light_agents(self, users): + for user in users: + make_light_agent(user, self.request.user) + @staticmethod def count_users(users) -> tuple: From abe44fec5f2cd85bc7e7966e7759b474322164a0 Mon Sep 17 00:00:00 2001 From: Yuriy Kulakov Date: Fri, 12 Mar 2021 13:01:41 +0300 Subject: [PATCH 05/17] Refactored some functions in AdminPageView --- main/views.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/main/views.py b/main/views.py index e831d34..3b96d92 100644 --- a/main/views.py +++ b/main/views.py @@ -184,10 +184,11 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView): """ Функция установки ролей пользователям """ + users = form.cleaned_data['users'] if 'engineer' in self.request.POST: - self.make_engineers(form.cleaned_data['users']) + self.make_engineers(users) elif 'light_agent' in self.request.POST: - self.make_light_agents(form.cleaned_data['users']) + self.make_light_agents(users) return super().form_valid(form) @staticmethod @@ -202,8 +203,6 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView): """ Функция формирования контента страницы администратора (с проверкой прав доступа) """ - if self.request.user.userprofile.role != 'admin': - raise PermissionDenied context = super().get_context_data(**kwargs) users = get_list_or_404( UserProfile, role='agent') From c1a10b6f2c7834e51d49e253ce29b245b7d5d0fb Mon Sep 17 00:00:00 2001 From: Yuriy Kulakov Date: Fri, 12 Mar 2021 14:54:57 +0300 Subject: [PATCH 06/17] Fixed bug with disappearance of table rows in control page at start --- main/extra_func.py | 9 +++++---- main/templates/pages/adm_ruleset.html | 3 ++- main/views.py | 5 +---- static/main/js/control.js | 10 +++++++--- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/main/extra_func.py b/main/extra_func.py index 295a677..8ad1b80 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -8,7 +8,7 @@ from main.models import UserProfile from django.core.exceptions import ObjectDoesNotExist -from access_controller.settings import ZENDESK_ROLES as ROLES, ZENDESK_ROLES +from access_controller.settings import ZENDESK_ROLES class ZendeskAdmin: @@ -132,14 +132,14 @@ def make_engineer(user_profile: UserProfile) -> UserProfile: """ Функция **make_engineer** устанавливапет пользователю роль инженера. """ - update_role(user_profile, ROLES['engineer']) + update_role(user_profile, ZENDESK_ROLES['engineer']) def make_light_agent(user_profile: UserProfile) -> UserProfile: """ Функция **make_light_agent** устанавливапет пользователю роль легкого агента. """ - update_role(user_profile, ROLES['light_agent']) + update_role(user_profile, ZENDESK_ROLES['light_agent']) def get_users_list() -> list: @@ -150,7 +150,8 @@ def get_users_list() -> list: # У пользователей должна быть организация SYSTEM org = next(zendesk.admin.search(type='organization', name='SYSTEM')) - return zendesk.admin.organizations.users(org) + users = zendesk.admin.organizations.users(org) + return users def update_profile(user_profile: UserProfile) -> UserProfile: diff --git a/main/templates/pages/adm_ruleset.html b/main/templates/pages/adm_ruleset.html index dd9d614..c230862 100644 --- a/main/templates/pages/adm_ruleset.html +++ b/main/templates/pages/adm_ruleset.html @@ -50,7 +50,7 @@ Checked - + {% for user in users %} {{ user.name }} @@ -60,6 +60,7 @@ {% endfor %} + {% endblock%} diff --git a/main/views.py b/main/views.py index 3b96d92..875763a 100644 --- a/main/views.py +++ b/main/views.py @@ -22,16 +22,13 @@ from main.forms import CustomRegistrationForm, AdminPageUsers, CustomAuthenticat from django_registration.views import RegistrationView from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin -from django.core.exceptions import PermissionDenied -from access_controller.settings import ZENDESK_ROLES from zenpy.lib.api_objects import User as ZenpyUser # Django REST from rest_framework import viewsets, status from main.serializers import ProfileSerializer from rest_framework.response import Response -from rest_framework.decorators import action content_type_temp = ContentType.objects.get_for_model(UserProfile) permission_temp, created = Permission.objects.get_or_create( @@ -226,7 +223,7 @@ class UsersViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = ProfileSerializer def list(self, request, *args, **kwargs): - users = update_users_in_model() + users = update_users_in_model().values count = count_users(users) profiles = UserProfile.objects.filter(role='agent') serializer = self.get_serializer(profiles, many=True) diff --git a/static/main/js/control.js b/static/main/js/control.js index e645196..62d2daf 100644 --- a/static/main/js/control.js +++ b/static/main/js/control.js @@ -8,6 +8,10 @@ function move_checkboxes() { let el = checkboxes[i].cloneNode(true); fields[i].appendChild(el); } + } else { + alert( + "Количество пользователей агентов не соответствует количеству полей в форме AdminPageUsers" + ); } } move_checkboxes(); @@ -27,6 +31,7 @@ class TableRow extends React.Component { type="checkbox" value={this.props.user.id} className="form-check-input" + name="users" /> @@ -52,7 +57,6 @@ class TableBody extends React.Component { light_agents: response.data.light_agents, }); let elements = document.querySelectorAll(".info-quantity-value"); - console.log(elements) elements[0].innerHTML = this.state.engineers; elements[1].innerHTML = this.state.light_agents; }); @@ -75,5 +79,5 @@ class TableBody extends React.Component { } } -ReactDOM.render(, document.getElementById("table")); - +ReactDOM.render(, document.getElementById("new_tbody")); +setTimeout(() => document.getElementById("old_tbody").remove(), 10000); From a434a4a30b3c4f2409b18edec2f74970c8bf6330 Mon Sep 17 00:00:00 2001 From: Yuriy Kulakov Date: Fri, 12 Mar 2021 15:39:54 +0300 Subject: [PATCH 07/17] Merge feature/periodic into develop --- main/extra_func.py | 1 + main/templates/pages/adm_ruleset.html | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/main/extra_func.py b/main/extra_func.py index 58e53a0..e9cafe9 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -204,6 +204,7 @@ def update_user_in_model(profile, zendesk_user): profile.name = zendesk_user.name profile.role = zendesk_user.role profile.image = zendesk_user.photo['content_url'] if zendesk_user.photo else None + profile.custom_role_id = zendesk_user.custom_role_id profile.save() diff --git a/main/templates/pages/adm_ruleset.html b/main/templates/pages/adm_ruleset.html index c230862..92a0628 100644 --- a/main/templates/pages/adm_ruleset.html +++ b/main/templates/pages/adm_ruleset.html @@ -19,9 +19,6 @@ {% block content %}
-
-

Основная информация о странице

-
{% block form %}
From a430ee871d20439a1a885e526285d65fdc16a706 Mon Sep 17 00:00:00 2001 From: Sokurov Idar Date: Sun, 14 Mar 2021 17:13:41 +0300 Subject: [PATCH 08/17] Add ticket unassignment(exclude solved tickets) --- access_controller/settings.py | 6 ++ main/extra_func.py | 73 ++++++++++--------- ...312_1225.py => 0014_auto_20210314_1455.py} | 10 ++- main/views.py | 20 ++--- 4 files changed, 59 insertions(+), 50 deletions(-) rename main/migrations/{0012_auto_20210312_1225.py => 0014_auto_20210314_1455.py} (61%) diff --git a/access_controller/settings.py b/access_controller/settings.py index deadf32..953d2f6 100644 --- a/access_controller/settings.py +++ b/access_controller/settings.py @@ -183,4 +183,10 @@ ZENDESK_ROLES = { 'light_agent': 360005208980, } +ZENDESK_GROUPS = { + 'employees': 'Поддержка', + 'buffer': 'Сменная группа', +} + ONE_DAY = 12 # Количество часов в 1 рабочем дне + diff --git a/main/extra_func.py b/main/extra_func.py index 3583867..c7946fc 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -5,7 +5,7 @@ from django.contrib.auth.models import User from zenpy import Zenpy from zenpy.lib.exception import APIException -from access_controller.settings import ZENDESK_ROLES as ROLES, ONE_DAY +from access_controller.settings import ZENDESK_ROLES as ROLES, ONE_DAY, ZENDESK_GROUPS from main.models import UserProfile, RoleChangeLogs, UnassignedTicket, UnassignedTicketStatus @@ -29,12 +29,6 @@ class ZendeskAdmin: email: str = os.getenv('ACCESS_CONTROLLER_API_EMAIL') token: str = os.getenv('ACCESS_CONTROLLER_API_TOKEN') password: str = os.getenv('ACCESS_CONTROLLER_API_PASSWORD') - _instance = None - - def __new__(cls, *args, **kwargs): - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance def __init__(self): self.create_admin() @@ -82,6 +76,12 @@ class ZendeskAdmin: """ return self.admin.users.search(email).values[0] + def get_group(self, name): + groups = self.admin.search(name) + for group in groups: + return group + return None + def get_user_org(self, email: str) -> str: """ Функция **get_user_org** возвращает организацию, к которой относится пользователь по его email @@ -126,43 +126,42 @@ def update_role(user_profile: UserProfile, role: str) -> UserProfile: zendesk.admin.users.update(user) -def make_engineer(user_profile: UserProfile) -> UserProfile: +def make_engineer(user_profile: UserProfile, who_changes: User) -> UserProfile: """ Функция **make_engineer** устанавливает пользователю роль инженера. """ - + RoleChangeLogs.objects.create( + user=user_profile.user, + old_role=user_profile.custom_role_id, + new_role=ROLES['engineer'], + changed_by=who_changes + ) update_role(user_profile, ROLES['engineer']) def make_light_agent(user_profile: UserProfile, who_changes: User) -> UserProfile: """ Функция **make_light_agent** устанавливапет пользователю роль легкого агента. - - .. todo:: - Решить проблему с ошибкой при выполнении этой функции из-за неотвязанных тикетов. А именно: - - - найти все тикеты, ответственным которых является снимаемый аккаунт - - для всех этих тикетов - перенести ответственность на буферную группу. - - [PARTIALY DONE] создать записи о снятых тикетах и их прошлом авторстве. Если тикет уже был закрыт - выставить в логе CLOSED. Иначе UNASSIGNED - - [DONE] после этого снять права c инженера - - [DONE] создать запись в логе о снятии прав инженера """ + tickets = get_ticket_list(user_profile.user.email) + for ticket in tickets: + if ticket.status=='solved': + continue + UnassignedTicket.objects.create( + assignee=user_profile.user, + ticket_id=ticket.id, + status=UnassignedTicketStatus.UNASSIGNED + ) + ticket.group = ZendeskAdmin().get_group(ZENDESK_GROUPS['buffer']) + ZendeskAdmin().admin.tickets.update(ticket) - # tickets = [] - # # TODO: set ticket fields correct - # for ticket in tickets: - # UnassignedTicket.create( - # assignee=user_profile.user, - # ticket_id=ticket.number, - # status=UnassignedTicketStatus.UNASSIGNED if ticket.status=='opened' else UnassignedTicketStatus.CLOSED - # ) - update_role(user_profile, ROLES['light_agent']) - RoleChangeLogs.create( + RoleChangeLogs.objects.create( user=user_profile.user, - old_role=ROLES['engineer'], + old_role=user_profile.custom_role_id, new_role=ROLES['light_agent'], changed_by=who_changes ) + update_role(user_profile, ROLES['light_agent']) def get_users_list() -> list: @@ -175,9 +174,13 @@ def get_users_list() -> list: return zendesk.admin.organizations.users(org) +def get_ticket_list(email): + return ZendeskAdmin().admin.search(assignee=email, type='ticket') + + def update_profile(user_profile: UserProfile) -> UserProfile: """ - Функция обновляет профиль пользователя в соотвтетствии с текущим в Zendesk + Функция обновляет профиль пользователя в соотвтетствии с текущим в Zendesk """ user = ZendeskAdmin().get_user(user_profile.user.email) user_profile.name = user.name @@ -189,7 +192,7 @@ def update_profile(user_profile: UserProfile) -> UserProfile: def check_user_exist(email: str) -> bool: """ - Функция проверяет, существует ли пользователь + Функция проверяет, существует ли пользователь """ return ZendeskAdmin().check_user(email) @@ -241,9 +244,9 @@ class StatisticData: self.warnings = list() self.data = dict() self.statistic = dict() - self._set_data() + self._init_data() if stat is None: - self._set_statistic() + self._init_statistic() else: self.statistic = stat @@ -346,7 +349,7 @@ class StatisticData: return False return True - def _set_data(self): + def _init_data(self): """ Получение логов в диапазоне дат start_date-end_date для пользователя с почтой email """ @@ -361,7 +364,7 @@ class StatisticData: except User.DoesNotExist: self.errors += ['Пользователь не найден'] - def _set_statistic(self): + def _init_statistic(self): """ Функция заполняет словарь, в котором ключ - дата, значение - кол-во проработанных в этот день секунд """ diff --git a/main/migrations/0012_auto_20210312_1225.py b/main/migrations/0014_auto_20210314_1455.py similarity index 61% rename from main/migrations/0012_auto_20210312_1225.py rename to main/migrations/0014_auto_20210314_1455.py index 6d33580..77db2ec 100644 --- a/main/migrations/0012_auto_20210312_1225.py +++ b/main/migrations/0014_auto_20210314_1455.py @@ -1,12 +1,13 @@ -# Generated by Django 3.1.6 on 2021-03-12 09:25 +# Generated by Django 3.1.6 on 2021-03-14 11:55 from django.db import migrations, models +import django.utils.timezone class Migration(migrations.Migration): dependencies = [ - ('main', '0011_auto_20210311_1734'), + ('main', '0013_auto_20210311_2040'), ] operations = [ @@ -15,6 +16,11 @@ class Migration(migrations.Migration): name='custom_role_id', field=models.IntegerField(default=0, help_text='Код роли пользователя'), ), + migrations.AlterField( + model_name='rolechangelogs', + name='change_time', + field=models.DateTimeField(default=django.utils.timezone.now, help_text='Дата и время изменения роли'), + ), migrations.AlterField( model_name='userprofile', name='role', diff --git a/main/views.py b/main/views.py index 4c596ad..221e693 100644 --- a/main/views.py +++ b/main/views.py @@ -1,6 +1,5 @@ import logging import os -from datetime import datetime from django.contrib.auth.decorators import login_required from django.contrib.auth.forms import PasswordResetForm @@ -19,11 +18,12 @@ from django_registration.views import RegistrationView from zenpy import Zenpy from zenpy.lib.api_objects import User as ZenpyUser -from access_controller.settings import EMAIL_HOST_USER, ZENDESK_ROLES +from access_controller.settings import EMAIL_HOST_USER, ZENDESK_ROLES, ZENDESK_GROUPS from main.extra_func import check_user_exist, update_profile, get_user_organization, make_engineer, make_light_agent, \ - get_users_list, StatisticData + get_users_list, StatisticData, get_ticket_list, ZendeskAdmin from main.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, StatisticForm -from .models import UserProfile +from .models import UserProfile, UnassignedTicket, UnassignedTicketStatus + class CustomRegistrationView(RegistrationView): """ @@ -106,12 +106,7 @@ def profile_page(request: WSGIRequest) -> HttpResponse: def auth_user(request): - admin_creds = { - 'email': os.environ.get('ACCESS_CONTROLLER_API_EMAIL'), - 'subdomain': 'ngenix1612197338', - 'token': os.environ.get('ACCESS_CONTROLLER_API_TOKEN'), - } - admin = Zenpy(**admin_creds) + admin = ZendeskAdmin().admin zenpy_user: ZenpyUser = admin.users.search(request.user.email).values[0] return zenpy_user, admin @@ -163,7 +158,7 @@ def work_become_engineer(request): def main_page(request): """ - Отображение логгирования на главной странице + Отображение логгирования на главной странице """ logger = logging.getLogger('main.index') logger.info('Index page opened') @@ -178,7 +173,7 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView): def form_valid(self, form: AdminPageUsers) -> AdminPageUsers: """ - Функция установки ролей пользователям + Функция установки ролей пользователям """ if 'engineer' in self.request.POST: self.make_engineers(form.cleaned_data['users']) @@ -194,7 +189,6 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView): for user in users: make_light_agent(user, self.request.user) - @staticmethod def count_users(users) -> tuple: """ From 0cc788c039a5263309bbf54798748ef280ffc2fa Mon Sep 17 00:00:00 2001 From: Sokurov Idar Date: Sun, 14 Mar 2021 17:26:41 +0300 Subject: [PATCH 09/17] minor fix --- main/extra_func.py | 1 + 1 file changed, 1 insertion(+) diff --git a/main/extra_func.py b/main/extra_func.py index c7946fc..0e0c0ed 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -152,6 +152,7 @@ def make_light_agent(user_profile: UserProfile, who_changes: User) -> UserProfil ticket_id=ticket.id, status=UnassignedTicketStatus.UNASSIGNED ) + ticket.assignee = None ticket.group = ZendeskAdmin().get_group(ZENDESK_GROUPS['buffer']) ZendeskAdmin().admin.tickets.update(ticket) From 5ec2407211bea13b147c0d24b00036914ca388d0 Mon Sep 17 00:00:00 2001 From: Sokurov Idar Date: Wed, 17 Mar 2021 09:35:15 +0300 Subject: [PATCH 10/17] Add ticket unassignment --- access_controller/settings.py | 2 +- main/extra_func.py | 35 +++++++++++++++++++++++------------ main/models.py | 1 + main/views.py | 12 ++++++------ 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/access_controller/settings.py b/access_controller/settings.py index 953d2f6..3a2d7df 100644 --- a/access_controller/settings.py +++ b/access_controller/settings.py @@ -187,6 +187,6 @@ ZENDESK_GROUPS = { 'employees': 'Поддержка', 'buffer': 'Сменная группа', } - +SOLVED_TICKETS_EMAIL = 'd.krikov@ngenix.net' ONE_DAY = 12 # Количество часов в 1 рабочем дне diff --git a/main/extra_func.py b/main/extra_func.py index 0e0c0ed..b601a03 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -2,10 +2,11 @@ import os from datetime import timedelta, datetime, date from django.contrib.auth.models import User +from django.utils import timezone from zenpy import Zenpy from zenpy.lib.exception import APIException -from access_controller.settings import ZENDESK_ROLES as ROLES, ONE_DAY, ZENDESK_GROUPS +from access_controller.settings import ZENDESK_ROLES as ROLES, ONE_DAY, ZENDESK_GROUPS, SOLVED_TICKETS_EMAIL from main.models import UserProfile, RoleChangeLogs, UnassignedTicket, UnassignedTicketStatus @@ -143,17 +144,18 @@ def make_light_agent(user_profile: UserProfile, who_changes: User) -> UserProfil """ Функция **make_light_agent** устанавливапет пользователю роль легкого агента. """ - tickets = get_ticket_list(user_profile.user.email) + tickets = get_tickets_list(user_profile.user.email) for ticket in tickets: - if ticket.status=='solved': - continue UnassignedTicket.objects.create( assignee=user_profile.user, ticket_id=ticket.id, - status=UnassignedTicketStatus.UNASSIGNED + status=UnassignedTicketStatus.SOLVED if ticket.status == 'solved' else UnassignedTicketStatus.UNASSIGNED ) - ticket.assignee = None - ticket.group = ZendeskAdmin().get_group(ZENDESK_GROUPS['buffer']) + if ticket.status == 'solved': + ticket.assignee = ZendeskAdmin().get_user(SOLVED_TICKETS_EMAIL) + else: + ticket.assignee = None + ticket.group = ZendeskAdmin().get_group(ZENDESK_GROUPS['buffer']) ZendeskAdmin().admin.tickets.update(ticket) RoleChangeLogs.objects.create( @@ -167,7 +169,7 @@ def make_light_agent(user_profile: UserProfile, who_changes: User) -> UserProfil def get_users_list() -> list: """ - Функция **get_users_list** возвращает список пользователей Zendesk, относящихся к организации. + Функция **get_users_list** возвращает список пользователей Zendesk, относящихся к организации. """ zendesk = ZendeskAdmin() admin = zendesk.get_user(zendesk.email) @@ -175,7 +177,10 @@ def get_users_list() -> list: return zendesk.admin.organizations.users(org) -def get_ticket_list(email): +def get_tickets_list(email): + """ + Функция возвращает список тикетов пользователя Zendesk + """ return ZendeskAdmin().admin.search(assignee=email, type='ticket') @@ -381,9 +386,15 @@ class StatisticData: if last_log.new_role == ROLES['engineer']: self.fill_daterange(last_log.change_time.date() + timedelta(days=1), self.end_date + timedelta(days=1)) - self.statistic[last_log.change_time.date()] += (timedelta(days=1) - get_timedelta(last_log)).total_seconds() - if self.end_date == datetime.now().date(): - self.statistic[self.end_date] = get_timedelta(None, datetime.now().time()).total_seconds() + if last_log.change_time.date() == timezone.now().date(): + self.statistic[last_log.change_time.date()] += ( + get_timedelta(None, timezone.now().time()) - get_timedelta(last_log) + ).total_seconds() + else: + self.statistic[last_log.change_time.date()] += ( + timedelta(days=1) - get_timedelta(last_log)).total_seconds() + if self.end_date == timezone.now().date(): + self.statistic[self.end_date] = get_timedelta(None, timezone.now().time()).total_seconds() for log_index in range(len(self.data) - 1): if self.data[log_index].new_role == ROLES['engineer']: diff --git a/main/models.py b/main/models.py index b202977..7bd76bb 100644 --- a/main/models.py +++ b/main/models.py @@ -47,6 +47,7 @@ class UnassignedTicketStatus(models.IntegerChoices): RESTORED = 1, 'Авторство восстановлено' NOT_FOUND = 2, 'Пока нас не было, тикет испарился из буферной группы. Дополнительные действия не требуются' CLOSED = 3, 'Тикет уже был закрыт. Дополнительные действия не требуются' + SOLVED = 4, 'Тикет решён. Записан на пользователя с почтой SOLVED_TICKETS_EMAIL' class UnassignedTicket(models.Model): diff --git a/main/views.py b/main/views.py index 221e693..d014807 100644 --- a/main/views.py +++ b/main/views.py @@ -20,7 +20,7 @@ from zenpy.lib.api_objects import User as ZenpyUser from access_controller.settings import EMAIL_HOST_USER, ZENDESK_ROLES, ZENDESK_GROUPS from main.extra_func import check_user_exist, update_profile, get_user_organization, make_engineer, make_light_agent, \ - get_users_list, StatisticData, get_ticket_list, ZendeskAdmin + get_users_list, StatisticData, get_tickets_list, ZendeskAdmin from main.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, StatisticForm from .models import UserProfile, UnassignedTicket, UnassignedTicketStatus @@ -181,9 +181,9 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView): self.make_light_agents(form.cleaned_data['users']) return super().form_valid(form) - @staticmethod - def make_engineers(users): - [make_engineer(user) for user in users] + def make_engineers(self, users): + for user in users: + make_engineer(user, self.request.user) def make_light_agents(self, users): for user in users: @@ -227,8 +227,8 @@ class CustomLoginView(LoginView): @login_required() def statistic_page(request): - if not request.user.is_superuser: - return redirect('index') + if not request.user.has_perm('main.has_control_access'): + raise PermissionDenied context = { 'pagename': 'страница статистики', 'errors': list(), From 2bb2c9556b5f2f732506cd0e57e7f8cf7fc2f6ad Mon Sep 17 00:00:00 2001 From: Iurii Tatishchev Date: Thu, 18 Mar 2021 09:32:46 -0700 Subject: [PATCH 11/17] fix `./manage.py migrate` errors --- main/views.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/main/views.py b/main/views.py index b14907b..cae5d90 100644 --- a/main/views.py +++ b/main/views.py @@ -32,12 +32,6 @@ from rest_framework import viewsets, status from main.serializers import ProfileSerializer from rest_framework.response import Response -content_type_temp = ContentType.objects.get_for_model(UserProfile) -permission_temp, created = Permission.objects.get_or_create( - codename='has_control_access', - content_type=content_type_temp, -) - class CustomRegistrationView(RegistrationView): """ From b5c3a1219373350ed6cd3031302389dd01a10205 Mon Sep 17 00:00:00 2001 From: Sokurov Idar Date: Thu, 18 Mar 2021 20:07:21 +0300 Subject: [PATCH 12/17] minor fix --- main/extra_func.py | 1 - 1 file changed, 1 deletion(-) diff --git a/main/extra_func.py b/main/extra_func.py index b601a03..0224e01 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -381,7 +381,6 @@ class StatisticData: first_log, last_log = self.data[0], self.data[len(self.data) - 1] if first_log.old_role == ROLES['engineer']: - self.fill_daterange(self.start_date, first_log.change_time.date()) self.statistic[first_log.change_time.date()] += get_timedelta(first_log).total_seconds() if last_log.new_role == ROLES['engineer']: From 358351d6daf2539cff18602fef227908b5bc0388 Mon Sep 17 00:00:00 2001 From: Sokurov Idar Date: Thu, 18 Mar 2021 20:19:08 +0300 Subject: [PATCH 13/17] optimize imports --- main/extra_func.py | 6 +----- main/views.py | 25 ++++++++----------------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/main/extra_func.py b/main/extra_func.py index 81ccaf9..6ae7266 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -2,15 +2,11 @@ import os from datetime import timedelta, datetime, date from django.contrib.auth.models import User +from django.core.exceptions import ObjectDoesNotExist from django.utils import timezone from zenpy import Zenpy from zenpy.lib.exception import APIException -from main.models import UserProfile, RoleChangeLogs -from django.core.exceptions import ObjectDoesNotExist - - -from access_controller.settings import ZENDESK_ROLES as ROLES, ONE_DAY from access_controller.settings import ZENDESK_ROLES as ROLES, ONE_DAY, ZENDESK_GROUPS, SOLVED_TICKETS_EMAIL from main.models import UserProfile, RoleChangeLogs, UnassignedTicket, UnassignedTicketStatus diff --git a/main/views.py b/main/views.py index 406ed45..9f8a48a 100644 --- a/main/views.py +++ b/main/views.py @@ -1,7 +1,8 @@ import logging -import os +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.views import LoginView @@ -13,29 +14,19 @@ from django.shortcuts import render, get_list_or_404, redirect from django.urls import reverse_lazy, reverse from django.views.generic import FormView from django_registration.views import RegistrationView -from django.contrib.auth.decorators import login_required -from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin - -from zenpy import Zenpy +# Django REST +from rest_framework import viewsets +from rest_framework.response import Response from zenpy.lib.api_objects import User as ZenpyUser -from access_controller.settings import EMAIL_HOST_USER, ZENDESK_ROLES, ZENDESK_GROUPS -from main.extra_func import check_user_exist, update_profile, get_user_organization, make_engineer, make_light_agent, \ - get_users_list, StatisticData, get_tickets_list, ZendeskAdmin +from access_controller.settings import EMAIL_HOST_USER, ZENDESK_ROLES +from main.extra_func import ZendeskAdmin from main.extra_func import check_user_exist, update_profile, get_user_organization, \ make_engineer, make_light_agent, get_users_list, update_users_in_model, count_users, \ StatisticData -from main.models import UserProfile from main.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, StatisticForm -from .models import UserProfile, UnassignedTicket, UnassignedTicketStatus - - -from access_controller.settings import EMAIL_HOST_USER, ZENDESK_ROLES - -# Django REST -from rest_framework import viewsets, status from main.serializers import ProfileSerializer -from rest_framework.response import Response +from .models import UserProfile class CustomRegistrationView(RegistrationView): From b4d91ecd063632c07dd9067be2d87331d4d3cbf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D1=80=D0=B0=D0=B2=D1=87=D0=B5=D0=BD=D0=BA=D0=BE=20?= =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Thu, 18 Mar 2021 17:43:55 +0000 Subject: [PATCH 14/17] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db34259..47468bb 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ sudo apt install make pip install --upgrade pip pip install -r requirements.txt ./manage.py migrate -./manage.py shell -c "from django.contrib.auth import get_user_model; get_user_model().objects.create_superuser('vasya', '1@abc.net', 'promprog')" +./manage.py loaddata data.json ./manage.py runserver ``` Создать токен From da46d019cbafb83c0ab33ea99742875400a832b5 Mon Sep 17 00:00:00 2001 From: Artyom Kravchenko Date: Thu, 18 Mar 2021 20:48:06 +0300 Subject: [PATCH 15/17] add TODO --- main/extra_func.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/extra_func.py b/main/extra_func.py index 6ae7266..f2a4df9 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -443,7 +443,7 @@ class StatisticData: if first_log.old_role == ROLES['engineer']: self.statistic[first_log.change_time.date()] += get_timedelta(first_log).total_seconds() - if last_log.new_role == ROLES['engineer']: + if last_log.new_role == ROLES['engineer']: #TODO отдельная функция self.fill_daterange(last_log.change_time.date() + timedelta(days=1), self.end_date + timedelta(days=1)) if last_log.change_time.date() == timezone.now().date(): self.statistic[last_log.change_time.date()] += ( @@ -455,7 +455,7 @@ class StatisticData: if self.end_date == timezone.now().date(): self.statistic[self.end_date] = get_timedelta(None, timezone.now().time()).total_seconds() - for log_index in range(len(self.data) - 1): + for log_index in range(len(self.data) - 1): #TODO отдельная функция if self.data[log_index].new_role == ROLES['engineer']: current_log, next_log = self.data[log_index], self.data[log_index + 1] if current_log.change_time.date() != next_log.change_time.date(): From 0c2e2d81bcf2fda7ed65922c1a5381393140acbd Mon Sep 17 00:00:00 2001 From: Iurii Tatishchev Date: Fri, 19 Mar 2021 23:45:08 -0700 Subject: [PATCH 16/17] #43 | Add max agents setting. Spots left count on /work/ and /control/ --- access_controller/settings.py | 3 ++- main/templates/pages/adm_ruleset.html | 4 ++++ main/templates/pages/work.html | 2 +- main/views.py | 4 +++- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/access_controller/settings.py b/access_controller/settings.py index 28feeb4..020480d 100644 --- a/access_controller/settings.py +++ b/access_controller/settings.py @@ -191,6 +191,8 @@ ZENDESK_GROUPS = { SOLVED_TICKETS_EMAIL = 'd.krikov@ngenix.net' +ZENDESK_MAX_AGENTS = 3 + REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. @@ -200,4 +202,3 @@ REST_FRAMEWORK = { } ONE_DAY = 12 # Количество часов в 1 рабочем дне - diff --git a/main/templates/pages/adm_ruleset.html b/main/templates/pages/adm_ruleset.html index 92a0628..92686f1 100644 --- a/main/templates/pages/adm_ruleset.html +++ b/main/templates/pages/adm_ruleset.html @@ -20,6 +20,10 @@ {% block content %}
+
+

Свободных Мест: {{ licences_remaining }}

+
+ {% block form %} {% csrf_token %} diff --git a/main/templates/pages/work.html b/main/templates/pages/work.html index a588ef5..1ce7dd3 100644 --- a/main/templates/pages/work.html +++ b/main/templates/pages/work.html @@ -14,7 +14,7 @@
-

Основаная информация о странице

+

Свободных Мест: {{ licences_remaining }}

diff --git a/main/views.py b/main/views.py index 9f8a48a..a8b3af5 100644 --- a/main/views.py +++ b/main/views.py @@ -19,7 +19,7 @@ from rest_framework import viewsets from rest_framework.response import Response from zenpy.lib.api_objects import User as ZenpyUser -from access_controller.settings import EMAIL_HOST_USER, ZENDESK_ROLES +from access_controller.settings import EMAIL_HOST_USER, ZENDESK_ROLES, ZENDESK_MAX_AGENTS from main.extra_func import ZendeskAdmin 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, \ @@ -131,6 +131,7 @@ def work_page(request, id): context = { 'engineers': engineers, 'agents': light_agents, + 'licences_remaining': max(0, ZENDESK_MAX_AGENTS - len(engineers)), 'pagename': 'Управление правами' } return render(request, 'pages/work.html', context) @@ -203,6 +204,7 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView): UserProfile, role='agent') context['users'] = users context['engineers'], context['light_agents'] = count_users(get_users_list()) + context['licences_remaining'] = max(0, ZENDESK_MAX_AGENTS - context['engineers']) return context # TODO: need to get profile page url From a8ca0b0eae3b978cbc5bceeb6bddeadffb145496 Mon Sep 17 00:00:00 2001 From: Yuriy Kulakov Date: Sun, 21 Mar 2021 19:47:42 +0300 Subject: [PATCH 17/17] Added notificcations about role changes --- main/extra_func.py | 12 +++++------ main/migrations/0015_auto_20210321_1600.py | 18 ++++++++++++++++ main/templates/base/success_messages.html | 14 +++++++++++++ main/templates/pages/adm_ruleset.html | 3 ++- main/templates/pages/work.html | 1 + main/views.py | 24 ++++++++++++++-------- static/main/js/control.js | 4 ++-- 7 files changed, 58 insertions(+), 18 deletions(-) create mode 100644 main/migrations/0015_auto_20210321_1600.py create mode 100644 main/templates/base/success_messages.html diff --git a/main/extra_func.py b/main/extra_func.py index f2a4df9..f5488d6 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -236,16 +236,14 @@ def update_user_in_model(profile, zendesk_user): profile.name = zendesk_user.name profile.role = zendesk_user.role profile.image = zendesk_user.photo['content_url'] if zendesk_user.photo else None - profile.custom_role_id = zendesk_user.custom_role_id + if zendesk_user.custom_role_id is not None: + profile.custom_role_id = int(zendesk_user.custom_role_id) profile.save() def count_users(users) -> tuple: """ Функция подсчета количества сотрудников с ролями engineer и light_a - - .. todo:: - this func counts users from all zendesk instead of just from a model: """ engineers, light_agents = 0, 0 for user in users: @@ -443,11 +441,11 @@ class StatisticData: if first_log.old_role == ROLES['engineer']: self.statistic[first_log.change_time.date()] += get_timedelta(first_log).total_seconds() - if last_log.new_role == ROLES['engineer']: #TODO отдельная функция + if last_log.new_role == ROLES['engineer']: # TODO отдельная функция self.fill_daterange(last_log.change_time.date() + timedelta(days=1), self.end_date + timedelta(days=1)) if last_log.change_time.date() == timezone.now().date(): self.statistic[last_log.change_time.date()] += ( - get_timedelta(None, timezone.now().time()) - get_timedelta(last_log) + get_timedelta(None, timezone.now().time()) - get_timedelta(last_log) ).total_seconds() else: self.statistic[last_log.change_time.date()] += ( @@ -455,7 +453,7 @@ class StatisticData: if self.end_date == timezone.now().date(): self.statistic[self.end_date] = get_timedelta(None, timezone.now().time()).total_seconds() - for log_index in range(len(self.data) - 1): #TODO отдельная функция + for log_index in range(len(self.data) - 1): # TODO отдельная функция if self.data[log_index].new_role == ROLES['engineer']: current_log, next_log = self.data[log_index], self.data[log_index + 1] if current_log.change_time.date() != next_log.change_time.date(): diff --git a/main/migrations/0015_auto_20210321_1600.py b/main/migrations/0015_auto_20210321_1600.py new file mode 100644 index 0000000..79b5726 --- /dev/null +++ b/main/migrations/0015_auto_20210321_1600.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.6 on 2021-03-21 13:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0014_auto_20210314_1455'), + ] + + operations = [ + migrations.AlterField( + model_name='unassignedticket', + name='status', + field=models.IntegerField(choices=[(0, 'Снят с пользователя, перенесён в буферную группу'), (1, 'Авторство восстановлено'), (2, 'Пока нас не было, тикет испарился из буферной группы. Дополнительные действия не требуются'), (3, 'Тикет уже был закрыт. Дополнительные действия не требуются'), (4, 'Тикет решён. Записан на пользователя с почтой SOLVED_TICKETS_EMAIL')], default=0), + ), + ] diff --git a/main/templates/base/success_messages.html b/main/templates/base/success_messages.html new file mode 100644 index 0000000..fdef313 --- /dev/null +++ b/main/templates/base/success_messages.html @@ -0,0 +1,14 @@ +
+ {% for message in messages %} + + {% endfor %} +
diff --git a/main/templates/pages/adm_ruleset.html b/main/templates/pages/adm_ruleset.html index 92686f1..b324ac7 100644 --- a/main/templates/pages/adm_ruleset.html +++ b/main/templates/pages/adm_ruleset.html @@ -101,12 +101,13 @@ -
{% endblock %} {% endblock %} + + {% include 'base/success_messages.html' %}
diff --git a/main/templates/pages/work.html b/main/templates/pages/work.html index 1ce7dd3..67c5846 100644 --- a/main/templates/pages/work.html +++ b/main/templates/pages/work.html @@ -61,6 +61,7 @@ Сдать права инженера
+ {% include 'base/success_messages.html' %} {% endblock %} diff --git a/main/views.py b/main/views.py index a8b3af5..2c00277 100644 --- a/main/views.py +++ b/main/views.py @@ -7,6 +7,7 @@ from django.contrib.auth.models import User, Permission from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.views import LoginView from django.contrib.contenttypes.models import ContentType +from django.contrib.messages.views import SuccessMessageMixin from django.core.exceptions import PermissionDenied from django.core.handlers.wsgi import WSGIRequest from django.http import HttpResponseRedirect, HttpResponse @@ -14,9 +15,12 @@ from django.shortcuts import render, get_list_or_404, redirect from django.urls import reverse_lazy, reverse from django.views.generic import FormView from django_registration.views import RegistrationView +from django.contrib import messages + # Django REST from rest_framework import viewsets from rest_framework.response import Response + from zenpy.lib.api_objects import User as ZenpyUser from access_controller.settings import EMAIL_HOST_USER, ZENDESK_ROLES, ZENDESK_MAX_AGENTS @@ -131,6 +135,7 @@ def work_page(request, id): context = { 'engineers': engineers, 'agents': light_agents, + 'messages': messages.get_messages(request), 'licences_remaining': max(0, ZENDESK_MAX_AGENTS - len(engineers)), 'pagename': 'Управление правами' } @@ -138,15 +143,19 @@ def work_page(request, id): return redirect("login") +def user_update(zenpy_user, admin, request): + admin.users.update(zenpy_user) + request.user.userprofile.role = "agent" + request.user.userprofile.save() + messages.success(request, "Права были изменены") + + @login_required() def work_hand_over(request): zenpy_user, admin = auth_user(request) - if zenpy_user.custom_role_id == ZENDESK_ROLES['engineer']: zenpy_user.custom_role_id = ZENDESK_ROLES['light_agent'] - admin.users.update(zenpy_user) - request.user.userprofile.role = "agent" - request.user.userprofile.save() + user_update(zenpy_user, admin, request) return HttpResponseRedirect(reverse('work', args=(request.user.id,))) @@ -155,9 +164,7 @@ def work_become_engineer(request): zenpy_user, admin = auth_user(request) if zenpy_user.custom_role_id == ZENDESK_ROLES['light_agent']: zenpy_user.custom_role_id = ZENDESK_ROLES['engineer'] - admin.users.update(zenpy_user) - request.user.userprofile.role = "agent" - request.user.userprofile.save() + user_update(zenpy_user, admin, request) return HttpResponseRedirect(reverse('work', args=(request.user.id,))) @@ -170,11 +177,12 @@ def main_page(request): return render(request, 'pages/index.html') -class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView): +class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, FormView): permission_required = 'main.has_control_access' template_name = 'pages/adm_ruleset.html' form_class = AdminPageUsers success_url = '/control/' + success_message = "Права были изменены." def form_valid(self, form: AdminPageUsers) -> AdminPageUsers: """ diff --git a/static/main/js/control.js b/static/main/js/control.js index 62d2daf..e518c36 100644 --- a/static/main/js/control.js +++ b/static/main/js/control.js @@ -65,7 +65,7 @@ class TableBody extends React.Component { componentDidMount() { this.interval = setInterval(() => { this.get_users(); - }, 10000); + }, 60000); } componentWillUnmount() { @@ -80,4 +80,4 @@ class TableBody extends React.Component { } ReactDOM.render(, document.getElementById("new_tbody")); -setTimeout(() => document.getElementById("old_tbody").remove(), 10000); +setTimeout(() => document.getElementById("old_tbody").remove(), 60000);