From bf69c870cdd88d2da042fe68d49f1cfceababcb2 Mon Sep 17 00:00:00 2001 From: Andrey Kovalev Date: Wed, 17 Feb 2021 20:43:57 +0300 Subject: [PATCH 01/16] Add rights management backend --- access_controller/urls.py | 5 +++- main/templates/pages/work.html | 31 ++++++++++++++++++++ main/views.py | 53 +++++++++++++++++++++++++++++++++- 3 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 main/templates/pages/work.html diff --git a/access_controller/urls.py b/access_controller/urls.py index b2603b2..bf79ff6 100644 --- a/access_controller/urls.py +++ b/access_controller/urls.py @@ -20,7 +20,7 @@ from django.urls import path, include from access_controller import settings from access_controller.settings import DEBUG -from main.views import main_page, profile_page, CustomRegistrationView +from main.views import main_page, profile_page, CustomRegistrationView, work_page, work_hand_over, work_become_engineer urlpatterns = [ path('admin/', admin.site.urls, name='admin'), @@ -30,4 +30,7 @@ urlpatterns = [ 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')), + path('work/', work_page, name="work"), + path('work/hand_over/', work_hand_over, name="work_hand_over"), + path('work/become_engineer/', work_become_engineer, name="work_become_engineer"), ] diff --git a/main/templates/pages/work.html b/main/templates/pages/work.html new file mode 100644 index 0000000..5f49290 --- /dev/null +++ b/main/templates/pages/work.html @@ -0,0 +1,31 @@ +{% extends 'base/base.html' %} + +{% load static %} + + +{% block title %}{{ pagename }}{% endblock %} + + +{% block heading %}Управление правами{% endblock %} + +{% block content %} +
+

Инженеры

+{%for engineer in engineers%} + {{ engineer.name }} +{% endfor %} + +

Агенты

+{%for agent in agents%} + {{ agent.name }} +{% endfor %} + +
+ +{% if role == "engineer" %} + Сдать смену +{% else %} + Запросить права инженера +{% endif %} + +{% endblock %} diff --git a/main/views.py b/main/views.py index d9f8fd9..ee22299 100644 --- a/main/views.py +++ b/main/views.py @@ -1,4 +1,5 @@ -from django.shortcuts import render +from django.shortcuts import render, redirect, reverse +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, \ @@ -12,6 +13,11 @@ from django_registration.views import RegistrationView from django.contrib.auth.decorators import login_required from zenpy import Zenpy +from zenpy.lib.api_objects import User as ZenpyUser + +from .models import UserProfile + +import os class CustomRegistrationView(RegistrationView): @@ -75,6 +81,51 @@ def profile_page(request): } return render(request, 'pages/profile.html', context) +def auth_user(request): + admin_creds = { + 'email': os.environ.get('Admin_email'), + 'subdomain': 'ngenix1612197338', + 'token': os.environ.get('Oauth_token'), + } + admin = Zenpy(**admin_creds) + zenpy_user: ZenpyUser = admin.users.search(request.user.email).values[0] + return zenpy_user, admin + +@login_required() +def work_page(request, id): + if request.user.is_authenticated and request.user.id == id: + zenpy_user, _ = auth_user(request) + + context = { + 'engineers': UserProfile.objects.filter(role=1), + 'agents': UserProfile.objects.filter(role=0), + 'role': zenpy_user.role, + 'pagename': 'Управление правами' + } + return render(request, 'pages/work.html', context) + return redirect("login") + +@login_required() +def work_hand_over(request): + zenpy_user, admin = auth_user(request) + if request.user.is_authenticated and zenpy_user.role == "end-user": + zenpy_user.role = "agent" + admin.users.update(zenpy_user) + request.user.userprofile.role = 0 + request.user.userprofile.save() + return HttpResponseRedirect(reverse('work', args=(request.user.id, ))) + + +@login_required() +def work_become_engineer(request): + zenpy_user, admin = auth_user(request) + if request.user.is_authenticated and zenpy_user.role == "agent": + zenpy_user.role = "end-user" + admin.users.update(zenpy_user) + request.user.userprofile.role = 1 + request.user.userprofile.save() + return HttpResponseRedirect(reverse('work', args=(request.user.id, ))) + def main_page(request): return render(request, 'pages/index.html') From 8ba662c0caecb5265cf2b891c0365157ecaf24a4 Mon Sep 17 00:00:00 2001 From: Andrey Kovalev Date: Wed, 17 Feb 2021 21:38:41 +0300 Subject: [PATCH 02/16] Fix role end-user -> admin --- main/templates/pages/work.html | 2 +- main/views.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/main/templates/pages/work.html b/main/templates/pages/work.html index 5f49290..4719492 100644 --- a/main/templates/pages/work.html +++ b/main/templates/pages/work.html @@ -22,7 +22,7 @@
-{% if role == "engineer" %} +{% if role == "admin" %} Сдать смену {% else %} Запросить права инженера diff --git a/main/views.py b/main/views.py index ee22299..21e5f97 100644 --- a/main/views.py +++ b/main/views.py @@ -108,7 +108,7 @@ def work_page(request, id): @login_required() def work_hand_over(request): zenpy_user, admin = auth_user(request) - if request.user.is_authenticated and zenpy_user.role == "end-user": + if request.user.is_authenticated and zenpy_user.role == "admin": zenpy_user.role = "agent" admin.users.update(zenpy_user) request.user.userprofile.role = 0 @@ -120,7 +120,7 @@ def work_hand_over(request): def work_become_engineer(request): zenpy_user, admin = auth_user(request) if request.user.is_authenticated and zenpy_user.role == "agent": - zenpy_user.role = "end-user" + zenpy_user.role = "admin" admin.users.update(zenpy_user) request.user.userprofile.role = 1 request.user.userprofile.save() From b1d42ece61614080af92453548788b5a0fc00822 Mon Sep 17 00:00:00 2001 From: Andrey Kovalev Date: Thu, 25 Feb 2021 19:01:09 +0300 Subject: [PATCH 03/16] Fixed name parametrs in env --- main/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/views.py b/main/views.py index 917d5fa..5d362d7 100644 --- a/main/views.py +++ b/main/views.py @@ -98,9 +98,9 @@ def profile_page(request): def auth_user(request): admin_creds = { - 'email': os.environ.get('Admin_email'), + 'email': os.environ.get('ACCESS_CONTROLLER_API_EMAIL'), 'subdomain': 'ngenix1612197338', - 'token': os.environ.get('Oauth_token'), + 'token': os.environ.get('ACCESS_CONTROLLER_API_TOKEN'), } admin = Zenpy(**admin_creds) zenpy_user: ZenpyUser = admin.users.search(request.user.email).values[0] From 33375967493c7f13b5d75ac1ef424b1f7ff39b76 Mon Sep 17 00:00:00 2001 From: Andrew Smirnov Date: Thu, 25 Feb 2021 20:05:04 +0300 Subject: [PATCH 04/16] Remove useless auth check --- main/views.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/main/views.py b/main/views.py index 5d362d7..c1e7794 100644 --- a/main/views.py +++ b/main/views.py @@ -1,4 +1,3 @@ - from django.shortcuts import render, redirect, reverse from django.http import HttpResponseRedirect @@ -17,7 +16,6 @@ from django_registration.views import RegistrationView from django.contrib.auth.decorators import login_required import logging - from zenpy import Zenpy from zenpy.lib.api_objects import User as ZenpyUser @@ -26,7 +24,6 @@ from .models import UserProfile import os - class CustomRegistrationView(RegistrationView): """ Отображение и логика работы страницы регистрации пользователя @@ -96,6 +93,7 @@ def profile_page(request): } return render(request, 'pages/profile.html', context) + def auth_user(request): admin_creds = { 'email': os.environ.get('ACCESS_CONTROLLER_API_EMAIL'), @@ -106,11 +104,12 @@ def auth_user(request): zenpy_user: ZenpyUser = admin.users.search(request.user.email).values[0] return zenpy_user, admin + @login_required() def work_page(request, id): if request.user.is_authenticated and request.user.id == id: zenpy_user, _ = auth_user(request) - + context = { 'engineers': UserProfile.objects.filter(role=1), 'agents': UserProfile.objects.filter(role=0), @@ -120,26 +119,27 @@ def work_page(request, id): return render(request, 'pages/work.html', context) return redirect("login") + @login_required() def work_hand_over(request): zenpy_user, admin = auth_user(request) - if request.user.is_authenticated and zenpy_user.role == "admin": + if zenpy_user.role == "admin": zenpy_user.role = "agent" admin.users.update(zenpy_user) request.user.userprofile.role = 0 request.user.userprofile.save() - return HttpResponseRedirect(reverse('work', args=(request.user.id, ))) + return HttpResponseRedirect(reverse('work', args=(request.user.id,))) @login_required() def work_become_engineer(request): zenpy_user, admin = auth_user(request) - if request.user.is_authenticated and zenpy_user.role == "agent": + if zenpy_user.role == "agent": zenpy_user.role = "admin" admin.users.update(zenpy_user) request.user.userprofile.role = 1 request.user.userprofile.save() - return HttpResponseRedirect(reverse('work', args=(request.user.id, ))) + return HttpResponseRedirect(reverse('work', args=(request.user.id,))) def main_page(request): From f7522c9c69db883e92b042f70cd8d84dffecd2f7 Mon Sep 17 00:00:00 2001 From: Andrey Kovalev Date: Sun, 28 Feb 2021 17:27:07 +0300 Subject: [PATCH 05/16] Fix page html code --- main/templates/pages/profile.html | 2 +- main/templates/pages/work.html | 60 ++++++------------------------- main/views.py | 18 +++++----- 3 files changed, 20 insertions(+), 60 deletions(-) diff --git a/main/templates/pages/profile.html b/main/templates/pages/profile.html index bfd8cd7..6c6ecd2 100644 --- a/main/templates/pages/profile.html +++ b/main/templates/pages/profile.html @@ -45,7 +45,7 @@
- + Запросить права доступа
{% endblock %} diff --git a/main/templates/pages/work.html b/main/templates/pages/work.html index c6f2e01..2fcb4cf 100644 --- a/main/templates/pages/work.html +++ b/main/templates/pages/work.html @@ -2,39 +2,10 @@ {% load static %} - - {% block title %}{{ pagename }}{% endblock %} - {% block heading %}Управление правами{% endblock %} -{% block content %} -
-

Инженеры

-{%for engineer in engineers%} - {{ engineer.name }} -{% endfor %} - -

Агенты

-{%for agent in agents%} - {{ agent.name }} -{% endfor %} - -
- -{% if role == "admin" %} - Сдать смену -{% else %} - Запросить права инженера -{% endif %} - -{% endblock %} - -{% block title %}{{ pagename }}{% endblock %} - -{% block heading %}Управление{% endblock %} - {% block extra_css %} {% endblock %} @@ -52,24 +23,15 @@ - - - + - - - - - - - - - - - - - + {% for engineer in engineers %} + + + + + {% endfor %}
IDemailExpiration DateName(link to profile)Name
1big_boss123@example.ru19:30 18.02.21Иван Иванов
2gachi_cool456@example.ru21:00 18.02.21Пётр Петров
{{ engineer.id }}{{ engineer.name }}
@@ -81,22 +43,22 @@
инженеров:
- 13 + {{ engineers|length }}
легких агентов:
- 22 + {{ agents|length }}
- - + Получить права инженера + Сдать права инженера
diff --git a/main/views.py b/main/views.py index c1e7794..70facff 100644 --- a/main/views.py +++ b/main/views.py @@ -88,6 +88,7 @@ def profile_page(request): 'email': user_profile.user.email, 'name': user_profile.name, 'role': user_profile.role, + 'id': user_profile.id, 'image_url': user_profile.image, 'pagename': 'Страница профиля' } @@ -107,13 +108,10 @@ def auth_user(request): @login_required() def work_page(request, id): - if request.user.is_authenticated and request.user.id == id: - zenpy_user, _ = auth_user(request) - + if request.user.id == id: context = { - 'engineers': UserProfile.objects.filter(role=1), - 'agents': UserProfile.objects.filter(role=0), - 'role': zenpy_user.role, + 'engineers': UserProfile.objects.filter(role="admin"), + 'agents': UserProfile.objects.filter(role="agent"), 'pagename': 'Управление правами' } return render(request, 'pages/work.html', context) @@ -123,10 +121,10 @@ def work_page(request, id): @login_required() def work_hand_over(request): zenpy_user, admin = auth_user(request) - if zenpy_user.role == "admin": + if zenpy_user.role == "admin" or zenpy_user.role == "end-user": zenpy_user.role = "agent" admin.users.update(zenpy_user) - request.user.userprofile.role = 0 + request.user.userprofile.role = "agent" request.user.userprofile.save() return HttpResponseRedirect(reverse('work', args=(request.user.id,))) @@ -134,10 +132,10 @@ def work_hand_over(request): @login_required() def work_become_engineer(request): zenpy_user, admin = auth_user(request) - if zenpy_user.role == "agent": + if zenpy_user.role == "agent" or zenpy_user.role == "end-user": zenpy_user.role = "admin" admin.users.update(zenpy_user) - request.user.userprofile.role = 1 + request.user.userprofile.role = "admin" request.user.userprofile.save() return HttpResponseRedirect(reverse('work', args=(request.user.id,))) From 042e59a989d65c913a27bf3bd1347aec7730c68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=B5=D0=BD=D0=BA?= =?UTF-8?q?=D0=BE=20=D0=9E=D0=BB=D1=8C=D0=B3=D0=B0?= Date: Mon, 1 Mar 2021 20:50:25 +0300 Subject: [PATCH 06/16] Change conf --- docs/Makefile | 3 ++ docs/source/conf.py | 89 -------------------------------------------- docs/source/todo.rst | 2 +- 3 files changed, 4 insertions(+), 90 deletions(-) delete mode 100644 docs/source/conf.py diff --git a/docs/Makefile b/docs/Makefile index d0c3cbf..461302a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -18,3 +18,6 @@ help: # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +spelling: + $(SPHINXBUILD) -b spelling -W $(ALLSPHINXOPTS) $(BUILDDIR)/spelling diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index 438d5a4..0000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,89 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys -sys.path.insert(0, os.path.abspath('../../')) - -import django -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'access_controller.settings') -django.setup() - -# -- Project information ----------------------------------------------------- - -project = 'ZenDesk Access Controller' -copyright = '2021, SHP S101, group 2' -author = 'SHP S101, group 2' - -# The full version, including alpha/beta/rc tags -release = 'v0.01' - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.todo', - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.viewcode', - 'sphinx_rtd_theme', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = 'ru' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "sphinx_rtd_theme" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - - -# -- Extension configuration ------------------------------------------------- - -# -- Options for intersphinx extension --------------------------------------- - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = { - 'https://docs.python.org/3/': None, - 'django': ( - 'https://docs.djangoproject.com/en/dev/', - 'https://docs.djangoproject.com/en/dev/_objects/' - ), -} - -# -- Options for todo extension ---------------------------------------------- - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True diff --git a/docs/source/todo.rst b/docs/source/todo.rst index a8d024b..ad6e4ed 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,5 +1,5 @@ Что необходимо доделать? -======================= +======================== From cd504275321e476a6fd0ccfdaa99f761604c61c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=B5=D0=BD=D0=BA?= =?UTF-8?q?=D0=BE=20=D0=9E=D0=BB=D1=8C=D0=B3=D0=B0?= Date: Mon, 1 Mar 2021 20:52:39 +0300 Subject: [PATCH 07/16] Change conf --- docs/source/conf.py | 201 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 docs/source/conf.py diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..6c8988c --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,201 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +import importlib +import inspect +sys.path.insert(0, os.path.abspath('../../')) + +import django +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'access_controller.settings') +os.environ.setdefault('DJANGO_CONFIGURATION', 'Dev') + +# Fix Django's FileFields +from django.db.models.fields.files import FileDescriptor +FileDescriptor.__get__ = lambda self, *args, **kwargs: self +from django.db.models.manager import ManagerDescriptor +ManagerDescriptor.__get__ = lambda self, *args, **kwargs: self.manager + + +# Stop Django from executing DB queries +from django.db.models.query import QuerySet +QuerySet.__repr__ = lambda self: self.__class__.__name__ +try: + import enchant # NoQA +except ImportError: + enchant = None + +django.setup() + +# -- Project information ----------------------------------------------------- + +project = 'ZenDesk Access Controller' +copyright = '2021, SHP S101, group 2' +author = 'SHP S101, group 2' + +# The full version, including alpha/beta/rc tags +release = 'v0.01' + +#Django sphinx setup by https://gist.github.com/codingjoe/314bda5a07ff3b41f247 + +# -- General configuration --------------------------------------------------- + +def process_django_models(app, what, name, obj, options, lines): + """Append params from fields to model documentation.""" + from django.utils.encoding import force_text + from django.utils.html import strip_tags + from django.db import models + + spelling_white_list = ['', '.. spelling::'] + + if inspect.isclass(obj) and issubclass(obj, models.Model): + for field in obj._meta.fields: + help_text = strip_tags(force_text(field.help_text)) + verbose_name = force_text(field.verbose_name).capitalize() + + if help_text: + lines.append(':param %s: %s - %s' % (field.attname, verbose_name, help_text)) + else: + lines.append(':param %s: %s' % (field.attname, verbose_name)) + + if enchant is not None: + from enchant.tokenize import basic_tokenize + + words = verbose_name.replace('-', '.').replace('_', '.').split('.') + words = [s for s in words if s != ''] + for word in words: + spelling_white_list += [" %s" % ''.join(i for i in word if not i.isdigit())] + spelling_white_list += [" %s" % w[0] for w in basic_tokenize(word)] + + field_type = type(field) + module = field_type.__module__ + if 'django.db.models' in module: + # scope with django.db.models * imports + module = 'django.db.models' + lines.append(':type %s: %s.%s' % (field.attname, module, field_type.__name__)) + if enchant is not None: + lines += spelling_white_list + return lines + + +def process_modules(app, what, name, obj, options, lines): + """Add module names to spelling white list.""" + if what != 'module': + return lines + from enchant.tokenize import basic_tokenize + + spelling_white_list = ['', '.. spelling::'] + words = name.replace('-', '.').replace('_', '.').split('.') + words = [s for s in words if s != ''] + for word in words: + spelling_white_list += [" %s" % ''.join(i for i in word if not i.isdigit())] + spelling_white_list += [" %s" % w[0] for w in basic_tokenize(word)] + lines += spelling_white_list + return lines + + +def skip_queryset(app, what, name, obj, skip, options): + """Skip queryset subclasses to avoid database queries.""" + from django.db import models + if isinstance(obj, (models.QuerySet, models.manager.BaseManager)) or name.endswith('objects'): + return True + return skip + + +def setup(app): + # Register the docstring processor with sphinx + app.connect('autodoc-process-docstring', process_django_models) + app.connect('autodoc-skip-member', skip_queryset) + if enchant is not None: + app.connect('autodoc-process-docstring', process_modules) + + + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.todo', + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', + 'sphinx_rtd_theme', + 'sphinx.ext.graphviz', + 'sphinx.ext.napoleon', + 'sphinx.ext.inheritance_diagram', + +] + +if enchant is not None: + extensions.append('sphinxcontrib.spelling') + + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'ru' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + 'https://docs.python.org/3/': None, + 'django': ( + 'https://docs.djangoproject.com/en/dev/', + 'https://docs.djangoproject.com/en/dev/_objects/' + ), + 'djangoextensions': ('https://django-extensions.readthedocs.org/en/latest/', None), + 'geoposition': ('https://django-geoposition.readthedocs.org/en/latest/', None), + 'braces': ('https://django-braces.readthedocs.org/en/latest/', None), + 'select2': ('https://django-select2.readthedocs.org/en/latest/', None), + 'celery': ('https://celery.readthedocs.org/en/latest/', None), +} + +autodoc_default_flags = ['members'] + +# spell checking +spelling_lang = 'en_US' +spelling_word_list_filename = 'spelling_wordlist.txt' +spelling_show_suggestions = True +spelling_ignore_pypi_package_names = True + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True From a64e641a33e0d5eb623e15ad227098432ed8c2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=B5=D0=BD=D0=BA?= =?UTF-8?q?=D0=BE=20=D0=9E=D0=BB=D1=8C=D0=B3=D0=B0?= Date: Mon, 1 Mar 2021 21:18:27 +0300 Subject: [PATCH 08/16] Change conf using github snippet --- docs/source/conf.py | 1 + main/models.py | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 6c8988c..b70857e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -85,6 +85,7 @@ def process_django_models(app, what, name, obj, options, lines): lines.append(':type %s: %s.%s' % (field.attname, module, field_type.__name__)) if enchant is not None: lines += spelling_white_list + print('ok') return lines diff --git a/main/models.py b/main/models.py index b7b30dd..2cf76ac 100644 --- a/main/models.py +++ b/main/models.py @@ -15,6 +15,7 @@ class UserProfile(models.Model): :type image: :class:`img` :param name: Имя пользователя на нашем сайте :type name: :class:`str` + """ user = models.OneToOneField(to=User, on_delete=models.CASCADE) From f9e6b79f0124755a90bb7b48f9f5e5104c260d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=B5=D0=BD=D0=BA?= =?UTF-8?q?=D0=BE=20=D0=9E=D0=BB=D1=8C=D0=B3=D0=B0?= Date: Tue, 2 Mar 2021 09:52:18 +0300 Subject: [PATCH 09/16] Change models documentation using snippets --- main/models.py | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/main/models.py b/main/models.py index 2cf76ac..8665570 100644 --- a/main/models.py +++ b/main/models.py @@ -8,20 +8,12 @@ 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) - role = models.CharField(default='None', max_length=100) - image = models.URLField(null=True, blank=True) - name = models.CharField(default='None', max_length=100) + user = models.OneToOneField(to=User, on_delete=models.CASCADE, help_text='Пользователь') + role = models.CharField(default='None', max_length=100, help_text='Код роли пользователя') + image = models.URLField(null=True, blank=True, help_text='Аватарка') + name = models.CharField(default='None', max_length=100, help_text='Имя пользователя на нашем сайте') @receiver(post_save, sender=User) @@ -39,17 +31,9 @@ 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') + user = models.ForeignKey(to=User, on_delete=models.CASCADE, help_text='Пользователь, которому присвоили другую роль') + name = models.TextField(help_text='Имя пользователя') + new_role = models.TextField(help_text='Присвоенная роль') + change_time = models.DateTimeField(help_text='Дата и время изменения роли') + changed_by = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name='changed_by', help_text='Кем была изменена роль') From 4a9b3d71a42678327d8a8861f3376d281f0d7f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=B5=D0=BD=D0=BA?= =?UTF-8?q?=D0=BE=20=D0=9E=D0=BB=D1=8C=D0=B3=D0=B0?= Date: Tue, 2 Mar 2021 12:30:56 +0300 Subject: [PATCH 10/16] Add typehints --- docs/source/conf.py | 6 ++++++ main/extra_func.py | 31 ++++++++++--------------------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index b70857e..fdcc4e4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -134,6 +134,7 @@ extensions = [ 'sphinx.ext.graphviz', 'sphinx.ext.napoleon', 'sphinx.ext.inheritance_diagram', + 'sphinx_autodoc_typehints' ] @@ -200,3 +201,8 @@ spelling_ignore_pypi_package_names = True # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True + +set_type_checking_flag = True +typehints_fully_qualified = True +always_document_param_types = True +typehints_document_rtype = True diff --git a/main/extra_func.py b/main/extra_func.py index 20c3712..2d780b8 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -20,65 +20,54 @@ class ZendeskAdmin: :type token: :class:`str` :param password: Пароль администратора, указанный в env :type password: :class:`str` + """ - credentials = { + credentials:dict = { 'subdomain': 'ngenix1612197338' } - email = os.getenv('ACCESS_CONTROLLER_API_EMAIL') - token = os.getenv('ACCESS_CONTROLLER_API_TOKEN') - password = os.getenv('ACCESS_CONTROLLER_API_PASSWORD') + email: str = os.getenv('ACCESS_CONTROLLER_API_EMAIL') + token: str = os.getenv('ACCESS_CONTROLLER_API_TOKEN') + password: str = os.getenv('ACCESS_CONTROLLER_API_PASSWORD') def __init__(self): self.create_admin() def check_user(self, email: str) -> bool: """ - Функция **check_user** осуществляет проверку существования пользователя в Zendesk + Функция **check_user** осуществляет проверку существования пользователя в Zendesk по email - :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** возвращает имя пользователя + Функция **get_user_name** возвращает имя пользователя по его email - :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** возвращает роль пользователя + Функция **get_user_role** возвращает роль пользователя по его email - :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 пользователя + Функция **get_user_id** возвращает id пользователя по его email - :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** возвращает аватар пользователя + Функция **get_user_image** возвращает аватар пользователя по его email - :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 From edbe0a66af24762b70678d175f30b90f20dc35d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=B5=D0=BD=D0=BA?= =?UTF-8?q?=D0=BE=20=D0=9E=D0=BB=D1=8C=D0=B3=D0=B0?= Date: Tue, 2 Mar 2021 15:10:28 +0300 Subject: [PATCH 11/16] Change extra_func documentation --- main/extra_func.py | 65 +++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/main/extra_func.py b/main/extra_func.py index 2d780b8..9e104be 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -13,9 +13,9 @@ class ZendeskAdmin: Класс **ZendeskAdmin** существует, чтобы в каждой фунциии отдельно не проверять аккаунт администратора :param credentials: Полномочия (первым указывается учетная запись организации в Zendesk) - :type credentials: :class:`list of dictionaries` + :type credentials: :class:`dict` :param email: Email администратора, указанный в env - :type email: :class:`email` + :type email: :class:`str` :param token: Токен администратора (формируется в Zendesk, указывается в env) :type token: :class:`str` :param password: Пароль администратора, указанный в env @@ -23,7 +23,7 @@ class ZendeskAdmin: """ - credentials:dict = { + credentials: dict = { 'subdomain': 'ngenix1612197338' } email: str = os.getenv('ACCESS_CONTROLLER_API_EMAIL') @@ -36,14 +36,12 @@ class ZendeskAdmin: def check_user(self, email: str) -> bool: """ Функция **check_user** осуществляет проверку существования пользователя в Zendesk по email - """ return True if self.admin.search(email, type='user') else False def get_user_name(self, email: str) -> str: """ Функция **get_user_name** возвращает имя пользователя по его email - """ user = self.admin.users.search(email).values[0] return user.name @@ -51,7 +49,6 @@ class ZendeskAdmin: def get_user_role(self, email: str) -> str: """ Функция **get_user_role** возвращает роль пользователя по его email - """ user = self.admin.users.search(email).values[0] return user.role @@ -59,7 +56,6 @@ class ZendeskAdmin: def get_user_id(self, email: str) -> str: """ Функция **get_user_id** возвращает id пользователя по его email - """ user = self.admin.users.search(email).values[0] return user.id @@ -67,23 +63,29 @@ class ZendeskAdmin: def get_user_image(self, email: str) -> str: """ Функция **get_user_image** возвращает аватар пользователя по его email - """ 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: + """ + Функция **get_user** возвращает пользователя (объект) по его email + """ return self.admin.users.search(email).values[0] def get_user_org(self, email: str) -> str: + """ + Функция **get_user_org** возвращает организацию, к которой относится пользователь по его email + """ user = self.admin.users.search(email).values[0] return user.organization.name - def create_admin(self) -> None: + def create_admin(self) -> Zenpy: """ Функция **Create_admin()** создает администратора, проверяя наличие вводимых данных в env. :param credentials: В список полномочий администратора вносятся email, token, password из env + :type credentials: :class:`dict` :raise: :class:`ValueError`: исключение, вызываемое если email не введен в env :raise: :class:`APIException`: исключение, вызываемое если пользователя с таким email не существует в Zendesk """ @@ -105,34 +107,43 @@ class ZendeskAdmin: raise ValueError('invalid access_controller`s login data') -def update_role(user_profile, role): +def update_role(user_profile: UserProfile, role: str) -> UserProfile: + """ + Функция **update_role** меняет роль пользователя. + """ zendesk = ZendeskAdmin() user = zendesk.get_user(user_profile.user.email) user.custom_role_id = role zendesk.admin.users.update(user) -def make_engineer(user_profile): +def make_engineer(user_profile: UserProfile) -> UserProfile: + """ + Функция **make_engineer** устанавливапет пользователю роль инженера. + """ update_role(user_profile, ROLES['engineer']) -def make_light_agent(user_profile): +def make_light_agent(user_profile: UserProfile) -> UserProfile: + """ + Функция **make_light_agent** устанавливапет пользователю роль легкого агента. + """ update_role(user_profile, ROLES['light_agent']) -def get_users_list(): +def get_users_list() -> list: + """ + Функция **get_users_list** возвращает список пользователей Zendesk, относящихся к организации. + """ zendesk = ZendeskAdmin() admin = zendesk.get_user(zendesk.email) org = next(zendesk.admin.users.organizations(user=admin)) return zendesk.admin.organizations.users(org) -def update_profile(user_profile: UserProfile): +def update_profile(user_profile: UserProfile) -> UserProfile: """ - Функция обновляет профиль пользователя в соотвтетствии с текущим в Zendesk - - :param user_profile: Объект профиля пользователя - :type user_profile: :class:`main.models.UserProfile` + Функция обновляет профиль пользователя в соотвтетствии с текущим в Zendesk """ user = ZendeskAdmin().get_user(user_profile.user.email) user_profile.name = user.name @@ -143,12 +154,7 @@ def update_profile(user_profile: UserProfile): def check_user_exist(email: str) -> bool: """ - Функция проверяет, существует ли пользователь - - :param email: Электронная почта пользователя - :type email: :class:`str` - :return: True, если существует, иначе False - :rtype: :class:`bool` + Функция проверяет, существует ли пользователь """ return ZendeskAdmin().check_user(email) @@ -156,11 +162,6 @@ def check_user_exist(email: str) -> bool: def get_user_organization(email: str) -> str: """ Функция возвращает организацию пользователя - - :param email: Электронная почта пользователя - :type email: :class:`str` - :return: Название организации - :rtype: :class:`str` """ return ZendeskAdmin().get_user_org(email) @@ -169,13 +170,7 @@ def check_user_auth(email: str, password: str) -> bool: """ Функция проверяет, верны ли входные данные - :param email: Электроная почта пользователя - :type email: :class:`str` - :param password: Пароль пользователя - :type password: :class:`str` - :return: True, если входные данные верны, иначе False :raise: :class:`APIException`: исключение, вызываемое если пользователь не аутентифицирован - :rtype: :class:`bool` """ creds = { 'email': email, From 525a5a61d226356187505ad459c39c2fede3b67f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=B5=D0=BD=D0=BA?= =?UTF-8?q?=D0=BE=20=D0=9E=D0=BB=D1=8C=D0=B3=D0=B0?= Date: Tue, 2 Mar 2021 16:52:20 +0300 Subject: [PATCH 12/16] Change extra_func documentation --- main/extra_func.py | 2 +- main/forms.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/main/extra_func.py b/main/extra_func.py index 9e104be..b07f242 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -78,7 +78,7 @@ class ZendeskAdmin: Функция **get_user_org** возвращает организацию, к которой относится пользователь по его email """ user = self.admin.users.search(email).values[0] - return user.organization.name + return user.organization def create_admin(self) -> Zenpy: """ diff --git a/main/forms.py b/main/forms.py index d80932c..176302e 100644 --- a/main/forms.py +++ b/main/forms.py @@ -6,11 +6,11 @@ from main.models import UserProfile class CustomRegistrationForm(RegistrationFormUniqueEmail): """ - Форма для регистрации :class:`django_registration.forms.RegistrationFormUniqueEmail` - с добавлением bootstrap-класса 'form-control' + Форма для регистрации :class:`django_registration.forms.RegistrationFormUniqueEmail` + с добавлением bootstrap-класса 'form-control' - :param password_zen: Поле для ввода пароля от Zendesk - :type password_zen: :class:`django.forms.CharField` + :param password_zen: Поле для ввода пароля от Zendesk + :type password_zen: :class:`django.forms.CharField` """ def __init__(self, *args, **kwargs): @@ -29,6 +29,13 @@ class CustomRegistrationForm(RegistrationFormUniqueEmail): class AdminPageUsers(forms.Form): + """ + Форма для установки статуса + + :param password_zen: Поле для ввода пароля от Zendesk + :type password_zen: :class:`django.forms.CharField` + """ + users = forms.ModelMultipleChoiceField( queryset=UserProfile.objects.filter(role='agent'), widget=forms.CheckboxSelectMultiple( From f50ce49d7766cd08432c7f97a6c37aec3f63242d Mon Sep 17 00:00:00 2001 From: Sokurov Idar Date: Tue, 2 Mar 2021 21:44:13 +0300 Subject: [PATCH 13/16] update login with email --- access_controller/auth.py | 19 +++++++++++++++++++ access_controller/settings.py | 6 ++++++ access_controller/urls.py | 5 +++-- main/extra_func.py | 15 +++++++++------ main/forms.py | 23 +++++++++++++++++++---- main/views.py | 18 ++++++++++++------ 6 files changed, 68 insertions(+), 18 deletions(-) create mode 100644 access_controller/auth.py diff --git a/access_controller/auth.py b/access_controller/auth.py new file mode 100644 index 0000000..be707e1 --- /dev/null +++ b/access_controller/auth.py @@ -0,0 +1,19 @@ +from django.contrib.auth.backends import ModelBackend +from django.contrib.auth.models import User + + +class EmailAuthBackend(ModelBackend): + def authenticate(self, request, username=None, password=None, **kwargs): + try: + user = User.objects.get(email=username) + if user.check_password(password): + return user + return None + except User.DoesNotExist: + return None + + def get_user(self, user_id): + try: + return User.objects.get(pk=user_id) + except User.DoesNotExist: + return None diff --git a/access_controller/settings.py b/access_controller/settings.py index 480cfbd..8b4ec96 100644 --- a/access_controller/settings.py +++ b/access_controller/settings.py @@ -135,6 +135,12 @@ ACCOUNT_ACTIVATION_DAYS = 7 LOGIN_REDIRECT_URL = '/' LOGOUT_REDIRECT_URL = '/' + +# Название_приложения.Название_файла.Название_класса_обработчика +AUTHENTICATION_BACKENDS = [ + 'access_controller.auth.EmailAuthBackend', +] + # Logging system # https://docs.djangoproject.com/en/3.1/topics/logging/ LOGGING = { diff --git a/access_controller/urls.py b/access_controller/urls.py index 45781bf..b91e747 100644 --- a/access_controller/urls.py +++ b/access_controller/urls.py @@ -14,17 +14,18 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin +from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.views import LoginView from django.contrib.auth import views as auth_views from django.urls import path, include -from main.views import main_page, profile_page, CustomRegistrationView +from main.views import main_page, profile_page, CustomRegistrationView, CustomLoginView urlpatterns = [ path('admin/', admin.site.urls, name='admin'), 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/login/', CustomLoginView.as_view(extra_context={}), name='login',), # TODO add extra context path('accounts/', include('django.contrib.auth.urls')), path('accounts/', include('django_registration.backends.activation.urls')), path('accounts/login/', include('django.contrib.auth.urls')), diff --git a/main/extra_func.py b/main/extra_func.py index 9e0a564..3ce296a 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -26,6 +26,12 @@ class ZendeskAdmin: email = os.getenv('ACCESS_CONTROLLER_API_EMAIL') token = os.getenv('ACCESS_CONTROLLER_API_TOKEN') password = os.getenv('ACCESS_CONTROLLER_API_PASSWORD') + _instance=None + + def __new__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance def __init__(self): self.create_admin() @@ -73,10 +79,7 @@ class ZendeskAdmin: def get_user_image(self, email: str) -> str: """ - Функция **get_user_image** возвращает аватар пользователя - - :param user_image: Аватар пользователя - :type user_image: :class:`img` + Функция **get_user_image** возвращает url-ссылку на аватар пользователя """ user = self.admin.users.search(email).values[0] return user.photo['content_url'] if user.photo else None @@ -86,7 +89,7 @@ class ZendeskAdmin: def get_user_org(self, email: str) -> str: user = self.admin.users.search(email).values[0] - return user.organization.name + return user.organization.name def create_admin(self) -> None: """ @@ -140,7 +143,7 @@ def check_user_exist(email: str) -> bool: return ZendeskAdmin().check_user(email) -def get_user_organization(email: str) -> bool: +def get_user_organization(email: str) -> str: """ Функция возвращает организацию пользователя diff --git a/main/forms.py b/main/forms.py index 6a058bb..e33e1ae 100644 --- a/main/forms.py +++ b/main/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.contrib.auth.forms import AuthenticationForm from django_registration.forms import RegistrationFormUniqueEmail @@ -6,11 +7,7 @@ class CustomRegistrationForm(RegistrationFormUniqueEmail): """ Форма для регистрации :class:`django_registration.forms.RegistrationFormUniqueEmail` с добавлением bootstrap-класса 'form-control' - - :param password_zen: Поле для ввода пароля от Zendesk - :type password_zen: :class:`django.forms.CharField` """ - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for visible in self.visible_fields(): @@ -24,3 +21,21 @@ class CustomRegistrationForm(RegistrationFormUniqueEmail): class Meta(RegistrationFormUniqueEmail.Meta): fields = RegistrationFormUniqueEmail.Meta.fields + + +class CustomAuthenticationForm(AuthenticationForm): + """ + Форма для авторизации :class:`django.contrib.auth.forms.AuthenticationForm` + с изменением поля username на email + """ + username = forms.CharField( + label="Электронная почта", + widget=forms.EmailInput(), + ) + error_messages = { + 'invalid_login': + "Пожалуйста, введите правильные электронную почту и пароль. Оба поля " + "могут быть чувствительны к регистру." + , + 'inactive': "Аккаунт не активен.", + } diff --git a/main/views.py b/main/views.py index a73017f..8fb892a 100644 --- a/main/views.py +++ b/main/views.py @@ -1,18 +1,17 @@ +import logging + 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.contrib.auth.views import LoginView from django.shortcuts import render from django.urls import reverse_lazy -from django_registration.backends.one_step.views import RegistrationView +from django_registration.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 main.forms import CustomRegistrationForm, CustomAuthenticationForm class CustomRegistrationView(RegistrationView): @@ -89,3 +88,10 @@ def main_page(request): logger = logging.getLogger('main.index') logger.info('Index page opened') return render(request, 'pages/index.html') + + +class CustomLoginView(LoginView): + """ + Отображение страницы авторизации пользователя + """ + form_class = CustomAuthenticationForm From 6cdd787cc8fcf4f8240fc3efdeeeb631d678ac32 Mon Sep 17 00:00:00 2001 From: Artyom Kravchenko Date: Wed, 3 Mar 2021 13:46:44 +0300 Subject: [PATCH 14/16] add typehints to requirements --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b2e4c64..4161b6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,4 @@ django_registration==3.1.1 # Documentation Sphinx==3.4.3 sphinx-rtd-theme==0.5.1 - +sphinx-autodoc-typehints==1.11.1 From 0cda9da3116820266e9f3113875cf0c87fb2ee24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=B5=D0=BD=D0=BA?= =?UTF-8?q?=D0=BE=20=D0=9E=D0=BB=D1=8C=D0=B3=D0=B0?= Date: Wed, 3 Mar 2021 20:59:53 +0300 Subject: [PATCH 15/16] Add views and forms documentation --- main/forms.py | 15 ++++++++------- main/views.py | 35 +++++++++++++++++++++++++---------- requirements.txt | 1 + 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/main/forms.py b/main/forms.py index 176302e..63a3556 100644 --- a/main/forms.py +++ b/main/forms.py @@ -7,13 +7,14 @@ from main.models import UserProfile class CustomRegistrationForm(RegistrationFormUniqueEmail): """ Форма для регистрации :class:`django_registration.forms.RegistrationFormUniqueEmail` - с добавлением bootstrap-класса 'form-control' - :param password_zen: Поле для ввода пароля от Zendesk - :type password_zen: :class:`django.forms.CharField` + с добавлением bootstrap-класса "form-control" + + :param visible_fields.email: Поле для ввода email, зарегистирированного на Zendesk + :type visible_fields.email: :class:`django_registration.forms.RegistrationFormUniqueEmail` """ - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> RegistrationFormUniqueEmail: super().__init__(*args, **kwargs) for visible in self.visible_fields(): if visible.field.widget.attrs.get('class', False): @@ -30,10 +31,10 @@ class CustomRegistrationForm(RegistrationFormUniqueEmail): class AdminPageUsers(forms.Form): """ - Форма для установки статуса + Форма для установки статусов engineer или light_agent пользователям - :param password_zen: Поле для ввода пароля от Zendesk - :type password_zen: :class:`django.forms.CharField` + :param users: Поле для установки статуса + :type users: :class:`ModelMultipleChoiceField` """ users = forms.ModelMultipleChoiceField( diff --git a/main/views.py b/main/views.py index 72d4e52..c7c1849 100644 --- a/main/views.py +++ b/main/views.py @@ -27,13 +27,17 @@ from access_controller.settings import ZENDESK_ROLES class CustomRegistrationView(RegistrationView): """ Отображение и логика работы страницы регистрации пользователя + + 1. Ввод email пользователя, указанный на Zendesk + 2. В случае если пользователь с данным паролем зарегистрирован на Zendesk и относится к определенной организации, происходит сброс ссылки с установлением пароля на указанный email + 3. Создается пользователь class User, а также его профиль """ form_class = CustomRegistrationForm template_name = 'django_registration/registration_form.html' success_url = reverse_lazy('django_registration_complete') is_allowed = True - def register(self, form): + def register(self, form: CustomRegistrationForm) -> User: self.is_allowed = True if check_user_exist(form.data['email']) and get_user_organization(form.data['email']) == 'SYSTEM': forms = PasswordResetForm(self.request.POST) @@ -61,7 +65,7 @@ class CustomRegistrationView(RegistrationView): else: self.is_allowed = False - def get_success_url(self, user=None): + def get_success_url(self, user: User = None) -> success_url: """ Возвращает url-адрес страницы, куда нужно перейти после успешной/неуспешной регистрации Используется самой django-registration @@ -73,13 +77,9 @@ class CustomRegistrationView(RegistrationView): @login_required() -def profile_page(request): +def profile_page(request: UserProfile) -> UserProfile: """ Отображение страницы профиля - - :param request: объект с деталями запроса - :type request: :class:`django.http.HttpResponse` - :return: объект ответа сервера с HTML-кодом внутри """ user_profile = request.user.userprofile update_profile(user_profile) @@ -95,17 +95,26 @@ def profile_page(request): def main_page(request): + """ + Отображение логгирования на главной странице + """ logger = logging.getLogger('main.index') logger.info('Index page opened') return render(request, 'pages/index.html') class AdminPageView(FormView, LoginRequiredMixin): + """ + Class AdminPageView - логика работы страницы администратора + """ template_name = 'pages/adm_ruleset.html' form_class = AdminPageUsers success_url = '/control/' - def form_valid(self, form): + def form_valid(self, form: AdminPageUsers) -> AdminPageUsers: + """ + Функция установки ролей пользователям + """ if 'engineer' in self.request.POST: self.make_engineers(form.cleaned_data['users']) elif 'light_agent' in self.request.POST: @@ -121,7 +130,10 @@ class AdminPageView(FormView, LoginRequiredMixin): [make_light_agent(user) for user in users] @staticmethod - def count_users(users): # TODO: this func counts users from all zendesk instead of just from a model + def count_users(users: User) -> int: #.. todolist:: :this func counts users from all zendesk instead of just from a model: + """ + Функция подсчета количества сотрудников с ролями engineer и light_a + """ engineers, light_agents = 0, 0 for user in users: if user.custom_role_id == ZENDESK_ROLES['engineer']: @@ -130,7 +142,10 @@ class AdminPageView(FormView, LoginRequiredMixin): light_agents += 1 return engineers, light_agents - def get_context_data(self, **kwargs): + def get_context_data(self, **kwargs) -> list: + """ + Функция формирования контента страницы администратора (с проверкой прав доступа) + """ if self.request.user.userprofile.role != 'admin': raise PermissionDenied context = super().get_context_data(**kwargs) diff --git a/requirements.txt b/requirements.txt index b2e4c64..826cc73 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,5 @@ django_registration==3.1.1 # Documentation Sphinx==3.4.3 sphinx-rtd-theme==0.5.1 +sphinx-autodoc-typehints==1.11.1 From cd47331ae7496b7a3b02cc1d1ba18c4bf05848ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=82=D0=B5=D0=BF=D0=B0=D0=BD=D0=B5=D0=BD=D0=BA?= =?UTF-8?q?=D0=BE=20=D0=9E=D0=BB=D1=8C=D0=B3=D0=B0?= Date: Wed, 3 Mar 2021 21:05:31 +0300 Subject: [PATCH 16/16] After mergin develop --- main/forms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/forms.py b/main/forms.py index a9624dd..32ed00b 100644 --- a/main/forms.py +++ b/main/forms.py @@ -51,8 +51,8 @@ class AdminPageUsers(forms.Form): class CustomAuthenticationForm(AuthenticationForm): """ - Форма для авторизации :class:`django.contrib.auth.forms.AuthenticationForm` - с изменением поля username на email + Форма для авторизации :class:`django.contrib.auth.forms.AuthenticationForm` + с изменением поля username на email """ username = forms.CharField( label="Электронная почта",