diff --git a/.gitignore b/.gitignore index d798070..1f6ea85 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ local_settings.py db.sqlite3 db.sqlite3-journal media/ +logs/ # If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ # in your Git repository. Update and uncomment the following line accordingly. diff --git a/access_controller/settings.py b/access_controller/settings.py index cee5805..92b3d29 100644 --- a/access_controller/settings.py +++ b/access_controller/settings.py @@ -143,41 +143,7 @@ AUTHENTICATION_BACKENDS = [ # Logging system # https://docs.djangoproject.com/en/3.1/topics/logging/ -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'verbose': { - 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', - 'style': '{', - }, - 'simple': { - 'format': '{levelname} {message}', - 'style': '{', - }, - }, - 'handlers': { - 'console': { - 'level': 'INFO', - 'class': 'logging.StreamHandler', - 'formatter': 'simple' - }, - 'mail_admins': { - 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler', - } - }, - 'loggers': { - 'django': { - 'handlers': ['console'], - 'propagate': True, - }, - 'main.index': { - 'handlers': ['console'], - 'level': 'INFO', - } - } -} + ZENDESK_ROLES = { 'engineer': 360005209000, diff --git a/data.json b/data.json index a4310a4..97678f3 100644 --- a/data.json +++ b/data.json @@ -1,7 +1,7 @@ [ { "model": "auth.user", - "pk": 1, + "pk": 3, "fields": { "password": "pbkdf2_sha256$216000$gHBBCr1jBELf$ZkEDW3IEd8Wij7u8vkv+0Eze32CS01bcaYWhcD9OIC4=", "last_login": null, @@ -19,16 +19,16 @@ }, { "model": "main.userprofile", - "pk": 1, + "pk": 3, "fields": { "name": "ZendeskAdmin", - "user": 1, + "user": 3, "role": "admin" } }, { "model": "auth.user", - "pk": 2, + "pk": 4, "fields": { "password": "pbkdf2_sha256$216000$5qLJgrm2Quq9$KDBNNymVZXkUx0HKBPFst2m83kLe0egPBnkW7KnkORU=", "last_login": null, @@ -46,10 +46,10 @@ }, { "model": "main.userprofile", - "pk": 2, + "pk": 4, "fields": { "name": "UserForAccessTest", - "user": 2, + "user": 4, "role": "agent", "custom_role_id": "360005209000" } diff --git a/docs/source/_static/admin_manage.png b/docs/source/_static/admin_manage.png new file mode 100644 index 0000000..38764b4 Binary files /dev/null and b/docs/source/_static/admin_manage.png differ diff --git a/docs/source/_static/admin_manage_done.png b/docs/source/_static/admin_manage_done.png new file mode 100644 index 0000000..6ae475b Binary files /dev/null and b/docs/source/_static/admin_manage_done.png differ diff --git a/docs/source/_static/login.png b/docs/source/_static/login.png index 2cf59cf..149ba55 100644 Binary files a/docs/source/_static/login.png and b/docs/source/_static/login.png differ diff --git a/docs/source/_static/main.png b/docs/source/_static/main.png index fcfd4f9..78afe48 100644 Binary files a/docs/source/_static/main.png and b/docs/source/_static/main.png differ diff --git a/docs/source/_static/main_logined.png b/docs/source/_static/main_logined.png deleted file mode 100644 index 5105101..0000000 Binary files a/docs/source/_static/main_logined.png and /dev/null differ diff --git a/docs/source/_static/main_logined_agent.png b/docs/source/_static/main_logined_agent.png deleted file mode 100644 index e4f76d1..0000000 Binary files a/docs/source/_static/main_logined_agent.png and /dev/null differ diff --git a/docs/source/_static/main_logout.png b/docs/source/_static/main_logout.png new file mode 100644 index 0000000..4dc98b7 Binary files /dev/null and b/docs/source/_static/main_logout.png differ diff --git a/docs/source/_static/permission_management.png b/docs/source/_static/permission_management.png deleted file mode 100644 index c1897ed..0000000 Binary files a/docs/source/_static/permission_management.png and /dev/null differ diff --git a/docs/source/_static/permission_request.png b/docs/source/_static/permission_request.png deleted file mode 100644 index 8752e91..0000000 Binary files a/docs/source/_static/permission_request.png and /dev/null differ diff --git a/docs/source/_static/profile.png b/docs/source/_static/profile.png index b84d139..c3ab5bf 100644 Binary files a/docs/source/_static/profile.png and b/docs/source/_static/profile.png differ diff --git a/docs/source/_static/request.png b/docs/source/_static/request.png new file mode 100644 index 0000000..bf80c3f Binary files /dev/null and b/docs/source/_static/request.png differ diff --git a/docs/source/_static/role_change.png b/docs/source/_static/role_change.png new file mode 100644 index 0000000..ff2d983 Binary files /dev/null and b/docs/source/_static/role_change.png differ diff --git a/docs/source/overview.rst b/docs/source/overview.rst index fb3c627..a7ca229 100644 --- a/docs/source/overview.rst +++ b/docs/source/overview.rst @@ -3,7 +3,7 @@ ========================= ****************************** -**Управление правами доступа** +Управление правами доступа ****************************** @@ -22,7 +22,7 @@ * **"Войти"** - если Вы уже являетесь зарегистрированным пользователем * **"Зарегистрироваться"** - при первом входе -.. image:: _static/main.png +.. image:: _static/main_logout.png Внимание! Для регистрации используется email с сайта Zendesk. Регистрация по каждому email возможна один раз @@ -32,7 +32,7 @@ * **"Профиль"** - просмотреть свои данные и запросить права доступа * **"Запрос прав"** - получение прав для работы с тикетами или **"Управление"** - доступно для администратора и предоставляет возможность группового назначения ролей пользователям -.. image:: _static/main_logined_agent.png +.. image:: _static/main.png ************* Регистрация @@ -72,7 +72,11 @@ На странице запроса прав Вам доступна информация о количестве и списке работающих над тикетами сотрудников, а также возможность сдать и запросить права. -.. image:: _static/permission_request.png +.. image:: _static/request.png + +Успешное изменение прав: + +.. image:: _static/role_change.png ****************************************** Управление правами доступа администратором @@ -84,6 +88,10 @@ * Количество и список инженеров и легких агентов * Возможность группового назначения прав с использованием чек-боксов -.. image:: _static/permission_management.png +.. image:: _static/admin_manage.png + +Изменение прав пользователей наглядно отразится в таблице пользователей: + +.. image:: _static/admin_manage_done.png .. |copy| unicode:: 0xA9 .. Школа программистов S101, группа 2. 2021гю diff --git a/docs/source/spelling_wordlist.txt b/docs/source/spelling_wordlist.txt index f190ae2..bd64cf9 100644 --- a/docs/source/spelling_wordlist.txt +++ b/docs/source/spelling_wordlist.txt @@ -80,6 +80,8 @@ API functions Serializer Serializers +Сериализатор +переадресации diff --git a/main/extra_func.py b/main/extra_func.py index 03add3e..758875a 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -1,3 +1,4 @@ +import logging import os from datetime import timedelta, datetime, date @@ -7,6 +8,7 @@ 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, SOLVED_TICKETS_EMAIL from main.models import UserProfile, RoleChangeLogs, UnassignedTicket, UnassignedTicketStatus @@ -139,7 +141,7 @@ class ZendeskAdmin: raise ValueError('invalid access_controller`s login data') -def update_role(user_profile: UserProfile, role: str) -> UserProfile: +def update_role(user_profile: UserProfile, role: int) -> UserProfile: """ Функция меняет роль пользователя. @@ -162,12 +164,6 @@ def make_engineer(user_profile: UserProfile, who_changes: User) -> UserProfile: :param user_profile: Профиль пользователя :return: Вызов функции **update_role** с параметрами: профиль пользователя, роль "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']) @@ -191,13 +187,6 @@ def make_light_agent(user_profile: UserProfile, who_changes: User) -> UserProfil 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']) @@ -274,7 +263,7 @@ def check_user_auth(email: str, password: str) -> bool: return True -def update_user_in_model(profile: UserProfile, zendesk_user: User) -> UserProfile: +def update_user_in_model(profile: UserProfile, zendesk_user: User): """ Функция обновляет профиль пользователя при изменении данных пользователя на Zendesk. @@ -607,3 +596,72 @@ class StatisticData: """ self.statistic.clear() self.fill_daterange(self.start_date, self.end_date + timedelta(days=1), 0) + + +class DatabaseHandler(logging.Handler): + def __init__(self): + logging.Handler.__init__(self) + + def emit(self, record): + database = RoleChangeLogs() + users = record.msg + if users[1]: + user = users[0] + admin = users[1] + elif not users[1]: + user = users[0] + admin = users[0] + database.name = user.name + database.user = user.user + database.changed_by = admin.user + if user.custom_role_id == ROLES['engineer']: + database.old_role = ROLES['light_agent'] + elif user.custom_role_id == ROLES['light_agent']: + database.old_role = ROLES['engineer'] + database.new_role = user.custom_role_id + database.save() + + +class CsvFormatter(logging.Formatter): + def __init__(self): + logging.Formatter.__init__(self) + + def format(self, record): + users = record.msg + if users[1]: + user = users[0] + admin = users[1] + elif not users[1]: + user = users[0] + admin = users[0] + msg = '' + msg += user.name + if user.custom_role_id == ROLES['engineer']: + msg += ',engineer,' + elif user.custom_role_id == ROLES['light_agent']: + msg += ',light_agent,' + time = str(timezone.now().today()) + msg += time[:16] + msg += ',' + msg += admin.name + return msg + + +def log(user, admin=0): + """ + Осуществляет запись логов в базу данных и csv файл + :param admin: + :param user: + :return: + """ + users = [user, admin] + logger = logging.getLogger('MY_LOGGER') + if not logger.hasHandlers(): + dbhandler = DatabaseHandler() + csvformatter = CsvFormatter() + csvhandler = logging.FileHandler('logs/logs.csv', "a") + csvhandler.setFormatter(csvformatter) + logger.addHandler(dbhandler) + logger.addHandler(csvhandler) + logger.setLevel('INFO') + logger.info(users) diff --git a/main/serializers.py b/main/serializers.py index cfc70a8..8436b54 100644 --- a/main/serializers.py +++ b/main/serializers.py @@ -1,6 +1,7 @@ from django.contrib.auth.models import User from rest_framework import serializers from main.models import UserProfile +from access_controller.settings import ZENDESK_ROLES class UserSerializer(serializers.HyperlinkedModelSerializer): @@ -13,9 +14,25 @@ class UserSerializer(serializers.HyperlinkedModelSerializer): class ProfileSerializer(serializers.HyperlinkedModelSerializer): - """Сериализатор для модели профиля пользователя""" + """Класс serializer для модели профиля пользователя""" user = UserSerializer() class Meta: model = UserProfile fields = ['user', 'id', 'name', 'zendesk_role'] + + +class ZendeskUserSerializer(serializers.Serializer): + """Класс serializer для объектов пользователей из zenpy""" + name = serializers.CharField() + zendesk_role = serializers.SerializerMethodField('get_zendesk_role') + email = serializers.EmailField() + + @staticmethod + def get_zendesk_role(obj): + if obj.custom_role_id == ZENDESK_ROLES['engineer']: + return 'engineer' + elif obj.custom_role_id == ZENDESK_ROLES['light_agent']: + return 'light_agent' + else: + return "empty" diff --git a/main/templates/pages/adm_ruleset.html b/main/templates/pages/adm_ruleset.html index 1fb9baf..c0a5ad9 100644 --- a/main/templates/pages/adm_ruleset.html +++ b/main/templates/pages/adm_ruleset.html @@ -34,7 +34,7 @@
Name | @@ -42,8 +42,8 @@Role | Checked | - - + +
---|
Данные загружаются...
{% endblock %} diff --git a/main/views.py b/main/views.py index 208ab25..ee41a42 100644 --- a/main/views.py +++ b/main/views.py @@ -1,11 +1,15 @@ +import logging +import os +from datetime import datetime + from smtplib import SMTPException from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.contrib.auth.forms import PasswordResetForm from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin from django.contrib.auth.models import User, Permission from django.contrib.auth.tokens import default_token_generator +from django.contrib.auth.forms import PasswordResetForm from django.contrib.auth.views import LoginView from django.contrib.contenttypes.models import ContentType from django.contrib.messages.views import SuccessMessageMixin @@ -24,9 +28,9 @@ from zenpy.lib.api_objects import User as ZenpyUser from access_controller.settings import EMAIL_HOST_USER, ZENDESK_ROLES, ZENDESK_MAX_AGENTS, ZENDESK_GROUPS from main.extra_func import check_user_exist, update_profile, get_user_organization, \ make_engineer, make_light_agent, get_users_list, update_users_in_model, count_users, \ - StatisticData, ZendeskAdmin + StatisticData, log, ZendeskAdmin from main.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, StatisticForm -from main.serializers import ProfileSerializer +from main.serializers import ProfileSerializer, ZendeskUserSerializer from .models import UserProfile @@ -197,6 +201,7 @@ def user_update(zenpy_user: User, admin: User, request: WSGIRequest) -> UserProf admin.users.update(zenpy_user) request.user.userprofile.role = "agent" + request.user.userprofile.custom_role_id = zenpy_user.custom_role_id request.user.userprofile.save() messages.success(request, "Права были изменены") @@ -214,6 +219,7 @@ def work_hand_over(request: WSGIRequest) -> HttpResponseRedirect: if zenpy_user.custom_role_id == ZENDESK_ROLES['engineer']: zenpy_user.custom_role_id = ZENDESK_ROLES['light_agent'] user_update(zenpy_user, admin, request) + log(request.user.userprofile) return HttpResponseRedirect(reverse('work', args=(request.user.id,))) @@ -229,10 +235,14 @@ def work_become_engineer(request: WSGIRequest) -> HttpResponseRedirect: if zenpy_user.custom_role_id == ZENDESK_ROLES['light_agent']: zenpy_user.custom_role_id = ZENDESK_ROLES['engineer'] user_update(zenpy_user, admin, request) + log(request.user.userprofile) return HttpResponseRedirect(reverse('work', args=(request.user.id,))) -def main_page(request): +def main_page(request: WSGIRequest) -> HttpResponse: + """ + Функция переадресации на главную страницу. + """ return render(request, 'pages/index.html') @@ -278,10 +288,18 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageM """ for user in users: make_engineer(user, self.request.user) + log(user, self.request.user.userprofile) def make_light_agents(self, users): + """ + Функция проходит по списку пользователей, проставляя статус "light agent". + + :param users: Список пользователей + :return: Обновленный список пользователей + """ for user in users: make_light_agent(user, self.request.user) + log(user, self.request.user.userprofile) def get_context_data(self, **kwargs) -> dict: """ @@ -308,15 +326,34 @@ class UsersViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = ProfileSerializer def list(self, request, *args, **kwargs): - users = update_users_in_model().values - count = count_users(users) + users = update_users_in_model() + count = count_users(users.values) profiles = UserProfile.objects.filter(role='agent') serializer = self.get_serializer(profiles, many=True) - return Response({ + res = { 'users': serializer.data, 'engineers': count[0], - 'light_agents': count[1] - }) + 'light_agents': count[1], + "zendesk_users": self.get_zendesk_users(self.choose_users(users.values, profiles)) + } + return Response(res) + + @staticmethod + def choose_users(zendesk, model): + users = [] + for zendesk_user in zendesk: + if zendesk_user.name not in [user.name for user in model]: + users.append(zendesk_user) + return users + + @staticmethod + def get_zendesk_users(users): + zendesk_users = ZendeskUserSerializer( + data=[user for user in users if user.role != 'admin'], + many=True + ) + zendesk_users.is_valid() + return zendesk_users.data @login_required() diff --git a/manage.py b/manage.py index 3601c76..2554164 100755 --- a/manage.py +++ b/manage.py @@ -6,7 +6,8 @@ import sys def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'access_controller.settings') + os.environ.setdefault('DJANGO_SETTINGS_MODULE', + 'access_controller.settings') try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/static/main/js/control.js b/static/main/js/control.js index 55d1d1a..82f9f27 100644 --- a/static/main/js/control.js +++ b/static/main/js/control.js @@ -1,10 +1,10 @@ "use strict"; // React -class TableRow extends React.Component { +class ModelUserTableRow extends React.Component { render() { return ( -