0

0

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

php中文网

php中文网

发布时间:2016-06-16 08:47:50

|

4712人浏览过

|

来源于php中文网

原创

Flask-Admin是Flask框架的一个扩展,用它能够快速创建Web管理界面,它实现了比如用户、文件的增删改查等常用的管理功能;如果对它的默认界面不喜欢,可以通过修改模板文件来定制;
Flask-Admin把每一个菜单(超链接)看作一个view,注册后才能显示出来,view本身也有属性来控制其是否可见;因此,利用这个机制可以定制自己的模块化界面,比如让不同权限的用户登录后看到不一样的菜单;

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

example/simple
这是最简单的一个样例,可以帮助我们快速、直观的了解基本概念,学会定制Flask-Admin的界面
simple.py:

from flask import Flask

from flask.ext import admin


# Create custom admin view
class 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 app
app = 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 interface
admin = 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 os
import os.path as op

from flask import Flask

from flask.ext import admin
from flask.ext.admin.contrib import fileadmin

# Create flask app
app = Flask(__name__, template_folder='templates', static_folder='files')

# Create dummy secrey key so we can use flash
app.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):

Inworld.ai
Inworld.ai

InWorldAI是一个AI角色开发平台,开发者可以创建具有自然语言、上下文意识和多模态的AI角色,并可以继承到游戏和实时媒体中

下载
# 调试信息
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 macro

class 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相对应。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

2

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

24

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

80

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

187

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

339

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

116

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

180

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

31

2026.03.03

Golang 测试体系与代码质量保障:工程级可靠性建设
Golang 测试体系与代码质量保障:工程级可靠性建设

Go语言测试体系与代码质量保障聚焦于构建工程级可靠性系统。本专题深入解析Go的测试工具链(如go test)、单元测试、集成测试及端到端测试实践,结合代码覆盖率分析、静态代码扫描(如go vet)和动态分析工具,建立全链路质量监控机制。通过自动化测试框架、持续集成(CI)流水线配置及代码审查规范,实现测试用例管理、缺陷追踪与质量门禁控制,确保代码健壮性与可维护性,为高可靠性工程系统提供质量保障。

81

2026.02.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Python Web框架Flask进阶视频教程
Python Web框架Flask进阶视频教程

共12课时 | 2.9万人学习

Python Web框架Flask入门视频教程
Python Web框架Flask入门视频教程

共7课时 | 2.6万人学习

Flask实战视频教程
Flask实战视频教程

共9课时 | 2.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号