Study_microblog笔记Part 11--添加分页

用户发表blog

增加用户发表blog功能,第一步创建相应的表格:app/forms/post.py:

from flask_wtf import FlaskForm
from wtforms import TextAreaField,SubmitField
from wtforms.validators import DataRequired,Length


class PostForm(FlaskForm):
    post = TextAreaField('Say something', validators=[DataRequired(), Length(min=1, max=140)])
    submit = SubmitField('Submit')

第二步:把表单在首页中进行渲染。app/templates/index.html:

{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Hi, {{ current_user.username }}!</h1>
<div class="row">
    <div class="col-md-4">
        {% if form %}
            {{wtf.quick_form(form)}}
        {% endif %}

        {% for post in posts %}
            {% include '_post.html' %}
        {% endfor %}

       
    </div>

</div>
{% endblock %}

第三步:在视图函数中进行逻辑处理,并把前期模拟的用户动态用实际的数据进行代替。
app/views/main.py:

@main.route('/',methods=['POST','GET'])
@main.route('/index',methods=['POST','GET'])
@login_required
def index():
    # user = {'username': 'Miguel'}
    form=PostForm()
    if form.validate_on_submit():
        post=Post(body=form.post.data,author=current_user)
        db.session.add(post)
        db.session.commit()
        flash('Your post is now live')
        return redirect(url_for('main.index'))
    posts=current_user.followed_posts().all()    
    return render_template('index.html', title='Home', posts=posts.items,form=form            )

通过一个简单的Post/Redirect/Get模式,避免了用户在提交网页表单后无意中刷新页面时插入重复的动态。
将模拟的用户blog用已经User模型中followed_posts()方法替换,它可以返回给定用户希望看到的用户动态的查询结果集。现在可以用真正的用户动态替换模拟的用户动态。
模拟的用户blog:

 posts = [
        {
            'author': {'username': 'John'},
            'body': 'Beautiful day in Portland!'
        },
        {
            'author': {'username': 'Susan'},
            'body': 'The Avengers movie was so cool!'
        }
    ]

User类的followed_posts方法返回一个SQLAlchemy查询对象,该对象被配置为从数据库中获取用户感兴趣的用户动态:

 posts=current_user.followed_posts().all()    

现在可以在首页输入用户动态,并上传并保存在数据库中。表格下展示所有的用户blog。

创建发现页,发现并关注用户

创建一个新的“发现”页面。该页面看起来像是主页,但是却不是只显示已关注用户的动态,而是展示所有用户的全部动态。发现视图函数为app/views/main.py:

@main.route('/explore')
@login_required
def explore():
    posts = current_user.followed_posts().all()
    return render_template('index.html', title='Home', posts=posts)

render_template()引用了我在应用的主页面中使用的index.html模板。 这个页面与主页非常相似,所以可以重用这个页面模板。
但与主页不同的是,在发现页面不需要一个发表用户动态表单,所以在这个视图函数中,不包含form参数。

对首页模板进行设置,加一个条件,只在传入表单参数后才会呈现该表单:app/templates/index.html:

{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Hi, {{ current_user.username }}!</h1>
<div class="row">
    <div class="col-md-4">
        {% if form %}
            {{wtf.quick_form(form)}}
        {% endif %}

        {% for post in posts %}
            {% include '_post.html' %}
        {% endfor %}        
    </div>

</div>
{% endblock %}

在base.html导航栏中添加explore:app/templates/base.html:

 <li><a href="{{ url_for('main.explore') }}">Explore</a></li>

在_post.html子模板中做一个小小的改进,将用户动态作者的用户名显示为一个链接:app/templates/_post.html:

<table>
    <tr valign="top">
        <td><img src="{{ post.author.avatar(36) }}"></td>
        <td>
            <a href="{{url_for('auth.user',username=post.author.username)}}">{{ post.author.username }} </a>
            says:<br>{{ post.body }}
        </td>
    </tr>
</table>

在主页和发现页中使用这个子模板来渲染用户动态
app/templates/index.html:

...
    {% for post in posts %}
        {% include '_post.html' %}
    {% endfor %}
...

再次尝试一下吧!

分页的使用

当用户动态积累到一定的数量,就需要用到分页的功能,Flask-SQLAlchemy的paginate()方法原生就支持分页。例如,想要获取用户关注的前20个动态,可以将all()结束调用替换成如下的查询:

user.followed_posts().paginate(1, 20, False).items

1、应用添加一个配置项,以表示每页展示的数据列表长度。在config.py中增加配置项:

class Config(object):
  #...
    #paginate
    POSTS_PER_PAGE = 10

2、主页和发现页的视图函数添加分页的代码变更如下,app/views/main.py:

@main.route('/explore')
@login_required
def explore():
    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', posts=posts.items)


@main.route('/',methods=['POST','GET'])
@main.route('/index',methods=['POST','GET'])
@login_required
def index():
    # user = {'username': 'Miguel'}
    form=PostForm()
    if form.validate_on_submit():
        post=Post(body=form.post.data,author=current_user)
        db.session.add(post)
        db.session.commit()
        flash('Your post is now live')
        return redirect(url_for('main.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', posts=posts.items,form=form)

两个路由决定了要显示的页码,可以从page查询字符串参数获得或是默认值1。然后使用paginate()方法来检索指定范围的结果。
决定页面数据列表大小的POSTS_PER_PAGE配置项是通过app.config对象中获取的。
来尝试下分页功能吧。 首先确保有三条以上的用户动态。 在发现页面中更方便测试,因为该页面显示所有用户的动态。 现在只会看到最近的三条用户动态。
如果你想看接下来的三条,请在浏览器的地址栏中输入http://localhost:5000/explore?page=2。

分页导航

paginate()的返回是Pagination类的实例,此对象的items属性,包含为所选页面检索的用户动态列表。这个分页对象还有一些其他的属性在构建分页链接时很有用:
has_next: 当前页之后存在后续页面时为真 has_prev: 当前页之前存在前置页面时为真 next_num: 下一页的页码
prev_num: 上一页的页码
有了这四个元素,我就可以生成上一页和下一页的链接并将其传入模板以渲染:

app/views/main.py:

@main.route('/explore')
@login_required
def explore():
    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('main.explore', page=posts.next_num) \
        if posts.has_next else None
    prev_url = url_for('main.explore', page=posts.prev_num) \
        if posts.has_prev else None

    return render_template('index.html', title='Home', posts=posts.items,
                           next_url=next_url, prev_url=prev_url)

@main.route('/',methods=['POST','GET'])
@main.route('/index',methods=['POST','GET'])
@login_required
def index():
    # user = {'username': 'Miguel'}
    form=PostForm()
    if form.validate_on_submit():
        post=Post(body=form.post.data,author=current_user)
        db.session.add(post)
        db.session.commit()
        flash('Your post is now live')
        return redirect(url_for('main.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('main.index',page=posts.next_num) \
                if posts.has_next else None
    prev_url=url_for('main.index',page=posts.prev_num) \
                if posts.has_prev else None

    return render_template('index.html', title='Home', posts=posts.items,form=form,
                           next_url=next_url,prev_url=prev_url)

两个视图函数中的next_url和prev_url只有在该方向上存在一个页面时,才会被设置为由url_for()返回的URL。
如果当前页面位于用户动态集合的末尾或者开头,那么Pagination实例的has_next或has_prev属性将为’False’,在这种情况下,将设置该方向的链接为None。
url_for()函数的一个有趣的地方是,你可以添加任何关键字参数,如果这些参数的名字没有直接在URL中匹配使用,那么Flask将它们设置为URL的查询字符串参数。

更改app/templates/index.html模板,并进行渲染:

{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Hi, {{ current_user.username }}!</h1>
<div class="row">
    <div class="col-md-4">
        {% if form %}
            {{wtf.quick_form(form)}}
        {% endif %}

        {% 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 %}
    </div>

</div>
{% endblock %}

主页和发现页都添加了分页链接。第一个链接标记为“Newer
posts”,并指向前一页(请记住,我显示的用户动态按时间的倒序来排序,所以第一页是最新的内容)。 第二个链接标记为“Older
posts”,并指向下一页的帖子。 如果这两个链接中的任何一个都是None,则通过条件过滤将其从页面中省略。

Study_microblog笔记Part 11--添加分页_第1张图片

个人主页中的分页

个人主页中也有一个用户动态列表,其中只显示个人主页拥有者的动态。 为了保持一致,个人主页也应该实现分页,以匹配主页的分页样式。更新个人主页视图函数,app/views/auth.py:

@auth.route('/user/')
@login_required
def user(username):
    user = User.query.filter_by(username=username).first_or_404()
    page = request.args.get('page', 1, type=int)
    posts = user.posts.order_by(Post.timestamp.desc()).paginate(
        page, app.config['POSTS_PER_PAGE'], False)
    next_url = url_for('auth.user', username=user.username, page=posts.next_num) \
        if posts.has_next else None
    prev_url = url_for('auth.user', username=user.username, page=posts.prev_num) \
        if posts.has_prev else None
    return render_template('user.html', user=user, posts=posts.items,
                           next_url=next_url, prev_url=prev_url)

利用User模型中已经定义好的user.posts一对多关系。
执行该查询并添加一个order_by()子句,首先得到最新的用户动态,然后按照对主页和发现页面中的用户动态所做的那样进行分页。
请注意,由url_for()函数生成的分页链接需要额外的username参数,因为它们指向个人主页,个人主页依赖用户名作为URL的动态组件。

对user.html模板的更改,app/templates/user.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 %}

可以将POSTS_PER_PAGE设置一个合理的值:

class Config(object):
    # ...
    POSTS_PER_PAGE = 10

现在再看动态分页,是不是可以了?

你可能感兴趣的:(python学习)