博客蓝图¶

    当你完成每个视图时,请保持开发服务器运行。当你保存修改后,请尝试在浏览器中访问 URL ,并进行测试。

    定义蓝图并注册到应用工厂。

    使用 在工厂中导入和注册蓝图。将新代码放在工厂函数的尾部,返回应用之前。

    flaskr/init.py

    1. def create_app():
    2. app = ...
    3. # existing code omitted
    4.  
    5. from . import blog
    6. app.register_blueprint(blog.bp)
    7. app.add_url_rule('/', endpoint='index')
    8.  
    9. return app

    与验证蓝图不同,博客蓝图没有 url_prefix 。因此 index 视图会用于/create 会用于 /create ,以此类推。博客是 Flaskr 的主要功能,因此把博客索引作为主索引是合理的。

    但是,下文的 index 视图的端点会被定义为 blog.index 。一些验证视图会指定向普通的 index 端点。 我们使用 关联端点名称 'index'/ URL ,这样 url_for('index')url_for('blog.index')都会有效,会生成同样的 / URL 。

    在其他应用中,可能会在工厂中给博客蓝图一个 url_prefix 并定义一个独立的index 视图,类似前文中的 hello 视图。在这种情况下 indexblog.index 的端点和 URL 会有所不同。

    索引会显示所有帖子,最新的会排在最前面。为了在结果中包含 user 表中的作者信息,使用了一个 JOIN

    flaskr/blog.py

    1. .route('/')
      def index():
      db = get_db()
      posts = db.execute(
      'SELECT p.id, title, body, created, author_id, username'
      ' FROM post p JOIN user u ON p.author_id = u.id'
      ' ORDER BY created DESC'
      ).fetchall()
      return render_template('blog/index.html', posts=posts)

    flaskr/templates/blog/index.html

    1. {% extends 'base.html' %}
    2.  
    3. {% block header %}
    4. <h1>{% block title %}Posts{% endblock %}</h1>
    5. {% if g.user %}
    6. <a class="action" href="{{ url_for('blog.create') }}">New</a>
    7. {% endif %}
    8. {% endblock %}
    9.  
    10. {% block content %}
    11. {% for post in posts %}
    12. <article class="post">
    13. <header>
    14. <div>
    15. <h1>{{ post['title'] }}</h1>
    16. <div class="about">by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}</div>
    17. </div>
    18. {% if g.user['id'] == post['author_id'] %}
    19. <a class="action" href="{{ url_for('blog.update', id=post['id']) }}">Edit</a>
    20. {% endif %}
    21. </header>
    22. <p class="body">{{ post['body'] }}</p>
    23. {% if not loop.last %}
    24. <hr>
    25. {% endif %}
    26. {% endfor %}
    27. {% endblock %}

    create 视图与 register 视图原理相同。要么显示表单,要么发送内容已通过验证且内容已加入数据库,或者显示一个出错信息。

    先前写的 login_required 装饰器用在了博客视图中,这样用户必须登录以后才能访问这些视图,否则会被重定向到登录页面。

    flaskr/blog.py

    1. @bp.route('/create', methods=('GET', 'POST'))

      def create():
      if request.method == 'POST':
      title = request.form['title']
      body = request.form['body']
      error = None

    2.     if not title:
    3.         error = &#39;Title is required.&#39;
    4.     if error is not None:
    5.     else:
    6.         db = get_db()
    7.         db.execute(
    8.             &#39;INSERT INTO post (title, body, author_id)&#39;
    9.             &#39; VALUES (?, ?, ?)&#39;,
    10.         )
    11.         db.commit()
    12.         return redirect(url_for(&#39;blog.index&#39;))
    13. return render_template(&#39;blog/create.html&#39;)

    flaskr/templates/blog/create.html

    1. {% extends 'base.html' %}
    2.  
    3. {% block header %}
    4. <h1>{% block title %}New Post{% endblock %}</h1>
    5. {% endblock %}
    6.  
    7. {% block content %}
    8. <form method="post">
    9. <label for="title">Title</label>
    10. <input name="title" id="title" value="{{ request.form['title'] }}" required>
    11. <label for="body">Body</label>
    12. <textarea name="body" id="body">{{ request.form['body'] }}</textarea>
    13. <input type="submit" value="Save">
    14. </form>

    updatedelete 视图都需要通过 id 来获取一个 post ,并且检查作者与登录用户是否一致。为避免重复代码,可以写一个函数来获取 post ,并在每个视图中调用它。

    flaskr/blog.py

    1. def get_post(id, check_author=True):
    2. post = get_db().execute(
    3. 'SELECT p.id, title, body, created, author_id, username'
    4. ' FROM post p JOIN user u ON p.author_id = u.id'
    5. ' WHERE p.id = ?',
    6. (id,)
    7. ).fetchone()
    8.  
    9. if post is None:
    10. abort(404, "Post id {0} doesn't exist.".format(id))
    11.  
    12. if check_author and post['author_id'] != g.user['id']:
    13. abort(403)
    14.  
    15. return post

    abort() 会引发一个特殊的异常,返回一个 HTTP 状态码。它有一个可选参数,用于显示出错信息,若不使用该参数则返回缺省出错信息。 404 表示“未找到”,403 代表“禁止访问”。( 401 表示“未授权”,但是我们重定向到登录页面来代替返回这个状态码)

    check_author 参数的作用是函数可以用于在不检查作者的情况下获取一个post 。这主要用于显示一个独立的帖子页面的情况,因为这时用户是谁没有关系,用户不会修改帖子。

    flaskr/blog.py

    1. @bp.route('/<int:id>/update', methods=('GET', 'POST'))

      def update(id):
      post = get_post(id)

    2. if request.method == &#39;POST&#39;:
    3.     title = request.form[&#39;title&#39;]
    4.     body = request.form[&#39;body&#39;]
    5.     error = None
    6.     if not title:
    7.     if error is not None:
    8.         flash(error)
    9.     else:
    10.         db = get_db()
    11.         db.execute(
    12.             &#39;UPDATE post SET title = ?, body = ?&#39;
    13.             (title, body, id)
    14.         )
    15.         db.commit()
    16.         return redirect(url_for(&#39;blog.index&#39;))
    17. return render_template(&#39;blog/update.html&#39;, post=post)

    和所有以前的视图不同, update 函数有一个 id 参数。该参数对应路由中的 <int:id> 。一个真正的 URL 类似 /1/update 。 Flask 会捕捉到 URL中的 1 ,确保其为一个 int ,并将其作为 id 参数传递给视图。如果没有指定 int: 而是仅仅写了 <id> ,那么将会传递一个字符串。要生成一个指向更新页面的 URL ,需要传递 id 参数给 :url_for('blog.update', id=post['id']) 。前文的 index.html 文件中同样如此。

    createupdate 视图看上去是相似的。主要的不同之处在于update 视图使用了一个 post 对象和一个UPDATE 查询代替了一个 INSERT 查询。作为一个明智的重构者,可以使用一个视图和一个模板来同时完成这两项工作。但是作为一个初学者,把它们分别处理要清晰一些。

    flaskr/templates/blog/update.html

    这个模板有两个表单。第一个提交已编辑过的数据给当前页面( /<id>/update )。另一个表单只包含一个按钮。它指定一个 action 属性,指向删除视图。这个按钮使用了一些 JavaScript 用以在提交前显示一个确认对话框。

    参数 {{ request.form['title'] or post['title'] }} 用于选择在表单显示什么数据。当表单还未提交时,显示原 post 数据。但是,如果提交了非法数据,然后需要显示这些非法数据以便于用户修改时,就显示 request.form 中的数据。 是又一个自动在模板中可用的变量。

    删除视图没有自己的模板。删除按钮已包含于 update.html 之中,该按钮指向/<id>/delete URL 。既然没有模板,该视图只处理 POST 方法并重定向到index 视图。

    flaskr/blog.py

    1. .route('/<int:id>/delete', methods=('POST',))
      @login_required
      def delete(id):
      get_post(id)
      db = get_db()
      db.execute('DELETE FROM post WHERE id = ?', (id,))
      db.commit()
      return redirect(url_for('blog.index'))

    恭喜,应用写完了!花点时间在浏览器中试试这个应用吧。然而,构建一个完整的应用还有一些工作要做。