flask-admin是后台管理系统
项目中使用比较多的是给model加默认视图,具体做法是:
1,项目目录
项目目录是这样的:
app/model目录下建 model文件
from app import db
class StuTest(db.Model):
__tablename__="stu_test"
id=db.Column(db.Integer,primary_key=True,nullable=False,autoincrement=True)
stu_name=db.Column(db.VARCHAR(20),default=None)
stu_age=db.Column(db.Integer,default=None)
stu_class=db.Column(db.Integer,default=None)
app/views中创建view.py文件和viewManager.py文件
from flask_admin.contrib.sqla import ModelView
class TestViewManager(ModelView):
page_size = 20
can_create=True
can_edit = True
can_delete = True
modelView继承了BaseModelView
2,BaseModelView
BaseModelView中定义了默认视图,源码如下:
# Views
@expose('/')
def index_view(self):
"""
List view
"""
print("index_view")
if self.can_delete:
delete_form = self.delete_form()
else:
delete_form = None
# Grab parameters from URL
view_args = self._get_list_extra_args()
# Map column index to column name
sort_column = self._get_column_by_idx(view_args.sort)
if sort_column is not None:
sort_column = sort_column[0]
# Get page size
page_size = view_args.page_size or self.page_size
# Get count and data
count, data = self.get_list(view_args.page, sort_column, view_args.sort_desc,
view_args.search, view_args.filters, page_size=page_size)
list_forms = {}
if self.column_editable_list:
for row in data:
list_forms[self.get_pk_value(row)] = self.list_form(obj=row)
# Calculate number of pages
if count is not None and page_size:
num_pages = int(ceil(count / float(page_size)))
elif not page_size:
num_pages = 0 # hide pager for unlimited page_size
else:
num_pages = None # use simple pager
# Various URL generation helpers
def pager_url(p):
# Do not add page number if it is first page
if p == 0:
p = None
return self._get_list_url(view_args.clone(page=p))
def sort_url(column, invert=False, desc=None):
if not desc and invert and not view_args.sort_desc:
desc = 1
return self._get_list_url(view_args.clone(sort=column, sort_desc=desc))
def page_size_url(s):
if not s:
s = self.page_size
return self._get_list_url(view_args.clone(page_size=s))
# Actions
actions, actions_confirmation = self.get_actions_list()
if actions:
action_form = self.action_form()
else:
action_form = None
clear_search_url = self._get_list_url(view_args.clone(page=0,
sort=view_args.sort,
sort_desc=view_args.sort_desc,
search=None,
filters=None))
print(" self.list_template:", self.list_template)
return self.render(
self.list_template,
data=data,
list_forms=list_forms,
delete_form=delete_form,
action_form=action_form,
# List
list_columns=self._list_columns,
sortable_columns=self._sortable_columns,
editable_columns=self.column_editable_list,
list_row_actions=self.get_list_row_actions(),
# Pagination
count=count,
pager_url=pager_url,
num_pages=num_pages,
can_set_page_size=self.can_set_page_size,
page_size_url=page_size_url,
page=view_args.page,
page_size=page_size,
default_page_size=self.page_size,
# Sorting
sort_column=view_args.sort,
sort_desc=view_args.sort_desc,
sort_url=sort_url,
# Search
search_supported=self._search_supported,
clear_search_url=clear_search_url,
search=view_args.search,
search_placeholder=self.search_placeholder(),
# Filters
filters=self._filters,
filter_groups=self._get_filter_groups(),
active_filters=view_args.filters,
filter_args=self._get_filters(view_args.filters),
# Actions
actions=actions,
actions_confirmation=actions_confirmation,
# Misc
enumerate=enumerate,
get_pk_value=self.get_pk_value,
get_value=self.get_list_value,
return_url=self._get_list_url(view_args),
# Extras
extra_args=view_args.extra_args,
)
最后返回的模板:self.list_template是指 admin/model/list.html
# Templates
list_template = 'admin/model/list.html'
"""Default list view template"""
如果想改变展示的列表样式,可以在TestViewManager中重新指定这个值到自己的样式文件
admin/model/list.html 文件样式代码是这样的:
{% extends 'admin/master.html' %}
{% import 'admin/lib.html' as lib with context %}
{% import 'admin/static.html' as admin_static with context%}
{% import 'admin/model/layout.html' as model_layout with context %}
{% import 'admin/actions.html' as actionlib with context %}
{% import 'admin/model/row_actions.html' as row_actions with context %}
{% block head %}
{{ super() }}
{{ lib.form_css() }}
{% endblock %}
{% block body %}
{% block model_menu_bar %}
<ul class="nav nav-tabs actions-nav">
<li class="active">
<a href="javascript:void(0)">{{ _gettext('List') }}{% if count %} ({{ count }}){% endif %}</a>
</li>
{% if admin_view.can_create %}
<li>
{%- if admin_view.create_modal -%}
{{ lib.add_modal_button(url=get_url('.create_view', url=return_url, modal=True), title=_gettext('Create New Record'), content=_gettext('Create')) }}
{% else %}
<a href="{{ get_url('.create_view', url=return_url) }}" title="{{ _gettext('Create New Record') }}">{{ _gettext('Create') }}</a>
{%- endif -%}
</li>
{% endif %}
{% if admin_view.can_export %}
{{ model_layout.export_options() }}
{% endif %}
{% block model_menu_bar_before_filters %}{% endblock %}
{% if filters %}
<li class="dropdown">
{{ model_layout.filter_options() }}
</li>
{% endif %}
{% if can_set_page_size %}
<li class="dropdown">
{{ model_layout.page_size_form(page_size_url) }}
</li>
{% endif %}
{% if actions %}
<li class="dropdown">
{{ actionlib.dropdown(actions) }}
</li>
{% endif %}
{% if search_supported %}
<li>
{{ model_layout.search_form() }}
</li>
{% endif %}
{% block model_menu_bar_after_filters %}{% endblock %}
</ul>
{% endblock %}
{% if filters %}
{{ model_layout.filter_form() }}
<div class="clearfix"></div>
{% endif %}
{% block model_list_table %}
<div class="table-responsive">
<table class="table table-striped table-bordered table-hover model-list">
<thead>
<tr>
{% block list_header scoped %}
{% if actions %}
<th class="list-checkbox-column">
<input type="checkbox" name="rowtoggle" class="action-rowtoggle" title="{{ _gettext('Select all records') }}" />
</th>
{% endif %}
{% block list_row_actions_header %}
{% if admin_view.column_display_actions %}
<th class="col-md-1"> </th>
{% endif %}
{% endblock %}
{% for c, name in list_columns %}
{% set column = loop.index0 %}
<th class="column-header col-{{c}}">
{% if admin_view.is_sortable(c) %}
{% if sort_column == column %}
<a href="{{ sort_url(column, True) }}" title="{{ _gettext('Sort by %(name)s', name=name) }}">
{{ name }}
{% if sort_desc %}
<span class="fa fa-chevron-up glyphicon glyphicon-chevron-up"></span>
{% else %}
<span class="fa fa-chevron-down glyphicon glyphicon-chevron-down"></span>
{% endif %}
</a>
{% else %}
<a href="{{ sort_url(column) }}" title="{{ _gettext('Sort by %(name)s', name=name) }}">{{ name }}</a>
{% endif %}
{% else %}
{{ name }}
{% endif %}
{% if admin_view.column_descriptions.get(c) %}
<a class="fa fa-question-circle glyphicon glyphicon-question-sign"
title="{{ admin_view.column_descriptions[c] }}"
href="javascript:void(0)" data-role="tooltip"
></a>
{% endif %}
</th>
{% endfor %}
{% endblock %}
</tr>
</thead>
{% for row in data %}
<tr>
{% block list_row scoped %}
{% if actions %}
<td>
<input type="checkbox" name="rowid" class="action-checkbox" value="{{ get_pk_value(row) }}" title="{{ _gettext('Select record') }}" />
</td>
{% endif %}
{% block list_row_actions_column scoped %}
{% if admin_view.column_display_actions %}
<td class="list-buttons-column">
{% block list_row_actions scoped %}
{% for action in list_row_actions %}
{{ action.render_ctx(get_pk_value(row), row) }}
{% endfor %}
{% endblock %}
</td>
{%- endif -%}
{% endblock %}
{% for c, name in list_columns %}
<td class="col-{{c}}">
{% if admin_view.is_editable(c) %}
{% set form = list_forms[get_pk_value(row)] %}
{% if form.csrf_token %}
{{ form[c](pk=get_pk_value(row), display_value=get_value(row, c), csrf=form.csrf_token._value()) }}
{% elif csrf_token %}
{{ form[c](pk=get_pk_value(row), display_value=get_value(row, c), csrf=csrf_token()) }}
{% else %}
{{ form[c](pk=get_pk_value(row), display_value=get_value(row, c)) }}
{% endif %}
{% else %}
{{ get_value(row, c) }}
{% endif %}
</td>
{% endfor %}
{% endblock %}
</tr>
{% else %}
<tr>
<td colspan="999">
{% block empty_list_message %}
<div class="text-center">
{{ admin_view.get_empty_list_message() }}
</div>
{% endblock %}
</td>
</tr>
{% endfor %}
</table>
</div>
{% block list_pager %}
{% if num_pages is not none %}
{{ lib.pager(page, num_pages, pager_url) }}
{% else %}
{{ lib.simple_pager(page, data|length == page_size, pager_url) }}
{% endif %}
{% endblock %}
{% endblock %}
{% block actions %}
{{ actionlib.form(actions, get_url('.action_view')) }}
{% endblock %}
{%- if admin_view.edit_modal or admin_view.create_modal or admin_view.details_modal -%}
{{ lib.add_modal_window() }}
{%- endif -%}
{% endblock %}
{% block tail %}
{{ super() }}
{% if filter_groups %}
<div id="filter-groups-data" style="display:none;">{{ filter_groups|tojson|safe }}</div>
<div id="active-filters-data" style="display:none;">{{ active_filters|tojson|safe }}</div>
{% endif %}
{{ lib.form_js() }}
<script src="{{ admin_static.url(filename='admin/js/filters.js', v='1.0.0') }}"></script>
{{ actionlib.script(_gettext('Please select at least one record.'),
actions,
actions_confirmation) }}
{% endblock %}
可以看出,list文件是继承了很多样式模板,然后重写 block head,block body等block,其中block body是展示的具体内容。具体展示如下:
如果想如上图一样改变默认的导航样式为自己的,保留body部分展示的数据样式。可以重写base.html 。因为:
list.html 继承了 admin/master.html
{% extends 'admin/master.html' %}
而master.html内容是
{% extends admin_base_template %}
admin_base_template值呢是在flask_admin/base.py值定义了,是
kwargs['admin_base_template'] = self.admin.base_template
是在实例化Admin是的时候,传入的base_template值(同样是在flask_admin/base.py中定义的),不传的话默认的是 admin/base.html
self.base_template = base_template or 'admin/base.html'
我的项目中,自定义了base_template值为“lib/base.html”
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
db=SQLAlchemy()
def create_app(SysConfig):
app=Flask(__name__,static_folder="static",template_folder="templates")
app.debug=True
from app.views import tool
app.register_blueprint(tool)
from app.views.test import test
app.register_blueprint(test)
db.init_app(app)
app.config.from_object(SysConfig)
admin=Admin(app,template_mode='bootstrap3',base_template='lib/base.html')
from app.views.test.testViewManager import TestViewManager
# 必须放于db实例化之后,不然会导致导入失败
from app.model.testModel import StuTest
admin.add_view(TestViewManager(StuTest,db.session))
return app
现在算是理清楚了flask-admin中模板的加载逻辑,就好自定义了,实现与自己的项目模板相融合。
3,重写tail部分
可以在我们自定义的lib/base.html中,block tail部分重写,因为admin/model/list.html中的tail部分是继承了base.html的
{% block tail %}
{{ super() }}
{% if filter_groups %}
<div id="filter-groups-data" style="display:none;">{{ filter_groups|tojson|safe }}</div>
<div id="active-filters-data" style="display:none;">{{ active_filters|tojson|safe }}</div>
{% endif %}
{{ lib.form_js() }}
<script src="{{ admin_static.url(filename='admin/js/filters.js', v='1.0.0') }}"></script>
{{ actionlib.script(_gettext('Please select at least one record.'),
actions,
actions_confirmation) }}
{% endblock %}
具体页面中,加载model默认视图的入口可以使用url_for
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% block page_content %}
<div class="jumbotron masthead" id="main_index" style="display: block">
<div class="container">
<h1 style="text-align:left ">Welcome </h1>
<h2 style="text-align:left ">welcome</h2>
<h3 style="text-align:left ">all rights reserved</h3>
</div>
<div>
<a href="{{url_for('stutest.index_view')}}">点一点</a>
</div>
<div>
{{hello}}
</div>
</div>
{% endblock %}
</body>
</html>
4,重写的base.html中引入的admin/layout.html 引入的是哪一个
若是自己重写了base.html中仍然引用了admin/layout.html,引入的是flask-admin lib包中的还是自己templates/admin/layout.html文件呢?
{% import 'static.html' as admin_static with context %}
{% import 'admin/layout.html' as layout with context -%}
实验证明,是有优先查找本地项目templates/admin中是否有layout.html文件,有的话使用,没有的话,就去找lask-admin lib包中layout.html
都存在的话,使用的是本地templates/admin中的layout.html
一番整理,对falsk-admin的认识稍加深刻,不过要想精通flask框架及其组件库,还得继续学习!