1. 做好准备工作
- 进入项目主目录
- 激活虚拟环境
2. 博客帖子分页
Flask-Alchemy的paginate()查询方法支持分页。比如说,我想要获取用户第一组20个的关注帖子,我可以将语句最后的all()替换为:
>>> user.followed_posts().paginate(1, 20, False).items
paginate方法可以被Flask-SQLAlchemy的任何查询对象调用。它接收三个参数:
- 页码,从1开始
- 每页的条目数
- 错误标志。如果设为True,当请求超过范围的页面时,它会自动返回一个404错误到客户端。如果设为False,请求超范围的页面会返回一个空列表。
paginate的返回值是一个Pagination对象。该对象的items属性包含请求页面的条目列表。
现在来实现index()视图函数的分页功能。首先,向应用添加一个配置项,用于确定每页显示多少个条目。
config.py
class Config(object):
# ...
POSTS_PER_PAGE = 3
将页码并入应用URL的常用方法是使用查询字符串参数来指定可选的页码,如果该参数没有给出则默认为1。下面是一些示例URL:
- 第一页,隐式:http://localhost:5000/index
- 第一页,显式:http://localhost:5000/index?page=1
- 第三页:http://localhost:5000/index?page=3
要获取查询字符串的参数,可以使用Flask的request.args对象。
app/routes.py
@app.route('/', methods=['GET', 'POST'])
@app.route('/index', methods=['GET', 'POST'])
@login_required
def index():
# ...
page = request.args.get('page', 1, type=int)
posts = current_user.followed_posts().paginate(
page, app.config['POSTS_PER_PAGE'], False)
return render_template('index.html', title='Home', form=form,
posts=posts.items)
@app.route('/explore')
@login_required
def explore():
page = request.args.get('page', 1, type=int)
posts = Post.query.order_by(Post.timestamp.desc()).paginate(
page, app.config['POSTS_PER_PAGE'], False)
return render_template("index.html", title='Explore', posts=posts.items)
通过这些更改,这两个路由就能确定要显示的页码了,是page查询字符串的参数或是默认值1,通过paginate()方法检索特定页面的结果。POSTS_PER_PAGE配置项则通过app.config对象确定了页面的条目数量。
3. 页面导航
下一个更改是在博客帖子列表的底部添加链接,使用户可以切换到下一页/上一页。前面提到,paginate()的返回值是Flask-SQLAlchemy的Pagination类的对象,除了上面用到的items属性之外,该对象还有其他几个对于创建分页对象有用的属性:
- has_next:如果当前页面之后还有至少一个页面时为True
- has_prev:如果当前页面之前还有至少一个页面时为True
- next_num:下一页的页码
- prev_num:上一页的页码
有了这四个元素,我就可以生成下一页和上一页的链接并传递给模板来渲染了:
app/routes.py
@app.route('/', methods=['GET', 'POST'])
@app.route('/index', methods=['GET', 'POST'])
@login_required
def index():
# ...
page = request.args.get('page', 1, type=int)
posts = current_user.followed_posts().paginate(
page, app.config['POSTS_PER_PAGE'], False)
next_url = url_for('index', page=posts.next_num) \
if posts.has_next else None
prev_url = url_for('index', page=posts.prev_num) \
if posts.has_prev else None
return render_template('index.html', title='Home', form=form,
posts=posts.items, next_url=next_url,
prev_url=prev_url)
@app.route('/explore')
@login_required
def explore():
page = request.args.get('page', 1, type=int)
posts = Post.query.order_by(Post.timestamp.desc()).paginate(
page, app.config['POSTS_PER_PAGE'], False)
next_url = url_for('explore', page=posts.next_num) \
if posts.has_next else None
prev_url = url_for('explore', page=posts.prev_num) \
if posts.has_prev else None
return render_template("index.html", title='Explore', posts=posts.items,
next_url=next_url, prev_url=prev_url)
这两个视图函数中的next_url和prev_url只有在有效时才会被设为url_for()返回的url。如果当前页是第一页或最后一页,则Pagination对象的has_prev或has_next属性会是False。
修改模板:
app/templates/index.html
...
{% for post in posts %}
{% include '_post.html' %}
{% endfor %}
{% if prev_url %}
<a href="{{ prev_url }}">Newer posts</a>
{% endif %}
{% if next_url %}
<a href="{{ next_url }}">Older posts</a>
{% endif %}
...