resolve conflicts
1
.gitignore
vendored
@ -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.
|
||||
|
@ -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,
|
||||
|
12
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"
|
||||
}
|
||||
|
BIN
docs/source/_static/admin_manage.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
docs/source/_static/admin_manage_done.png
Normal file
After Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 53 KiB |
BIN
docs/source/_static/main_logout.png
Normal file
After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 65 KiB |
BIN
docs/source/_static/request.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
docs/source/_static/role_change.png
Normal file
After Width: | Height: | Size: 63 KiB |
@ -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гю
|
||||
|
@ -80,6 +80,8 @@ API
|
||||
functions
|
||||
Serializer
|
||||
Serializers
|
||||
Сериализатор
|
||||
переадресации
|
||||
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -34,7 +34,7 @@
|
||||
<h6 class="table-title">Список сотрудников</h6>
|
||||
|
||||
{% block table %}
|
||||
<table class="light-table">
|
||||
<table class="table table-dark light-table">
|
||||
|
||||
<thead>
|
||||
<th>Name</th>
|
||||
@ -42,8 +42,8 @@
|
||||
<th>Role</th>
|
||||
<th>Checked</th>
|
||||
</thead>
|
||||
<tbody id="tbody">
|
||||
</tbody>
|
||||
<tbody id="tbody"></tbody>
|
||||
|
||||
</table>
|
||||
<p id="loading">Данные загружаются...</p>
|
||||
{% endblock %}
|
||||
|
@ -3,10 +3,10 @@ import os
|
||||
from datetime import datetime
|
||||
|
||||
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
|
||||
@ -29,9 +29,9 @@ from access_controller.settings import EMAIL_HOST_USER, ZENDESK_ROLES, ZENDESK_M
|
||||
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
|
||||
StatisticData, log
|
||||
from main.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, StatisticForm
|
||||
from main.serializers import ProfileSerializer
|
||||
from main.serializers import ProfileSerializer, ZendeskUserSerializer
|
||||
from .models import UserProfile
|
||||
|
||||
|
||||
@ -194,6 +194,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, "Права были изменены")
|
||||
|
||||
@ -207,9 +208,7 @@ def work_hand_over(request: WSGIRequest) -> HttpResponseRedirect:
|
||||
:param request: данные текущего пользователя (login_required)
|
||||
:return: перезагрузка текущей страницы после выполнения смены роли
|
||||
"""
|
||||
|
||||
make_light_agent(request.user.userprofile,request.user)
|
||||
|
||||
return HttpResponseRedirect(reverse('work', args=(request.user.id,)))
|
||||
|
||||
|
||||
@ -222,16 +221,19 @@ def work_become_engineer(request: WSGIRequest) -> HttpResponseRedirect:
|
||||
:return: перезагрузка текущей страницы после выполнения смены роли
|
||||
"""
|
||||
zenpy_user, admin = auth_user(request)
|
||||
make_engineer(request.user.userprofile,request.user)
|
||||
|
||||
make_engineer(request.user.userprofile,request.user)
|
||||
return HttpResponseRedirect(reverse('work', args=(request.user.id,)))
|
||||
|
||||
|
||||
def main_page(request):
|
||||
def main_page(request: WSGIRequest) -> HttpResponse:
|
||||
"""
|
||||
Функция переадресации на главную страницу.
|
||||
"""
|
||||
return render(request, 'pages/index.html')
|
||||
|
||||
|
||||
class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin,SuccessMessageMixin, FormView):
|
||||
class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, FormView):
|
||||
"""
|
||||
Класс отображения страницы администратора.
|
||||
|
||||
@ -273,10 +275,18 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin,SuccessMessageMi
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
@ -303,16 +313,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()
|
||||
|
@ -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:
|
||||
|
@ -1,10 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
// React
|
||||
class TableRow extends React.Component {
|
||||
class ModelUserTableRow extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<tr>
|
||||
<tr className={"table-dark"}>
|
||||
<td>
|
||||
<a href="#">{this.props.user.name}</a>
|
||||
</td>
|
||||
@ -23,6 +23,43 @@ class TableRow extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
class ModelUserTableRows extends React.Component {
|
||||
render() {
|
||||
return ReactDOM.createPortal(
|
||||
this.props.users.map((user, key) => (
|
||||
<ModelUserTableRow user={user} key={key} />
|
||||
)),
|
||||
document.getElementById("tbody")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ZendeskUserTableRow extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<tr className={"table-secondary"}>
|
||||
<td>
|
||||
<a href="#">{this.props.user.name}</a>
|
||||
</td>
|
||||
<td>{this.props.user.email}</td>
|
||||
<td>{this.props.user.zendesk_role}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ZendeskUserTableRows extends React.Component {
|
||||
render() {
|
||||
return ReactDOM.createPortal(
|
||||
this.props.users.map((user, key) => (
|
||||
<ZendeskUserTableRow user={user} key={key} />
|
||||
)),
|
||||
document.getElementById("tbody")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TableBody extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -30,6 +67,7 @@ class TableBody extends React.Component {
|
||||
users: [],
|
||||
engineers: 0,
|
||||
light_agents: 0,
|
||||
zendesk_users: [],
|
||||
};
|
||||
}
|
||||
|
||||
@ -39,6 +77,7 @@ class TableBody extends React.Component {
|
||||
users: response.data.users,
|
||||
engineers: response.data.engineers,
|
||||
light_agents: response.data.light_agents,
|
||||
zendesk_users: response.data.zendesk_users,
|
||||
});
|
||||
let elements = document.querySelectorAll(".info-quantity-value");
|
||||
elements[0].innerHTML = this.state.engineers;
|
||||
@ -62,9 +101,12 @@ class TableBody extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.state.users.map((user, key) => (
|
||||
<TableRow user={user} key={key} />
|
||||
));
|
||||
return (
|
||||
<tr>
|
||||
<ModelUserTableRows users={this.state.users} />
|
||||
<ZendeskUserTableRows users={this.state.zendesk_users} />
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|