Flask-Admin

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

    Хочу представить проект, над которым работал в последнее время: 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('.help')
        return self.render('myproject/admin/index.html', url=url)
    
      @expose('/help/')
      def help(self):
        return self.render('myproject/admin/help.html', id=id)
    


    Дальше разработка ничем не отличается от написания обычного кода под Flask.

    Итого


    На текущий момент API библиотеки более-менее стабилизировалось и успешно используется в нескольких проектах.

    Примеры лежат тут: github.com/mrjoes/flask-admin/tree/master/examples
    Документация тут: flask-admin.readthedocs.org/en/latest

    И, как обычно, патчи всегда приветствуются.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 22
    • 0
      Спасибо за статью и труд, симпатично.
      Вызывает желание ознакомиться с Flask как таковым. Можете вкратце осветить его основные ± по сравнению с Django?
      • +6
        Я бы назвал такие:
        1. Меньший размер, мало зависимостей, можно быстро писать приложения в одном .py файле
        2. Отдельные компоненты из которых состоит Flask лучше аналогов из Django
        3. Explicit is better than implicit, никаких автоимпортов и другой магии кроме threadlocals (которые Django тоже использует)

        Из минусов:
        1. Надо привыкнуть к чтению документации из кучи разных мест (запросы — Werkzeug, шаблоны — Jinja2, ORM скорее всего SQLAlchemy и так далее).
        2. Сторонних компонент явно меньше
        3. Начинающим сложнее — слишком много свободы и непонятно что делать

        Как-то так.
        • 0
          А чем он глобально отличается от того же web.py?
          • +2
            Я когда-то щупал web.py.

            У него другая идеология — будем комбайном а-ля Django, что бы все было из коробки. Своя ORM, свои шаблоны и т.д. А в результате, для проектов больше одного файла, все равно используют сторонние библиотеки.

            Грубо говоря, это попытка быть Django без ресурсов и community Django. Может оно и не так, но внешне очень похоже.
      • +1
        >Отдельные компоненты из которых состоит Flask лучше аналогов из Django
        Ээээ, а можете привести примеры?
        >… кроме threadlocals (которые Django тоже использует)
        Использование threadlocals в джанге считается очень неудачной практикой (причём считается его создателями). Но тут часто действует принцип «если нельзя, но очень хочется...»
        • +5
          > Ээээ, а можете привести примеры?
          Конечно. Там всего две зависимости: Werkzeug и Jinja2 и очень тонкая прослойка между ними.

          Werkzeug умеет много всякого хорошего, например вот такой вот замечательный, интерактивный дебаггер: werkzeug.pocoo.org/docs/debug/

          Jinja2, же, очень хороший шаблонизатор с синтаксисом Django. Лучше хотя бы тем, что при ошибках в шаблонах умеет показывать stacktrace и банально работает быстрее.

          Если взять третью компоненту — ORM, то это скорее всего будет SQLAlchemy (некоторые еще Peewee используют, я его даже не смотрел). А алхимия сама по себе лучше ORM Django. Можно спорить о удобности и привычности синтаксиса, но как ни крутить — позволяет больше и не заставляет писать сырой SQL в относительно сложных случаях.

          Тут скорее дело в том, что Django это комбайн все в одном. Разработчики распыляют свои усилия по достаточно большой кодовой базе. И в каждой своей части, Django работает хоть и не плохо, но хуже чем отдельные специализированные решения. Да, можно сказать что у Django лучше связанность компонент, но на самом деле Flask от слабой связанности не сильно страдает.
          • 0
            peewee кстати может и зря порпускаем — мельком смотрел, вроде неплохо. Хотя вот админка приколоченная к орму, эту болезнь уже вроде проходили.
            • 0
              Дебаггер из werkzeug легко и к джанге правда приколачивается. runplus, или как там, давно не писал под. А вот джинджу с большими трудами, и стандартные компоненты все равно рисуют через джангу, и в общем возможны неудобства.

              У Flask мне кажется не просто компоненты — сам принцип построения удачнее.
            • +4
              threadlocals не очень верно. На самом деле, при нырянии в код джанги и werkzeug, становится понятно, что flask под gevent можно запускать смело, django лучше не стоит — werkzeug учитывает гринлеты, и делает для них специальные обертки.
              Рыбу фугу тоже жрать опасно, но если руки у повара прямые, то можно.

              Ну в качестве примера можно привести sqlalchemy, хоть она и не компонент именно фласка. Jinja2 — ну тоже как бы отдельно можно использовать, но в общем джанговские темплейты она обходит и по скорости и по фичастости/гибкости.
              Flask-Admin вот еще например — заложенная гибкость действительно на хорошем уровне. Ну и в общем все что под фласк написано, оно несколько с другой философией идет, более питоник, более модульное.

              После знакомства с фласком у меня получается писать с минимумом магии, и при этом весьма и весьма прикладисто.
            • +3
              Пользуюсь этой штукой еще в бытность ее админкой в svarga. С переносом по flask стала еще лучше.
              То есть моя личная рекомендация очень пристально смотреть и проникнуться.
              • +3
                Кстати, что со Сваргой — померла, как и большинство микрофреймворков на WZ с появлением Flask?
                • +2
                  Ага, была заморожена.

                  Сварга, идеологически, была очень похожа на Flask. Ну или наоборот, Flask появился на пол года позже. Те же идеи с threadlocals, та же основа — Werkzeug, Jinja2. Разница только в том, что у Сварги было много идей позаимствовано из Django и идеи эти были не самыми лучшими. Например — структура приложений, автоимпорт моделей и т.д.

                  Когда анонсировали Flask, стало сразу понятно что его будет использовать больше народа чисто за счет большего community pocoo. И было принято решение заморозить Сваргу и потихоньку перетаскивать наработки во Flask. Что, собственно, и происходит.
              • +2
                Спасибо, люблю Фласк и давно пользуюсь вашей админкой!
                • +1
                  Огромное спасибо за Ваш труд. Месяц назад начал использовать flask-admin и был приятно удивлен гибкостью. Я думаю, что bootstrap здесь очень к месту. В целом нравится намного больше, чем админка в django.
                  • 0
                    вобще приятно что наконец начали появлятся такие штуки.
                    а то даже я от лени предпочитаю джангу — лижбы с админкой не парится
                    • +1
                      Пользуемся вашей админкой. Допиливал только не очень гибкий is_accessible.
                      Большое вам спасибо :)
                      • +1
                        А что там не работало?
                        • +1
                          В моем случае надо было при ошибке доступа редиректить на форму логина и сохранять пут для возврата, а не возвращать ошибку. Возможно это не редкая ситуация
                          • +3
                            Но это просто решилось переопределением метода handle_view + к is_accessible
                            def _handle_view(self, name, **kwargs):
                                    if not self.is_accessible():
                                        #Some Actions
                            
                            • +4
                              А, понял что имеется в виду.

                              Да, можно сделать так:
                                  def _handle_view(self, name, **kwargs):
                                      if not self.is_accessible():
                                          return login.current_app.login_manager.unauthorized()
                              


                              А можно кинуть RequestRedirect(url) из самого is_accessible и произойдет редирект.
                        • 0
                          Спасибо вам за вашу работу!
                          • 0
                            Приладил к Pylons через middleware.
                            Спасибо, очень симпатично и удобно.

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.