• Flask学习之九 分页


    英文博客地址:http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-ix-pagination

    中文翻译地址:http://www.pythondoc.com/flask-mega-tutorial/pagination.html

    开源中国社区:http://www.oschina.net/translate/the-flask-mega-tutorial-part-ix-pagination

    一、Blog Posts的提交

    之前我们的程序用的都是定义的blog,现在我们需要有一个地方让用户发表blog。

    首先我们定义一个单字段的表单对象(文件 app/forms.py):

    class PostForm(Form):
        post = StringField('post', validators=[DataRequired()])

    接着,把表单添加到模板中(文件 app/templates/index.html):

    <!-- extend base layout -->
    {% extends "base.html" %}
    
    {% block content %}
      <h1>Hi, {{ g.user.nickname }}!</h1>
      <form action="" method="post" name="post">
          {{ form.hidden_tag() }}
          <table>
              <tr>
                  <td>Say something:</td>
                  <td>{{ form.post(size=30, maxlength=140) }}</td>
                  <td>
                  {% for error in form.post.errors %}
                  <span style="color: red;">[{{ error }}]</span><br>
                  {% endfor %}
                  </td>
              </tr>
              <tr>
                  <td></td>
                  <td><input type="submit" value="Post!"></td>
                  <td></td>
              </tr>
          </table>
      </form>
      {% for post in posts %}
      <p>
        {{ post.author.nickname }} says: <b>{{ post.body }}</b>
      </p>
      {% endfor %}
    {% endblock %}

    最后,把这一切联系起来的视图函数需要被扩展用来处理表单(文件 app/views.py):

    from forms import LoginForm, EditForm, PostForm
    from models import User, Post
    
    @app.route('/', methods=['GET', 'POST'])
    @app.route('/index', methods=['GET', 'POST'])
    @login_required
    def index():
        form = PostForm()
        if form.validate_on_submit():
            post = Post(body=form.post.data, timestamp=datetime.utcnow(), author=g.user)
            db.session.add(post)
            db.session.commit()
            flash('Your post is now live!')
            return redirect(url_for('index'))
        posts = [
            { 
                'author': {'nickname': 'John'}, 
                'body': 'Beautiful day in Portland!' 
            },
            { 
                'author': {'nickname': 'Susan'}, 
                'body': 'The Avengers movie was so cool!' 
            }
        ]
        return render_template('index.html',
                               title='Home',
                               form=form,
                               posts=posts)

    不要忘了在与 index 视图函数相关联的两个路由上,接受 POST 请求,因为我们需要接受提交的 blog。

    当我们在数据库中插入一个新的 Post 后,我们将会重定向到首页:

    return redirect(url_for('index'))

    为什么要重定向?想想当一个用户写下一个blog post请求之后,点击提交,然后再点击浏览器刷新按钮。刷新的命令将会做些什么?浏览器将会重新发送上一次的请求作为刷新命令的结果。

    如果没有重定向,上一次的请求是提交表单的 POST 请求,因此刷新动作将会重新提交表单,导致与第一个相同的第二个 Post 记录被写入数据库。这并不好。

    有了重定向,我们迫使浏览器在表单提交后发送另外一个请求,即重定向页的请求。这是一个简单的 GET 请求,因此一个刷新动作将会重复 GET 请求而不是多次提交表单。

    这个小技巧避免了用户在提交 blog 后不小心触发刷新的动作而导致插入重复的 blog。

    二、显示blog

    从上面我们可以看到显示出来的blog内容依然不是数据库里的真实数据,我们需要把posts进行如下修改(文件 app/views.py):

        posts = g.user.followed_posts().all()

    如同上一章提到的一样,它允许我们获取关注的用户的所有的 blog。

    现在可以试一下创建一些用户,让他们follow其他人,然后发布一些信息来看一下效果。

    三、分页

    我们把所有关注者的 blog 展示在首页上。如果数据量太大,处理如此大数据量的列表对象将会极其低效的。

    我们可以显示把这么大量的post分组来显示,或者分页。

    Flask-SQLAlchemy可以很好的支持分页。例如,我们可以通过如下方法,轻松获取某个用户的前3篇的followed posts:

        posts = g.user.followed_posts().paginate(1, 3, False).items

    paginate 方法能够被任何查询调用。它接受三个参数:

    • 页数,从 1 开始,
    • 每一页的项目数,这里也就是说每一页显示的 blog 数,
    • 错误标志。如果是 True,当请求的范围页超出范围的话,一个 404 错误将会自动地返回到客户端的网页浏览器。如果是 False,返回一个空列表而不是错误。

    paginate 返回的值是一个 Pagination 对象。这个对象的 items 成员包含了请求页面项目(我们这个程序中指 blog)的列表。

    要实现分页,首先,在配置文件中添加一些决定每页显示的 blog 数的配置项(文件 config.py):

    # pagination
    POSTS_PER_PAGE = 3

    之后,先知道URLs是如何判断请求不同的页面的。Flask的routes可以接受参数的,所以可以在URL添加后缀,来指明我们想要的页面:

    http://localhost:5000/         <-- page #1 (default)
    http://localhost:5000/index    <-- page #1 (default)
    http://localhost:5000/index/1  <-- page #1
    http://localhost:5000/index/2  <-- page #2
    

     这种格式的 URLs 能够轻易地通过在我们的视图函数中附加一个 route 来实现(文件 app/views.py):

    from config import POSTS_PER_PAGE
    
    @app.route('/', methods=['GET', 'POST'])
    @app.route('/index', methods=['GET', 'POST'])
    @app.route('/index/<int:page>', methods=['GET', 'POST'])
    @login_required
    def index(page=1):
        form = PostForm()
        if form.validate_on_submit():
            post = Post(body=form.post.data, timestamp=datetime.utcnow(), author=g.user)
            db.session.add(post)
            db.session.commit()
            flash('Your post is now live!')
            return redirect(url_for('index'))
        posts = g.user.followed_posts().paginate(page, POSTS_PER_PAGE, False).items
        return render_template('index.html',
                               title='Home',
                               form=form,
                               posts=posts)

    我们的新route接受分页参数,并把参数定义为整数。

    因为那三个route当中有两个route没有分页参数,我们还需要添加分页参数到index函数当中,并给它们一个默认值。

    四、页面导航

    我们现在需要添加链接允许用户访问下一页以及/或者前一页,用户不可能自行更改url的。

    在我们目前的视图函数中,我们使用的是:

    posts = g.user.followed_posts().paginate(page, POSTS_PER_PAGE, False).items

    我们只保留了paginate方法返回的Pagination对象的item成员。但是这个对象还有很多其它有用的东西在里面,因此我们还是使用整个对象(文件 app/views.py):

    posts = g.user.followed_posts().paginate(page, POSTS_PER_PAGE, False)

    这里修改后,还必须修改模板(文件 app/templates/index.html):

    <!-- posts is a Paginate object -->
    {% for post in posts.items %}
    <p>
      {{ post.author.nickname }} says: <b>{{ post.body }}</b>
    </p>
    {% endfor %}

    这样模版能够使用完全的 Paginate 对象。我们使用的这个对象的成员有:

    • has_next:如果在目前页后至少还有一页的话,返回 True
    • has_prev:如果在目前页之前至少还有一页的话,返回 True
    • next_num:下一页的页面数
    • prev_num:前一页的页面数

    这样要进行导航就很容易了,(文件 app/templates/index.html):

    <!-- posts is a Paginate object -->
    {% for post in posts.items %}
    <p>
      {{ post.author.nickname }} says: <b>{{ post.body }}</b>
    </p>
    {% endfor %}
    {% if posts.has_prev %}<a href="{{ url_for('index', page=posts.prev_num) }}">&lt;&lt; Newer posts</a>{% else %}&lt;&lt; Newer posts{% endif %} | 
    {% if posts.has_next %}<a href="{{ url_for('index', page=posts.next_num) }}">Older posts &gt;&gt;</a>{% else %}Older posts &gt;&gt;{% endif %}

    &lt; 是转义字符,表示“<”

    五、实现 Post 子模板

    之前我们定义了一个子模板,现在是时候在我们的首页上也包含这个子模板。(文件 app/templates/index.html):

    <!-- posts is a Paginate object -->
    {% for post in posts.items %}
        {% include 'post.html' %}
    {% endfor %}

    六、用户信息页

    我们在用户信息页上显示了 blog。为了保持一致性,用户信息页也跟首页一样。

    改变是跟修改首页一样的。这是我们需要做的列表:

    • 添加一个额外的路由获取页面数的参数
    • 添加一个默认值为 1 的 page 参数到视图函数
    • 用合适的数据库查询与分页代替伪造的 blog
    • 更新模板使用分页对象

    下面就是更新后的视图函数(文件 app/views.py):

    @app.route('/user/<nickname>')
    @app.route('/user/<nickname>/<int:page>')
    @login_required
    def user(nickname, page=1):
        user = User.query.filter_by(nickname=nickname).first()
        if user is None:
            flash('User %s not found.' % nickname)
            return redirect(url_for('index'))
        posts = user.posts.paginate(page, POSTS_PER_PAGE, False)
        return render_template('user.html',
                               user=user,
                               posts=posts)

    注意上面的视图函数已经有一个 nickname 参数,我们把 page 作为它的第二个参数。

    模版的改变同样很简单(文件 app/templates/user.html):

    <!-- posts is a Paginate object -->
    {% for post in posts.items %}
        {% include 'post.html' %}
    {% endfor %}
    {% if posts.has_prev %}<a href="{{ url_for('user', nickname=user.nickname, page=posts.prev_num) }}">&lt;&lt; Newer posts</a>{% else %}&lt;&lt; Newer posts{% endif %} | 
    {% if posts.has_next %}<a href="{{ url_for('user', nickname=user.nickname, page=posts.next_num) }}">Older posts &gt;&gt;</a>{% else %}Older posts &gt;&gt;{% endif %}
    
    
    
  • 相关阅读:
    PHP应用目录结构设计
    php 项目性能优化
    Zend Framework的PHP编码规范【1】
    php 如何做在线人数统计
    linux 文件权限
    总结:常用的通用数据处理指令
    全排列(含递归和非递归的解法)
    局部变量,静态局部变量,全局变量,静态全局变量在内存中的存放区别(转)
    C++中引用详解
    Pascal三角形
  • 原文地址:https://www.cnblogs.com/AminHuang/p/4276905.html
Copyright © 2020-2023  润新知