test role change

This commit is contained in:
Artyom Kravchenko 2021-02-25 18:24:14 +03:00
commit 61ea10f1d2
29 changed files with 841 additions and 166 deletions

View File

@ -51,6 +51,15 @@ MIDDLEWARE = [
ROOT_URLCONF = 'access_controller.urls' ROOT_URLCONF = 'access_controller.urls'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.mail.ru'
EMAIL_PORT = 2525
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'djgr.02@mail.ru'
EMAIL_HOST_PASSWORD = 'djangogroup02'
SERVER_EMAIL = EMAIL_HOST_USER
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
@ -119,11 +128,47 @@ STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticroot') STATIC_ROOT = os.path.join(BASE_DIR, 'staticroot')
STATICFILES_DIRS = [ STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'), os.path.join(BASE_DIR, 'static'),
os.path.join(BASE_DIR, 'media'),
] ]
MEDIA_ROOT = BASE_DIR / 'media' ACCOUNT_ACTIVATION_DAYS = 7
MEDIA_URL = '/media/'
LOGIN_REDIRECT_URL = '/' LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/' LOGOUT_REDIRECT_URL = '/'
# 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',
}
}
}

View File

@ -13,15 +13,19 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path 1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.views import LoginView from django.contrib.auth.views import LoginView
from django.contrib.auth import views as auth_views
from django.urls import path, include from django.urls import path, include
from access_controller import settings from access_controller import settings
from access_controller.settings import DEBUG from access_controller.settings import DEBUG
from main.views import main_page, profile_page, CustomRegistrationView, work_page, work_hand_over, work_become_engineer from main.views import main_page, profile_page, CustomRegistrationView, work_page, work_hand_over, work_become_engineer
from main.views import main_page, profile_page, CustomRegistrationView
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'),
@ -33,4 +37,30 @@ urlpatterns = [
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')),
]
urlpatterns += [
path(
'password_reset/',
auth_views.PasswordResetView.as_view(),
name='password_reset'
),
path(
'password-reset/done/',
auth_views.PasswordResetDoneView.as_view(),
name='password_reset_done'
),
path(
'reset/<uidb64>/<token>/',
auth_views.PasswordResetConfirmView.as_view(),
name='password_reset_confirm'
),
path(
'reset/done/',
auth_views.PasswordResetCompleteView.as_view(),
name='password_reset_complete'
),
] ]

View File

@ -1,16 +1,34 @@
***** Документация разработчика
TODOs =========================
*****
******
Models
******
.. automodule:: main.models
:members:
******
Forms
******
.. automodule:: main.forms
:members:
***************
Extra Functions Extra Functions
--------------- ***************
.. automodule:: main.extra_func .. automodule:: main.extra_func
:members: :members:
*****
Views Views
----- *****
.. automodule:: main.views .. automodule:: main.views
:members: :members:

View File

@ -6,13 +6,14 @@
Welcome to ZenDesk Access Controller's documentation! Welcome to ZenDesk Access Controller's documentation!
===================================================== =====================================================
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
:caption: Contents: :caption: Contents:
code.rst overview
todo.rst code
todo
Indices and tables Indices and tables

18
docs/source/overview.rst Normal file
View File

@ -0,0 +1,18 @@
Документация пользователя
=========================
**Управление правами доступа**
**ZenDesk Access Controller** - Web-приложение, для выдачи прав пользователям системы по запросу самого пользователя.
Например, из 12 человек 3 сейчас работают с правами администратора, по окончании рабочей смены они сдают свои права (освобождают места) и другие пользователи могут запросить эти права в свое пользование.
Оставшиеся 9 человек получают права легкого агента - без прав редактирования, а только чтение.
**Интерфейс пользователя:**
.. |copy| unicode:: 0xA9 .. Школа программистов S101, группа 2. 2021гю

View File

@ -1,5 +1,11 @@
Что необходимо доделать?
=======================
***** *****
TODOs TODOs
***** *****
.. todolist:: .. todolist::

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
layouts/work/work.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

38
main/apiauth.py Normal file
View File

@ -0,0 +1,38 @@
import os
from zenpy import Zenpy
from zenpy.lib.api_objects import User as ZenpyUser
def api_auth():
credentials = {
'subdomain': 'ngenix1612197338'
}
email = os.getenv('ACCESS_CONTROLLER_API_EMAIL')
token = os.getenv('ACCESS_CONTROLLER_API_TOKEN')
password = os.getenv('ACCESS_CONTROLLER_API_PASSWORD')
if email is None:
raise ValueError('access_controller email not in env')
credentials['email'] = os.getenv('ACCESS_CONTROLLER_API_EMAIL')
# prefer token, use password if token not provided
if token:
credentials['token'] = token
elif password:
credentials['password'] = password
else:
raise ValueError('access_controller token or password not in env')
zenpy_client = Zenpy(**credentials)
zenpy_user: ZenpyUser = zenpy_client.users.search(email).values[0]
user = {
'id': zenpy_user.id,
'name': zenpy_user.name, # Zendesk doesn't have separate first and last name fields
'email': zenpy_user.email,
'role': zenpy_user.role, # str like 'admin' or 'agent', not id
'photo': zenpy_user.photo['content_url'] if zenpy_user.photo is not None else None,
}
return user

View File

@ -6,56 +6,126 @@ from zenpy.lib.exception import APIException
from main.models import UserProfile from main.models import UserProfile
# Дополнительные функции class ZendeskAdmin:
def set_and_get_name(user_profile: UserProfile):
""" """
Функция устанавливает поле :class:`username` текущим именем в Zendesk Класс **ZendeskAdmin** существует, чтобы в каждой фунциии отдельно не проверять аккаунт администратора
.. TODO:: :param credentials: Полномочия (первым указывается учетная запись организации в Zendesk)
Переделать с получением данных через API :type credentials: :class:`list of dictionaries`
:param email: Email администратора, указанный в env
:param UP: Объект профиля пользователя :type email: :class:`email`
:type UP: :class:`main.models.UserProfile` :param token: Токен администратора (формируется в Zendesk, указывается в env)
:return: Имя пользователя :type token: :class:`str`
:rtype: :class:`str` :param password: Пароль администратора, указанный в env
:type password: :class:`str`
""" """
return user_profile.user.username
credentials = {
'subdomain': 'ngenix1612197338'
}
email = os.getenv('ACCESS_CONTROLLER_API_EMAIL')
token = os.getenv('ACCESS_CONTROLLER_API_TOKEN')
password = os.getenv('ACCESS_CONTROLLER_API_PASSWORD')
def __init__(self):
self.create_admin()
def check_user(self, email: str) -> bool:
"""
Функция **check_user** осуществляет проверку существования пользователя в Zendesk
:param email: Электронная почта пользователя
:type email: :class:`email`
:return: True, если существует, иначе False
:rtype: :class:`bool`
"""
return True if self.admin.search(email, type='user') else False
def get_user_name(self, email: str) -> str:
"""
Функция **get_user_name** возвращает имя пользователя
:param user_name: Имя пользователя
:type user_name: :class:`str`
"""
user = self.admin.users.search(email).values[0]
return user.name
def get_user_role(self, email: str) -> str:
"""
Функция **get_user_role** возвращает роль пользователя
:param user_role: Роль пользователя
:type user_role: :class:`str`
"""
user = self.admin.users.search(email).values[0]
return user.role
def get_user_id(self, email: str) -> str:
"""
Функция **get_user_id** возвращает id пользователя
:param user_id: ID пользователя
:type user_id: :class:`str`
"""
user = self.admin.users.search(email).values[0]
return user.id
def get_user_image(self, email: str) -> str:
"""
Функция **get_user_image** возвращает аватар пользователя
:param user_image: Аватар пользователя
:type user_image: :class:`img`
"""
user = self.admin.users.search(email).values[0]
return user.photo['content_url'] if user.photo else None
def get_user(self, email: str) -> str:
return self.admin.users.search(email).values[0]
def get_user_org(self, email: str) -> str:
user = self.admin.users.search(email).values[0]
return user.organization.name
def create_admin(self) -> None:
"""
Функция **Create_admin()** создает администратора, проверяя наличие вводимых данных в env.
:param credentials: В список полномочий администратора вносятся email, token, password из env
:raise: :class:`ValueError`: исключение, вызываемое если email не введен в env
:raise: :class:`APIException`: исключение, вызываемое если пользователя с таким email не существует в Zendesk
"""
if self.email is None:
raise ValueError('access_controller email not in env')
self.credentials['email'] = os.getenv('ACCESS_CONTROLLER_API_EMAIL')
if self.token:
self.credentials['token'] = self.token
elif self.password:
self.credentials['password'] = self.password
else:
raise ValueError('access_controller token or password not in env')
self.admin = Zenpy(**self.credentials)
try:
self.admin.search(self.email, type='user')
except APIException:
raise ValueError('invalid access_controller`s login data')
def set_and_get_email(user_profile: UserProfile): # TODO: Переделать с получением данных через API def update_profile(user_profile: UserProfile):
""" """
Функция устанавливает поле :class:`user.email` текущей почтой в Zendesk Функция обновляет профиль пользователя в соотвтетствии с текущим в Zendesk
:param UP: Объект профиля пользователя :param user_profile: Объект профиля пользователя
:type UP: :class:`main.models.UserProfile` :type user_profile: :class:`main.models.UserProfile`
:return: Почта пользователя
:rtype: :class:`str`
""" """
return user_profile.user.email user = ZendeskAdmin().get_user(user_profile.user.email)
user_profile.name = user.name
user_profile.role = user.role
def set_and_get_role(user_profile: UserProfile): # TODO: Переделать с получением данных через API user_profile.image = user.photo['content_url'] if user.photo else None
""" user_profile.save()
Функция устанавливает поле :class:`role` текущей ролью в Zendesk
:param UP: Объект профиля пользователя
:type UP: :class:`main.models.UserProfile`
:return: Роль пользователя
:rtype: :class:`str`
"""
return user_profile.role
def load_and_get_image(user_profile: UserProfile): # TODO: Переделать с получением изображения через API
"""
Функция загружает и устанавливает изображение в поле :class:`image`
:param UP: Объект профиля пользователя
:type UP: :class:`main.models.UserProfile`
:return: Название изображения
:rtype: :class:`str`
"""
return user_profile.image.name
def check_user_exist(email: str) -> bool: def check_user_exist(email: str) -> bool:
@ -67,16 +137,19 @@ def check_user_exist(email: str) -> bool:
:return: True, если существует, иначе False :return: True, если существует, иначе False
:rtype: :class:`bool` :rtype: :class:`bool`
""" """
admin_creds = { return ZendeskAdmin().check_user(email)
'email': os.environ.get('Admin_email'),
'subdomain': 'ngenix1612197338',
'token': os.environ.get('Oauth_token'), def get_user_organization(email: str) -> bool:
} """
admin = Zenpy(**admin_creds) Функция возвращает организацию пользователя
zenpy_user = admin.search(email, type='user')
if zenpy_user: :param email: Электронная почта пользователя
return True :type email: :class:`str`
return False :return: Название организации
:rtype: :class:`str`
"""
return ZendeskAdmin().get_user_org(email)
def check_user_auth(email: str, password: str) -> bool: def check_user_auth(email: str, password: str) -> bool:
@ -88,15 +161,15 @@ def check_user_auth(email: str, password: str) -> bool:
:param password: Пароль пользователя :param password: Пароль пользователя
:type password: :class:`str` :type password: :class:`str`
:return: True, если входные данные верны, иначе False :return: True, если входные данные верны, иначе False
:raise :class:`APIException`: исключение, вызываемое если пользователь не аутентифицирован :raise: :class:`APIException`: исключение, вызываемое если пользователь не аутентифицирован
:rtype: :class:`bool` :rtype: :class:`bool`
""" """
creds = {
'email': email,
'password': password,
'subdomain': 'ngenix1612197338',
}
try: try:
creds = {
'email': email,
'subdomain': 'ngenix1612197338',
'password': password,
}
user = Zenpy(**creds) user = Zenpy(**creds)
user.search(email, type='user') user.search(email, type='user')
except APIException: except APIException:

View File

@ -5,30 +5,22 @@ from django_registration.forms import RegistrationFormUniqueEmail
class CustomRegistrationForm(RegistrationFormUniqueEmail): class CustomRegistrationForm(RegistrationFormUniqueEmail):
""" """
Форма для регистрации :class:`django_registration.forms.RegistrationFormUniqueEmail` Форма для регистрации :class:`django_registration.forms.RegistrationFormUniqueEmail`
с полем для ввода пароля от Zendesk аккаунта и с добавлением bootstrap-класса 'form-control' для всех полей с добавлением bootstrap-класса 'form-control'
:param password_zen: Поле для ввода пароля от Zendesk :param password_zen: Поле для ввода пароля от Zendesk
:type password_zen: :class:`django.forms.CharField` :type password_zen: :class:`django.forms.CharField`
""" """
password_zen = forms.CharField(
required=True,
label="Пароль от Zendesk аккаунта",
strip=False,
widget=forms.PasswordInput(attrs={
'class': 'form-control'
})
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
for visible in self.visible_fields(): for visible in self.visible_fields():
if visible.field.widget.attrs.get('class', False): if visible.field.widget.attrs.get('class', False):
print(visible.field.widget.attrs['class'].find('form-control'))
if visible.field.widget.attrs['class'].find('form-control') < 0: if visible.field.widget.attrs['class'].find('form-control') < 0:
visible.field.widget.attrs['class'] += 'form-control' visible.field.widget.attrs['class'] += 'form-control'
else: else:
visible.field.widget.attrs['class'] = 'form-control' visible.field.widget.attrs['class'] = 'form-control'
if visible.html_name !='email':
visible.field.required = False
class Meta(RegistrationFormUniqueEmail.Meta): class Meta(RegistrationFormUniqueEmail.Meta):
fields = RegistrationFormUniqueEmail.Meta.fields fields = RegistrationFormUniqueEmail.Meta.fields
fields.insert(2, 'password_zen')

View File

@ -0,0 +1,23 @@
# Generated by Django 3.1.6 on 2021-02-16 19:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0002_userprofile_name'),
]
operations = [
migrations.AlterField(
model_name='userprofile',
name='image',
field=models.URLField(blank=True, null=True),
),
migrations.AlterField(
model_name='userprofile',
name='role',
field=models.CharField(default='None', max_length=100),
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 3.1.6 on 2021-02-17 17:25
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', '0003_auto_20210216_2222'),
]
operations = [
migrations.CreateModel(
name='RoleChangeLogs',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.TextField()),
('new_role', models.TextField()),
('change_time', models.DateTimeField()),
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='changed_by', to=settings.AUTH_USER_MODEL)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,11 +1,54 @@
import os
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
class UserProfile(models.Model): class UserProfile(models.Model):
"""
Модель профиля пользователя
:param user: OneToOneField к модели :class:`django.contrib.auth.models.User`
:param role: Код роли пользователя
:type role: :class:`integer`
:param image: Аватарка
:type image: :class:`img`
:param name: Имя пользователя на нашем сайте
:type name: :class:`str`
"""
user = models.OneToOneField(to=User, on_delete=models.CASCADE) user = models.OneToOneField(to=User, on_delete=models.CASCADE)
role = models.IntegerField() role = models.CharField(default='None', max_length=100)
image = models.ImageField(upload_to='user_avatars') image = models.URLField(null=True, blank=True)
name = models.CharField(default='None', max_length=100) name = models.CharField(default='None', max_length=100)
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.userprofile.save()
class RoleChangeLogs(models.Model):
"""
Модель для логирования изменений ролей пользователя
:param user: Пользователь, которому присвоили другую роль, ForeignKey к модели :class:`django.contrib.auth.models.User`
:param name: Имя пользователя
:type name: :class:`str`
:param new_role: Присвоенная роль
:type new_role: :class:`str`
:param change_time: Дата изменения роли`
:type change_time: :class:`datetime.datetime`
:param changed_by: Кем была изменена роль, ForeignKey к модели :class:`django.contrib.auth.models.User`
"""
user = models.ForeignKey(to=User, on_delete=models.CASCADE)
name = models.TextField()
new_role = models.TextField()
change_time = models.DateTimeField()
changed_by = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='changed_by')

View File

@ -2,57 +2,57 @@
<html lang="ru" class="h-100"> <html lang="ru" class="h-100">
{% load static %} {% load static %}
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% block title %}{% endblock %}</title> <title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">
integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2"
crossorigin="anonymous">
<style> <style>
.bd-placeholder-img { .bd-placeholder-img {
font-size: 1.125rem; font-size: 1.125rem;
text-anchor: middle; text-anchor: middle;
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
user-select: none; user-select: none;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.bd-placeholder-img-lg { .bd-placeholder-img-lg {
font-size: 3.5rem; font-size: 3.5rem;
}
} }
</style> }
</style>
{% block extra_css %}{% endblock %} {% block extra_css %}{% endblock %}
</head> </head>
<body class="d-flex flex-column h-100"> <body class="d-flex flex-column h-100">
{% include 'base/menu.html' %} {% include 'base/menu.html' %}
<main class="flex-shrink-0"> <main class="flex-shrink-0">
<div class="container mt-4 mb-4"> <div class="container mt-4 mb-4">
<h1 class="mb-4 text-center"> <h1 class="mb-4 text-center">
{% block heading %} {% block heading %}
{% endblock %}
</h1>
{% block content %}
{% endblock %} {% endblock %}
</h1> </div>
{% block content %} </main>
{% endblock %}
</div>
</main>
<footer class="footer mt-auto py-3 bg-light"> <footer class="footer mt-auto py-3 bg-light">
<div class="container"> <div class="container">
<small class="text-muted mt-auto">Сайт сделан учениками Школы Программистов (Группа №02)</small> <small class="text-muted mt-auto">Сайт сделан учениками Школы Программистов (Группа №02)</small>
</div> </div>
</footer> </footer>
<script <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js"
src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous">
integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" </script>
crossorigin="anonymous"
></script>
</body> </body>
</html> </html>

View File

@ -11,5 +11,5 @@
{% block content %} {% block content %}
<br> <br>
<h4> Нет пользователя с указаным адресом электронной почты, либо был введён неверный пароль</h4> <h4> Нет пользователя с указаным адресом электронной почты.</h4>
{% endblock %} {% endblock %}

View File

@ -11,14 +11,12 @@
{% block content %} {% block content %}
<form method="post" action=""> <form method="post" action="">
{% csrf_token %} {% csrf_token %}
{% for field in form %} {{ form.email.label_tag }}
{{ field.label_tag }} {{ form.email }}
{{ field }}
<br> <br>
{% if field.errors %} {% if form.email.errors %}
<span>{{ field.errors }}</span> <span>{{ form.email.errors }}</span>
{% endif %} {% endif %}
{% endfor %}
<input type="submit" value="Зарегистрироваться" class="clearfix"> <input type="submit" value="Зарегистрироваться" class="clearfix">
</form> </form>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,77 @@
{% extends 'base/base.html' %}
{% load static %}
{% block title %}{{ pagename }}{% endblock %}
{% block heading %}Управление{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'main/css/work.css' %}">
{% endblock %}
{% block content %}
<div class="container-md">
<div class="new-section">
<p class="row page-description">Основаная информация о странице</p>
</div>
<div class="row justify-content-center new-section">
<div class="col-10">
<h6 class="table-title">Список сотрудников</h6>
<table class="light-table">
<thead>
<th>ID</th>
<th>Email</th>
<th>Role</th>
<th>Name(link to profile)</th>
<th>Checked</th>
</thead>
<tbody>
<tr>
<td>1</td>
<td>big_boss123@example.ru</td>
<td>engineer</td>
<td><a href="#">Иван Иванов</a></td>
<td><input class="form-check-input" type="checkbox" value=""></td>
</tr>
<tr>
<td>2</td>
<td>gachi_cool456@example.ru</td>
<td>light engineer</td>
<td><a href="#">Пётр Петров</a></td>
<td><input class="form-check-input" type="checkbox"value=""></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row justify-content-center new-section">
<div class="col-5">
<div class="info">
<div class="info-row">
<div class="info-target">Инженеров:</div>
<div class="info-quantity">
<div class="status-circle-small light-green"></div>
<span class="info-quantity-value">13</span>
</div>
</div>
<div class="info-row">
<div class="info-target">Легких агентов:</div>
<div class="info-quantity">
<div class="status-circle-small light-yellow"></div>
<span class="info-quantity-value">22</span>
</div>
</div>
</div>
</div>
<div class="col-5">
<button class="request-acess-button default-button">Назначить выбранных на роль инженера</button>
<button class="hand-over-acess-button default-button">Назначить выбранных на роль легкого агента</button>
</div>
</div>
</div>
{% endblock %}

View File

@ -14,8 +14,8 @@
.img{ .img{
width:auto; width:auto;
height:auto; height:auto;
max-width:300px!important; max-width:100px!important;
max-height:500px!important; max-height:100px!important;
} }
@ -28,8 +28,8 @@
<div class="row"> <div class="row">
<div class="col-auto"> <div class="col-auto">
<div class="container"> <div class="container">
{% if image_name %} {% if image_url %}
<img src="{% static image_name %}" class="img img-thumbnail" alt="Аватар"> <img src={{image_url}} class="img img-thumbnail" alt="Аватар">
{% else %} {% else %}
<img src="{% static 'no_avatar.png' %}" class="img img-thumbnail" alt="Нет изображения"> <img src="{% static 'no_avatar.png' %}" class="img img-thumbnail" alt="Нет изображения">
{% endif %} {% endif %}

View File

@ -3,6 +3,7 @@
{% load static %} {% load static %}
{% block title %}{{ pagename }}{% endblock %} {% block title %}{{ pagename }}{% endblock %}
@ -29,3 +30,75 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block title %}{{ pagename }}{% endblock %}
{% block heading %}Управление{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'main/css/work.css' %}">
{% endblock %}
{% block content %}
<div class="container-md">
<div class="new-section">
<p class="row page-description">Основаная информация о странице</p>
</div>
<div class="row justify-content-center new-section">
<div class="col-10">
<h6 class="table-title">Список сотрудников с правами инженера</h6>
<table class="light-table">
<thead>
<th>ID</th>
<th>email</th>
<th>Expiration Date</th>
<th>Name(link to profile)</th>
</thead>
<tbody>
<tr>
<td>1</td>
<td>big_boss123@example.ru</td>
<td>19:30 18.02.21</td>
<td><a href="#">Иван Иванов</a></td>
</tr>
<tr>
<td>2</td>
<td>gachi_cool456@example.ru</td>
<td>21:00 18.02.21</td>
<td><a href="#">Пётр Петров</a></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row justify-content-center new-section">
<div class="col-5">
<div class="info">
<div class="info-row">
<div class="info-target">инженеров: </div>
<div class="info-quantity">
<div class="status-circle-small light-green"></div>
<span class="info-quantity-value">13</span>
</div>
</div>
<div class="info-row">
<div class="info-target">легких агентов:</div>
<div class="info-quantity">
<div class="status-circle-small light-yellow"></div>
<span class="info-quantity-value">22</span>
</div>
</div>
</div>
</div>
<div class="col-5">
<button class="request-acess-button default-button">Получить права инженера</button>
<button class="hand-over-acess-button default-button">Сдать права инженера</button>
</div>
</div>
</div>
{% endblock %}

View File

@ -31,7 +31,7 @@
{% endif %} {% endif %}
<div class="text-center"> <div class="text-center">
<button type="submit" class="btn btn-primary">Войти</button> <button type="submit" class="btn btn-primary">Войти</button>
<a href="" class="btn btn-link" style="display: block;">Забыли пароль?</a> <a href="password_reset" class="btn btn-link" style="display: block;">Забыли пароль?</a>
</div> </div>
</form> </form>
</div> </div>

View File

@ -0,0 +1,13 @@
{% extends "base/base.html" %}
{% block title %}
{{ pagename }}
{% endblock %}
{% block heading %}
Восстановление пароля
{% endblock %}
{% block content %}
<p>Ваш новый пароль был установлен. Вы можете <a href="{% url 'login' %}">войти</a> сейчас</p>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "base/base.html" %}
{% block title %}
{{ pagename }}
{% endblock %}
{% block heading %}
Восстановление пароля
{% endblock %}
{% block content %}
{% if validlink %}
<p>Пожалуйста, введите пароль дважды:</p>
<form action="." method="post">
{{ form.as_p }}
{% csrf_token %}
<p><input class="btn btn-success" type="submit" value="Сменить пароль"/></p>
</form>
{% else %}
<p>Неверная ссылка восстановления пароля, возможно она уже была использована.
Пожалуйста, запросите новый сброс пароля</p>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,14 @@
{% extends "base/base.html" %}
{% block title %}
{{ pagename }}
{% endblock %}
{% block heading %}
Восстановление пароля
{% endblock %}
{% block content %}
<p>Мы отправили вам на почту инструкцию по восстановлению</p>
<p>Если вы не получили сообщение, убедитесь что верно ввели адрес электронной почты.</p>
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends "base/base.html" %}
{% block title %}
{{ pagename }}
{% endblock %}
{% block heading %}
Забыли пароль?
{% endblock %}
{% block content %}
<p>Введте свой e-mail адрес для восстановления пароля.</p>
<form action="." method="post">
{{ form.as_p }}
<p><input class="btn btn-success" type="submit" value="Отпрваить e-mail"></p>
{% csrf_token %}
</form>
{% endblock %}

View File

@ -1,16 +1,22 @@
from django.shortcuts import render, redirect, reverse from django.shortcuts import render, redirect, reverse
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.urls import reverse_lazy
from main.extra_func import set_and_get_name, set_and_get_email, load_and_get_image, set_and_get_role, check_user_exist, \
check_user_auth
from main.models import UserProfile
from django.contrib.auth.models import User
from main.forms import CustomRegistrationForm
from django_registration.views import RegistrationView
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.models import User
from django.contrib.auth.tokens import default_token_generator
from django.shortcuts import render
from django.urls import reverse_lazy
from django_registration.backends.one_step.views import RegistrationView
from access_controller.settings import EMAIL_HOST_USER
from main.extra_func import check_user_exist, update_profile, get_user_organization
from main.forms import CustomRegistrationForm
from django_registration.views import RegistrationView
from django.contrib.auth.decorators import login_required
import logging
from zenpy import Zenpy from zenpy import Zenpy
from zenpy.lib.api_objects import User as ZenpyUser from zenpy.lib.api_objects import User as ZenpyUser
@ -20,6 +26,7 @@ from .models import UserProfile
import os import os
class CustomRegistrationView(RegistrationView): class CustomRegistrationView(RegistrationView):
""" """
Отображение и логика работы страницы регистрации пользователя Отображение и логика работы страницы регистрации пользователя
@ -30,21 +37,30 @@ class CustomRegistrationView(RegistrationView):
is_allowed = True is_allowed = True
def register(self, form): def register(self, form):
if check_user_exist(form.data['email']) and check_user_auth(form.data['email'], form.data['password_zen']): self.is_allowed = True
user = User.objects.create_user( if check_user_exist(form.data['email']) and get_user_organization(form.data['email']) == 'SYSTEM':
username=form.data['username'], forms = PasswordResetForm(self.request.POST)
email=form.data['email'], if forms.is_valid():
password=form.data['password1'] opts = {
) 'use_https': self.request.is_secure(),
profile = UserProfile( 'token_generator': default_token_generator,
user=user, 'from_email': EMAIL_HOST_USER,
role=0, 'email_template_name': 'registration/password_reset_email.html',
) 'subject_template_name': 'registration/password_reset_subject.txt',
set_and_get_name(profile) 'request': self.request,
set_and_get_email(profile) 'html_email_template_name': None,
set_and_get_role(profile) 'extra_email_context': None,
load_and_get_image(profile) }
profile.save() user = User.objects.create_user(
username=form.data['email'],
email=form.data['email'],
password=User.objects.make_random_password(length=50)
)
forms.save(**opts)
update_profile(user.userprofile)
return user
else:
raise ValueError('Непредвиденная ошибка')
else: else:
self.is_allowed = False self.is_allowed = False
@ -54,7 +70,7 @@ class CustomRegistrationView(RegistrationView):
Используется самой django-registration Используется самой django-registration
""" """
if self.is_allowed: if self.is_allowed:
return reverse_lazy('django_registration_complete') return reverse_lazy('password_reset_done')
else: else:
return reverse_lazy('django_registration_disallowed') return reverse_lazy('django_registration_disallowed')
@ -64,19 +80,18 @@ def profile_page(request):
""" """
Отображение страницы профиля Отображение страницы профиля
:param request: объект с деталями запроса :param request: объект с деталями запроса
:type request: :class:`django.http.HttpResponse` :type request: :class:`django.http.HttpResponse`
:return: объект ответа сервера с HTML-кодом внутри :return: объект ответа сервера с HTML-кодом внутри
""" """
if request.user.is_authenticated: user_profile = request.user.userprofile
user_profile = request.user.userprofile update_profile(user_profile)
context = { context = {
'name': set_and_get_name(user_profile), 'email': user_profile.user.email,
'email': set_and_get_email(user_profile), 'name': user_profile.name,
'role': set_and_get_role(user_profile), 'role': user_profile.role,
'image_name': load_and_get_image(user_profile), 'image_url': user_profile.image,
'pagename': 'Страница профиля' 'pagename': 'Страница профиля'
} }
return render(request, 'pages/profile.html', context) return render(request, 'pages/profile.html', context)
@ -128,4 +143,6 @@ def work_become_engineer(request):
def main_page(request): def main_page(request):
logger = logging.getLogger('main.index')
logger.info('Index page opened')
return render(request, 'pages/index.html') return render(request, 'pages/index.html')

View File

@ -2,7 +2,7 @@
Django==3.1.6 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
# Documentation # Documentation

128
static/main/css/work.css Normal file
View File

@ -0,0 +1,128 @@
/* .all {
display: flex;
justify-content: start;
} */
/* .menu {
position: absolute;
top: 0;
left: 0;
display: inline-flex;
width: 150px;
height: 100vh;
background: #45729C;
} */
.form-check-input {
border-radius: 0px;
background-image: url("../img/check.png");
width: 30px;
height: 30px;
background-size: 20px auto;
}
.page-title {
margin: auto;
display: block;
text-align: center;
}
.page-description {
display: block;
margin: auto;
font-size: 1rem;
text-align: center;
}
.new-section {
margin-top: 50px;
}
.table-title {
text-align: center;
margin: auto;
font-size: 1.2rem;
font-weight: bolder;
}
.container {
width: calc(100% - 150px);
/* margin-left: calc(150px);
margin-right: calc((100vw - 150px) / 2); */
}
.light-table {
font-weight: bold;
margin: auto;
margin-top: 10px;
font-size: 1.2rem;
width: 100%;
}
.light-table th {
background: #515A63;
color: white;
padding: 10px;
}
.light-table td {
padding: 5px;
padding-left: 10px;
}
.light-table tr:nth-child(odd) {
background: #93A2AF;
}
.info-row {
margin: 5px auto;
height: 53px;
}
.info-target {
width: 200px;
display: inline-block;
font-size: 1.2rem;
}
.info-quantity {
max-width: 200px;
display: inline-block;
}
.light-green {
background: #83DC87;
}
.light-yellow {
background: #F3EB3C;
}
.info-quantity-value {
font-weight: bold;
}
.status-circle-small {
margin: auto;
display: inline-block;
border-radius: 4px;
width: 8px;
height: 8px;
}
.request-acess-button,
.hand-over-acess-button {
font-size: 1.2rem;
width: 100%;
}
.default-button {
border: none;
display: block;
margin: 5px auto;
text-align: center;
padding: 10px;
background: #3B91D4;
color: white;
}

BIN
static/main/img/check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B