Merge branch 'develop' into feature/registration_bug

# Conflicts:
#	main/views.py
This commit is contained in:
Sokurov Idar 2021-04-13 22:11:31 +03:00
commit 2c14f65d4f
23 changed files with 212 additions and 80 deletions

1
.gitignore vendored
View File

@ -12,6 +12,7 @@ local_settings.py
db.sqlite3 db.sqlite3
db.sqlite3-journal db.sqlite3-journal
media/ media/
logs/
# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ # 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. # in your Git repository. Update and uncomment the following line accordingly.

View File

@ -143,41 +143,7 @@ AUTHENTICATION_BACKENDS = [
# Logging system # Logging system
# https://docs.djangoproject.com/en/3.1/topics/logging/ # 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 = { ZENDESK_ROLES = {
'engineer': 360005209000, 'engineer': 360005209000,

View File

@ -1,7 +1,7 @@
[ [
{ {
"model": "auth.user", "model": "auth.user",
"pk": 1, "pk": 3,
"fields": { "fields": {
"password": "pbkdf2_sha256$216000$gHBBCr1jBELf$ZkEDW3IEd8Wij7u8vkv+0Eze32CS01bcaYWhcD9OIC4=", "password": "pbkdf2_sha256$216000$gHBBCr1jBELf$ZkEDW3IEd8Wij7u8vkv+0Eze32CS01bcaYWhcD9OIC4=",
"last_login": null, "last_login": null,
@ -19,16 +19,16 @@
}, },
{ {
"model": "main.userprofile", "model": "main.userprofile",
"pk": 1, "pk": 3,
"fields": { "fields": {
"name": "ZendeskAdmin", "name": "ZendeskAdmin",
"user": 1, "user": 3,
"role": "admin" "role": "admin"
} }
}, },
{ {
"model": "auth.user", "model": "auth.user",
"pk": 2, "pk": 4,
"fields": { "fields": {
"password": "pbkdf2_sha256$216000$5qLJgrm2Quq9$KDBNNymVZXkUx0HKBPFst2m83kLe0egPBnkW7KnkORU=", "password": "pbkdf2_sha256$216000$5qLJgrm2Quq9$KDBNNymVZXkUx0HKBPFst2m83kLe0egPBnkW7KnkORU=",
"last_login": null, "last_login": null,
@ -46,10 +46,10 @@
}, },
{ {
"model": "main.userprofile", "model": "main.userprofile",
"pk": 2, "pk": 4,
"fields": { "fields": {
"name": "UserForAccessTest", "name": "UserForAccessTest",
"user": 2, "user": 4,
"role": "agent", "role": "agent",
"custom_role_id": "360005209000" "custom_role_id": "360005209000"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -3,7 +3,7 @@
========================= =========================
****************************** ******************************
**Управление правами доступа** Управление правами доступа
****************************** ******************************
@ -22,7 +22,7 @@
* **"Войти"** - если Вы уже являетесь зарегистрированным пользователем * **"Войти"** - если Вы уже являетесь зарегистрированным пользователем
* **"Зарегистрироваться"** - при первом входе * **"Зарегистрироваться"** - при первом входе
.. image:: _static/main.png .. image:: _static/main_logout.png
Внимание! Для регистрации используется email с сайта Zendesk. Регистрация по каждому email Внимание! Для регистрации используется 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гю .. |copy| unicode:: 0xA9 .. Школа программистов S101, группа 2. 2021гю

View File

@ -80,6 +80,8 @@ API
functions functions
Serializer Serializer
Serializers Serializers
Сериализатор
переадресации

View File

@ -1,3 +1,4 @@
import logging
import os import os
from datetime import timedelta, datetime, date from datetime import timedelta, datetime, date
@ -7,6 +8,7 @@ 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, ZENDESK_GROUPS, SOLVED_TICKETS_EMAIL from access_controller.settings import ZENDESK_ROLES as ROLES, ONE_DAY, ZENDESK_GROUPS, SOLVED_TICKETS_EMAIL
from main.models import UserProfile, RoleChangeLogs, UnassignedTicket, UnassignedTicketStatus from main.models import UserProfile, RoleChangeLogs, UnassignedTicket, UnassignedTicketStatus
@ -139,7 +141,7 @@ class ZendeskAdmin:
raise ValueError('invalid access_controller`s login data') 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: Профиль пользователя :param user_profile: Профиль пользователя
:return: Вызов функции **update_role** с параметрами: профиль пользователя, роль "engineer" :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']) 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.assignee = None
ticket.group = ZendeskAdmin().get_group(ZENDESK_GROUPS['buffer']) ticket.group = ZendeskAdmin().get_group(ZENDESK_GROUPS['buffer'])
ZendeskAdmin().admin.tickets.update(ticket) 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'])
@ -274,7 +263,7 @@ def check_user_auth(email: str, password: str) -> bool:
return True return True
def update_user_in_model(profile: UserProfile, zendesk_user: User) -> UserProfile: def update_user_in_model(profile: UserProfile, zendesk_user: User):
""" """
Функция обновляет профиль пользователя при изменении данных пользователя на Zendesk. Функция обновляет профиль пользователя при изменении данных пользователя на Zendesk.
@ -607,3 +596,72 @@ class StatisticData:
""" """
self.statistic.clear() self.statistic.clear()
self.fill_daterange(self.start_date, self.end_date + timedelta(days=1), 0) 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)

View File

@ -1,6 +1,7 @@
from django.contrib.auth.models import User from django.contrib.auth.models import User
from rest_framework import serializers from rest_framework import serializers
from main.models import UserProfile from main.models import UserProfile
from access_controller.settings import ZENDESK_ROLES
class UserSerializer(serializers.HyperlinkedModelSerializer): class UserSerializer(serializers.HyperlinkedModelSerializer):
@ -13,9 +14,25 @@ class UserSerializer(serializers.HyperlinkedModelSerializer):
class ProfileSerializer(serializers.HyperlinkedModelSerializer): class ProfileSerializer(serializers.HyperlinkedModelSerializer):
"""Сериализатор для модели профиля пользователя""" """Класс serializer для модели профиля пользователя"""
user = UserSerializer() user = UserSerializer()
class Meta: class Meta:
model = UserProfile model = UserProfile
fields = ['user', 'id', 'name', 'zendesk_role'] 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"

View File

@ -34,7 +34,7 @@
<h6 class="table-title">Список сотрудников</h6> <h6 class="table-title">Список сотрудников</h6>
{% block table %} {% block table %}
<table class="light-table"> <table class="table table-dark light-table">
<thead> <thead>
<th>Name</th> <th>Name</th>
@ -42,8 +42,8 @@
<th>Role</th> <th>Role</th>
<th>Checked</th> <th>Checked</th>
</thead> </thead>
<tbody id="tbody"> <tbody id="tbody"></tbody>
</tbody>
</table> </table>
<p id="loading">Данные загружаются...</p> <p id="loading">Данные загружаются...</p>
{% endblock %} {% endblock %}

View File

@ -1,11 +1,15 @@
import logging
import os
from datetime import datetime
from smtplib import SMTPException from smtplib import SMTPException
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required 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.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.contrib.auth.models import User, Permission 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.forms import PasswordResetForm
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.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 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, \ 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, \ 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.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, StatisticForm
from main.serializers import ProfileSerializer from main.serializers import ProfileSerializer, ZendeskUserSerializer
from .models import UserProfile from .models import UserProfile
@ -197,6 +201,7 @@ def user_update(zenpy_user: User, admin: User, request: WSGIRequest) -> UserProf
admin.users.update(zenpy_user) admin.users.update(zenpy_user)
request.user.userprofile.role = "agent" request.user.userprofile.role = "agent"
request.user.userprofile.custom_role_id = zenpy_user.custom_role_id
request.user.userprofile.save() request.user.userprofile.save()
messages.success(request, "Права были изменены") messages.success(request, "Права были изменены")
@ -214,6 +219,7 @@ def work_hand_over(request: WSGIRequest) -> HttpResponseRedirect:
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']
user_update(zenpy_user, admin, request) user_update(zenpy_user, admin, request)
log(request.user.userprofile)
return HttpResponseRedirect(reverse('work', args=(request.user.id,))) 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']: 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']
user_update(zenpy_user, admin, request) user_update(zenpy_user, admin, request)
log(request.user.userprofile)
return HttpResponseRedirect(reverse('work', args=(request.user.id,))) return HttpResponseRedirect(reverse('work', args=(request.user.id,)))
def main_page(request): def main_page(request: WSGIRequest) -> HttpResponse:
"""
Функция переадресации на главную страницу.
"""
return render(request, 'pages/index.html') return render(request, 'pages/index.html')
@ -278,10 +288,18 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageM
""" """
for user in users: for user in users:
make_engineer(user, self.request.user) make_engineer(user, self.request.user)
log(user, self.request.user.userprofile)
def make_light_agents(self, users): def make_light_agents(self, users):
"""
Функция проходит по списку пользователей, проставляя статус "light agent".
:param users: Список пользователей
:return: Обновленный список пользователей
"""
for user in users: for user in users:
make_light_agent(user, self.request.user) make_light_agent(user, self.request.user)
log(user, self.request.user.userprofile)
def get_context_data(self, **kwargs) -> dict: def get_context_data(self, **kwargs) -> dict:
""" """
@ -308,15 +326,34 @@ class UsersViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = ProfileSerializer serializer_class = ProfileSerializer
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
users = update_users_in_model().values users = update_users_in_model()
count = count_users(users) count = count_users(users.values)
profiles = UserProfile.objects.filter(role='agent') profiles = UserProfile.objects.filter(role='agent')
serializer = self.get_serializer(profiles, many=True) serializer = self.get_serializer(profiles, many=True)
return Response({ res = {
'users': serializer.data, 'users': serializer.data,
'engineers': count[0], '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() @login_required()

View File

@ -6,7 +6,8 @@ import sys
def main(): def main():
"""Run administrative tasks.""" """Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'access_controller.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE',
'access_controller.settings')
try: try:
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
except ImportError as exc: except ImportError as exc:

View File

@ -1,10 +1,10 @@
"use strict"; "use strict";
// React // React
class TableRow extends React.Component { class ModelUserTableRow extends React.Component {
render() { render() {
return ( return (
<tr> <tr className={"table-dark"}>
<td> <td>
<a href="#">{this.props.user.name}</a> <a href="#">{this.props.user.name}</a>
</td> </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 { class TableBody extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -30,6 +67,7 @@ class TableBody extends React.Component {
users: [], users: [],
engineers: 0, engineers: 0,
light_agents: 0, light_agents: 0,
zendesk_users: [],
}; };
} }
@ -39,6 +77,7 @@ class TableBody extends React.Component {
users: response.data.users, users: response.data.users,
engineers: response.data.engineers, engineers: response.data.engineers,
light_agents: response.data.light_agents, light_agents: response.data.light_agents,
zendesk_users: response.data.zendesk_users,
}); });
let elements = document.querySelectorAll(".info-quantity-value"); let elements = document.querySelectorAll(".info-quantity-value");
elements[0].innerHTML = this.state.engineers; elements[0].innerHTML = this.state.engineers;
@ -62,9 +101,12 @@ class TableBody extends React.Component {
} }
render() { render() {
return this.state.users.map((user, key) => ( return (
<TableRow user={user} key={key} /> <tr>
)); <ModelUserTableRows users={this.state.users} />
<ZendeskUserTableRows users={this.state.zendesk_users} />
</tr>
);
} }
} }