Flask-Admin

в 19:49, , рубрики: django, flask, python, sqlalchemy, метки: , , ,

Доброе время суток.

Хочу представить проект, над которым работал в последнее время: 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 все же удобнее, чем без него.

Вот так выглядит список моделей для этого примера:
Flask Admin

А вот так — встроенный файловый менеджер:
Flask Admin

Ближе к коду

И так, для того что бы подключить админку к приложению, нужно:
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. Выглядит так:
Flask Admin

Для генерации ссылок между вьюшками, можно использовать обычный 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

* - обязательные к заполнению поля