flaskWeb开发(基于python的web开发实战)-第一部分-Flask简介

来源:互联网 发布:蔡夫人知乎 编辑:程序博客网 时间:2024/06/10 06:15

第二章 程序的基本结构

from flask import Flaskfrom flask import request  # 程序上下文包括current_app, g, 请求上下文包括request, sessionfrom flask import make_response  # 用户构造返回对象from flask import redirect  # 重定向from flask import abort  # 处理错误app = Flask(__name__)  # 创建flask实例,参数指定程序主模块的名字,决定了程序的根目录,以便稍后能够找到相对于程序根目录的资源文件位置@app.route('/')def index():    user_agent = request.headers.get('User-Agent')  # flask 使用上下文使某些特定变量在一个线程中全局可访问,且不会影响其他线程    return '<h1>hello world</h1><p>your browser is %s<p>' %user_agent@app.route('/bad')def index_bad():    return '<h1>bad request</h1>', 400  # 返回状态码@app.route('/make_response')def index_make_response():    response = make_response('<h1>This document carried a cookie!</h1>')    response.set_cookie('ansewr', '42')    return response@app.route('/redirect')def index_redirect():    return redirect('/bad')@app.route('/abort/<id>')def index_abort(id):    ids = ['1', 2, 3]    if id not in ids:  # id默认传的是字符串        abort(404)  # abort不会把控制权交还给视图函数,而是交给web服务器    return id@app.route('/user/<name>')def user(name):    return '<h1>hello, %s!<h1>' %name@app.route('/user/str/<path:name>')  # 动态部分默认匹配字符串,也可以匹配定义的类型,flask支持int, float, path(字符串,但不把斜线视为分隔符)类型def user_2(name):    return '<h1>hello, %s!<h1>' %name# flask 实现了四种请求钩子(before_first_request, before_request, after_request, teardown_request),可在视图函数之前或之后执行if __name__ == '__main__':    app.run(debug = True)

第三章 模板

包含:jinja2引擎,flask-bootstrap,自定义错误页面,动态路由,静态文件引用,flask-moment本地化日期时间
# hello.pyfrom datetime import datetimefrom flask import Flaskfrom flask import render_template  # 函数把Jinja2模板引擎集成到了程序中from flask import url_for  # 动态路由,也可以在html文件中使用from flask.ext.bootstrap import Bootstrapfrom flask.ext.moment import Momentapp = Flask(__name__)bootstrap = Bootstrap(app)moment = Moment(app)@app.route('/')def index():    return render_template('index.html', current_time=datetime.utcnow())  # current_time供moment.js使用@app.route('/user/<name>')def user(name):    return render_template('user.html', name=name)@app.route('/bootstrap_test/<name>')def bootstrap_test(name):    return render_template('bootstrap_test.html', name=name)@app.errorhandler(404)def page_not_found(e):    return render_template('404.html'), 404  # 自定义404页面@app.route('/url_for_test/<name>')def url_for_test(name):    index_url = url_for('index', _external=True)  # _external 生成绝对地址    user_url = url_for('user', name=name, page=2)  # name=name 动态部分作为关键字传入, page=2 额外参数    return render_template('url_for_test.html', index_url=index_url, user_url=user_url)if __name__ == '__main__':    app.run(debug=True)# base.html<!--继承自 bootstrap/base.html,定义好了导航条的格式的基模板--><!--静态文件的导入-->{% extends "bootstrap/base.html" %}{% block title %}Flasky{% endblock %}{% block head %}{{ super() }}<link rel="shortcut icon" href="{{ url_for('static', filename = 'favicon.ico') }}" type="image/x-icon"><link rel="icon" href="{{ url_for('static', filename = 'favicon.ico') }}" type="image/x-icon"><!--默认flask会在static下寻找静态文件,对静态文件使用url_for如上-->{% 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 %}</div>{% endblock %}{% block scripts %} {{ super() }}{{ moment.include_moment() }}  <!--引入moment.js-->{{ moment.lang('zh-cn') }}  <!--设置语言为中文-->{% endblock %}# 404.html<!--自定义的404错误页面-->{% extends "base.html" %}{% block title %}Flasky - Page Not Found{% endblock %}{% block page_content %}<div class="page-header">    <h1>Not Found</h1></div>{% endblock %}# bootstrap_test.html<!--flask-bootstrap使用-->{% 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">    <div class="page-header">        <h1>Hello, {{ name }}!</h1>    </div></div>{% endblock %}<!--块  名             说  明doc 整个             HTML 文档html_attribs        <html> 标签的属性html                <html> 标签中的内容head                <head> 标签中的内容title               <title> 标签中的内容metas               一组 <meta> 标签styles              层叠样式表定义body_attribs        <body> 标签的属性body                <body> 标签中的内容navbar              用户定义的导航条content             用户定义的页面内容scripts             文档底部的 JavaScript 声明表中的很多块都是 Flask-Bootstrap 自用的,如果直接重定义可能会导致一些问题。例如,Bootstrap 所需的文件在 styles 和 scripts 块中声明。如果程序需要向已经有内容的块中添加新内容,必须使用 Jinja2 提供的 super() 函数。例如,如果要在衍生模板中添加新的 JavaScript 文件,需要这么定义 scripts 块:{% block scripts %}{{ super() }}<script type="text/javascript" src="my-script.js"></script>{% endblock %}--># index.html<!--本地化日期时间-->{% extends "base.html" %}{% block page_content %}<h1>Hello World!</h1><!--在模板中渲染moment.js--><p>The local date and time is {{current_time }}.</p><p>The local date and time is {{ moment(current_time).format('LLL') }}.</p><p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>{% endblock %}# user.html<!--jinja2使用--><h1>Hello, {{ name }}!</h1><h1>Hello, {{ name|upper }}!</h1><!--常用jinja2过滤器:safe 渲染时不转义,在可信的数据来源上可用,一般用户输入都是不可信的capitalize 首字母大写,其他字母小写lower 小写upper 大写title 每个单词的首字母大写trim 去掉首尾空格striptags 去掉HTML标签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 %}另一种常见需求是在模板中渲染一组元素。下例展示了如何使用 for 循环实现这一需求:{% for comment in comments %}    <li>{{ comment }}</li>{% endfor %}Jinja2 还支持宏。宏类似于 Python 代码中的函数。例如:{% 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>需要在多处重复使用的模板代码片段可以写入单独的文件,再包含在所有模板中,以避免重复:{% include 'common.html' %}另一种重复使用代码的强大方式是模板继承,它类似于 Python 代码中的类继承。首先,创建一个名为 base.html 的基模板:<html>    <head>        {% block head %}            <title>{% block title %}{% endblock %} - My Application</title>        {% endblock %}    </head>    <body>        {% block body %}        {% endblock %}    </body></html>block 标签定义的元素可在衍生模板中修改。在本例中,我们定义了名为 head 、 title 和body 的块。注意, title 包含在 head 中。下面这个示例是基模板的衍生模板:{% extends "base.html" %}{% block title %}Index{% endblock %}{% block head %}    {{ super() }}    <style>    </style>{% endblock %}{% block body %}    <h1>Hello, World!</h1>{% endblock %}extends 指令声明这个模板衍生自 base.html。在 extends 指令之后,基模板中的 3 个块被重新定义,模板引擎会将其插入适当的位置。注意新定义的 head 块,在基模板中其内容不是空的,所以使用 super() 获取原来的内容--># url_for_test.html<!--动态路由-->{% extends "base.html" %}{% block page_content %}<div class="page-header">    <h1>{{ index_url }}</h1>    <h1>{{ user_url }}</h1></div>{% endblock %}

第四章 Web表单

跨站请求伪造保护,表单渲染,重定向,flash消息
hello.pyfrom flask import Flaskfrom flask import render_template  # 函数把Jinja2模板引擎集成到了程序中from flask import session, url_for, redirectfrom flask import flash  # 请求完成后,有时需要让用户知道状态发生了变化。这里可以使用确认消息、警告或者错误提醒。一个典型例子是,用户提交了有一项错误的登录表单后,服务器发回的响应重新渲染了登录表单,并在表单上面显示一个消息,提示用户用户名或密码错误from flask.ext.bootstrap import Bootstrapfrom flask.ext.wtf import FlaskForm as BaseFormfrom wtforms import StringField, SubmitFieldfrom wtforms.validators import Requiredapp = Flask(__name__)bootstrap = Bootstrap(app)# 默认情况下,Flask-WTF 能保护所有表单免受跨站请求伪造(Cross-Site Request Forgery,CSRF)的攻击。恶意网站把请求发送到被攻击者已登录的其他网站时就会引发 CSRF 攻击.# 为了实现 CSRF 保护,Flask-WTF 需要程序设置一个密钥。Flask-WTF 使用这个密钥生成加密令牌,再用令牌验证请求中表单数据的真伪.# app.config 字典可用来存储框架、扩展和程序本身的配置变量.SECRET_KEY 配置变量是通用密钥,可在 Flask 和多个第三方扩展中使用# 为了增强安全性,密钥不应该直接写入代码,而要保存在环境变量中app.config['SECRET_KEY'] = 'hard to guess string'# 使用 Flask-WTF 时,每个 Web 表单都由一个继承自 Form 的类表示。这个类定义表单中的一组字段,每个字段都用对象表示。字段对象可附属一个或多个验证函数。验证函数用来验证用户提交的输入值是否符合要求.# StringField类表示属性为 type="text" 的 <input> 元素。 SubmitField 类表示属性为 type="submit" 的<input> 元素。字段构造函数的第一个参数是把表单渲染成 HTML 时使用的标号.# StringField 构造函数中的可选参数 validators 指定一个由验证函数组成的列表,在接受用户提交的数据之前验证数据。验证函数 Required() 确保提交的字段不为空.# Form 基类由 Flask-WTF 扩展定义,所以从 flask.ext.wtf 中导入。字段和验证函数却可以直接从 WTForms 包中导入.class NameForm(BaseForm):    name = StringField('what is your name?', validators=[Required()])    submit = SubmitField('Submit')'''WTForms 支持的 HTML 标准字段如表所示字段类型             说  明StringField         文本字段TextAreaField       多行文本字段PasswordField       密码文本字段HiddenField         隐藏文本字段DateField           文本字段,值为 datetime.date 格式DateTimeField       文本字段,值为 datetime.datetime 格式IntegerField        文本字段,值为整数DecimalField        文本字段,值为 decimal.DecimalFloatField          文本字段,值为浮点数BooleanField        复选框,值为 True 和 FalseRadioField          一组单选框SelectField         下拉列表SelectMultipleField 下拉列表,可选择多个值FileField           文件上传字段SubmitField         表单提交按钮FormField           把表单作为字段嵌入另一个表单FieldList           一组指定类型的字段WTForms验证函数如表所示验证函数             说  明Email               验证电子邮件地址EqualTo             比较两个字段的值;常用于要求输入两次密码进行确认的情况IPAddress           验证 IPv4 网络地址Length              验证输入字符串的长度NumberRange         验证输入的值在数字范围内Optional            无输入值时跳过其他验证函数Required            确保字段中有数据Regexp              使用正则表达式验证输入值URL                 验证 URLAnyOf               确保输入值在可选值列表中NoneOf              确保输入值不在可选值列表中'''# 用户输入名字后提交表单,然后点击浏览器的刷新按钮,会看到一个莫名其妙的警告,要求在再次提交表单之前进行确认。之所以出现这种情况,是因为刷新页面时浏览器会重新发送之前已经发送过的最后一个请求。如果这个请求是一个包含表单数据的 POST 请求,刷新页面后会再次提交表单。大多数情况下,这并不是理想的处理方式。最好别让 Web 程序把 POST 请求作为浏览器发送的最后一个请求。# 这种需求的实现方式是,使用重定向作为 POST 请求的响应,而不是使用常规响应。重定向是一种特殊的响应,响应内容是 URL,而不是包含 HTML 代码的字符串。浏览器收到这种响应时,会向重定向的 URL 发起 GET 请求,显示页面的内容。这个页面的加载可能要多花几微秒,因为要先把第二个请求发给服务器。除此之外,用户不会察觉到有什么不同。现在,最后一个请求是 GET 请求,所以刷新命令能像预期的那样正常使用了。这个技巧称为 Post/ 重定向 /Get 模式.# 默认情况下,用户会话保存在客户端 cookie 中,使用设置的 SECRET_KEY 进行加密签名。如果篡改了 cookie 中的内容,签名就会失效,会话也会随之失效。@app.route('/', methods=['GET', 'POST'])  # app.route 修饰器中添加的 methods 参数告诉 Flask 在 URL 映射中把这个视图函数注册为GET 和 POST 请求的处理程序。如果没指定 methods 参数,就只把视图函数注册为 GET 请求的处理程序def index():  # 视图函数 index() 不仅要渲染表单,还要接收表单中的数据    form = NameForm()    if form.validate_on_submit():  # 如果数据能被所有验证函数接受,那么 validate_on_submit() 方法的返回值为 True ,否则返回 False 。这个函数的返回值决定是重新渲染表单还是处理表单提交的数据。        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!')  # flash() 函数在发给客户端的下一个响应中显示一个消息。仅调用 flash() 函数并不能把消息显示出来,程序使用的模板要渲染这些消息。Flask 把 get_flashed_messages() 函数开放给模板,用来获取并渲染消息        session['name'] = form.name.data  # 程序可以把数据存储在用户会话中,在请求之间“记住”数据        return redirect(url_for('index'))  # redirect() 是个辅助函数,用来生成 HTTP 重定向响应    return render_template('index.html', form=form, name=session.get('name'))if __name__ == '__main__':    app.run(debug=True)index.html{% extends "bootstrap/base.html" %}{% import "bootstrap/wtf.html" as wtf %} <!--import 指令的使用方法和普通 Python 代码一样,允许导入模板中的元素并用在多个模板中-->{% block content %}{% for message in get_flashed_messages() %}  <!--在模板中使用循环是因为在之前的请求循环中每次调用 flash() 函数时都会生成一个消息,所以可能有多个消息在排队等待显示。 get_flashed_messages() 函数获取的消息在下次调用时不会再次返回,因此 Flash 消息只显示一次,然后就消失了--><div class="alert alert-warning">    <button type="button" class="close" data-dismiss="alert">&times;</button>    {{ message }}</div>{% endfor %}<div class="page-header"><h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1></div>{{ wtf.quick_form(form) }} <!--Flask-Bootstrap 提供了一个非常高端的辅助函数,可以使用 Bootstrap 中预先定义好的表单样式渲染整个 Flask-WTF 表单-->{% endblock %}

第五章 数据库

hello.pyimport osfrom flask import Flaskfrom flask import render_template  # 函数把Jinja2模板引擎集成到了程序中from flask import session, url_for, redirectfrom flask import flash  # 请求完成后,有时需要让用户知道状态发生了变化。这里可以使用确认消息、警告或者错误提醒。一个典型例子是,用户提交了有一项错误的登录表单后,服务器发回的响应重新渲染了登录表单,并在表单上面显示一个消息,提示用户用户名或密码错误from flask.ext.bootstrap import Bootstrapfrom flask_script import Manager, Shellfrom flask.ext.wtf import FlaskForm as BaseFormfrom wtforms import StringField, SubmitFieldfrom wtforms.validators import Requiredfrom flask.ext.sqlalchemy import SQLAlchemyapp = Flask(__name__)manager = Manager(app)basedir = os.path.abspath(os.path.dirname(__file__))app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'data.sqlite')  # 程序使用的数据库 URL 必须保存到 Flask 配置对象的 SQLALCHEMY_DATABASE_URI 键中app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True  # SQLALCHEMY_COMMIT_ON_TEARDOWN 键,将其设为 True时,每次请求结束后都会自动提交数据库中的变动app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True  # 新版本需要加的配置db = SQLAlchemy(app)class Role(db.Model):    '''    users 属性代表这个关系的面向对象视角。对于一个 Role 类的实例,其 users 属性将返回与角色相关联的用户组成的列表。 db.relationship() 的第一个参数表明这个关系的另一端是哪个模型。如果模型类尚未定义,可使用字符串形式指定。    db.relationship() 中的 backref 参数向 User 模型中添加一个 role 属性,从而定义反向关系。这一属性可替代 role_id 访问 Role 模型,此时获取的是模型对象,而不是外键的值.    大多数情况下, db.relationship() 都能自行找到关系中的外键,但有时却无法决定把哪一列作为外键。例如,如果 User 模型中有两个或以上的列定义为 Role 模型的外键,SQLAlchemy 就不知道该使用哪列。如果无法决定外键,你就要为 db.relationship() 提供额外参数,从而确定所用外键.    这里定义了'一对多'关系。一对一关系可以用前面介绍的一对多关系表示,但调用 db.relationship() 时要把 uselist 设为 False。多对一关系也可使用一对多表示,对调两个表即可,或者把外键和 db.relationship() 都放在“多”这一侧。最复杂的关系类型是多对多,需要用到第三张表,这个表称为关系表    '''    __tablename__ = 'roles'  # 类变量 __tablename__ 定义在数据库中使用的表名    id = db.Column(db.Integer, primary_key=True)    name = db.Column(db.String(64), unique=True)    users = db.relationship('User', backref='role', lazy='dynamic')    def __repr__(self):  #  __repr()__ 方法返回一个具有可读性的字符串表示模型        return '<Role %r>' % self.nameclass User(db.Model):    __tablename__ = 'users'    id = db.Column(db.Integer, primary_key=True)    username = db.Column(db.String(64), unique=True, index=True)    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))  # role_id 列被定义为外键,传给 db.ForeignKey() 的参数 'roles.id' 表明,这列的值是 roles 表中行的 id 值    def __repr__(self):        return '<User %r>' % self.username'''最常用的SQLAlchemy列类型类型名               Python类型          说  明Integer             int                 普通整数,一般是 32 位SmallInteger        int                 取值范围小的整数,一般是 16 位BigInteger          int 或 long         不限制精度的整数Float               float               浮点数Numeric             decimal.Decimal     定点数String              str                 变长字符串Text                str                 变长字符串,对较长或不限长度的字符串做了优化Unicode             unicode             变长 Unicode 字符串UnicodeText         unicode             变长 Unicode 字符串,对较长或不限长度的字符串做了优化Boolean             bool                布尔值Date                datetime.date       日期Time                datetime.time       时间DateTime            datetime.datetime   日期和时间Interval            datetime.timedelta  时间间隔Enum                str                 一组字符串PickleType          任何 Python 对象     自动使用 Pickle 序列化LargeBinary         str                 二进制文件最常使用的SQLAlchemy列选项选项名               说  明primary_key         如果设为 True ,这列就是表的主键unique              如果设为 True ,这列不允许出现重复的值index               如果设为 True ,为这列创建索引,提升查询效率nullable            如果设为 True ,这列允许使用空值;如果设为 False ,这列不允许使用空值default             为这列定义默认值常用的SQLAlchemy关系选项选项名                说  明backref              在关系的另一个模型中添加反向引用primaryjoin          明确指定两个模型之间使用的联结条件。只在模棱两可的关系中需要指定lazy                 指定如何加载相关记录。可选值有 select (首次访问时按需加载)、 immediate (源对象加载后就加载)、 joined (加载记录,但使用联结)、 subquery (立即加载,但使用子查询),noload (永不加载)和 dynamic (不加载记录,但提供加载记录的查询)uselist              如果设为 Fales ,不使用列表,而使用标量值order_by             指定关系中记录的排序方式secondary            指定多对多关系中关系表的名字secondaryjoin        SQLAlchemy 无法自行决定时,指定多对多关系中的二级联结条件常用的SQLAlchemy查询过滤器过滤器           说  明filter()        把过滤器添加到原查询上,返回一个新查询filter_by()     把等值过滤器添加到原查询上,返回一个新查询limit()         使用指定的值限制原查询返回的结果数量,返回一个新查询offset()        偏移原查询返回的结果,返回一个新查询order_by()      根据指定条件对原查询结果进行排序,返回一个新查询group_by()      根据指定条件对原查询结果进行分组,返回一个新查询最常使用的SQLAlchemy查询执行函数方 法          说  明all()           以列表形式返回查询的所有结果first()         返回查询的第一个结果,如果没有结果,则返回 Nonefirst_or_404()  返回查询的第一个结果,如果没有结果,则终止请求,返回 404 错误响应get()           返回指定主键对应的行,如果没有对应的行,则返回 Noneget_or_404()    返回指定主键对应的行,如果没找到指定的主键,则终止请求,返回 404 错误响应count()         返回查询结果的数量paginate()      返回一个 Paginate 对象,它包含指定范围内的结果'''bootstrap = Bootstrap(app)app.config['SECRET_KEY'] = 'hard to guess string'def make_shell_context():  # 执行python hello.py shell时,会自动导入DB配置和数据库实例    return dict(app=app, db=db, User=User, Role=Role)manager.add_command("shell", Shell(make_context=make_shell_context))class NameForm(BaseForm):    name = StringField('what is your name?', validators=[Required()])    submit = SubmitField('Submit')@app.route('/', methods=['GET', 'POST'])def index():    form = NameForm()    if form.validate_on_submit():        user = User.query.filter_by(username=form.name.data).first()        if user is None:            user = User(username=form.name.data)            db.session.add(user)            session['known'] = False        else:            session['known'] = True        session['name'] = form.name.data        return redirect(url_for('index'))    return render_template('index.html', form=form, name=session.get('name'),known=session.get('known', False))if __name__ == '__main__':    # app.run(debug=True)    manager.run()index.html{% extends "bootstrap/base.html" %}{% import "bootstrap/wtf.html" as wtf %}{% block title %}Flasky{% endblock %}{% block content %}<div class="page-header">    <h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>    {% if not known %}    <p>Pleased to meet you!</p>    {% else %}    <p>Happy to see you again!</p>    {% endif %}</div>{{ wtf.quick_form(form) }}{% endblock %}

第六章 电子邮件

hello.pyimport osfrom threading import Threadfrom flask import Flaskfrom flask import render_templatefrom flask import session, url_for, redirectfrom flask import flashfrom flask.ext.bootstrap import Bootstrapfrom flask_script import Manager, Shellfrom flask.ext.wtf import FlaskForm as BaseFormfrom wtforms import StringField, SubmitFieldfrom wtforms.validators import Requiredfrom flask.ext.sqlalchemy import SQLAlchemyfrom flask.ext.mail import Mailfrom flask.ext.mail import Messageapp = Flask(__name__)manager = Manager(app)app.config['MAIL_SERVER'] = 'smtp.163.com'  # 邮件相关配置app.config['MAIL_PORT'] = 25app.config['MAIL_USE_TLS'] = Trueapp.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')  # 用户名密码配置在环境变量里面app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')mail = Mail(app)def send_email(mail_from, mail_to, subject, template, **kwargs):    msg = Message(subject, sender=mail_from, recipients=mail_to)    msg.body = render_template(template + '.txt', **kwargs)    msg.html = render_template(template + '.html', **kwargs)  #TODO: html的内容应该会覆盖body,不是很确定    mail.send(msg)def send_async_email(app, msg):     with app.app_context():        mail.send(msg)def send_email_async(mail_from, mail_to, subject, template, **kwargs):    msg = Message(subject, sender=mail_from, recipients=mail_to)    msg.body = render_template(template + '.txt', **kwargs)    msg.html = render_template(template + '.html', **kwargs)    thr = Thread(target=send_async_email, args=[app, msg])   # 异步发送邮件,否则浏览器界面会卡顿,当然最好是使用celery这样的任务队列    thr.start()    return thr'''Flask-Mail SMTP服务器的配置配  置             默认值          说  明MAIL_SERVER         localhost       电子邮件服务器的主机名或 IP 地址MAIL_PORT           25              电子邮件服务器的端口MAIL_USE_TLS        False           启用传输层安全(Transport Layer Security,TLS)协议MAIL_USE_SSL        False           启用安全套接层(Secure Sockets Layer,SSL)协议MAIL_USERNAME       None            邮件账户的用户名MAIL_PASSWORD       None            邮件账户的密码'''basedir = os.path.abspath(os.path.dirname(__file__))app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'data.sqlite')app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = Trueapp.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = Truedb = SQLAlchemy(app)class Role(db.Model):    __tablename__ = 'roles'    id = db.Column(db.Integer, primary_key=True)    name = db.Column(db.String(64), unique=True)    users = db.relationship('User', backref='role', lazy='dynamic')    def __repr__(self):        return '<Role %r>' % self.nameclass User(db.Model):    __tablename__ = 'users'    id = db.Column(db.Integer, primary_key=True)    username = db.Column(db.String(64), unique=True, index=True)    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))    def __repr__(self):        return '<User %r>' % self.usernamebootstrap = Bootstrap(app)app.config['SECRET_KEY'] = 'hard to guess string'def make_shell_context():    return dict(app=app, db=db, User=User, Role=Role)manager.add_command("shell", Shell(make_context=make_shell_context))class NameForm(BaseForm):    name = StringField('what is your name?', validators=[Required()])    submit = SubmitField('Submit')@app.route('/', methods=['GET', 'POST'])def index():    form = NameForm()    if form.validate_on_submit():        user = User.query.filter_by(username=form.name.data).first()        if user is None:            user = User(username=form.name.data)            db.session.add(user)            session['known'] = False            send_email_async(app.config['MAIL_USERNAME'], [app.config['MAIL_USERNAME']], 'New User', 'mail/new_user', user=user)        else:            session['known'] = True        session['name'] = form.name.data        return redirect(url_for('index'))    return render_template('index.html', form=form, name=session.get('name'),known=session.get('known', False))if __name__ == '__main__':    manager.run()templates/index,html{% extends "bootstrap/base.html" %}{% import "bootstrap/wtf.html" as wtf %}{% block title %}Flasky{% endblock %}{% block content %}<div class="page-header">    <h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>    {% if not known %}    <p>Pleased to meet you!</p>    {% else %}    <p>Happy to see you again!</p>    {% endif %}</div>{{ wtf.quick_form(form) }}{% endblock %}templates/mail/new_user.htmlUser <h1>{{ user.username }}</h1> has joined.templates/mail/new_user.txtUser {{ user.username }} has joined.

第七章 大型程序的结构

略,太复杂,写不清楚多文件 Flask 程序的基本结构|-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这种结构有 4 个顶级文件夹:Flask 程序一般都保存在名为 app 的包中和之前一样,migrations 文件夹包含数据库迁移脚本单元测试编写在 tests 包中和之前一样,venv 文件夹包含 Python 虚拟环境同时还创建了一些新文件:requirements.txt 列出了所有依赖包,便于在其他电脑中重新生成相同的虚拟环境config.py 存储配置manage.py 用于启动程序以及其他的程序任务
0 0
原创粉丝点击