国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Python > 正文

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

2019-11-25 16:44:21
字體:
來源:轉載
供稿:網友

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 '<a href="/admin/">Click me to get to Admin!</a>'# 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!<br/>  <a href="{{ url_for('.test') }}">Click me to go to test view</a>{% 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) %}<li class="active dropdown">{% else %}<li class="dropdown">{% endif %}     <a class="dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">{{ item.name }}<b class="caret"></b></a>     <ul class="dropdown-menu">      {% for child in children %}       {% if child.is_active(admin_view) %}<li class="active">{% else %}<li>{% endif %}        <a href="{{ child.get_url() }}">{{ child.name }}</a>       </li>      {% endfor %}     </ul>    </li>   {% endif %}  {% else %}   {% if item.is_accessible() and item.is_visible() %}    {% if item.is_active(admin_view) %}<li class="active">{% else %}<li>{% endif %}     <a href="{{ item.get_url() }}">{{ item.name }}</a>    </li>   {% 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 '<a href="/admin/">Click me to get to Admin!</a>'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': (<bound method FileAdmin.action_edit of <flask_admin.contrib.fileadmin.FileAdmin object at 0x1aafc50>>, lu'Edit', None), 'delete': (<bound method FileAdmin.action_delete of <flask_admin.contrib.fileadmin.FileAdmin object at 0x1aafc50>>, 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 %}  <div class="btn-group">    {{ actionslib.dropdown(actions, 'dropdown-toggle btn btn-large') }}  </div>{% 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') -%}  <a class="{{ btn_class }}" data-toggle="dropdown" href="javascript:void(0)">{{ _gettext('With selected') }}<b class="caret"></b></a>  <ul class="dropdown-menu">    {% for p in actions %}    <li>      <a href="javascript:void(0)" onclick="return modelActions.execute('{{ p[0] }}');">{{ _gettext(p[1]) }}</a>    </li>    {% endfor %}  </ul>{% endmacro %}{% macro form(actions, url) %}  {% if actions %}  <form id="action_form" action="{{ url }}" method="POST" style="display: none">    {% if csrf_token %}    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>    {% endif %}    <input type="hidden" id="action" name="action" />  </form>  {% endif %}{% endmacro %}{% macro script(message, actions, actions_confirmation) %}  {% if actions %}  <script src="{{ admin_static.url(filename='admin/js/actions.js') }}"></script>  <script language="javascript">    var modelActions = new AdminModelActions({{ message|tojson|safe }}, {{ actions_confirmation|tojson|safe }});  </script>  {% endif %}{% endmacro %}

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

<div class="btn-group">  <a class="dropdown-toggle btn btn-large" href="javascript:void(0)" data-toggle="dropdown">    With selected    <b class="caret"></b>  </a>  <ul class="dropdown-menu">    <li>      <a onclick="return modelActions.execute('delete');" href="javascript:void(0)">Delete</a>    </li>    <li>      <a onclick="return modelActions.execute('edit');" href="javascript:void(0)">Edit</a>    </li>  </ul></div><form id="action_form" action="/admin/fileadmin/action/" method="POST" style="display: none">  <input type="hidden" id="action" name="action" /></form><script src="/admin/static/admin/js/actions.js"></script><script language="javascript">  var modelActions = new AdminModelActions("Please select at least one file.", {"delete": "Are you sure you want to delete these files?"});</script>

選擇菜單后的處理方法在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);    });  });};

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

# 初始化<form id="action_form" style="display: none" method="POST" action="/admin/fileadmin/action/">  <input id="action" type="hidden" name="action"></form># 'Delete'選中的三個文件<form id="action_form" style="display: none" method="POST" action="/admin/fileadmin/action/">  <input id="action" type="hidden" name="action" value="delete">  <input class="action-checkbox" type="checkbox" value="1.png" name="rowid">  <input class="action-checkbox" type="checkbox" value="2.png" name="rowid">  <input class="action-checkbox" type="checkbox" value="3.png" name="rowid"></form># 'Edit'選中的一個文件<form id="action_form" style="display: none" method="POST" action="/admin/fileadmin/action/">  <input id="action" type="hidden" name="action" value="edit">  <input class="action-checkbox" type="checkbox" value="1.png" name="rowid"></form>

總結一下,當我們點擊下拉菜單中的菜單項(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相對應。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 保定市| 萨迦县| 岫岩| 清水县| 信宜市| 威海市| 福安市| 大洼县| 岳普湖县| 志丹县| 莱州市| 梁河县| 喀什市| 吐鲁番市| 卢龙县| 隆化县| 右玉县| 华宁县| 淄博市| 潜江市| 赣州市| 高淳县| 玉屏| 新巴尔虎左旗| 天柱县| 浑源县| 池州市| 武隆县| 石阡县| 津南区| 容城县| 澜沧| 宁强县| 江陵县| 福鼎市| 兴业县| 阜南县| 思南县| 泰宁县| 泾源县| 抚顺市|