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

第二章 程序的基本结构

from flask import Flask
from flask import request  # 程序上下文包括current_app, g, 请求上下文包括request, session
from 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 '

hello world

your browser is %s

' %user_agent @app.route('/bad') def index_bad(): return '

bad request

'
, 400 # 返回状态码 @app.route('/make_response') def index_make_response(): response = make_response('

This document carried a cookie!

'
) response.set_cookie('ansewr', '42') return response @app.route('/redirect') def index_redirect(): return redirect('/bad') @app.route('/abort/') def index_abort(id): ids = ['1', 2, 3] if id not in ids: # id默认传的是字符串 abort(404) # abort不会把控制权交还给视图函数,而是交给web服务器 return id @app.route('/user/') def user(name): return '

hello, %s!

' %name @app.route('/user/str/') # 动态部分默认匹配字符串,也可以匹配定义的类型,flask支持int, float, path(字符串,但不把斜线视为分隔符)类型 def user_2(name): return '

hello, %s!

' %name # flask 实现了四种请求钩子(before_first_request, before_request, after_request, teardown_request),可在视图函数之前或之后执行 if __name__ == '__main__': app.run(debug = True)

第三章 模板

包含:jinja2引擎,flask-bootstrap,自定义错误页面,动态路由,静态文件引用,flask-moment本地化日期时间
# hello.py
from datetime import datetime
from flask import Flask
from flask import render_template  # 函数把Jinja2模板引擎集成到了程序中
from flask import url_for  # 动态路由,也可以在html文件中使用

from flask.ext.bootstrap import Bootstrap
from flask.ext.moment import Moment

app = 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



{% 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">

{% 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 navigationspan>
                    <span class="icon-bar">span>
                    <span class="icon-bar">span>
                    <span class="icon-bar">span>
                button>
                <a class="navbar-brand" href="/">Flaskya>
            div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a href="/">Homea>li>
                ul>
            div>
        div>
    div>
{% endblock %}
{% block content %}
<div class="container">
    {% block page_content %}{% endblock %}
div>
{% endblock %}
{% block scripts %} 
{{ super() }}
{{ moment.include_moment() }}  
{{ moment.lang('zh-cn') }}  
{% endblock %}

# 404.html


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

# bootstrap_test.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 navigationspan>
                <span class="icon-bar">span>
                <span class="icon-bar">span>
                <span class="icon-bar">span>
            button>
            <a class="navbar-brand" href="/">Flaskya>
        div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a href="/">Homea>li>
            ul>
        div>
    div>
div>
{% endblock %}
{% block content %}
<div class="container">
    <div class="page-header">
        <h1>Hello, {{ name }}!h1>
    div>
div>
{% endblock %}



# index.html

{% extends "base.html" %}
{% block page_content %}
<h1>Hello World!h1>

<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


<h1>Hello, {{ name }}!h1>
<h1>Hello, {{ name|upper }}!h1>



# 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.py
from flask import Flask
from flask import render_template  # 函数把Jinja2模板引擎集成到了程序中
from flask import session, url_for, redirect
from flask import flash  # 请求完成后,有时需要让用户知道状态发生了变化。这里可以使用确认消息、警告或者错误提醒。一个典型例子是,用户提交了有一项错误的登录表单后,服务器发回的响应重新渲染了登录表单,并在表单上面显示一个消息,提示用户用户名或密码错误

from flask.ext.bootstrap import Bootstrap

from flask.ext.wtf import FlaskForm as BaseForm
from wtforms import StringField, SubmitField
from wtforms.validators import Required

app = 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" 的  元素。 SubmitField 类表示属性为 type="submit" 的 元素。字段构造函数的第一个参数是把表单渲染成 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.Decimal
FloatField          文本字段,值为浮点数
BooleanField        复选框,值为 True 和 False
RadioField          一组单选框
SelectField         下拉列表
SelectMultipleField 下拉列表,可选择多个值
FileField           文件上传字段
SubmitField         表单提交按钮
FormField           把表单作为字段嵌入另一个表单
FieldList           一组指定类型的字段

WTForms验证函数如表所示
验证函数             说  明
Email               验证电子邮件地址
EqualTo             比较两个字段的值;常用于要求输入两次密码进行确认的情况
IPAddress           验证 IPv4 网络地址
Length              验证输入字符串的长度
NumberRange         验证输入的值在数字范围内
Optional            无输入值时跳过其他验证函数
Required            确保字段中有数据
Regexp              使用正则表达式验证输入值
URL                 验证 URL
AnyOf               确保输入值在可选值列表中
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">
    
    {{ message }}
div>
{% endfor %}

<div class="page-header">

Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!

div> {{ wtf.quick_form(form) }} --Flask-Bootstrap 提供了一个非常高端的辅助函数,可以使用 Bootstrap 中预先定义好的表单样式渲染整个 Flask-WTF 表单--> {% endblock %}

第五章 数据库

hello.py
import os

from flask import Flask
from flask import render_template  # 函数把Jinja2模板引擎集成到了程序中
from flask import session, url_for, redirect
from flask import flash  # 请求完成后,有时需要让用户知道状态发生了变化。这里可以使用确认消息、警告或者错误提醒。一个典型例子是,用户提交了有一项错误的登录表单后,服务器发回的响应重新渲染了登录表单,并在表单上面显示一个消息,提示用户用户名或密码错误

from flask.ext.bootstrap import Bootstrap
from flask_script import Manager, Shell

from flask.ext.wtf import FlaskForm as BaseForm
from wtforms import StringField, SubmitField
from wtforms.validators import Required

from flask.ext.sqlalchemy import SQLAlchemy

app = 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 '' % 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)
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))  # role_id 列被定义为外键,传给 db.ForeignKey() 的参数 'roles.id' 表明,这列的值是 roles 表中行的 id 值

    def __repr__(self):
        return '' % 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()         返回查询的第一个结果,如果没有结果,则返回 None
first_or_404()  返回查询的第一个结果,如果没有结果,则终止请求,返回 404 错误响应
get()           返回指定主键对应的行,如果没有对应的行,则返回 None
get_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 %}
"page-header">

Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1> {% if not known %}

Pleased to meet you!> {% else %}

Happy to see you again!</p> {% endif %} > {{ wtf.quick_form(form) }} {% endblock %}

第六章 电子邮件

hello.py
import os
from threading import Thread

from flask import Flask
from flask import render_template
from flask import session, url_for, redirect
from flask import flash

from flask.ext.bootstrap import Bootstrap
from flask_script import Manager, Shell

from flask.ext.wtf import FlaskForm as BaseForm
from wtforms import StringField, SubmitField
from wtforms.validators import Required

from flask.ext.sqlalchemy import SQLAlchemy

from flask.ext.mail import Mail
from flask.ext.mail import Message

app = Flask(__name__)
manager = Manager(app)

app.config['MAIL_SERVER'] = 'smtp.163.com'  # 邮件相关配置
app.config['MAIL_PORT'] = 25
app.config['MAIL_USE_TLS'] = True
app.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'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = 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 '' % 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)
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

    def __repr__(self):
        return '' % self.username

bootstrap = 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 %}
"page-header">

Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!

{% if not known %}

Pleased to meet you!

{% else %}

Happy to see you again!

{% endif %}
{{ wtf.quick_form(form) }} {% endblock %} templates/mail/new_user.html User

{{ user.username }}

has joined. templates/mail/new_user.txt User {{ 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 用于启动程序以及其他的程序任务

你可能感兴趣的:(flask,book)