Flask Bootstrap

Flask Bootstrap

Flask Bootstrap

Bootstrap框架

Bootstrap中文主站:https://www.bootcss.com/

Bootstrap4:https://getbootstrap.com/

Bootstrap是基于HTMLCSSJAVASCRIPT前端框架.

  • 移动设备优先
  • 主流浏览器支持
  • 响应式设计

Flask Bootstrap

对于Flask来说,前期有一个相关扩展Flask-Bootstrap,不过作者很长时间没有更新过.于是有人基于它做了另外的一个扩展Bootstrap-Flask.

Bootstrap-Flask支持Bootstrap4,并且基于Jinja2模板引擎的宏做了相关扩展.

参考代码:https://github.com/ningwenyan/demo_code/tree/master/flask_demo_code/T27

安装

$ pip install bootstrap-flask

如果之前安装过flask-bootstrap,需要先卸载,再安装bootstrap-flask

$ pip uninstall flask-bootstrap bootstrap-flask
$ pip install bootstrap-flask

官方文档:https://bootstrap-flask.readthedocs.io/en/stable/

初始化

初始化很简单

from flask_bootstrap import Bootstrap
from flask import Flask

app = Flask(__name__)

bootstrap = Bootstrap(app)

或者使用init_app的方式初始化

from flask_bootstrap import Bootstrap
from flask import Flask

app = Flask(__name__)

bootstrap = Bootstrap()
bootstrap.init_app(app)

可以浏览一下源码

class Bootstrap(object):
  def __init__(self, app=None):
      if app is not None:
          self.init_app(app)

  def init_app(self, app):

      if not hasattr(app, 'extensions'):
          app.extensions = {}
      app.extensions['bootstrap'] = self

      blueprint = Blueprint('bootstrap', __name__, template_folder='templates',
                            static_folder='static', static_url_path='/bootstrap' + app.static_url_path)
      app.register_blueprint(blueprint)

      app.jinja_env.globals['bootstrap'] = self
      app.jinja_env.globals['bootstrap_is_hidden_field'] = is_hidden_field_filter
      app.jinja_env.globals['get_table_titles'] = get_table_titles
      app.jinja_env.add_extension('jinja2.ext.do')
      # default settings
      app.config.setdefault('BOOTSTRAP_SERVE_LOCAL'False)
      app.config.setdefault('BOOTSTRAP_BTN_STYLE''primary')
      app.config.setdefault('BOOTSTRAP_BTN_SIZE''md')
      app.config.setdefault('BOOTSTRAP_BOOTSWATCH_THEME'None)
      app.config.setdefault('BOOTSTRAP_ICON_SIZE''1em')
      app.config.setdefault('BOOTSTRAP_ICON_COLOR'None)
      app.config.setdefault('BOOTSTRAP_MSG_CATEGORY''primary')

  @staticmethod
  def load_css(version=VERSION_BOOTSTRAP):
      """Load Bootstrap's css resources with given version.

      .. versionadded:: 0.1.0

      :param version: The version of Bootstrap.
      """

      css_filename = 'bootstrap.min.css'
      serve_local = current_app.config['BOOTSTRAP_SERVE_LOCAL']
      bootswatch_theme = current_app.config['BOOTSTRAP_BOOTSWATCH_THEME']

      if not bootswatch_theme:
          base_path = 'css/'
      else:
          base_path = 'css/swatch/%s/' % bootswatch_theme.lower()

      if serve_local:
          css = '<link rel="stylesheet" href="%s" type="text/css">' % 
              url_for('bootstrap.static', filename=base_path + css_filename)
      else:
          if not bootswatch_theme:
              css = '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@%s/dist/css/%s"' 
                  ' type="text/css">' % (version, css_filename)
          else:
              css = '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@%s/dist/%s/%s"' 
                  ' type="text/css"' % (version, bootswatch_theme.lower(), css_filename)
      return Markup(css)

  @staticmethod
  def load_js(version=VERSION_BOOTSTRAP, jquery_version=VERSION_JQUERY,
              popper_version=VERSION_POPPER, with_jquery=True, with_popper=True)
:

      """Load Bootstrap and related library's js resources with given version.

      .. versionadded:: 0.1.0

      :param version: The version of Bootstrap.
      :param jquery_version: The version of jQuery.
      :param popper_version: The version of Popper.js.
      :param with_jquery: Include jQuery or not.
      :param with_popper: Include Popper.js or not.
      """

      js_filename = 'bootstrap.min.js'
      jquery_filename = 'jquery.min.js'
      popper_filename = 'popper.min.js'

      serve_local = current_app.config['BOOTSTRAP_SERVE_LOCAL']

      if serve_local:
          js = '<script src="%s"></script>' % url_for('bootstrap.static', filename='js/' + js_filename)
      else:
          js = '<script src="https://cdn.jsdelivr.net/npm/bootstrap@%s/dist/js/%s">' 
               '</script>' % (version, js_filename)

      if with_jquery:
          if serve_local:
              jquery = '<script src="%s"></script>' % url_for('bootstrap.static', filename=jquery_filename)
          else:
              jquery = '<script src="https://cdn.jsdelivr.net/npm/jquery@%s/dist/%s">' 
               '</script>' % (jquery_version, jquery_filename)
      else:
          jquery = ''

      if with_popper:
          if serve_local:
              popper = '<script src="%s"></script>' % url_for('bootstrap.static', filename=popper_filename)
          else:
              popper = '<script src="https://cdn.jsdelivr.net/npm/popper.js@%s/dist/umd/%s">' 
                   '</script>' % (popper_version, popper_filename)
      else:
          popper = ''
      return Markup('''%s
  %s
  %s'''
 % (jquery, popper, js))

很简单的初始化后,会把bootstrap添加到Jinja2的虚拟环境中,它默认使用的是bootstrap4,并且提供了2个基本的静态方法

  • bootstrap.load_css():默认是bootstrap4,使用的是CDN分发CSS样式
  • bootstrap.load_js():默认是bootstrap4,使用的是CDN分发js样式

当然,如果你不想使用bootstrap4,也可以自定义版本,基本配置config

配置 默认 说明
BOOTSTRAP_SERVE_LOCAL False False:自訂版本,True:系統默认版本
BOOTSTRAP_BTN_STYLE primary button的样式
BOOTSTRAP_BTN_SIZE md button大小
BOOTSTRAP_ICON_SIZE lem 图标icon大小
BOOTSTRAP_ICON_COLOR None 图标颜色
BOOTSTRAP_BOOTSWATCH_THEME None bootswatch主题,参考
BOOTSTRAP_MSG_CATEGORY primary flask flash样式

bootstrap-flask不提供基础模板base.html,需要自己创建.load_css, load_js都可以放置在base.html中,类似如下

<head>
....
{{ bootstrap.load_css() }}
</head>
<body>
...
{{ bootstrap.load_js() }}
</body>

启用宏

Macro Templates Path Description
render_field() bootstrap/form.html 渲染一个WTForm表单字段
render_form() bootstrap/form.html 渲染一个WTForm表单
render_form_row() bootstrap/form.html Render a row of a grid form
render_hidden_errors() bootstrap/form.html Render error messages for hidden form field
render_pager() bootstrap/pagination.html Render a basic Flask-SQLAlchemy pagniantion 分页标签
render_pagination() bootstrap/pagination.html Render a standard Flask-SQLAlchemy pagination 分页标签
render_nav_item() bootstrap/nav.html Render a navigation item
render_breadcrumb_item() bootstrap/nav.html Render a breadcrumb item
render_static() bootstrap/utils.html Render a resource reference code (i.e. <link>, <script>)
render_messages() bootstrap/utils.html Render flashed messages send by flash() function
render_icon() bootstrap/utils.html Render a Bootstrap icon
render_table() bootstrap/table.html Render a table with given data

有关它的详细解释,参考官方文档:https://bootstrap-flask.readthedocs.io/en/stable/macros.html#render-pagination

以下选取几个常用的进行渲染.

render_nav_item

渲染导航栏中的URL链接.

render_nav_item(endpoint, text, badge='', use_li=False, **kwargs)

查看它的定义能更容易理解

{% macro render_nav_item(endpoint, text, badge='', use_li=False) %}
{% if use_li %}<li class="nav-item">{% endif %}
<a class="{% if not use_li %}nav-item{% endif %} nav-link {% if request.endpoint and request.endpoint == endpoint %}active{% endif %}"
href="{{%20url_for(endpoint,%20**kwargs)%20}}">
{{ text }} {% if badge %}<span class="badge badge-light">{{ badge }}</span>{% endif %}
</a>
{% if use_li %}</li>{% endif %}
{% endmacro %}

可以到bootstrap4上找一个到导航示例https://getbootstrap.com/docs/4.5/components/navbar/,进行改造

base.html

{# 引入 分页导航 #}
{% from 'bootstrap/nav.html' import render_nav_item %}
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>
       {% block title %}

       {% endblock %}
   </title>
   {# 加载bootstrap 资源    #}
   {{ bootstrap.load_css() }}
   {{ bootstrap.load_js() }}
</head>
<body>
   {#导航栏#}
   <nav class="navbar navbar-expand-lg navbar-light bg-light">
       <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
               <span class="navbar-toggler-icon"></span>
           </button>

       <div class="collapse navbar-collapse" id="navbarSupportedContent">
           <ul class="navbar-nav mr-auto">
               {# 应用宏#}
               {{ render_nav_item('index', '首页', use_li=True) }}
               {{ render_nav_item('index', '首页', use_li=True) }}
               {{ render_nav_item('index', '首页', use_li=True) }}
           </ul>
       </div>
   </nav>
   <main class="container">
   </main>
</body>
</html>

然后在index.html中继承模板,

{% extends 'base.html' %}
{% block title %}
首页
{% endblock %}

{% block content %}
<h1>这是首页</h1>
{% endblock %}

运行项目,可以查看结果.

render_messages

其实就是用来接受后端flash消息的,但是会内置一些样式在其中.

参数可以参考文档

使用如下

base.html

{# 引入 分页导航 #}
{% from 'bootstrap/nav.html' import render_nav_item %}
{# 引入messages解析 #}
{% from 'bootstrap/utils.html' import render_messages %}
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>
       {% block title %}

       {% endblock %}
   </title>
   {# 加载bootstrap 资源    #}
   {{ bootstrap.load_css() }}
   {{ bootstrap.load_js() }}
</head>
<body>
   {#导航栏#}
   <nav class="navbar navbar-expand-lg navbar-light bg-light">
       <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
               <span class="navbar-toggler-icon"></span>
           </button>

       <div class="collapse navbar-collapse" id="navbarSupportedContent">
           <ul class="navbar-nav mr-auto">
               {# 应用宏#}
               {{ render_nav_item('index', '首页', use_li=True) }}
              {{ render_nav_item('get_flash_messages', 'Flash messages', use_li=True) }}
               {{ render_nav_item('index', '首页', use_li=True) }}
           </ul>
       </div>
   </nav>
   <main class="container">
       {{ render_messages(container=False, dismissible=True, dismiss_animate=True) }}
       {% block content %}
       {% endblock %}
   </main>
</body>
</html>

设定一个视图函数

@app.route('/get_flash_messages/', methods=['GET', 'POST'])
def get_flash_messages():
   flash('A simple default alert—check it out!')
   flash('A simple primary alert—check it out!''primary')
   flash('A simple secondary alert—check it out!''secondary')
   flash('A simple success alert—check it out!''success')
   flash('A simple danger alert—check it out!''danger')
   flash('A simple warning alert—check it out!''warning')
   flash('A simple info alert—check it out!''info')
   flash('A simple light alert—check it out!''light')
   flash('A simple dark alert—check it out!''dark')
   flash(Markup(
       'A simple success alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.'),
         'success')
   return render_template('get_flash_messages.html')

运行页面,可以得到不同样式的提示框.(primary, seondary, success等等.)

render_form

它会渲染一个完整的flask_wtf/wtforms的表单,并且能够提示验证错误信息.同样的,它拥有很多的参数,不过一个基本的实例如下

  • 添加CSRF保护
csrf.init_app(app=app)
  • 表单验证
class GetForm(FlaskForm):
    username = StringField('用户名', validators=[Length(49'请输入正确长度的用户名')], render_kw={'placeholder':'username'})
    password = PasswordField('密码', validators=[Length(49'请输入正确长度的密码')], render_kw={'placeholder':'password'})
    submit  = SubmitField('登录')
  • 视图设计
@app.route('/get_form/', methods=['GET', 'POST'])
def get_form():
    form = GetForm()
    if form.validate_on_submit():
        return redirect(url_for('index'))
    return render_template('get_form.html', form=form)
  • 前端页面
{% extends 'base.html' %}
{% from 'bootstrap/form.html' import render_form %}

{% block title %}
Form
{% endblock %}

{% block content %}
{{ render_form(form) }}
{% endblock %}

在前端只需要渲染form即可.render_form会根据flask_wtf/wtforms的表单定义自己生成表单内容.(有关这一部分可以查看源码宏定义)

并且能处理错误信息.

render_field

渲染一个flask-wtf/wtformsField,并且能够处理错误信息.

  • 视图
@app.route('/get_form_filed', methods=['GET', 'POST'])
def get_form_field():
    form = GetFormField()
    if form.validate_on_submit():
        return redirect(url_for('index'))
    return render_template('get_form_field.html', form=form)

  • 约束
class GetFormField(FlaskForm):
    username = StringField('用户名', validators=[Length(49'请输入正确长度的用户名')], render_kw={'placeholder''username'})
    password = PasswordField('密码', validators=[Length(49'请输入正确长度的密码')], render_kw={'placeholder''password'})
    submit = SubmitField('登录')
  • 后端
{% extends 'base.html' %}
{% from 'bootstrap/form.html' import render_field %}

{% block title %}
Form Field
{% endblock %}

{% block content %}
<form action="" method="post">
{{ form.csrf_token }}
{{ render_field(form.username) }}
{{ render_field(form.password) }}
{{ render_field(form.submit) }}
</form>
{% endblock %}

注意添加csrf保护,它能自动弹出错误.

render_pager/render_pagination

分页查询,这涉及到flask-sqlalchemy的分页查询函数

db.session.query(User).filter_by().paginate(page=None, per_page=None,
   error_out=True, max_per_page=None)
  • page 查询的页数

  • per_page 每页的条数

  • max_per_page 每页最大条数,有值时,per_page 受它影响

  • error_out当值为 True 时,下列情况会报错

    • 当 page 为 1 时,找不到任何数据
    • page 小于 1,或者 per_page 为负数
    • page 或 per_page 不是整数

该方法返回一个分页对象 Pagination:调用 paginate() 方法,会返回一个 Pagination 对象,它封装了当前页的各种数据和方法

  • has_next 如果下一页存在,返回 True
  • has_prev 如果上一页存在,返回 True
  • items 当前页的数据列表
  • next_num 下一页的页码
  • page 当前页码
  • pages 总页数
  • per_page 每页的条数
  • prev_num 上一页的页码
  • query 用于创建此分页对象的无限查询对象。
  • total 总条数
  • iter_pages(left_edge=2, left_current=2, right_current=5, right_edge=2)迭代分页中的页码,四个参数,分别控制了省略号左右两侧各显示多少页码
  • next(error_out=False) 返回下一页的分页对象
  • prev(error_out=False) 返回上一页的分页对象
  • 前端渲染
{% macro render_pagination(pagination, endpoint) %}
<div class=pagination>
{%- for page in pagination.iter_pages() %}
{% if page %}
{% if page != pagination.page %}
<a href="{{%20url_for(endpoint,%20page=page)%20}}">{{ page }}</a>
{% else %}
<strong>{{ page }}</strong>
{% endif %}
{% else %}
<span class=ellipsis>…</span>
{% endif %}
{%- endfor %}
</div>
{% endmacro %}

理解以上之后,再观看源码

{% macro render_pager(pagination,
fragment='',
prev=('<span aria-hidden="true">&larr;</span> Previous')|safe,
next=('Next <span aria-hidden="true">&rarr;</span>')|safe,
align='') -%}
<nav aria-label="Page navigation">
<ul class="pagination {% if align == 'center' %}justify-content-center{% elif align == 'right' %}justify-content-end{% endif %}">
<li class="page-item {% if not pagination.has_prev %}disabled{% endif %}">
<a class="page-link"
href="{{%20url_for(request.endpoint,%20page=pagination.prev_num,%20**kwargs)%20+%20fragment%20if%20pagination.has_prev%20else%20'#'%20}}">
{{ prev }}
</a>
</li>
<li class="page-item {% if not pagination.has_next %}disabled{% endif %}">
<a class="page-link"
href="{{%20url_for(request.endpoint,%20page=pagination.next_num,%20**kwargs)%20+%20fragment%20if%20pagination.has_next%20else%20'#'%20}}">
{{ next }}
</a>
</li>
</ul>
</nav>
{%- endmacro %}

以上就很好理解了,它会生成一个类似http://127.0.0.1/?page=1的结构体,所以可以指定如下视图

@app.route('/get_pager/', methods=['GET', 'POST'])
def get_pager():
    db.drop_all()
    db.create_all()
    for i in range(100):
        user = User(name='{}'.format(str(i)), password='h{}'.format(str(i)))
        db.session.add(user)
    db.session.commit()
    page = request.args.get('page'1, type=int)
    pagination = User.query.paginate(page, 10)
    messages = pagination.items
    return render_template('get_pager.html', pagination=pagination, messages=messages)
  • requst.args.get('key', 'dafault', 'type')

  • page是必须的,对应后端模板

后端

{% extends 'base.html' %}
{% from 'bootstrap/pagination.html' import render_pager %}
{% from 'bootstrap/pagination.html' import render_pagination %}
{% block title %}
pagination
{% endblock %}

{% block content %}
{% for message in messages %}
Message:{{ message.id }}<br>
{% endfor %}

{{ render_pager(pagination) }}
{{ render_pagination(pagination) }}
{% endblock %}

运行可以得到如下:

Flask Bootstrap
10322

– END –


原文始发于微信公众号(Flask学习笔记):Flask Bootstrap

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/36395.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!