Merge branch 'develop' into feature/documentation
# Conflicts: # main/extra_func.py # main/models.py # main/views.py
This commit is contained in:
commit
ae9f41fd23
@ -44,7 +44,7 @@ sudo apt install make
|
|||||||
pip install --upgrade pip
|
pip install --upgrade pip
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
./manage.py migrate
|
./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
|
./manage.py runserver
|
||||||
```
|
```
|
||||||
Создать токен
|
Создать токен
|
||||||
|
@ -36,6 +36,7 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django_registration',
|
'django_registration',
|
||||||
|
'rest_framework',
|
||||||
'main',
|
'main',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -183,4 +184,21 @@ ZENDESK_ROLES = {
|
|||||||
'light_agent': 360005208980,
|
'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 рабочем дне
|
ONE_DAY = 12 # Количество часов в 1 рабочем дне
|
||||||
|
@ -16,23 +16,26 @@ Including another URLconf
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
from django.urls import path, include
|
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 main_page, profile_page, CustomRegistrationView, CustomLoginView
|
||||||
from main.views import work_page, work_hand_over, work_become_engineer, \
|
from main.views import work_page, work_hand_over, work_become_engineer, \
|
||||||
AdminPageView, statistic_page
|
AdminPageView, statistic_page
|
||||||
|
from main.urls import router
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls, name='admin'),
|
path('admin/', admin.site.urls, name='admin'),
|
||||||
path('', main_page, name='index'),
|
path('', main_page, name='index'),
|
||||||
path('accounts/profile/', profile_page, name='profile'),
|
path('accounts/profile/', profile_page, name='profile'),
|
||||||
path('accounts/register/', CustomRegistrationView.as_view(), name='registration'),
|
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.contrib.auth.urls')),
|
||||||
path('accounts/', include('django_registration.backends.one_step.urls')),
|
path('accounts/', include('django_registration.backends.one_step.urls')),
|
||||||
path('work/<int:id>', work_page, name="work"),
|
path('work/<int:id>', work_page, name="work"),
|
||||||
path('work/hand_over/', work_hand_over, name="work_hand_over"),
|
path('work/hand_over/', work_hand_over, name="work_hand_over"),
|
||||||
path('work/become_engineer/', work_become_engineer, name="work_become_engineer"),
|
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('control/', AdminPageView.as_view(), name='control'),
|
||||||
path('statistic/', statistic_page, name='statistic')
|
path('statistic/', statistic_page, name='statistic')
|
||||||
]
|
]
|
||||||
@ -59,3 +62,8 @@ urlpatterns += [
|
|||||||
name='password_reset_complete'
|
name='password_reset_complete'
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Django REST
|
||||||
|
urlpatterns += [
|
||||||
|
path('api/', include(router.urls))
|
||||||
|
]
|
||||||
|
@ -2,11 +2,13 @@ import os
|
|||||||
from datetime import timedelta, datetime, date
|
from datetime import timedelta, datetime, date
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.utils import timezone
|
||||||
from zenpy import Zenpy
|
from zenpy import Zenpy
|
||||||
from zenpy.lib.exception import APIException
|
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, SOLVED_TICKETS_EMAIL
|
||||||
from main.models import UserProfile, RoleChangeLogs
|
from main.models import UserProfile, RoleChangeLogs, UnassignedTicket, UnassignedTicketStatus
|
||||||
|
|
||||||
|
|
||||||
class ZendeskAdmin:
|
class ZendeskAdmin:
|
||||||
@ -29,12 +31,6 @@ class ZendeskAdmin:
|
|||||||
email: str = os.getenv('ACCESS_CONTROLLER_API_EMAIL')
|
email: str = os.getenv('ACCESS_CONTROLLER_API_EMAIL')
|
||||||
token: str = os.getenv('ACCESS_CONTROLLER_API_TOKEN')
|
token: str = os.getenv('ACCESS_CONTROLLER_API_TOKEN')
|
||||||
password: str = os.getenv('ACCESS_CONTROLLER_API_PASSWORD')
|
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):
|
def __init__(self):
|
||||||
self.create_admin()
|
self.create_admin()
|
||||||
@ -50,10 +46,7 @@ class ZendeskAdmin:
|
|||||||
|
|
||||||
def get_user_name(self, email: str) -> str:
|
def get_user_name(self, email: str) -> str:
|
||||||
"""
|
"""
|
||||||
Функция возвращает имя пользователя по его email.
|
Функция **get_user_name** возвращает имя пользователя по его email
|
||||||
|
|
||||||
:param email: Email пользователя
|
|
||||||
:return: Имя пользователя
|
|
||||||
"""
|
"""
|
||||||
user = self.admin.users.search(email).values[0]
|
user = self.admin.users.search(email).values[0]
|
||||||
return user.name
|
return user.name
|
||||||
@ -97,6 +90,12 @@ class ZendeskAdmin:
|
|||||||
"""
|
"""
|
||||||
return self.admin.users.search(email).values[0]
|
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:
|
def get_user_org(self, email: str) -> str:
|
||||||
"""
|
"""
|
||||||
Функция возвращает организацию, к которой относится пользователь по его email.
|
Функция возвращает организацию, к которой относится пользователь по его email.
|
||||||
@ -148,34 +147,69 @@ def update_role(user_profile: UserProfile, role: str) -> UserProfile:
|
|||||||
zendesk.admin.users.update(user)
|
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: Профиль пользователя
|
:param user_profile: Профиль пользователя
|
||||||
:return: Вызов функции **update_role** с параметрами: профиль пользователя, роль "endineer"
|
: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'])
|
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: Профиль пользователя
|
:param user_profile: Профиль пользователя
|
||||||
:return: Вызов функции **update_role** с параметрами: профиль пользователя, роль "light_agent"
|
: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'])
|
update_role(user_profile, ROLES['light_agent'])
|
||||||
|
|
||||||
|
|
||||||
def get_users_list() -> list:
|
def get_users_list() -> list:
|
||||||
"""
|
"""
|
||||||
Функция возвращает список пользователей Zendesk, относящихся к организации.
|
Функция **get_users_list** возвращает список пользователей Zendesk, относящихся к организации.
|
||||||
"""
|
"""
|
||||||
zendesk = ZendeskAdmin()
|
zendesk = ZendeskAdmin()
|
||||||
admin = zendesk.get_user(zendesk.email)
|
|
||||||
org = next(zendesk.admin.users.organizations(user=admin))
|
# У пользователей должна быть организация SYSTEM
|
||||||
return zendesk.admin.organizations.users(org)
|
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:
|
def update_profile(user_profile: UserProfile) -> UserProfile:
|
||||||
@ -213,7 +247,62 @@ def get_user_organization(email: str) -> str:
|
|||||||
return ZendeskAdmin().get_user_org(email)
|
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, исключая правую границу.
|
Функция возвращает список дней с start_date по end_date, исключая правую границу.
|
||||||
|
|
||||||
@ -227,7 +316,7 @@ def daterange(start_date: date, end_date: date) -> list:
|
|||||||
return dates
|
return dates
|
||||||
|
|
||||||
|
|
||||||
def get_timedelta(log: RoleChangeLogs, time=None) -> timedelta:
|
def get_timedelta(log, time=None) -> timedelta:
|
||||||
"""
|
"""
|
||||||
Функция возвращает объект класса timedelta, который хранит промежуток времени от начала суток до момента,
|
Функция возвращает объект класса timedelta, который хранит промежуток времени от начала суток до момента,
|
||||||
который находится в log (объект класса RoleChangeLogs) или в time(datetime.time), если введён.
|
который находится в log (объект класса RoleChangeLogs) или в time(datetime.time), если введён.
|
||||||
@ -287,9 +376,9 @@ class StatisticData:
|
|||||||
self.warnings = list()
|
self.warnings = list()
|
||||||
self.data = dict()
|
self.data = dict()
|
||||||
self.statistic = dict()
|
self.statistic = dict()
|
||||||
self._set_data()
|
self._init_data()
|
||||||
if stat is None:
|
if stat is None:
|
||||||
self._set_statistic()
|
self._init_statistic()
|
||||||
else:
|
else:
|
||||||
self.statistic = stat
|
self.statistic = stat
|
||||||
|
|
||||||
@ -409,7 +498,7 @@ class StatisticData:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _set_data(self) -> dict:
|
def _init_data(self):
|
||||||
"""
|
"""
|
||||||
Функция возвращает логи в диапазоне дат start_date-end_date для пользователя с указанным email.
|
Функция возвращает логи в диапазоне дат start_date-end_date для пользователя с указанным email.
|
||||||
|
|
||||||
@ -426,7 +515,7 @@ class StatisticData:
|
|||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
self.errors += ['Пользователь не найден']
|
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]
|
first_log, last_log = self.data[0], self.data[len(self.data) - 1]
|
||||||
|
|
||||||
if first_log.old_role == ROLES['engineer']:
|
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()
|
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.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 last_log.change_time.date() == timezone.now().date():
|
||||||
if self.end_date == datetime.now().date():
|
self.statistic[last_log.change_time.date()] += (
|
||||||
self.statistic[self.end_date] = get_timedelta(None, datetime.now().time()).total_seconds()
|
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']:
|
if self.data[log_index].new_role == ROLES['engineer']:
|
||||||
current_log, next_log = self.data[log_index], self.data[log_index + 1]
|
current_log, next_log = self.data[log_index], self.data[log_index + 1]
|
||||||
if current_log.change_time.date() != next_log.change_time.date():
|
if current_log.change_time.date() != next_log.change_time.date():
|
||||||
|
29
main/migrations/0012_auto_20210311_2027.py
Normal file
29
main/migrations/0012_auto_20210311_2027.py
Normal 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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
18
main/migrations/0013_auto_20210311_2040.py
Normal file
18
main/migrations/0013_auto_20210311_2040.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
@ -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
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('main', '0011_auto_20210311_1734'),
|
('main', '0013_auto_20210311_2040'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
@ -15,6 +16,11 @@ class Migration(migrations.Migration):
|
|||||||
name='custom_role_id',
|
name='custom_role_id',
|
||||||
field=models.IntegerField(default=0, help_text='Код роли пользователя'),
|
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(
|
migrations.AlterField(
|
||||||
model_name='userprofile',
|
model_name='userprofile',
|
||||||
name='role',
|
name='role',
|
18
main/migrations/0015_auto_20210321_1600.py
Normal file
18
main/migrations/0015_auto_20210321_1600.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
@ -2,6 +2,7 @@ from django.db import models
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
class UserProfile(models.Model):
|
class UserProfile(models.Model):
|
||||||
@ -41,10 +42,22 @@ class RoleChangeLogs(models.Model):
|
|||||||
|
|
||||||
user = models.ForeignKey(to=User, on_delete=models.CASCADE,
|
user = models.ForeignKey(to=User, on_delete=models.CASCADE,
|
||||||
help_text='Пользователь, которому присвоили другую роль')
|
help_text='Пользователь, которому присвоили другую роль')
|
||||||
name = models.TextField(help_text='Имя пользователя')
|
|
||||||
old_role = models.IntegerField(default=0, help_text='Старая роль')
|
old_role = models.IntegerField(default=0, help_text='Старая роль')
|
||||||
new_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',
|
changed_by = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='changed_by',
|
||||||
help_text='Кем была изменена роль')
|
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
17
main/serializers.py
Normal 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']
|
@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
</style>
|
</style>
|
||||||
{% block extra_css %}{% endblock %}
|
{% block extra_css %}{% endblock %}
|
||||||
|
{% block extra_scripts %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="d-flex flex-column h-100">
|
<body class="d-flex flex-column h-100">
|
||||||
|
14
main/templates/base/success_messages.html
Normal file
14
main/templates/base/success_messages.html
Normal 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>
|
@ -10,11 +10,19 @@
|
|||||||
<link rel="stylesheet" href="{% static 'main/css/work.css' %}"/>
|
<link rel="stylesheet" href="{% static 'main/css/work.css' %}"/>
|
||||||
{% endblock %}
|
{% 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 %}
|
{% block content %}
|
||||||
<div class="container-md">
|
<div class="container-md">
|
||||||
<div class="new-section">
|
|
||||||
<p class="row page-description">Основная информация о странице</p>
|
<div class="new-section">
|
||||||
</div>
|
<p class="row page-description">Свободных Мест: {{ licences_remaining }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
@ -37,25 +45,23 @@
|
|||||||
<table class="light-table">
|
<table class="light-table">
|
||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
<th>ID</th>
|
<th>Name</th>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<th>Role</th>
|
<th>Role</th>
|
||||||
<th>Name(link to profile)</th>
|
|
||||||
<th>Checked</th>
|
<th>Checked</th>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody id="old_tbody">
|
||||||
{% for user in users %}
|
{% for user in users %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ user.id }}</td>
|
<td><a href="#">{{ user.name }}</a></td>
|
||||||
<td>{{ user.user.email }}</td>
|
<td>{{ user.user.email }}</td>
|
||||||
<td>{{ user.role }}</td>
|
<td>{{ user.role }}</td>
|
||||||
<td><a href="#">{{ user.name }}</a></td>
|
|
||||||
<td class="checkbox_field"></td>
|
<td class="checkbox_field"></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
<tbody id="new_tbody"></tbody>
|
||||||
</table>
|
</table>
|
||||||
{% endblock%}
|
{% endblock%}
|
||||||
|
|
||||||
@ -95,14 +101,15 @@
|
|||||||
<button type="submit" name="light_agent" class="hand-over-acess-button default-button">
|
<button type="submit" name="light_agent" class="hand-over-acess-button default-button">
|
||||||
Назначить выбранных на роль легкого агента
|
Назначить выбранных на роль легкого агента
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% include 'base/success_messages.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="{% static 'main/js/control.js'%}"></script>
|
<script src="{% static 'main/js/control.js'%}" type="text/babel"></script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<div class="container-md">
|
<div class="container-md">
|
||||||
|
|
||||||
<div class="new-section">
|
<div class="new-section">
|
||||||
<p class="row page-description">Основаная информация о странице</p>
|
<p class="row page-description">Свободных Мест: {{ licences_remaining }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row justify-content-center new-section">
|
<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>
|
<a href="/work/hand_over" class="hand-over-acess-button default-button">Сдать права инженера</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% include 'base/success_messages.html' %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
6
main/urls.py
Normal file
6
main/urls.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
from main.views import UsersViewSet
|
||||||
|
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register(r'users', UsersViewSet)
|
115
main/views.py
115
main/views.py
@ -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.tokens import default_token_generator
|
||||||
from django.contrib.auth.views import LoginView
|
from django.contrib.auth.views import LoginView
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.handlers.wsgi import WSGIRequest
|
from django.core.handlers.wsgi import WSGIRequest
|
||||||
from django.http import HttpResponseRedirect, HttpResponse
|
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.urls import reverse_lazy, reverse
|
||||||
from django.views.generic import FormView
|
from django.views.generic import FormView
|
||||||
from django_registration.views import RegistrationView
|
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 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 check_user_exist, update_profile, get_user_organization, make_engineer, make_light_agent, \
|
from main.extra_func import ZendeskAdmin
|
||||||
get_users_list, StatisticData
|
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.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, StatisticForm
|
||||||
|
from main.serializers import ProfileSerializer
|
||||||
from .models import UserProfile
|
from .models import UserProfile
|
||||||
|
|
||||||
|
|
||||||
class CustomRegistrationView(RegistrationView):
|
class CustomRegistrationView(RegistrationView):
|
||||||
"""
|
"""
|
||||||
Класс отображения и логики работы страницы регистрации пользователя
|
Отображение и логика работы страницы регистрации пользователя
|
||||||
|
|
||||||
:param form_class: Форма, которую необходимо заполнить для регистрации
|
:param form_class: Форма, которую необходимо заполнить для регистрации
|
||||||
:type form_class: :class:`forms.CustomRegistrationForm`
|
:type form_class: :class:`forms.CustomRegistrationForm`
|
||||||
@ -135,12 +145,7 @@ def auth_user(request: WSGIRequest) -> ZenpyUser:
|
|||||||
:param request: email, subdomain и token пользователя
|
:param request: email, subdomain и token пользователя
|
||||||
:return: объект пользователя Zendesk
|
:return: объект пользователя Zendesk
|
||||||
"""
|
"""
|
||||||
admin_creds = {
|
admin = ZendeskAdmin().admin
|
||||||
'email': os.environ.get('ACCESS_CONTROLLER_API_EMAIL'),
|
|
||||||
'subdomain': 'ngenix1612197338',
|
|
||||||
'token': os.environ.get('ACCESS_CONTROLLER_API_TOKEN'),
|
|
||||||
}
|
|
||||||
admin = Zenpy(**admin_creds)
|
|
||||||
zenpy_user: ZenpyUser = admin.users.search(request.user.email).values[0]
|
zenpy_user: ZenpyUser = admin.users.search(request.user.email).values[0]
|
||||||
return zenpy_user, admin
|
return zenpy_user, admin
|
||||||
|
|
||||||
@ -168,12 +173,21 @@ def work_page(request: WSGIRequest, id: int) -> HttpResponse:
|
|||||||
context = {
|
context = {
|
||||||
'engineers': engineers,
|
'engineers': engineers,
|
||||||
'agents': light_agents,
|
'agents': light_agents,
|
||||||
|
'messages': messages.get_messages(request),
|
||||||
|
'licences_remaining': max(0, ZENDESK_MAX_AGENTS - len(engineers)),
|
||||||
'pagename': 'Управление правами'
|
'pagename': 'Управление правами'
|
||||||
}
|
}
|
||||||
return render(request, 'pages/work.html', context)
|
return render(request, 'pages/work.html', context)
|
||||||
return redirect("login")
|
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()
|
@login_required()
|
||||||
def work_hand_over(request: WSGIRequest) -> HttpResponseRedirect:
|
def work_hand_over(request: WSGIRequest) -> HttpResponseRedirect:
|
||||||
"""
|
"""
|
||||||
@ -184,12 +198,9 @@ def work_hand_over(request: WSGIRequest) -> HttpResponseRedirect:
|
|||||||
:return: перезагрузка текущей страницы после выполнения смены роли
|
:return: перезагрузка текущей страницы после выполнения смены роли
|
||||||
"""
|
"""
|
||||||
zenpy_user, admin = auth_user(request)
|
zenpy_user, admin = auth_user(request)
|
||||||
|
|
||||||
if zenpy_user.custom_role_id == ZENDESK_ROLES['engineer']:
|
if zenpy_user.custom_role_id == ZENDESK_ROLES['engineer']:
|
||||||
zenpy_user.custom_role_id = ZENDESK_ROLES['light_agent']
|
zenpy_user.custom_role_id = ZENDESK_ROLES['light_agent']
|
||||||
admin.users.update(zenpy_user)
|
user_update(zenpy_user, admin, request)
|
||||||
request.user.userprofile.role = "agent"
|
|
||||||
request.user.userprofile.save()
|
|
||||||
return HttpResponseRedirect(reverse('work', args=(request.user.id,)))
|
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)
|
zenpy_user, admin = auth_user(request)
|
||||||
if zenpy_user.custom_role_id == ZENDESK_ROLES['light_agent']:
|
if zenpy_user.custom_role_id == ZENDESK_ROLES['light_agent']:
|
||||||
zenpy_user.custom_role_id = ZENDESK_ROLES['engineer']
|
zenpy_user.custom_role_id = ZENDESK_ROLES['engineer']
|
||||||
admin.users.update(zenpy_user)
|
user_update(zenpy_user, admin, request)
|
||||||
request.user.userprofile.role = "agent"
|
|
||||||
request.user.userprofile.save()
|
|
||||||
return HttpResponseRedirect(reverse('work', args=(request.user.id,)))
|
return HttpResponseRedirect(reverse('work', args=(request.user.id,)))
|
||||||
|
|
||||||
|
|
||||||
@ -242,6 +251,7 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
|||||||
template_name = 'pages/adm_ruleset.html'
|
template_name = 'pages/adm_ruleset.html'
|
||||||
form_class = AdminPageUsers
|
form_class = AdminPageUsers
|
||||||
success_url = '/control/'
|
success_url = '/control/'
|
||||||
|
success_message = "Права были изменены."
|
||||||
|
|
||||||
def form_valid(self, form: AdminPageUsers) -> AdminPageUsers:
|
def form_valid(self, form: AdminPageUsers) -> AdminPageUsers:
|
||||||
"""
|
"""
|
||||||
@ -250,68 +260,67 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView):
|
|||||||
:param form: Форма страницы администратора
|
:param form: Форма страницы администратора
|
||||||
:return: Обновленная страница (пользователям проставлены новые статусы)
|
:return: Обновленная страница (пользователям проставлены новые статусы)
|
||||||
"""
|
"""
|
||||||
|
users = form.cleaned_data['users']
|
||||||
if 'engineer' in self.request.POST:
|
if 'engineer' in self.request.POST:
|
||||||
self.make_engineers(form.cleaned_data['users'])
|
self.make_engineers(users)
|
||||||
elif 'light_agent' in self.request.POST:
|
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)
|
return super().form_valid(form)
|
||||||
|
|
||||||
@staticmethod
|
def make_engineers(self, users):
|
||||||
def make_engineers(users):
|
|
||||||
"""
|
"""
|
||||||
Функция проходит по списку пользователей, проставляя статус "engineer".
|
Функция проходит по списку пользователей, проставляя статус "engineer".
|
||||||
|
|
||||||
:param users: Список пользователей
|
:param users: Список пользователей
|
||||||
:return: Обновленный список пользователей
|
: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:
|
for user in users:
|
||||||
if user.custom_role_id == ZENDESK_ROLES['engineer']:
|
make_engineer(user, self.request.user)
|
||||||
engineers += 1
|
|
||||||
elif user.custom_role_id == ZENDESK_ROLES['light_agent']:
|
def make_light_agents(self, users):
|
||||||
light_agents += 1
|
for user in users:
|
||||||
return engineers, light_agents
|
make_light_agent(user, self.request.user)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs) -> dict:
|
def get_context_data(self, **kwargs) -> dict:
|
||||||
"""
|
"""
|
||||||
Функция формирования контента страницы администратора (с проверкой прав доступа)
|
Функция формирования контента страницы администратора (с проверкой прав доступа)
|
||||||
"""
|
"""
|
||||||
if self.request.user.userprofile.role != 'admin':
|
|
||||||
raise PermissionDenied
|
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['users'] = get_list_or_404(
|
users = get_list_or_404(
|
||||||
UserProfile, role='agent')
|
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
|
return context # TODO: need to get profile page url
|
||||||
|
|
||||||
|
|
||||||
class CustomLoginView(LoginView):
|
class CustomLoginView(LoginView):
|
||||||
"""
|
"""
|
||||||
Классс отображения страницы авторизации пользователя.
|
Отображение страницы авторизации пользователя
|
||||||
"""
|
"""
|
||||||
form_class = CustomAuthenticationForm
|
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()
|
@login_required()
|
||||||
def statistic_page(request: WSGIRequest) -> HttpResponse:
|
def statistic_page(request: WSGIRequest) -> HttpResponse:
|
||||||
"""
|
"""
|
||||||
|
@ -3,6 +3,8 @@ Django==3.1.6
|
|||||||
Pillow==8.1.0
|
Pillow==8.1.0
|
||||||
zenpy~=2.0.24
|
zenpy~=2.0.24
|
||||||
django_registration==3.1.1
|
django_registration==3.1.1
|
||||||
|
djangorestframework==3.12.2
|
||||||
|
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
Sphinx==3.4.3
|
Sphinx==3.4.3
|
||||||
|
@ -1,9 +1,83 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
let checkboxes = document.getElementsByName("users");
|
|
||||||
let fields = document.querySelectorAll(".checkbox_field");
|
function move_checkboxes() {
|
||||||
if (checkboxes.length == fields.length) {
|
let checkboxes = document.getElementsByName("users");
|
||||||
for (let i = 0; i < fields.length; ++i) {
|
let fields = document.querySelectorAll(".checkbox_field");
|
||||||
let el = checkboxes[i].cloneNode(true);
|
if (checkboxes.length == fields.length) {
|
||||||
fields[i].appendChild(el);
|
for (let i = 0; i < fields.length; ++i) {
|
||||||
|
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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user