Merge branch 'develop' into feature/documentation

# Conflicts:
#	main/extra_func.py
#	main/models.py
#	main/views.py
This commit is contained in:
Степаненко Ольга 2021-03-23 14:10:27 +03:00
commit ae9f41fd23
18 changed files with 443 additions and 108 deletions

View File

@ -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
```
Создать токен

View File

@ -36,6 +36,7 @@ INSTALLED_APPS = [
'django.contrib.messages',
'django.contrib.staticfiles',
'django_registration',
'rest_framework',
'main',
]
@ -183,4 +184,21 @@ ZENDESK_ROLES = {
'light_agent': 360005208980,
}
ZENDESK_GROUPS = {
'employees': 'Поддержка',
'buffer': 'Сменная группа',
}
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.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}
ONE_DAY = 12 # Количество часов в 1 рабочем дне

View File

@ -16,23 +16,26 @@ Including another URLconf
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.views import work_page, work_hand_over, work_become_engineer, \
AdminPageView, statistic_page
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(), name='login'),
path('accounts/', include('django.contrib.auth.urls')),
path('accounts/', include('django_registration.backends.one_step.urls')),
path('work/<int:id>', work_page, name="work"),
path('work/hand_over/', work_hand_over, name="work_hand_over"),
path('work/become_engineer/', work_become_engineer, name="work_become_engineer"),
path('accounts/', include('django_registration.backends.activation.urls')),
path('accounts/login/', include('django.contrib.auth.urls')),
path('control/', AdminPageView.as_view(), name='control'),
path('statistic/', statistic_page, name='statistic')
]
@ -59,3 +62,8 @@ urlpatterns += [
name='password_reset_complete'
),
]
# Django REST
urlpatterns += [
path('api/', include(router.urls))
]

View File

@ -2,11 +2,13 @@ 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 access_controller.settings import ZENDESK_ROLES as ROLES, ONE_DAY
from main.models import UserProfile, RoleChangeLogs
from access_controller.settings import ZENDESK_ROLES as ROLES, ONE_DAY, ZENDESK_GROUPS, SOLVED_TICKETS_EMAIL
from main.models import UserProfile, RoleChangeLogs, UnassignedTicket, UnassignedTicketStatus
class ZendeskAdmin:
@ -29,12 +31,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()
@ -50,10 +46,7 @@ class ZendeskAdmin:
def get_user_name(self, email: str) -> str:
"""
Функция возвращает имя пользователя по его email.
:param email: Email пользователя
:return: Имя пользователя
Функция **get_user_name** возвращает имя пользователя по его email
"""
user = self.admin.users.search(email).values[0]
return user.name
@ -97,6 +90,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:
"""
Функция возвращает организацию, к которой относится пользователь по его email.
@ -148,34 +147,69 @@ 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:
"""
Функция устанавливапет пользователю роль инженера.
:param user_profile: Профиль пользователя
:return: Вызов функции **update_role** с параметрами: профиль пользователя, роль "endineer"
"""
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) -> UserProfile:
def make_light_agent(user_profile: UserProfile, who_changes: User) -> UserProfile:
"""
Функция устанавливапет пользователю роль легкого агента.
:param user_profile: Профиль пользователя
:return: Вызов функции **update_role** с параметрами: профиль пользователя, роль "light_agent"
"""
tickets = get_tickets_list(user_profile.user.email)
for ticket in tickets:
UnassignedTicket.objects.create(
assignee=user_profile.user,
ticket_id=ticket.id,
status=UnassignedTicketStatus.SOLVED if ticket.status == 'solved' else UnassignedTicketStatus.UNASSIGNED
)
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(
user=user_profile.user,
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:
"""
Функция возвращает список пользователей Zendesk, относящихся к организации.
Функция **get_users_list** возвращает список пользователей Zendesk, относящихся к организации.
"""
zendesk = ZendeskAdmin()
admin = zendesk.get_user(zendesk.email)
org = next(zendesk.admin.users.organizations(user=admin))
return zendesk.admin.organizations.users(org)
# У пользователей должна быть организация SYSTEM
org = next(zendesk.admin.search(type='organization', name='SYSTEM'))
users = zendesk.admin.organizations.users(org)
return users
def get_tickets_list(email):
"""
Функция возвращает список тикетов пользователя Zendesk
"""
return ZendeskAdmin().admin.search(assignee=email, type='ticket')
def update_profile(user_profile: UserProfile) -> UserProfile:
@ -213,7 +247,62 @@ def get_user_organization(email: str) -> str:
return ZendeskAdmin().get_user_org(email)
def daterange(start_date: date, end_date: date) -> list:
def check_user_auth(email: str, password: str) -> bool:
"""
Функция проверяет, верны ли входные данные
:raise: :class:`APIException`: исключение, вызываемое если пользователь не аутентифицирован
"""
creds = {
'email': email,
'password': password,
'subdomain': 'ngenix1612197338',
}
try:
user = Zenpy(**creds)
user.search(email, type='user')
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
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
"""
engineers, light_agents = 0, 0
for user in users:
if user.custom_role_id == ROLES['engineer']:
engineers += 1
elif user.custom_role_id == ROLES['light_agent']:
light_agents += 1
return engineers, light_agents
def update_users_in_model():
"""
Обновляет пользователей в модели UserProfile по списку пользователей в организации
"""
users = get_users_list()
for user in users:
try:
profile = User.objects.get(email=user.email).userprofile
update_user_in_model(profile, user)
except ObjectDoesNotExist:
pass
return users
def daterange(start_date, end_date) -> list:
"""
Функция возвращает список дней с start_date по end_date, исключая правую границу.
@ -227,7 +316,7 @@ def daterange(start_date: date, end_date: date) -> list:
return dates
def get_timedelta(log: RoleChangeLogs, time=None) -> timedelta:
def get_timedelta(log, time=None) -> timedelta:
"""
Функция возвращает объект класса timedelta, который хранит промежуток времени от начала суток до момента,
который находится в log (объект класса RoleChangeLogs) или в time(datetime.time), если введён.
@ -287,9 +376,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
@ -409,7 +498,7 @@ class StatisticData:
return False
return True
def _set_data(self) -> dict:
def _init_data(self):
"""
Функция возвращает логи в диапазоне дат start_date-end_date для пользователя с указанным email.
@ -426,7 +515,7 @@ class StatisticData:
except User.DoesNotExist:
self.errors += ['Пользователь не найден']
def _set_statistic(self) -> dict:
def _init_statistic(self) -> dict:
"""
Функция заполняет словарь, в котором ключ - дата, значение - кол-во проработанных в этот день секунд.
@ -439,16 +528,21 @@ 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']:
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))
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):
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():

View File

@ -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)),
],
),
]

View File

@ -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),
),
]

View File

@ -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',

View File

@ -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),
),
]

View File

@ -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):
@ -41,10 +42,22 @@ 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='Дата и время изменения роли')
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='Кем была изменена роль')
class UnassignedTicketStatus(models.IntegerChoices):
UNASSIGNED = 0, 'Снят с пользователя, перенесён в буферную группу'
RESTORED = 1, 'Авторство восстановлено'
NOT_FOUND = 2, 'Пока нас не было, тикет испарился из буферной группы. Дополнительные действия не требуются'
CLOSED = 3, 'Тикет уже был закрыт. Дополнительные действия не требуются'
SOLVED = 4, 'Тикет решён. Записан на пользователя с почтой SOLVED_TICKETS_EMAIL'
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)

17
main/serializers.py Normal file
View File

@ -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', 'id', 'role', 'name']

View File

@ -27,6 +27,7 @@
</style>
{% block extra_css %}{% endblock %}
{% block extra_scripts %}{% endblock %}
</head>
<body class="d-flex flex-column h-100">

View File

@ -0,0 +1,14 @@
<div class="mt-5">
{% for message in messages %}
<div
class="alert alert-{{ message.tags }} alert-dismissible fade show p-2"
role="alert"
style="display: flex; align-items: center; justify-content: space-between;"
>
{{ message }}
<div>
<button type="button" class="btn btn-light p-2" data-bs-dismiss="alert" aria-label="Close">X</button>
</div>
</div>
{% endfor %}
</div>

View File

@ -10,10 +10,18 @@
<link rel="stylesheet" href="{% static 'main/css/work.css' %}"/>
{% endblock %}
{% block extra_scripts %}
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
{% endblock%}
{% block content %}
<div class="container-md">
<div class="new-section">
<p class="row page-description">Основная информация о странице</p>
<p class="row page-description">Свободных Мест: {{ licences_remaining }}</p>
</div>
{% block form %}
@ -37,25 +45,23 @@
<table class="light-table">
<thead>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Name(link to profile)</th>
<th>Checked</th>
</thead>
<tbody>
<tbody id="old_tbody">
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td><a href="#">{{ user.name }}</a></td>
<td>{{ user.user.email }}</td>
<td>{{ user.role }}</td>
<td><a href="#">{{ user.name }}</a></td>
<td class="checkbox_field"></td>
</tr>
{% endfor %}
</tbody>
<tbody id="new_tbody"></tbody>
</table>
{% endblock%}
@ -95,14 +101,15 @@
<button type="submit" name="light_agent" class="hand-over-acess-button default-button">
Назначить выбранных на роль легкого агента
</button>
</div>
</div>
{% endblock %}
</form>
{% endblock %}
{% include 'base/success_messages.html' %}
</div>
<script src="{% static 'main/js/control.js'%}"></script>
<script src="{% static 'main/js/control.js'%}" type="text/babel"></script>
{% endblock %}

View File

@ -14,7 +14,7 @@
<div class="container-md">
<div class="new-section">
<p class="row page-description">Основаная информация о странице</p>
<p class="row page-description">Свободных Мест: {{ licences_remaining }}</p>
</div>
<div class="row justify-content-center new-section">
@ -61,6 +61,7 @@
<a href="/work/hand_over" class="hand-over-acess-button default-button">Сдать права инженера</a>
</div>
</div>
{% include 'base/success_messages.html' %}
</div>
{% endblock %}

6
main/urls.py Normal file
View File

@ -0,0 +1,6 @@
from rest_framework.routers import DefaultRouter
from main.views import UsersViewSet
router = DefaultRouter()
router.register(r'users', UsersViewSet)

View File

@ -9,6 +9,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
@ -16,18 +17,27 @@ 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 zenpy import Zenpy
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
from main.extra_func import check_user_exist, update_profile, get_user_organization, make_engineer, make_light_agent, \
get_users_list, StatisticData
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, \
StatisticData
from main.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, StatisticForm
from main.serializers import ProfileSerializer
from .models import UserProfile
class CustomRegistrationView(RegistrationView):
"""
Класс отображения и логики работы страницы регистрации пользователя
Отображение и логика работы страницы регистрации пользователя
:param form_class: Форма, которую необходимо заполнить для регистрации
:type form_class: :class:`forms.CustomRegistrationForm`
@ -135,12 +145,7 @@ def auth_user(request: WSGIRequest) -> ZenpyUser:
:param request: email, subdomain и token пользователя
:return: объект пользователя Zendesk
"""
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
@ -168,12 +173,21 @@ def work_page(request: WSGIRequest, id: int) -> HttpResponse:
context = {
'engineers': engineers,
'agents': light_agents,
'messages': messages.get_messages(request),
'licences_remaining': max(0, ZENDESK_MAX_AGENTS - len(engineers)),
'pagename': 'Управление правами'
}
return render(request, 'pages/work.html', context)
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: WSGIRequest) -> HttpResponseRedirect:
"""
@ -184,12 +198,9 @@ def work_hand_over(request: WSGIRequest) -> HttpResponseRedirect:
:return: перезагрузка текущей страницы после выполнения смены роли
"""
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,)))
@ -204,9 +215,7 @@ def work_become_engineer(request: WSGIRequest) -> HttpResponseRedirect:
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,)))
@ -242,6 +251,7 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
template_name = 'pages/adm_ruleset.html'
form_class = AdminPageUsers
success_url = '/control/'
success_message = "Права были изменены."
def form_valid(self, form: AdminPageUsers) -> AdminPageUsers:
"""
@ -250,68 +260,67 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
:param form: Форма страницы администратора
:return: Обновленная страница (пользователям проставлены новые статусы)
"""
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
def make_engineers(users):
def make_engineers(self, users):
"""
Функция проходит по списку пользователей, проставляя статус "engineer".
:param users: Список пользователей
:return: Обновленный список пользователей
"""
[make_engineer(user) for user in users]
@staticmethod
def make_light_agents(users):
"""
Функция проходит по списку пользователей, проставляя статус "light_agent".
:param users: Список пользователей
:return: Обновленный список пользователей
"""
[make_light_agent(user) for user in users]
@staticmethod
def count_users(users) -> tuple:
"""
Функция подсчета количества сотрудников с ролями "engineer" и "light_agent".
.. 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
make_engineer(user, self.request.user)
def make_light_agents(self, users):
for user in users:
make_light_agent(user, self.request.user)
def get_context_data(self, **kwargs) -> dict:
"""
Функция формирования контента страницы администратора (с проверкой прав доступа)
"""
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(get_users_list())
context['licences_remaining'] = max(0, ZENDESK_MAX_AGENTS - context['engineers'])
return context # TODO: need to get profile page url
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().values
count = count_users(users)
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]
})
@login_required()
def statistic_page(request: WSGIRequest) -> HttpResponse:
"""

View File

@ -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

View File

@ -1,4 +1,6 @@
"use strict";
function move_checkboxes() {
let checkboxes = document.getElementsByName("users");
let fields = document.querySelectorAll(".checkbox_field");
if (checkboxes.length == fields.length) {
@ -6,4 +8,76 @@ if (checkboxes.length == fields.length) {
let el = checkboxes[i].cloneNode(true);
fields[i].appendChild(el);
}
} else {
alert(
"Количество пользователей агентов не соответствует количеству полей в форме AdminPageUsers"
);
}
}
move_checkboxes();
// React
class TableRow extends React.Component {
render() {
return (
<tr>
<td>
<a href="#">{this.props.user.name}</a>
</td>
<td>{this.props.user.user.email}</td>
<td>{this.props.user.role}</td>
<td>
<input
type="checkbox"
value={this.props.user.id}
className="form-check-input"
name="users"
/>
</td>
</tr>
);
}
}
class TableBody extends React.Component {
constructor(props) {
super(props);
this.state = {
users: [],
engineers: 0,
light_agents: 0,
};
}
get_users() {
axios.get("/api/users").then((response) => {
this.setState({
users: response.data.users,
engineers: response.data.engineers,
light_agents: response.data.light_agents,
});
let elements = document.querySelectorAll(".info-quantity-value");
elements[0].innerHTML = this.state.engineers;
elements[1].innerHTML = this.state.light_agents;
});
}
componentDidMount() {
this.interval = setInterval(() => {
this.get_users();
}, 60000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return this.state.users.map((user, key) => (
<TableRow user={user} key={key} />
));
}
}
ReactDOM.render(<TableBody />, document.getElementById("new_tbody"));
setTimeout(() => document.getElementById("old_tbody").remove(), 60000);