Merge branch 'develop' into feature/work/html

This commit is contained in:
Ivan Pesnya 2021-02-17 15:50:10 +03:00
commit 905a9167f5
16 changed files with 235 additions and 146 deletions

View File

@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/3.1/topics/settings/
For the full list of settings and their values, see For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/ https://docs.djangoproject.com/en/3.1/ref/settings/
""" """
import os
from pathlib import Path from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
@ -116,12 +116,14 @@ USE_TZ = True
# https://docs.djangoproject.com/en/3.1/howto/static-files/ # https://docs.djangoproject.com/en/3.1/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticroot')
STATICFILES_DIRS = [ STATICFILES_DIRS = [
'static' os.path.join(BASE_DIR, 'static'),
os.path.join(BASE_DIR, 'media'),
] ]
MEDIA_ROOT = BASE_DIR / 'media' MEDIA_ROOT = BASE_DIR / 'media'
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
LOGIN_REDIRECT_URL = '/' LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/' LOGOUT_REDIRECT_URL = '/'

View File

@ -15,24 +15,19 @@ Including another URLconf
""" """
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.urls import path, include
from django.contrib.auth.views import LoginView from django.contrib.auth.views import LoginView
from django.urls import path, include from django.urls import path, include
from access_controller import settings from access_controller import settings
from main.views import * from access_controller.settings import DEBUG
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), path('', main_page, name='index'),
path('register/', CustomRegistrationView.as_view(), name='registration'), path('accounts/profile/', profile_page, name='profile'),
#path('', include('django_registration.backends.one_step.urls')), path('accounts/register/', CustomRegistrationView.as_view(), name='registration'),
path('profile/', profile_page, name='profile'), path('accounts/login/', LoginView.as_view(extra_context={}), name='login'), # TODO add extra context
path('accounts/login/', LoginView.as_view(extra_context={})), # TODO add extra context path('accounts/', include('django.contrib.auth.urls')),
path('accounts/', include('django.contrib.auth.urls')) path('accounts/', include('django_registration.backends.one_step.urls')),
] ]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

16
docs/source/code.rst Normal file
View File

@ -0,0 +1,16 @@
*****
TODOs
*****
Extra Functions
---------------
.. automodule:: main.extra_func
:members:
Views
-----
.. automodule:: main.views
:members:

View File

@ -6,10 +6,13 @@
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
todo.rst
Indices and tables Indices and tables

5
docs/source/todo.rst Normal file
View File

@ -0,0 +1,5 @@
*****
TODOs
*****
.. 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

@ -7,52 +7,66 @@ from main.models import UserProfile
# Дополнительные функции # Дополнительные функции
def set_and_get_name(UP: UserProfile): # TODO: Переделать с получением данных через API class ZendeskAdmin:
# Класс существует, чтобы в каждой фунциии отдельно не проверять аккаунт администратора
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:
return True if self.admin.search(email, type='user') else False
def get_user_name(self, email: str) -> str:
user = self.admin.users.search(email).values[0]
return user.name
def get_user_role(self, email: str) -> str:
user = self.admin.users.search(email).values[0]
return user.role
def get_user_id(self, email: str) -> str:
user = self.admin.users.search(email).values[0]
return user.id
def get_user_image(self, email: str) -> str:
user = self.admin.users.search(email).values[0]
return user.photo['content_url'] if user.photo else None
def create_admin(self) -> None:
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 update_profile(user_profile: UserProfile):
""" """
Функция устанавливает поле :class:`username` текущим именем в 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 UP.user.username user_profile.name = ZendeskAdmin().get_user_name(user_profile.user.email)
user_profile.role = ZendeskAdmin().get_user_role(user_profile.user.email)
user_profile.image = ZendeskAdmin().get_user_image(user_profile.user.email)
def set_and_get_email(UP: UserProfile): # TODO: Переделать с получением данных через API user_profile.save()
"""
Функция устанавливает поле :class:`user.email` текущей почтой в Zendesk
:param UP: Объект профиля пользователя
:type UP: :class:`main.models.UserProfile`
:return: Почта пользователя
:rtype: :class:`str`
"""
return UP.user.email
def set_and_get_role(UP: UserProfile): # TODO: Переделать с получением данных через API
"""
Функция устанавливает поле :class:`role` текущей ролью в Zendesk
:param UP: Объект профиля пользователя
:type UP: :class:`main.models.UserProfile`
:return: Роль пользователя
:rtype: :class:`str`
"""
return UP.role
def load_and_get_image(UP: UserProfile): # TODO: Переделать с получением изображения через API
"""
Функция загружает и устанавливает изображение в поле :class:`image`
:param UP: Объект профиля пользователя
:type UP: :class:`main.models.UserProfile`
:return: Название изображения
:rtype: :class:`str`
"""
return UP.image.name
def check_user_exist(email: str) -> bool: def check_user_exist(email: str) -> bool:
@ -60,20 +74,11 @@ def check_user_exist(email: str) -> bool:
Функция проверяет, существует ли пользователь Функция проверяет, существует ли пользователь
:param email: Электронная почта пользователя :param email: Электронная почта пользователя
:type email: :class:`str :type email: :class:`str`
: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'),
}
admin = Zenpy(**admin_creds)
zenpy_user = admin.search(email, type='user')
if zenpy_user:
return True
return False
def check_user_auth(email: str, password: str) -> bool: def check_user_auth(email: str, password: str) -> bool:
@ -85,15 +90,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 APIException: исключение, вызываемое если пользователь не аутентифицирован :raise :class:`APIException`: исключение, вызываемое если пользователь не аутентифицирован
:rtype: :class:`bool` :rtype: :class:`bool`
""" """
try: creds = {
creds = {
'email': email, 'email': email,
'subdomain': 'ngenix1612197338',
'password': password, 'password': password,
'subdomain': 'ngenix1612197338',
} }
try:
user = Zenpy(**creds) user = Zenpy(**creds)
user.search(email, type='user') user.search(email, type='user')
except APIException: except APIException:

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

@ -1,11 +1,22 @@
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):
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()

View File

@ -1,6 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ru" class="h-100"> <html lang="ru" class="h-100">
{% 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" />

View File

@ -1,26 +1,23 @@
{% load static %}
<header> <header>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<nav class="navbar navbar-light" style="background-color: #a3f2fd;"> <nav class="navbar navbar-light" style="background-color: #00FF00;">
<a class="navbar-brand" href="#"> <a class="navbar-brand" href="{% url 'index' %}">
<img src="media/images/access.png" width="30" height="30" class="d-inline-block align-top" alt="" loading="lazy"> <img src="{% static 'main/img/logo.png' %}" width="30" height="30" class="d-inline-block align-top" alt="" loading="lazy">
Access Controller Access Controller
</a> </a>
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<div class="btn-group" role="group" aria-label="Basic example">
<div class="btn-group" role="group" aria-label="Basic example"> <a class="btn btn-secondary" href="{% url 'profile' %}">Профиль</a>
<a class="btn btn-secondary" href="/accounts/logout">Выйти</a> <a class="btn btn-secondary" href="{% url 'logout' %}">Выйти</a>
<a class="btn btn-secondary" href="">Профиль</a> </div>
</div>
{% else %} {% else %}
<div class="btn-group" role="group" aria-label="Basic example">
<div class="btn-group" role="group" aria-label="Basic example"> <a class="btn btn-secondary" href="/accounts/login">Войти</a>
<a class="btn btn-secondary" href="/accounts/login">Войти</a> <a class="btn btn-secondary" href="/accounts/register">Зарегистрироваться</a>
<a class="btn btn-secondary" href="/accounts/register">Зарегистрироваться</a> </div>
</div>
{% endif %} {% endif %}
</nav> </nav>
</header> </header>

View File

@ -10,37 +10,42 @@
{% block extra_css %} {% block extra_css %}
<style> <style>
.img{ .img{
width:auto; width:auto;
height:auto; height:auto;
max-width:150px!important; max-width:100px!important;
max-height:500px!important; max-height:100px!important;
} }
</style>
</style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<br> <br>
<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="/media/{{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 %}
</div>
</div>
<div class="row g-5">
<div class="col g-5">
<h4><span class="badge bg-secondary">Имя пользователя</span> {{name}}</h4>
<h4><span class="badge bg-secondary">Электронная почта</span> {{email}}</h4>
<h4><span class="badge bg-secondary">Текущая роль</span> {{role}}</h4>
<form action="">
<button class="btn btn-primary">Запросить права доступа</button>
</form>
</div>
</div> </div>
</div> </div>
<div class="col">
<h5><span class="badge bg-secondary text-light">Имя пользователя</span> {{name}}</h5>
<br>
<h5><span class="badge bg-secondary text-light">Электронная почта</span> {{email}}</h5>
<br>
<h5><span class="badge bg-secondary text-light">Текущая роль</span> {{role}}</h5>
</div>
</div>
<div align="center">
<form action="">
<button class="btn btn-primary"><big>Запросить права доступа</big></button>
</form>
</div>
{% endblock %} {% endblock %}

View File

@ -1,9 +1,7 @@
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse_lazy from django.urls import reverse_lazy
from main.extra_func import check_user_exist, check_user_auth, update_profile
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 main.models import UserProfile
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -25,28 +23,21 @@ class CustomRegistrationView(RegistrationView):
is_allowed = True is_allowed = True
def register(self, form): def register(self, form):
self.is_allowed = True
if check_user_exist(form.data['email']) and check_user_auth(form.data['email'], form.data['password_zen']): if check_user_exist(form.data['email']) and check_user_auth(form.data['email'], form.data['password_zen']):
user = User.objects.create_user( user = User.objects.create_user(
username=form.data['username'], username=form.data['username'],
email=form.data['email'], email=form.data['email'],
password=form.data['password1'] password=form.data['password1']
) )
profile = UserProfile( profile = user.userprofile
image='None.png', update_profile(profile)
user=user,
role=0,
)
set_and_get_name(profile)
set_and_get_email(profile)
set_and_get_role(profile)
load_and_get_image(profile)
profile.save()
else: else:
self.is_allowed = False self.is_allowed = False
def get_success_url(self, request): def get_success_url(self, user=None):
""" """
Вовзращет url-адресс страницы, куда нужно перейти после успешной/неуспешной регистрации Возвращает url-адрес страницы, куда нужно перейти после успешной/неуспешной регистрации
Используется самой django-registration Используется самой django-registration
""" """
if self.is_allowed: if self.is_allowed:
@ -54,26 +45,24 @@ class CustomRegistrationView(RegistrationView):
else: else:
return reverse_lazy('django_registration_disallowed') return reverse_lazy('django_registration_disallowed')
@login_required() @login_required()
def profile_page(request): 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
#UP = UserProfile.objects.get(user=request.user) update_profile(user_profile)
UP = UserProfile.objects.get(user=request.user)
#else: # TODO: Убрать после появления регистрации и авторизации, добавить login_required()
#UP = UserProfile.objects.get(user=1)
context = { context = {
'name': set_and_get_name(UP), 'email': user_profile.user.email,
'email': set_and_get_email(UP), 'name': user_profile.name,
'role': set_and_get_role(UP), 'role': user_profile.role,
'image_name': load_and_get_image(UP), 'image_url': user_profile.image,
'pagename': 'Страница профиля' 'pagename': 'Страница профиля'
} }
return render(request, 'pages/profile.html', context) return render(request, 'pages/profile.html', context)
@ -81,4 +70,3 @@ def profile_page(request):
def main_page(request): def main_page(request):
return render(request, 'pages/index.html') return render(request, 'pages/index.html')

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB