flask-admin 模板加载逻辑整理

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">&nbsp;</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框架及其组件库,还得继续学习!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值