Доброе время суток.
Хочу представить проект, над которым работал в последнее время: Flask-Admin. Если в двух словах, это расширение для фреймворка Flask, которое позволяет быстро создавать административный интерфейс в стиле Django.
Архитектура
Попробую описать как это все работает и в чем отличие от админки Django.
Базовый кирпичик Flask-Admin это класс у которого есть view методы. Есть немного базового кода, который собирает из кирпичиков админку и рисует меню. Все.
Как такой подход позволяет эффективно строить административный интерфейс?
Возьмем, к примеру, типовую задачу — CRUD для моделей.
Можно сделать «в лоб» — написать код, который примет на входе список моделей, создаст формы и таблички для отображения каждой и т.д. Данный код будет монолитным, будет работать с конкретным типом ORM и вообще его будет тяжело расширять.
А можно сделать класс, который умеет работать с одной моделью. Создав экземпляр такого класса и подключив его в админку, получим интерфейс управления этой моделью. Повторяем для остальных моделей и админка почти готова.
Кроме того, при таком подходе можно легко расширять функционал. Вместо monkey patching'а, достаточно переопределить нужные методы и, в результате, получаем новое поведение.
Это и есть основное отличие от Django — возможность быстро и безболезненно расширить или заменить функционал админки под конкретные задачи.
Flask-Admin
Что умеет Flask-Admin, из коробки:
1. Генерацию меню (до двух уровней) из подключенных кирпичиков с учетом правил доступа
2. Возможность управления доступом, без каких либо предположений о используемой системе авторизации
3. Набор базовых классов для создания своих «кирпичиков»
3. CRUD для моделей SQLAlchemy, включая пейджинг, сортировку, фильтры, поиск и тому подобное.
4. Файловый менеджер
5. Локализация. Работает с помощью модифицированной версии Flask-Babel, патч отправлен Армину, но до сих пор не принят. Временно можно установить версию из моего репозитория, она обратно-совместима с текущим stable из PyPI.
Клиентская часть работает поверх Twitter Bootstrap. Причина очень простая — адекватный внешний вид и куча UI вкусностей для быстрого создания UI. Так или иначе, админка обычно недоступна обычным пользователей, а писать UI с bootstrap все же удобнее, чем без него.
Вот так выглядит список моделей для этого примера:
А вот так — встроенный файловый менеджер:
Ближе к коду
И так, для того что бы подключить админку к приложению, нужно:
1. Создать экземпляр класса Admin
2. Добавить экземпляров класса BaseView (все «кирпичики» наследуются от него)
Например, есть две модели: User и Post, нужно создать админку. Код инициализации будет выглядеть так:
from flask.ext.admin import Admin
from flask.ext.admin.contrib.sqlamodel import ModelView
admin = Admin(app)
admin.add_view(ModelView(User, db.session))
admin.add_view(ModelView(Post, db.session))
db.session это сессия алхимии.
ModelView расширяется по образу и подобию Django — есть набор свойств на уровне класса/экземпляра, которые можно менять.
Например, если в списке моделей User нужно исключить поле password, делаем так:
class MyUserAdmin(ModelView):
excluded_list_columns = ('password',)
admin = Admin(app)
admin.add_view(MyUserAdmin(User, db.session))
Ничто не мешает менять свойства в конструкторе до вызова предка:
class MyUserAdmin(ModelView):
def __init__(self, session, name, excluded=None):
if excluded:
self.excluded_list_columns = excluded
super(MyUserAdmin, self).__init__(User, session, name=name)
admin = Admin(app)
admin.add_view(MyUserAdmin(db.session, 'View1', ('password',))
admin.add_view(MyUserAdmin(db.session, 'View2', ('email','password'))
Если хочется, можно добавить еще одну «вьюшку» и кнопку в шаблон, при нажатии на которую будет показывается новая вьюшка. Код будет такой:
from flask.ext.admin import expose
class MyUserAdmin(ModelView):
# Кнопка будет в шаблоне
list_template = 'myproject/admin/userlist.html'
@expose('/report/<int:id>/')
def report(self, id):
# Логика тут
return self.render('myproject/admin/userreport.html', id=id)
def __init__(self, session):
super(MyUserAdmin, self).__init__(User, session)
admin = Admin(app)
admin.add_view(MyUserAdmin(db.session))
Для того, что бы получить нормальный look and feel, в шаблонах нужно унаследоваться от 'admin/master.html':
{% extends 'admin/master.html' %}
{% block body %}
Hello World from MyView!
{% endblock %}
Поведение шаблонов и view методов полностью аналогично стандартным из Flask'а.
Расширение функционала
Расширение функционала можно разделить на две части:
1. Изменение поведения встроенных «батареек»
2. Написание чего-то нового
Батарейки
Архитектурно, scaffolding моделей состоит из двух слоев:
1. Уровень доступа к данным. Тут находится логика взаимодействия с конкретной реализацией ORM — от интроспекции модели до методов доступа к данным
2. Уровень UI и остальной логики
Комбинируя оба уровня (через наследование), получаем готовую «батарейку» для конкретной ORM. Нужно поддержать, скажем, mongo-alchemy — пишем логику, наследуемся от базового класса, получаем CRUD для mongo.
ModelView умеет конфигурировать себя через свойства класса (или экземпляра, если прописали в конструкторе). Однако, если готовых возможностей настройки не хватает — всегда можно унаследоваться, переопределить нужные методы и получить совершенно другое поведение.
Аналогично работает файловый менеджер. Например, если нужно запретить доступ к директории reports для пользователя Mike, сделать можно как-то так:
class MyFileAdmin(FileAdmin):
def is_accessible_path(self, path):
if path.startswith('reports'):
return user.login != 'mike'
return True
Новый функционал
Теперь о добавлении совершенно нового функционала. Вот так добавляется новый «кирпичик»:
from flask.ext.admin import Admin, BaseView, expose
class MyView(BaseView):
@expose('/')
def index(self):
return self.render('myproject/admin/index.html')
admin = Admin(app)
admin.add_view(MyView(name='Hello'))
В меню появится пункт и при открытии пункта 'Hello' вызовется вьюшка index. Выглядит так:
Для генерации ссылок между вьюшками, можно использовать обычный url_for с точкой в начале имени вьюшки:
from flask import url_for
class MyView(BaseView):
@expose('/')
def index(self):
url = url_for('.edit')
return self.render('myproject/admin/index.html', url=url)
@expose('/edit/<int:id>/')
def edit(self, id):
return self.render('myproject/admin/edit.html', id=id)
Дальше разработка ничем не отличается от написания обычного кода под Flask.
Итого
На текущий момент API библиотеки более-менее стабилизировалось и успешно используется в нескольких проектах.
Примеры лежат тут: github.com/mrjoes/flask-admin/tree/master/examples
Документация тут: flask-admin.readthedocs.org/en/latest/
И, как обычно, патчи всегда приветствуются.
Автор: Joes