Flask 系列之 Pagination

  • 2020 年 1 月 16 日
  • 筆記

说明

  • 操作系统:Windows 10
  • Python 版本:3.7x
  • 虚拟环境管理器:virtualenv
  • 代码编辑器:VS Code

实验目标

实现当前登录用户的事务浏览、添加、删除 操作

实现

首先,在我们的 todolistforms.py 中添加事务添加对应的表单类 ThingForm,示例代码如下所示:

from flask_wtf import FlaskForm  from wtforms import StringField, SubmitField, TextAreaField, PasswordField  from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError  from models import User      class RegisterForm(FlaskForm):      username = StringField('用户名:', validators=[                             DataRequired(), Length(min=6, max=20)])      email = StringField('邮箱:', validators=[DataRequired(), Email()])      pwd = PasswordField('密码:', validators=[          DataRequired(), Length(min=8, max=120)])      confirm = PasswordField('确认密码:', validators=[                              DataRequired(), EqualTo('pwd')])      submit = SubmitField('提交')        def validate_username(self, username):          user = User.query.filter_by(name=username.data).first()          if user:              raise ValidationError("用户昵称已存在。")        def validate_email(self, email):          user = User.query.filter_by(email=email.data).first()          if user:              raise ValidationError('邮箱已存在.')      class LoginForm(FlaskForm):      username = StringField('用户名:', validators=[                             DataRequired(), Length(min=6, max=20)])      password = PasswordField('密码:', validators=[DataRequired()])      submit = SubmitField('登陆')        def validate_username(self, username):          user = User.query.filter_by(name=username.data)          if not user:              raise ValidationError('用户名不存在。')      class ThingForm(FlaskForm):      title = StringField('标题:', validators=[                          DataRequired(), Length(min=6, max=20)])      text = TextAreaField('内容:', validators=[DataRequired()])      submit = SubmitField('提交')

接着修改 todolistappviews.py ,添加当前用户事务的添加、删除,示例代码如下所示:

from flask import render_template, redirect, url_for, flash, request  from flask_login import login_user, login_required, current_user, logout_user  from app import app, db  from forms import ThingForm, RegisterForm, LoginForm  from models import User, Thing      @app.context_processor  def inject_user():      user = User.query.first()      return dict(user=user)      @app.route('/', methods=['GET', 'POST'])  @app.route('/index', methods=['GET', 'POST'])  def index():      form = ThingForm()      if not current_user.is_authenticated:          return redirect(url_for('login'))      if request.method == 'POST' and form.validate_on_submit():          user_id = current_user.id          title = form.title.data          text = form.text.data          thing = Thing(user_id=user_id, title=title, text=text)          db.session.add(thing)          db.session.commit()          flash('添加成功')      page = request.args.get('page', 1, type=int)      things = current_user.things.order_by(          Thing.add_date.desc()).paginate(page, 2, False)      print(things)      return render_template('index.html', title="首页", form=form, things=things)      @app.route('/login', methods=['POST', 'GET'])  def login():      form = LoginForm()      if form.validate_on_submit():          name = form.username.data          pwd = form.password.data          user = User.query.filter_by(name=name).first()          if user and user.check_password_hash(pwd):              login_user(user)              flash('登陆成功。', category='info')              return redirect(url_for('index'))          else:              flash("密码或账户错误。", category='error')      return render_template('login.html', title='登录', form=form)      @app.route('/logout')  @login_required  def logout():      logout_user()      flash('再见!')      return redirect(url_for('login'))      @app.route('/register', methods=['POST', 'GET'])  def register():      form = RegisterForm()      if form.validate_on_submit():          username = form.username.data          email = form.email.data          pwd = form.pwd.data          user = User(name=username, email=email)          user.generate_password_hash(pwd)          db.session.add(user)          db.session.commit()          flash('注册成功', category='info')          return redirect(url_for('login'))      return render_template('register.html', title='注册', form=form)      @app.route('/delete/<int:id>')  @login_required  def delete(id):      thing = Thing.query.get(id)      if thing:          db.session.delete(thing)          db.session.commit()          return redirect(url_for('index'))

最后,完善 todolistapptemplatesindex.html,添加数据展示相关代码,示例代码如下所示:

{% extends 'base.html' %} {% block content %} {% if current_user.is_authenticated and user %}  <h1 class="m-4">{{ current_user.name }},欢迎回来</h1>  {% endif %}    <div class="container-fluid">      <p>          <a class="btn btn-primary" data-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false" aria-controls="collapseExample">              添加新事务          </a>      </p>      <div class="collapse" id="collapseExample">          <div class="card card-body mb-4">              {% from 'bootstrap/form.html' import render_form %} {{ render_form(form) }}          </div>      </div>        <ul class="list-group">          {% for thing in things.items %}          <li class="list-group-item">              <h4 style="display:block;float:left;padding-top:2px">                  {{ thing.title }}              </h4>              <div style="display:block;float: right;">                  <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModalCenter{{thing.id}}">查看</button>                  <a class="btn btn-danger" href='/delete/{{ thing.id }}'>删除</a>              </div>          </li>            <div class="modal fade" id="exampleModalCenter{{thing.id}}" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">              <div class="modal-dialog modal-dialog-centered" role="document">                  <div class="modal-content">                      <div class="modal-header">                          <h5 class="modal-title" id="exampleModalLongTitle">{{ thing.title }}</h5>                      </div>                      <div class="modal-body">                          {{ thing.text }}                      </div>                      <div class="modal-footer">                          <small>{{ thing.add_date }}</small>                          <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>                      </div>                  </div>              </div>          </div>          {% endfor %}      </ul>        <nav aria-label="Page navigation example" class="m-4">          <ul class="pagination justify-content-center">              <li class="page-item {% if not things.has_prev %}disabled{% endif %}">                  <a class="page-link" href="{{ url_for('index',page=things.prev_num) }}">上一页</a>              </li>                {% for page in things.iter_pages(1,1,3,2) %} {% if page %}              <li class="page-item {%if page==things.page%}active{%endif%}">                  <a class="page-link" href="{{ url_for('index',page=page) }}">{{page}}</a>              </li>              {% else %}              <li class="page-item disabled">                  <a class="page-link" href="#">&hellip;</a>              </li>              {% endif %} {% endfor %}                <li class="page-item {% if not things.has_next %}disabled{% endif %}">                  <a class="page-link" href="{{ url_for('index',page=things.next_num) }}">下一页</a>              </li>          </ul>      </nav>    </div>    {% endblock %}

此时,当我们运行起我们的网站后进入注册页面 http://127.0.0.1:5000 就可以进行当前登录用户的事务录入、查看、删除、和事务分页的效果了。

补充

一个 Pagination 对象的常用属性有:

  • items 当前页面中的所有记录(比如当前页上有5条记录,items就是以列表形式组织这5个记录)
  • query 当前页的query对象(通过query对象调用paginate方法获得的Pagination对象)
  • page 当前页码(比如当前页是第5页,返回5)
  • prev_num 上一页页码
  • next_num 下一页页码
  • has_next 是否有下一页 True/False
  • has_prev 是否有上一页 True/False
  • pages 查询得到的总页数 per_page 每页显示的记录条数
  • total 总的记录条数

常用方法有:

  • prev() 上一页的分页对象Pagination
  • next() 下一页的分页对象Pagination
  • iter_pages(left_edge=2,left_current=2,right_current=5,right_edge=2)
  • iter_pages 用来获得针对当前页的应显示的分页页码列表。
  • 假设当前共有100页,当前页为50页,按照默认的参数设置调用iter_pages获得的列表为:[1,2,None,48,49,50,51,52,53,54,55,None,99,100]