Flask的圖形化管理介面搭建架構Flask-Admin的使用教程

來源:互聯網
上載者:User
Flask-Admin是Flask架構的一個擴充,用它能夠快速建立Web管理介面,它實現了比如使用者、檔案的增刪改查等常用的管理功能;如果對它的預設介面不喜歡,可以通過修改模板檔案來定製;
Flask-Admin把每一個菜單(超連結)看作一個view,註冊後才能顯示出來,view本身也有屬性來控制其是否可見;因此,利用這個機制可以定製自己的模組化介面,比如讓不同許可權的使用者登入後看到不一樣的菜單;

項目地址:https://flask-admin.readthedocs.io/en/latest/

example/simple
這是最簡單的一個範例,可以協助我們快速、直觀的瞭解基本概念,學會定製Flask-Admin的介面
simple.py:

from flask import Flaskfrom flask.ext import admin# Create custom admin viewclass MyAdminView(admin.BaseView):  @admin.expose('/')  def index(self):    return self.render('myadmin.html')class AnotherAdminView(admin.BaseView):  @admin.expose('/')  def index(self):    return self.render('anotheradmin.html')  @admin.expose('/test/')  def test(self):    return self.render('test.html')# Create flask appapp = Flask(__name__, template_folder='templates')app.debug = True# Flask views@app.route('/')def index():  return 'Click me to get to Admin!'# Create admin interfaceadmin = admin.Admin()admin.add_view(MyAdminView(category='Test'))admin.add_view(AnotherAdminView(category='Test'))admin.init_app(app)if __name__ == '__main__':  # Start app  app.run()

在這裡可以看到運行效果

BaseView

所有的view都必須繼承自BaseView:

複製代碼 代碼如下:


class BaseView(name=None, category=None, endpoint=None, url=None, static_folder=None, static_url_path=None)


name: view在頁面上表現為一個menu(超連結),menu name == 'name',預設就用小寫class name
category: 如果多個view有相同的category就全部放到一個dropdown裡面(dropdown name=='category')
endpoint: 假設endpoint='xxx',則可以用url_for(xxx.index),也能改變頁面URL(/admin/xxx)
url: 頁面URL,優先順序url > endpoint > class name
static_folder: static目錄的路徑
static_url_path: static目錄的URL
anotheradmin.html:

{% extends 'admin/master.html' %}{% block body %}  Hello World from AnotherMyAdmin!
Click me to go to test view{% endblock %}

如果AnotherAdminView增加參數endpoint='xxx',那這裡就可以寫成url_for('xxx.text'),然後頁面URL會由/admin/anotheradminview/變成/admin/xxx
如果同時指定參數url='aaa',那頁面URL會變成/admin/aaa,url優先順序比endpoint高
Admin

複製代碼 代碼如下:


class Admin(app=None, name=None, url=None, subdomain=None, index_view=None, translations_path=None, endpoint=None, static_url_path=None, base_template=None)


app: Flask Application Object;本例中可以不寫admin.init_app(app),直接用admin = admin.Admin(app=app)是一樣的
name: Application name,預設'Admin';會顯示為main menu name('Home'左邊的'Admin')和page title
subdomain: ???
index_view: 'Home'那個menu對應的就叫index view,預設AdminIndexView
base_template: 基礎模板,預設admin/base.html,該模板在Flask-Admin的源碼目錄裡面
部分Admin代碼如下:

class MenuItem(object):  """    Simple menu tree hierarchy.  """  def __init__(self, name, view=None):    self.name = name    self._view = view    self._children = []    self._children_urls = set()    self._cached_url = None    self.url = None    if view is not None:      self.url = view.url  def add_child(self, view):    self._children.append(view)    self._children_urls.add(view.url)class Admin(object):  def __init__(self, app=None, name=None,         url=None, subdomain=None,         index_view=None,         translations_path=None,         endpoint=None,         static_url_path=None,         base_template=None):    self.app = app    self.translations_path = translations_path    self._views = []    self._menu = []    self._menu_categories = dict()    self._menu_links = []    if name is None:      name = 'Admin'    self.name = name    self.index_view = index_view or AdminIndexView(endpoint=endpoint, url=url)    self.endpoint = endpoint or self.index_view.endpoint    self.url = url or self.index_view.url    self.static_url_path = static_url_path    self.subdomain = subdomain    self.base_template = base_template or 'admin/base.html'    # Add predefined index view    self.add_view(self.index_view)    # Register with application    if app is not None:      self._init_extension()  def add_view(self, view):    # Add to views    self._views.append(view)    # If app was provided in constructor, register view with Flask app    if self.app is not None:      self.app.register_blueprint(view.create_blueprint(self))      self._add_view_to_menu(view)  def _add_view_to_menu(self, view):    if view.category:      category = self._menu_categories.get(view.category)      if category is None:        category = MenuItem(view.category)        self._menu_categories[view.category] = category        self._menu.append(category)      category.add_child(MenuItem(view.name, view))    else:      self._menu.append(MenuItem(view.name, view))  def init_app(self, app):    self.app = app    self._init_extension()    # Register views    for view in self._views:      app.register_blueprint(view.create_blueprint(self))      self._add_view_to_menu(view)

從上面的代碼可以看出init_app(app)和Admin(app=app)是一樣的:
將每個view註冊為blueprint(Flask裡的概念,可以簡單理解為模組)
記錄所有view,以及所屬的category和url
AdminIndexView

複製代碼 代碼如下:


class AdminIndexView(name=None, category=None, endpoint=None, url=None, template='admin/index.html')


name: 預設'Home'
endpoint: 預設'admin'
url: 預設'/admin'
如果要封裝出自己的view,可以參照AdminIndexView的寫法:

class AdminIndexView(BaseView):  def __init__(self, name=None, category=None,         endpoint=None, url=None,         template='admin/index.html'):    super(AdminIndexView, self).__init__(name or babel.lazy_gettext('Home'),                       category,                       endpoint or 'admin',                       url or '/admin',                       'static')    self._template = template  @expose()  def index(self):    return self.render(self._template)base_template

base_template預設是/admin/base.html,是頁面的主要代碼(基於bootstrap),它裡面又import admin/layout.html;
layout是一些宏,主要用於展開、顯示menu;
在模板中使用一些變數來取出之前註冊view時儲存的資訊(如menu name和url等):
# admin/layout.html (部分)

{% macro menu() %} {% for item in admin_view.admin.menu() %}  {% if item.is_category() %}   {% set children = item.get_children() %}   {% if children %}    {% if item.is_active(admin_view) %}
  • {% else %}
  • {% endif %} {{ item.name }}
      {% for child in children %} {% if child.is_active(admin_view) %}
    • {% else %}
    • {% endif %} {{ child.name }}
    • {% endfor %}
  • {% endif %} {% else %} {% if item.is_accessible() and item.is_visible() %} {% if item.is_active(admin_view) %}
  • {% else %}
  • {% endif %} {{ item.name }}
  • {% endif %} {% endif %} {% endfor %}{% endmacro %}

    example/file
    這個範例能協助我們快速搭建起檔案管理介面,但我們的重點是學習使用ActionsMixin模組
    file.py:

    import osimport os.path as opfrom flask import Flaskfrom flask.ext import adminfrom flask.ext.admin.contrib import fileadmin# Create flask appapp = Flask(__name__, template_folder='templates', static_folder='files')# Create dummy secrey key so we can use flashapp.config['SECRET_KEY'] = '123456790'# Flask views@app.route('/')def index():  return 'Click me to get to Admin!'if __name__ == '__main__':  # Create directory  path = op.join(op.dirname(__file__), 'files')  try:    os.mkdir(path)  except OSError:    pass  # Create admin interface  admin = admin.Admin(app)  admin.add_view(fileadmin.FileAdmin(path, '/files/', name='Files'))  # Start app  app.run(debug=True)

    FileAdmin是已經寫好的的一個view,直接用即可:

    複製代碼 代碼如下:


    class FileAdmin(base_path, base_url, name=None, category=None, endpoint=None, url=None, verify_path=True)


    base_path: 檔案存放的相對路徑
    base_url: 檔案目錄的URL
    FileAdmin中和ActionsMixin相關代碼如下:
    class FileAdmin(BaseView, ActionsMixin):

      def __init__(self, base_path, base_url,         name=None, category=None, endpoint=None, url=None,         verify_path=True):    self.init_actions()@expose('/action/', methods=('POST',))def action_view(self):  return self.handle_action()# Actions@action('delete',    lazy_gettext('Delete'),    lazy_gettext('Are you sure you want to delete these files?'))def action_delete(self, items):  if not self.can_delete:    flash(gettext('File deletion is disabled.'), 'error')    return  for path in items:    base_path, full_path, path = self._normalize_path(path)    if self.is_accessible_path(path):      try:        os.remove(full_path)        flash(gettext('File "%(name)s" was successfully deleted.', name=path))      except Exception as ex:        flash(gettext('Failed to delete file: %(name)s', name=ex), 'error')@action('edit', lazy_gettext('Edit'))def action_edit(self, items):  return redirect(url_for('.edit', path=items))@action()用於wrap跟在後面的函數,這裡的作用就是把參數儲存起來:def action(name, text, confirmation=None)  def wrap(f):    f._action = (name, text, confirmation)    return f  return wrap

    name: action name
    text: 可用於按鈕名稱
    confirmation: 彈框確認資訊
    init_actions()把所有action的資訊儲存到ActionsMixin裡面:

    # 調試資訊_actions = [('delete', lu'Delete'), ('edit', lu'Edit')]_actions_data = {'edit': (>, lu'Edit', None), 'delete': (>, lu'Delete', lu'Are you sure you want to delete these files?')}

    action_view()用於處理POST給/action/的請求,然後調用handle_action(),它再調用不同的action處理,最後返回當前頁面:

    # 省略無關代碼def handle_action(self, return_view=None):  action = request.form.get('action')  ids = request.form.getlist('rowid')  handler = self._actions_data.get(action)  if handler and self.is_action_allowed(action):    response = handler[0](ids)    if response is not None:      return response  if not return_view:    url = url_for('.' + self._default_view)  else:    url = url_for('.' + return_view)  return redirect(url)

    ids是一個檔案清單,作為參數傳給action處理函數(參數items):

    # 調試資訊ids: [u'1.png', u'2.png']

    再分析頁面代碼,Files頁面對應檔案為admin/file/list.html,重點看With selected下拉式功能表相關代碼:
    {% import 'admin/actions.html' as actionslib with context %}

    {% if actions %}      {{ actionslib.dropdown(actions, 'dropdown-toggle btn btn-large') }}  {% endif %}{% block actions %}  {{ actionslib.form(actions, url_for('.action_view')) }}{% endblock %}{% block tail %}  {{ actionslib.script(_gettext('Please select at least one file.'),           actions,           actions_confirmation) }}{% endblock %}

    上面用到的三個宏在actions.html:

    {% macro dropdown(actions, btn_class='dropdown-toggle') -%}  {{ _gettext('With selected') }}  
     
      {% for p in actions %}
    • {{ _gettext(p[1]) }}
    • {% endfor %}
    {% endmacro %}{% macro form(actions, url) %} {% if actions %} {% endif %}{% endmacro %}{% macro script(message, actions, actions_confirmation) %} {% if actions %} {% endif %}{% endmacro %}

    最終產生的頁面(部分):

          With selected        
     
    • Delete
    • Edit

    選擇菜單後的處理方法在actions.js:

    var AdminModelActions = function(actionErrorMessage, actionConfirmations) {  // Actions helpers. TODO: Move to separate file  this.execute = function(name) {    var selected = $('input.action-checkbox:checked').size();    if (selected === 0) {      alert(actionErrorMessage);      return false;    }    var msg = actionConfirmations[name];    if (!!msg)      if (!confirm(msg))        return false;    // Update hidden form and submit it    var form = $('#action_form');    $('#action', form).val(name);    $('input.action-checkbox', form).remove();    $('input.action-checkbox:checked').each(function() {      form.append($(this).clone());    });    form.submit();    return false;  };  $(function() {    $('.action-rowtoggle').change(function() {      $('input.action-checkbox').attr('checked', this.checked);    });  });};

    對比一下修改前後的表單:

    # 初始化# 'Delete'選中的三個檔案# 'Edit'選中的一個檔案

    總結一下,當我們點擊下拉式功能表中的功能表項目(Delete,Edit),本地JavaScript代碼會彈出確認框(假設有確認資訊),然後提交一個表單給/admin/fileadmin/action/,請求處理函數action_view()根據表單類型再調用不同的action處理函數,最後返回一個頁面。

    Flask-Admin欄位(列)格式化
    在某些情況下,我們需要對模型的某個屬性進行格式化。比如,預設情況下,日期時間顯示出來會比較長,這時可能需要只顯示月和日,這時候,列格式化就派上用場了。

    比如,如果你要顯示雙倍的價格,你可以這樣做:

    class MyModelView(BaseModelView):  column_formatters = dict(price=lambda v, c, m, p: m.price*2)

    或者在Jinja2模板中使用宏:

    from flask.ext.admin.model.template import macroclass MyModelView(BaseModelView):  column_formatters = dict(price=macro('render_price'))# in template{% macro render_price(model, column) %}  {{ model.price * 2 }}{% endmacro %}

    回呼函數模型:

    def formatter(view, context, model, name):  # `view` is current administrative view  # `context` is instance of jinja2.runtime.Context  # `model` is model instance  # `name` is property name  pass

    正好和上面的v, c, m, p相對應。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.