Есть много способов построить авторизацию и административную часть (личный кабинет) на Flask. Некоторые предполагают самостоятельно все строить, другие имеют уже готовые концепции.
Статья входит в цикл статей по разработке на python.
Перед тем как приступить к этой статье следует сделать всё что описано в статье делаем приложение на Flask в локальной среде.
Введение
Нет особого смысла делать с нуля то, что уже было сделано и оттестировано другими. Мы пойдем по проторенной дорожке и выберем готовые ингредиенты, которые останется только правильно смешать.
Flask — это микрофреймворк, что предполагает создание проекта по модулям.
Ниже мы построим личный кабинет с авторизацией.
Установка расширений
Flask-Login — расширение, которое обеспечит управление пользовательскими сеансами во Flask. Благодаря этому мы сможем входить и выходить из системы, ограничивать страницы авторизацией.
Flask-Admin — с помощью этого расширения мы сможем создать личный кабинет на подобии того, что есть в Django.
Flask-WTF — расширение для Flask, являющееся оберткой WTForms, с помощью которого можно строить безопасные формы.
Также установим здесь email_validator, библиотеку для проверки эмейлов.
pip install email_validator
Flask-Security — разделение пользователей на роли.
Создаем модели для базы данных
Создаем файл:
app/models.py
Внутри которого пишем:
from flask_login import UserMixin
from flask_security import RoleMixin
from app import db, login_manager
from werkzeug.security import generate_password_hash, check_password_hash
roles_users = db.Table(
'roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('users.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('roles.id'))
)
class Role(db.Model, RoleMixin):
__tablename__ = 'roles'
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
def __str__(self):
return self.name
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, unique=True, primary_key=True)
name = db.Column(db.String)
username = db.Column(db.String, unique=True)
email = db.Column(db.String, unique=True)
password = db.Column(db.String)
created_on = db.Column(db.DateTime(), default=datetime.utcnow)
updated_on = db.Column(db.DateTime(), default=datetime.utcnow, onupdate=datetime.utcnow)
# Нужен для security!
active = db.Column(db.Boolean())
# Для получения доступа к связанным объектам
roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic'))
# Flask - Login
@property
def is_authenticated(self):
return True
@property
def is_active(self):
return True
@property
def is_anonymous(self):
return False
# Flask-Security
def has_role(self, *args):
return set(args).issubset({role.name for role in self.roles})
def get_id(self):
return self.id
# Required for administrative interface
def __unicode__(self):
return self.username
def set_password(self, password):
self.password = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password, password)
# Отвечает за сессию пользователей. Запрещает доступ к роутам, перед которыми указано @login_required
@login_manager.user_loader
def load_user(user_id):
return db.session.query(User).get(user_id)
Разберем код.
class Role — класс ролей связанная с таблицей roles.
class User — класс пользователей связанный с таблицей users.
roles_users = db.Table — создание связей между этими двумя таблицами, значение которых будет записываться в новую таблицу roles_users.
@property в виде is_authenticated, is_active, is_anonymous нужны будут для определения авторизованных, активных и анонимных пользователей.
@login_required — пригодится позже для разделения показа страниц на авторизованных и не авторизованных пользователей.
Другие дополнительные участки кода тоже пригодятся или могут пригодиться в дальнейшем.
Запускаем миграцию этих таблиц:
flask db migrate -m "Initial migration."
flask db upgrade
На этом этапе скорее всего покажет ошибку, что бд нет. В этом случае команды миграции можно пропустить. Мы их запустим когда будем создавать базу данных.
Подробнее о миграции таблиц в SQLAlchemy.
Добавляем модуль admin
Создаём директорию admin в директории app со следующими файлами:
Содержимое файла __init__.py:
from flask import url_for, redirect, request, abort
from app.models import User, Role
# flask-login
from flask_login import current_user
import flask_login as login
# flask-security
from flask_security import SQLAlchemyUserDatastore, Security
# flask-admin
import flask_admin
from flask_admin import helpers, expose
from flask_admin.contrib import sqla
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
# Create customized model view class
class MyModelView(sqla.ModelView):
def is_accessible(self):
return (current_user.is_active and
current_user.is_authenticated and
current_user.has_role('admin')
)
def _handle_view(self, name, **kwargs):
"""
Override builtin _handle_view in order to redirect users when a view is not accessible.
"""
if not self.is_accessible():
if current_user.is_authenticated:
# permission denied
abort(403)
else:
return redirect(url_for('security.login', next=request.url))
# Переадресация страниц (используется в шаблонах)
class MyAdminIndexView(flask_admin.AdminIndexView):
@expose('/')
def index(self):
if not current_user.is_authenticated:
return redirect(url_for('.login_page'))
return super(MyAdminIndexView, self).index()
@expose('/login/', methods=('GET', 'POST'))
def login_page(self):
if current_user.is_authenticated:
return redirect(url_for('.index'))
return super(MyAdminIndexView, self).index()
@expose('/logout/')
def logout_page(self):
login.logout_user()
return redirect(url_for('.index'))
@expose('/reset/')
def reset_page(self):
return redirect(url_for('.index'))
# Create admin
admin = flask_admin.Admin(app, index_view=MyAdminIndexView(), base_template='admin/master-extended.html')
# Add view
admin.add_view(MyModelView(User, db.session))
# define a context processor for merging flask-admin's template context into the
# flask-security views.
@security.context_processor
def security_context_processor():
return dict(
admin_base_template=admin.base_template,
admin_view=admin.index_view,
h=helpers,
get_url=url_for
)
Создаем шаблоны для административной части
templates/admin
Создаем внутри директории templates директорию admin. В этой директории добавляем файлы.
master-extended.html
Файлом master-extended.html мы расширяем админ панель. В конкретно данном случае добавляем кнопку выхода на админ панель справа:
Содержимое файла:
{% block access_control %}
{% if current_user.is_authenticated %}
<div class="btn-group pull-right">
<a class="btn" href="{{ url_for('admin.logout_page') }}">{{ current_user.username }} - Log out</a>
</div>
{% endif %}
{% endblock %}
index.html
Файл index.html у нас выступит формой входа:
Содержимое файла может быть таким:
{% block body %}
{{ super() }}
<div class="row-fluid">
<div>
{% if current_user.is_authenticated %}
<div>
<h2>Logged in User details</h2>
<ul>
<li>Username: {{ current_user.username }}</li>
<li>Email: {{ current_user.email }}</li>
<li>Created on: {{ current_user.created_on }}</li>
<li>Updated on: {{ current_user.updated_on }}</li>
</ul>
<h2>Основные ваши настройки</h2>
<p class="lead">Вы можете:</p>
<p><a href="/admin/change/">Изменить пароль</a></p>
</div>
{% else %}
<form method="POST" action="">
{{ form.hidden_tag() if form.hidden_tag }}
{% for f in form if f.type != 'CSRFTokenField' %}
<div>
{{ f.label }}
{{ f }}
{% if f.errors %}
<ul>
{% for e in f.errors %}
<li>{{ e }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endfor %}
<button class="btn" type="submit">Submit</button>
</form>
{{ link | safe }}
{% endif %}
</div>
</div>
{% endblock body %}
templates/security
Когда было установлено расширение Flask-Security в директории templates появилась директория security, а в ней следующие файлы:
Подробно на них останавливаться не будем. Скажу лишь, что эти файлы переопределяют шаблоны расширения Flask-Security.
Шаблоны, которые они расширяют находятся здесь:
Если какой-то из шаблонов хочется поменять, его нужно скопировать оттуда к себе в директорию security. И дальше можно его менять.
Config
Для того, чтобы все работало нам нужно добавить данные в файл конфига config-extended. К этому моменты вы должны были ознакомиться с конфигурационными файлами Flask.
Содержимое конфига:
# Flask-Security
################
# URLs
SECURITY_URL_PREFIX = "/admin"
SECURITY_LOGIN_URL = "/login/"
SECURITY_LOGOUT_URL = "/logout/"
SECURITY_POST_LOGIN_VIEW = "/admin/"
SECURITY_POST_LOGOUT_VIEW = "/admin/"
SECURITY_POST_REGISTER_VIEW = "/admin/"
# Включает регистрацию
SECURITY_REGISTERABLE = True
SECURITY_REGISTER_URL = "/register/"
SECURITY_SEND_REGISTER_EMAIL = False
# Включет сброс пароля
SECURITY_RECOVERABLE = True
SECURITY_RESET_URL = "/reset/"
SECURITY_SEND_PASSWORD_RESET_EMAIL = True
# Включает изменение пароля
SECURITY_CHANGEABLE = True
SECURITY_CHANGE_URL = "/change/"
SECURITY_SEND_PASSWORD_CHANGE_EMAIL = False
С другими директивами можно ознакомиться в официальном источнике.
Blueprint
Последнее что нам нужно сделать для работы админки добавить регистрацию путей через Blueprint. Зачем нужен Blueprint и его настройка.
В файл app/__init__.py нужно добавить:
from app.admin.routes import admin_bp
app.register_blueprint(admin_bp, url_prefix="/admin")
Может быть вы обратили внимание, что на скрине выше в модуле admin есть файл routes.py.
Если мы строим большой и расширяемый проект, то у нас в каждом модуле, где требуются роуты, будет свой файл routes.py с путями.
В файл routes.py надо добавить:
admin_bp = Blueprint('admin_blueprint', __name__)
В этом файле на текущий момент больше ничего не нужно. Все наши роуты для админки прописаны в файле admin/__init__.py там где класс class MyAdminIndexView, но именно потому что они идут немного в другом формате и переплетены с функционалом. Чтобы все не усложнять мы оставим как есть, но практику применения роутов добавим.
Вход в админку
Теперь у нас есть админка. Заходим по адресу:
Регистрируемся на свой эмейл и заходим внутрь. Наша админка выглядит так (пока видны не все вкладки, мы дадим доступ к ним ниже):
Отдельные вкладки для неё мы можем добавлять в файле __init__.py. Например, вот как мы можем добавить новую вкладку для работы с таблицей UserSetting в базе данных (предварительно для неё должна быть создана модель в файле models.py, по инструкции выше):
admin.add_view(MyModelView(User, db.session))
admin.add_view(MyModelView(UserSetting, db.session))
Добавим роли для админки. Заходим к себе в БД. Как зайти в локальную ДБ через DataGrip или соединяемся удаленно с базой данных.
В таблице role добавим данные:
И добавим нашему пользователю роль администратора в таблице roles_users:
Добрый день, а где исходники шаблонов, которые мы расширяем base.html и master.html?
Да очень интересно. Не хватает фаилов base.html и master.html?
Исходники устанавливаются с расширениями:
pip install Flask-Login
pip install Flask-Admin
В самом начале статьи.
Или ссылку на git?