diff --git a/access_controller/settings.py b/access_controller/settings.py index 50d3201..eecfa19 100644 --- a/access_controller/settings.py +++ b/access_controller/settings.py @@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/3.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.1/ref/settings/ """ - +import os from pathlib import Path # 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/ STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(BASE_DIR, 'staticroot') STATICFILES_DIRS = [ - 'static' + os.path.join(BASE_DIR, 'static'), + os.path.join(BASE_DIR, 'media'), ] MEDIA_ROOT = BASE_DIR / 'media' MEDIA_URL = '/media/' + LOGIN_REDIRECT_URL = '/' LOGOUT_REDIRECT_URL = '/' - diff --git a/access_controller/urls.py b/access_controller/urls.py index 0063265..b2603b2 100644 --- a/access_controller/urls.py +++ b/access_controller/urls.py @@ -15,24 +15,19 @@ Including another URLconf """ from django.conf.urls.static import static from django.contrib import admin - -from django.urls import path, include from django.contrib.auth.views import LoginView from django.urls import path, include + 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 = [ path('admin/', admin.site.urls, name='admin'), - path('', main_page), - path('register/', CustomRegistrationView.as_view(), name='registration'), - #path('', include('django_registration.backends.one_step.urls')), - path('profile/', profile_page, name='profile'), - path('accounts/login/', LoginView.as_view(extra_context={})), # TODO add extra context - path('accounts/', include('django.contrib.auth.urls')) - ] - - - - -urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + path('', main_page, name='index'), + path('accounts/profile/', profile_page, name='profile'), + path('accounts/register/', CustomRegistrationView.as_view(), name='registration'), + path('accounts/login/', LoginView.as_view(extra_context={}), name='login'), # TODO add extra context + path('accounts/', include('django.contrib.auth.urls')), + path('accounts/', include('django_registration.backends.one_step.urls')), +] diff --git a/docs/source/code.rst b/docs/source/code.rst new file mode 100644 index 0000000..1c8af9f --- /dev/null +++ b/docs/source/code.rst @@ -0,0 +1,16 @@ +***** +TODOs +***** + +Extra Functions +--------------- + +.. automodule:: main.extra_func + :members: + + +Views +----- + +.. automodule:: main.views + :members: diff --git a/docs/source/index.rst b/docs/source/index.rst index a95aa50..778a091 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,10 +6,13 @@ Welcome to ZenDesk Access Controller's documentation! ===================================================== + .. toctree:: :maxdepth: 2 :caption: Contents: + code.rst + todo.rst Indices and tables diff --git a/docs/source/todo.rst b/docs/source/todo.rst new file mode 100644 index 0000000..d6ad446 --- /dev/null +++ b/docs/source/todo.rst @@ -0,0 +1,5 @@ +***** +TODOs +***** + +.. todolist:: diff --git a/layouts/adm_layout/adm_layout.png b/layouts/adm_layout/adm_layout.png new file mode 100644 index 0000000..eaa954b Binary files /dev/null and b/layouts/adm_layout/adm_layout.png differ diff --git a/layouts/work/work.png b/layouts/work/work.png new file mode 100644 index 0000000..6d4b426 Binary files /dev/null and b/layouts/work/work.png differ diff --git a/main/apiauth.py b/main/apiauth.py new file mode 100644 index 0000000..4e3f761 --- /dev/null +++ b/main/apiauth.py @@ -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 diff --git a/main/extra_func.py b/main/extra_func.py index 8f850b8..73c533b 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -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: Объект профиля пользователя - :type UP: :class:`main.models.UserProfile` - :return: Имя пользователя - :rtype: :class:`str` + :param user_profile: Объект профиля пользователя + :type user_profile: :class:`main.models.UserProfile` """ - return UP.user.username - - -def set_and_get_email(UP: UserProfile): # TODO: Переделать с получением данных через API - """ - Функция устанавливает поле :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 + 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) + user_profile.save() def check_user_exist(email: str) -> bool: @@ -60,20 +74,11 @@ def check_user_exist(email: str) -> bool: Функция проверяет, существует ли пользователь :param email: Электронная почта пользователя - :type email: :class:`str + :type email: :class:`str` :return: True, если существует, иначе False - :rtype: :class:`bool' + :rtype: :class:`bool` """ - admin_creds = { - '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 + return ZendeskAdmin().check_user(email) def check_user_auth(email: str, password: str) -> bool: @@ -85,15 +90,15 @@ def check_user_auth(email: str, password: str) -> bool: :param password: Пароль пользователя :type password: :class:`str` :return: True, если входные данные верны, иначе False - :raise APIException: исключение, вызываемое если пользователь не аутентифицирован + :raise :class:`APIException`: исключение, вызываемое если пользователь не аутентифицирован :rtype: :class:`bool` """ - try: - creds = { + creds = { 'email': email, - 'subdomain': 'ngenix1612197338', 'password': password, + 'subdomain': 'ngenix1612197338', } + try: user = Zenpy(**creds) user.search(email, type='user') except APIException: diff --git a/main/migrations/0003_auto_20210216_2222.py b/main/migrations/0003_auto_20210216_2222.py new file mode 100644 index 0000000..33076ac --- /dev/null +++ b/main/migrations/0003_auto_20210216_2222.py @@ -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), + ), + ] diff --git a/main/models.py b/main/models.py index 96d04db..58ae492 100644 --- a/main/models.py +++ b/main/models.py @@ -1,11 +1,22 @@ -import os - from django.contrib.auth.models import User from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver class UserProfile(models.Model): user = models.OneToOneField(to=User, on_delete=models.CASCADE) - role = models.IntegerField() - image = models.ImageField(upload_to='user_avatars') + role = models.CharField(default='None', max_length=100) + image = models.URLField(null=True, blank=True) 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() diff --git a/main/templates/base/base.html b/main/templates/base/base.html index 4507bc7..0d18dcd 100644 --- a/main/templates/base/base.html +++ b/main/templates/base/base.html @@ -1,6 +1,7 @@ +{% load static %} diff --git a/main/templates/base/menu.html b/main/templates/base/menu.html index a7c0c3f..8448b0c 100644 --- a/main/templates/base/menu.html +++ b/main/templates/base/menu.html @@ -1,26 +1,23 @@ +{% load static %}
-
diff --git a/main/templates/pages/profile.html b/main/templates/pages/profile.html index 0b855b4..bfd8cd7 100644 --- a/main/templates/pages/profile.html +++ b/main/templates/pages/profile.html @@ -10,37 +10,42 @@ {% block extra_css %} - + + + + {% endblock %} {% block content %} -
-
-
-
- {% if image_name %} - Аватар - {% else %} - Нет изображения - {% endif %} -
-
-
-
-

Имя пользователя {{name}}

-

Электронная почта {{email}}

-

Текущая роль {{role}}

-
- -
-
+
+
+
+
+ {% if image_url %} + Аватар + {% else %} + Нет изображения + {% endif %}
+
+
Имя пользователя {{name}}
+
+
Электронная почта {{email}}
+
+
Текущая роль {{role}}
+
+
+
+
+ +
+
{% endblock %} diff --git a/main/views.py b/main/views.py index bd260b2..797ea8b 100644 --- a/main/views.py +++ b/main/views.py @@ -1,9 +1,7 @@ from django.shortcuts import render 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.extra_func import check_user_exist, check_user_auth, update_profile from main.models import UserProfile from django.contrib.auth.models import User @@ -25,28 +23,21 @@ class CustomRegistrationView(RegistrationView): is_allowed = True 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']): user = User.objects.create_user( username=form.data['username'], email=form.data['email'], password=form.data['password1'] ) - profile = UserProfile( - image='None.png', - 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() + profile = user.userprofile + update_profile(profile) else: self.is_allowed = False - def get_success_url(self, request): + def get_success_url(self, user=None): """ - Вовзращет url-адресс страницы, куда нужно перейти после успешной/неуспешной регистрации + Возвращает url-адрес страницы, куда нужно перейти после успешной/неуспешной регистрации Используется самой django-registration """ if self.is_allowed: @@ -54,26 +45,24 @@ class CustomRegistrationView(RegistrationView): else: return reverse_lazy('django_registration_disallowed') + @login_required() def profile_page(request): """ Отображение страницы профиля - :param request: объект с деталями запроса :type request: :class:`django.http.HttpResponse` :return: объект ответа сервера с HTML-кодом внутри """ - if request.user.is_authenticated: - #UP = UserProfile.objects.get(user=request.user) - UP = UserProfile.objects.get(user=request.user) - #else: # TODO: Убрать после появления регистрации и авторизации, добавить login_required() - #UP = UserProfile.objects.get(user=1) + user_profile = request.user.userprofile + update_profile(user_profile) + context = { - 'name': set_and_get_name(UP), - 'email': set_and_get_email(UP), - 'role': set_and_get_role(UP), - 'image_name': load_and_get_image(UP), + 'email': user_profile.user.email, + 'name': user_profile.name, + 'role': user_profile.role, + 'image_url': user_profile.image, 'pagename': 'Страница профиля' } return render(request, 'pages/profile.html', context) @@ -81,4 +70,3 @@ def profile_page(request): def main_page(request): return render(request, 'pages/index.html') - diff --git a/static/main/img/logo.png b/static/main/img/logo.png new file mode 100644 index 0000000..c14cd28 Binary files /dev/null and b/static/main/img/logo.png differ