diff --git a/access_controller/settings.py b/access_controller/settings.py index 3a2d7df..28feeb4 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', ] @@ -187,6 +188,16 @@ ZENDESK_GROUPS = { 'employees': 'Поддержка', 'buffer': 'Сменная группа', } + SOLVED_TICKETS_EMAIL = 'd.krikov@ngenix.net' + +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 рабочем дне diff --git a/access_controller/urls.py b/access_controller/urls.py index da72f11..e174717 100644 --- a/access_controller/urls.py +++ b/access_controller/urls.py @@ -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/', 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)) +] diff --git a/data.json b/data.json new file mode 100644 index 0000000..a4310a4 --- /dev/null +++ b/data.json @@ -0,0 +1,57 @@ +[ + { + "model": "auth.user", + "pk": 1, + "fields": { + "password": "pbkdf2_sha256$216000$gHBBCr1jBELf$ZkEDW3IEd8Wij7u8vkv+0Eze32CS01bcaYWhcD9OIC4=", + "last_login": null, + "is_superuser": true, + "username": "admin@gmail.com", + "first_name": "", + "last_name": "", + "email": "admin@gmail.com", + "is_staff": true, + "is_active": true, + "date_joined": "2021-03-10T16:38:56.303Z", + "groups": [], + "user_permissions": [33] + } + }, + { + "model": "main.userprofile", + "pk": 1, + "fields": { + "name": "ZendeskAdmin", + "user": 1, + "role": "admin" + } + }, + { + "model": "auth.user", + "pk": 2, + "fields": { + "password": "pbkdf2_sha256$216000$5qLJgrm2Quq9$KDBNNymVZXkUx0HKBPFst2m83kLe0egPBnkW7KnkORU=", + "last_login": null, + "is_superuser": false, + "username": "123@test.ru", + "first_name": "", + "last_name": "", + "email": "123@test.ru", + "is_staff": false, + "is_active": true, + "date_joined": "2021-03-10T16:38:56.303Z", + "groups": [], + "user_permissions": [] + } + }, + { + "model": "main.userprofile", + "pk": 2, + "fields": { + "name": "UserForAccessTest", + "user": 2, + "role": "agent", + "custom_role_id": "360005209000" + } + } +] diff --git a/main/extra_func.py b/main/extra_func.py index 0224e01..81ccaf9 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -6,6 +6,11 @@ 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 @@ -172,9 +177,11 @@ 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)) - 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): @@ -210,6 +217,63 @@ def get_user_organization(email: str) -> str: return ZendeskAdmin().get_user_org(email) +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 + profile.custom_role_id = 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: + 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 исключая правую границу diff --git a/main/serializers.py b/main/serializers.py new file mode 100644 index 0000000..f72fc86 --- /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', 'id', '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..92a0628 100644 --- a/main/templates/pages/adm_ruleset.html +++ b/main/templates/pages/adm_ruleset.html @@ -10,11 +10,15 @@ {% endblock %} +{% block extra_scripts %} + + + + +{% endblock%} + {% block content %}
-
-

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

-
{% block form %}
@@ -37,25 +41,23 @@ - + - - + {% 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 +105,6 @@ {% endblock %}
- + {% endblock %} diff --git a/main/templates/registration/login.html b/main/templates/registration/login.html index 7a3ab68..68c557f 100644 --- a/main/templates/registration/login.html +++ b/main/templates/registration/login.html @@ -30,7 +30,7 @@ {% endif %}
- Забыли пароль? + Забыли пароль?
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 d014807..406ed45 100644 --- a/main/views.py +++ b/main/views.py @@ -1,9 +1,7 @@ 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 @@ -15,16 +13,31 @@ 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 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 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 + + class CustomRegistrationView(RegistrationView): """ Отображение и логика работы страницы регистрации пользователя @@ -175,10 +188,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) def make_engineers(self, users): @@ -189,32 +203,15 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, FormView): for user in users: make_light_agent(user, self.request.user) - @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: """ Функция формирования контента страницы администратора (с проверкой прав доступа) """ - 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()) return context # TODO: need to get profile page url @@ -225,6 +222,26 @@ 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): if not request.user.has_perm('main.has_control_access'): 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..62d2daf 100644 --- a/static/main/js/control.js +++ b/static/main/js/control.js @@ -1,9 +1,83 @@ "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); + } + } else { + alert( + "Количество пользователей агентов не соответствует количеству полей в форме AdminPageUsers" + ); } } +move_checkboxes(); + +// React +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: [], + 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(); + }, 10000); + } + + componentWillUnmount() { + clearInterval(this.interval); + } + + render() { + return this.state.users.map((user, key) => ( + + )); + } +} + +ReactDOM.render(, document.getElementById("new_tbody")); +setTimeout(() => document.getElementById("old_tbody").remove(), 10000);