【狗书记录】第一部分 Flask简介

匿名 (未验证) 提交于 2019-12-03 00:22:01

本笔记只是个人记录,非指导、解惑类博客

客户端(例如Web 浏览器)把请求发送给Web 服务器,Web 服务器再把请求发送给Flask程序实例。处理URL 和函数之间关系的程序称为路由。

from flask import Flask  app = Flask(__name__)   @app.route('/') def index():     return '<h1>Hello World!</h1>'    @app.route('/user/<name>') def user(name):     return '<h1>Hello, {}!</h1>'.format(name)     if __name__ == "__main__":     app.run(debug=True) 

index 函数称为视图函数(view function)。视图函数返回的响应可以是包含
HTML 的简单字符串,也可以是复杂的表.这个函数的返回值称为响应,是客户端接收到的内容

'/user/<name>'尖括号中的内容就是动态部分,任何能匹配静态部分的URL 都会映射到这个路由上

Flask 使用上下文临时把某些对象变为全局可访问。有了上下文,就可以写出下面的视图函数:

from flask import request  app = Flask(__name__)   @app.route('/') def index():     user_agent = request.headers.get('User-Agent')     return '<p>Your browser is {}</p>'.format(user_agent)

在Flask 中有两种上下文:

/ 和/user/ 路由在程序中使用app.route 修饰器定义。/static/ 路由是
Flask 添加的特殊路由,用于访问静态文件。

在请求开始时,我们可能需要创建数据库连接或者认证发起请求的用户。为了避免在每个视图函数中都使用重复的代码,Flask提供了注册通用函数的功能,注册的函数可在请求被分发到视图函数之前或之后调用。

  • before_first_request:注册一个函数,在处理第一个请求之前运行。
  • before_request:注册一个函数,在每次请求之前运行。
  • after_request:注册一个函数,如果没有未处理的异常抛出,在每次请求之后运行。
  • teardown_request:注册一个函数,即使有未处理的异常抛出,也在每次请求之后运行。

Flask 调用视图函数后,会将其返回值作为响应的内容。大多数情况下,响应就是一个简
单的字符串,作为HTML 页面回送客户端。

视图函数的作用很明确,即生成请求的响应,如第2 章中的示例所示。对最简单的请求来说,
这就足够了,但一般而言,请求会改变程序的状态,而这种变化也会在视图函数中产生。

例如,用户在网站中注册了一个新账户。用户在表单中输入电子邮件地址和密码,然后点
击提交按钮。服务器接收到包含用户输入数据的请求,然后Flask 把请求分发到处理注册
请求的视图函数。这个视图函数需要访问数据库,添加新用户,然后生成响应回送浏览
器。这两个过程分别称为业务逻辑和表现逻辑。

模板是一个包含响应文本的文件,其中包含用占位变量表示的动态部分,其具体值只在请
求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程
称为渲染。为了渲染模板,Flask 使用了一个名为Jinja2 的强大模板引擎。

<p>A value from a dictionary: {{ mydict['key'] }}.</p> <p>A value from a list: {{ mylist[3] }}.</p> <p>A value from a list, with a variable index: {{ mylist[myintvar] }}.</p> <p>A value from an object's method: {{ myobj.somemethod() }}.</p>

Jinja2 提供了多种控制结构,可用来改变模板的渲染流程。

{% if user %} Hello, {{ user }}! {% else %} Hello, Stranger! {% endif %}
<ul>     {% for comment in comments %}     <li>{{ comment }}</li>     {% endfor %} </ul>
{% macro render_comment(comment) %}     <li>{{ comment }}</li> {% endmacro %} <ul>     {% for comment in comments %}         {{ render_comment(comment) }}     {% endfor %} </ul>

重复使用宏,我们可以将其保存在单独的文件中,然后在需要使用的模板中导入

{% import 'macros.html' as macros %} <ul>     {% for comment in comments %}         {{ macros.render_comment(comment) }}     {% endfor %} </ul>
  • 基模板
<html> <head>     {% block head %}         <title>{% block title %}{% endblock %} - My Application</title>     {% endblock %} </head> <body>     {% block body %}     {% endblock %} </body> </html>
  • 衍生模板
{% extends "base.html" %} {% block title %}Index{% endblock %} {% block head %}     {{ super() }}     <style>     </style> {% endblock %} {% block body %} <h1>Hello, World!</h1> {% endblock %}

Bootstrap 是客户端框架,因此不会直接涉及服务器。服务器需要做的只是提供引用了
Bootstrap 层叠样式表(CSS) 和JavaScript 文件的HTML 响应, 并在HTML、CSS 和
JavaScript 代码中实例化所需组件。

初始化Flask-Bootstrap 之后,就可以在程序中使用一个包含所有Bootstrap 文件的基模板。

下面是使用templates/user.html:使用Flask-Bootstrap 的模板:

{% extends "bootstrap/base.html" %}<!--extends 指令从Flask-Bootstrap 中导入bootstrap/base.html-->  {% block title %}Flasky{% endblock %}<!--title 块-->  {% block navbar %}<!--navbar 块--> <div class="navbar navbar-inverse" role="navigation">     <div class="container">         <div class="navbar-header">             <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">                 <span class="sr-only">Toggle navigation</span>                 <span class="icon-bar"></span>                 <span class="icon-bar"></span>                 <span class="icon-bar"></span>             </button>             <a class="navbar-brand" href="/">Flasky</a>         </div>         <div class="navbar-collapse collapse">             <ul class="nav navbar-nav">                 <li><a href="/">Home</a></li>             </ul>         </div>     </div> </div> {% endblock %}  {% block content %}<!--content 块--> <div class="container">     <div class="page-header">         <h1>Hello, {{ name }}!</h1>     </div> </div> {% endblock %}
  • title 块的作用很明显,其中的内容会出现在渲染后的HTML 文档头部,放在 标签中。
  • navbar 和content 这两个块分别表示页面中的导航条和主体内容。

如果程序需要向已经有内容的块中添加新内容,必须使用Jinja2 提供的super() 函数。例如,如果要在衍生模板中添加新的JavaScript 文件,需要这么定义scripts 块:

{% block scripts %} {{ super() }} <script type="text/javascript" src="my-script.js"></script> {% endblock %}

下面展示了templates/base.html 的内容,这是一个继承自bootstrap/base.html 的新模板,其中定义了导航条。这个模板本身也可作为其他模板的基模板,例如templates/user.html、templates/404.html 和templates/500.html。

templates/base.html:包含导航条的程序基模板

{% extends "bootstrap/base.html" %}  {% block title %}Flasky{% endblock %}  {% block navbar %} <div class="navbar navbar-inverse" role="navigation">     <div class="container">         <div class="navbar-header">             <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">                 <span class="sr-only">Toggle navigation</span>                 <span class="icon-bar"></span>                 <span class="icon-bar"></span>                 <span class="icon-bar"></span>             </button>             <a class="navbar-brand" href="/">Flasky</a>         </div>         <div class="navbar-collapse collapse">             <ul class="nav navbar-nav">                 <li><a href="/">Home</a></li>             </ul>         </div>     </div> </div> {% endblock %}  {% block content %} <div class="container">     {% block page_content %}{% endblock %}<!--page_content 的新的空块,--> </div> {% endblock %}

使用模板继承机制自定义404 错误页面

{% extends "base.html" %} {% block title %}Flasky - Page Not Found{% endblock %} {% block page_content %} <div class="page-header">     <h1>Not Found</h1> </div> {% endblock %}

templates/user.html 现在可以通过继承这个基模板来简化内容

{% extends "base.html" %} {% block title %}Flasky{% endblock %} {% block page_content %} <div class="page-header">     <h1>Hello, {{ name }}!</h1> </div> {% endblock %}

Flask 提供了url_for() 辅助函数,它可以使用程序URL映射中保存的信息生成URL。

url_for('index')  # /  url_for('index', _external=True) # http://localhost:5000/ 返回绝对地址  url_for('user', name='john', _external=True) # http://localhost:5000/user/john 

静态文件: HTML代码中引用的图片、JavaScript 源码文件和CSS。

默认设置下,Flask 在程序根目录中名为static的子目录中寻找静态文件,例如,调用
url_for('static', filename='css/styles.css', _external=True) 得到的结果是http://
localhost:5000/static/css/styles.css。

服务器需要统一时间单位,这和用户所在的地理位置无关,所以一般使用协调世界时
(Coordinated Universal Time,UTC)。

但是时间戳的表示方式很难一眼看出时间。解决方案是,把时间单位发送给Web浏览器,转换成当地时间,然后渲染。JavaScript开发的优秀客户端开源代码库,名为moment.js。根据书中的几个代码,可以得出如下图所示的时间戳,显示的时间会随时间而变化:

Flask-WTF 可以把轻松地处理Web表单

默认情况下,Flask-WTF 能保护所有表单免受跨站请求伪造(Cross-Site Request Forgery,CSRF)的攻击。为了实现CSRF 保护,Flask-WTF 需要程序设置一个密钥。Flask-WTF 使用这个密钥生成加密令牌,再用令牌验证请求中表单数据的真伪。

app = Flask(__name__) app.config['SECRET_KEY'] = 'hard to guess string' # app.config 字典可用来存储框架、扩展和程序本身的配置变量

下面是一个简单的Web 表单,包含一个文本字段和一个提交按钮。

 from flask.ext.wtf import Form from wtforms import StringField, SubmitField from wtforms.validators import Required   class NameForm(Form):     """     StringField类表示属性为type="text" 的<input> 元素。     SubmitField 类表示属性为type="submit" 的<input> 元素。     """     name = StringField('What is your name?', validators=[Required()]) # 验证函数Required()确保提交的字段不为空。     submit = SubmitField('Submit')

Flask-Bootstrap 提供了一个非常高端的辅助函数,可以使用Bootstrap中预先定义好的表单样式渲染整个Flask-WTF表单,而这些操作只需一次调用即可完成。

{% import "bootstrap/wtf.html" as wtf %} {{ wtf.quick_form(form) }}

如上所示:导入的bootstrap/wtf.html文件中定义了一个使用Bootstrap渲染Falsk-WTF表单对象的辅助函数。wtf.quick_form() 函数的参数为Flask-WTF表单对象,使用Bootstrap的默认样式渲染传入的表单。

<--! 使用Flask-WTFFlask-Bootstrap 渲染表单-->  {% extends "base.html" %} {% import "bootstrap/wtf.html" as wtf %}  {% block title %}Flasky{% endblock %}  {% block page_content %} <div class="page-header">     <h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1> </div> {{ wtf.quick_form(form) }} {% endblock %}

新的视图函数index(), 不仅要渲染表单,还要接收表单中的数据.

@app.route('/', methods=['GET', 'POST']) # 没指定methods 参数,默认为GET def index():     name = None     form = NameForm()     if form.validate_on_submit():         name = form.name.data         form.name.data = ''     return render_template('index.html', form=form, name=name)

刷新页面时浏览器会重新发送之前已经发送过的最后一个请求:

基于这个原因,最好别让Web 程序把POST请求作为浏览器发送的最后一个请求。解决方法是:使用重定向作为POST 请求的响应。重定向是一种特殊的响应,响应内容是URL,而不是包含HTML代码的字符串。

请求完成后,有时需要让用户知道状态发生了变化。这里可以使用确认消息、警告或者错误提醒。一个典型例子是,用户提交了有一项错误的登录表单后,服务器发回的响应重新渲染了登录表单,并在表单上面显示一个消息,提示用户用户名或密码错误。

from flask import Flask, render_template, session, redirect, url_for, flash @app.route('/', methods=['GET', 'POST']) def index():     form = NameForm()     if form.validate_on_submit():         old_name = session.get('name')         if old_name is not None and old_name != form.name.data:             flash('Looks like you have changed your name!')         session['name'] = form.name.data         return redirect(url_for('index'))     return render_template('index.html',         form = form, name = session.get('name'))

下面展示了如何初始化及配置一个简单的SQLite数据库。

# 配置数据库 from flask.ext.sqlalchemy import SQLAlchemy basedir = os.path.abspath(os.path.dirname(__file__)) app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'data.sqlite') app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True db = SQLAlchemy(app)

db对象是SQLAlchemy类的实例,表示程序使用的数据库,同时还获得了Flask-SQLAlchemy提供的所有功能。

模型这个术语表示程序使用的持久化实体。下面的代码定义了数据库的模型

class Role(db.Model):     __tablename__ = 'roles'     id = db.Column(db.Integer, primary_key=True)     name = db.Column(db.String(64), unique=True)      def __repr__(self):         return '<Role %r>' % self.name class User(db.Model):     __tablename__ = 'users'     id = db.Column(db.Integer, primary_key=True)     username = db.Column(db.String(64), unique=True, index=True)      def __repr__(self):         return '<User %r>' % self.username
  • 类变量__tablename__定义在数据库中使用的表名。

  • db.Column类构造函数的第一个参数是数据库列和模型属性的类型。

关系型数据库使用关系把不同表中的行联系起来。例如外键等,这里看书就行。书下面几节都是关于数据库的,对数据库有了解的都比较简单,这里就不记录了。

Flask-Mail 是链接到SMTP服务器的,默认会使用本地localhost的25端口。配置示例如下:

import os # ... app.config['MAIL_SERVER'] = 'smtp.googlemail.com' app.config['MAIL_PORT'] = 587 app.config['MAIL_USE_TLS'] = True app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME') app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
|-flasky     |-app/         |-templates/         |-static/         |-main/             |-__init__.py             |-errors.py             |-forms.py             |-views.py         |-__init__.py         |-email.py         |-models.py     |-migrations/     |-tests/         |-__init__.py         |-test*.py     |-venv/     |-requirements.txt     |-config.py     |-manage.py
  • Flask程序一般都保存在名为app 的包中;
  • 和之前一样,migrations文件夹包含数据库迁移脚本;
  • 单元测试编写在tests包中;
  • 和之前一样,venv 文件夹包含Python 虚拟环境。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!