0x1 数据库html
1.1 Flask-SQLAlchemypython
git checkout 5amysql
Python数据库框架能够直接处理Python对象,不用处理表、文档或查询语言等数据库实体git
SQLAlcchemy是一个强大的关系型数据库框架,提供了高层ORM和使用原生SQL的底层功能sql
(venv) $ pip install flask-sqlalchemy
Flask-SQLAlchemy数据库URLshell
MySQL mysql://username:password@hostname/database Postgres postgresql://username:password@hostname/database SQLite(Unix) sqlite:////absolute/path/to/database SQLite(Windows) sqlite:///c:/absolute/path/to/database
SQLite不须要使用服务器,所以不用指定hostname、username、password 数据库
Flask-SQLAlchemy要求每一个模型都要定义主键,这一列常常命名为idflask
hello.pywindows
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)
程序使用的数据库URL必须保存到Flask配置对象的SQLALCHEMY_DATABASE_URI键中安全
SQLALCHEMY_COMMIT_ON_TEARDOWN设为True,每次请求结束后都会自动提交数据库中的变更
模型表示程序使用的持久化实体,ORM中,模型通常是一个Python类,类中属性对应数据库表中的列
定义模型:Flask-SQLAlchemy建立的数据库实例为模型提供了一个基类和一系列辅助类和辅助函数,用于定义模型的结构
hello.py:定义Role和User模型
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.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 '<User %r>' % self.username
__tablename__定义在数据库中使用的表名
__repr()__ 方法返回一个具备可读性的字符串表示模型,可在调试和测试时使用
添加到User模型中的role_id列被定义为外键,传给db.ForeignKey()的参数roles.id代表这列的值是roles表中行的id值
db.Column类构造函数的第一个参数是数据库列和模型属性的类型
最经常使用的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 为这列定义默认值
对于Role类的实例,其users属性将返回与角色相关联的用户组成的列表,db.relationship()第一个参数代表这个关系的另外一端是哪一个模型
经常使用SQLAlchemy关系选项
backref 在关系的另外一个模型中添加反向引用
primaryjoin 明确指定两个模型之间使用的联结条件。只在模棱两可的关系中须要指定
lazy 指定如何加载相关记录。可选值有 select (首次访问时按需加载)、 immediate (源对象加
载后就加载)、 joined (加载记录,但使用联结)、 subquery (当即加载,但使用子查询),
noload (永不加载)和 dynamic (不加载记录,但提供加载记录的查询)
1.2 数据库操做
git checkout 5b
建立表
(venv) $ python hello.py shell >>> from hello import db >>> db.create_all()
删除表
>>> db.drop_all()
插入行
>>> from hello import Role, User >>> admin_role = Role(name='Admin') >>> mod_role = Role(name='Moderator') >>> user_role = Role(name='User') >>> user_john = User(username='john', role=admin_role) >>> user_susan = User(username='susan', role=user_role) >>> user_david = User(username='david', role=user_role)
添加到会话
>>> db.session.add(admin_role) >>> db.session.add(mod_role) >>> db.session.add(user_role) >>> db.session.add(user_john) >>> db.session.add(user_susan) >>> db.session.add(user_david) 简写 >>> db.session.add_all([admin_role, mod_role, user_role, ... user_john, user_susan, user_david])
把对象写入数据库,调用commit()方法提交会话
>>> db.session.commit()
修改行:把 "Admin" 角色重命名为 "Administrator"
>>> admin_role.name = 'Administrator'
>>> db.session.add(admin_role) >>> db.session.commit()
删除行:把 "Moderator" 角色从数据库中删除
>>> db.session.delete(mod_role) >>> db.session.commit()
查询行
>>> Role.query.all() [<Role u'Administrator'>, <Role u'User'>] >>> User.query.all() [<User u'john'>, <User u'susan'>, <User u'david'>] >>> User.query.filter_by(role=user_role).all() [<User u'susan'>, <User u'david'>]
查看 SQLAlchemy 为查询生成的原生 SQL 查询语句,把 query 对象转换成字符串:
>>> str(User.query.filter_by(role=user_role))
起一个查询,加载名为"User"的用户角色
>>> user_role = Role.query.filter_by(name='User').first()
filter_by()过滤器在query对象上调用,返回更精确query对象
经常使用SQLAlchemy过滤器
完整SQLAlchemy文档:http://docs.sqlalchemy.org/en/latest/
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 对象,它包含指定范围内的结果
lazy='dynamic' 禁止自动执行查询,user_role.users回返回一个还没有执行的查询,所以能够添加过滤器
>>> user_role.users.order_by(User.username).all() [<User u'david'>, <User u'susan'>] >>> user_role.users.count() 2
1.3 在视图函数中操做数据库
hello.py:在视图函数中操做数据库
@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 form.name.data = ''
return redirect(url_for('index')) return render_template('index.html', form=form, name=session.get('name'),know=session.get('know',False))
提交表单后,程序使用filter_by()在数据库中查找提交的名字
index.html使用known参数显示用户是否在数据库中
templates/index.html
{% if not known %} <p>Pleased to meet you</p> {% else %} <p>Happy to see you again!</p> {% endif %}
集成Python shell,让Flask-Script的shell命令自动导入特定的对象
git checkout 5c
hello.py:为shell命令添加一个上下文
from flask.ext.script import Shell def make_shell_context(): return dict(app=app, db=db, User=User, Role=Role) manager.add_command("shell", Shell(make_context=make_shell_context))
1.4 Flask-Migrate数据库迁移
git checkout 5d
更新表的更好方法是使用数据库迁移框架
数据库迁移框架能跟踪数据库模式的变化,而后增量式的把变化应用到数据库中
(venv) $ pip install flask-migrate
hello.py:配置Flask-Migrate
from flask.ext.migrate import Migrate, MigrateCommand #...
migrate = Migrate(app, db) manager.add_command('db',MigrateCommand)
用init子命令迁移仓库:
(venv) $ python hello.py db init
这个命令会建立一个migrations文件夹,全部迁移脚本都存放其中
migrate 子命令用来自动建立迁移脚本:
(venv) $ python hello.py db migrate -m "initial migration"
更新数据库
(venv) $ python hello.py db upgrade
0x2 电子邮件
2.1 Flask-Mail
git checkout 6a
(venv) $ pip install flask-mail
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 邮件帐户的密码
hello.py:配置Flask-Mail使用Gmail
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')
hello.py:初始化Flask-Mail
from flask.ext.mail import Mail mail = Mail(app)
环境中定义用户名密码
Linux: (venv) $ export MAIL_USERNAME=<Gmail username> (venv) $ export MAIL_PASSWORD=<Gmail password> windows: (venv) $ set MAIL_USERNAME=<Gmail username> (venv) $ set MAIL_PASSWORD=<Gmail password>
Python shell中发送电子邮件
(venv) $ python hello.py shell >>> from flask.ext.mail import Message >>> from hello import mail >>> msg = Message('test subject', sender='xxx@qq.com', ... recipients=['xxx@qq.com']) >>> msg.body = 'text body'
>>> msg.html = '<b>HTML</b> body'
>>> with app.app_context(): ... mail.send(msg) ...
2.2 程序中集成发电子邮件功能
git checkout 6a
避免每次手动编邮件信息,还可使用Jinja2渲染邮件正文
from flask.ext.mail import Mail,Message app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = 'Flask' app.config['FLASKY_MAIL_SENDER'] = 'xxx@gmail.com' app.config['FLASKY_ADMIN'] = os.environ.get('FLASKY_ADMIN') def send_mail(to, subject, template, **kwargs): msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + ' '+subject, sender = app.config['FLASKY_MAIL_SENDER'], recipients=[to]) msg.body = render_template(template + '.txt', **kwargs) msg.html = render_template(template + '.html', **kwargs) mail.send(msg)
这个函数定义了邮件主题前缀和发件人地址
电子邮件收件人保存在FLASKY_ADMIN环境变量中
email函数参数分别为收件人地址、主题、渲染邮件正文的模板和关键字参数列表
指定模板时不能包含扩展名,这样才能使用两个模板分别渲染纯文本正文和富文本正文
index()要实现功能:表单提交数据库没有的用户名,程序就会向管理员发送一封电子邮件
hello.py:电子邮件示例
@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 if app.config['FLASKY_ADMIN']: send_mail(app.config['FLASKY_ADMIN'], 'New User', 'mail/new_user', user=user) else: session['known'] = True session['name'] = form.name.data form.name.data = ''
return redirect(url_for('index')) return render_template('index.html', form=form, name=session.get('name'),know=session.get('know',False))
定义FLASKY_ADMIN环境变量
Linux:(venv) $ export FLASKY_ADMIN=<your-email-address> Windows:(venv) $ set FLASKY_ADMIN=<Gmail username>
2.3 异步发送电子邮件
git checkout 6b
mail.send()函数在发送电子邮件是停滞了几秒,为避免请求过程当中没必要要的延迟,能够把发送电子邮件函数移到后台线程
hello.py:异步发送电子邮件
from threading import Thread def send_async_email(app, msg): with app.app_context(): mail.send(msg) def send_mail(to, subject, template, **kwargs): msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + ' '+subject, sender = app.config['FLASKY_MAIL_SENDER'], recipients=[to]) msg.body = render_template(template + '.txt', **kwargs) msg.html = render_template(template + '.html', **kwargs) thr = Thread(target=send_async_email, args=[app, msg]) thr.start() return thr
发送大量电子邮件时能够把执行send_async_email()函数操做发给Celery(http://www.celeryproject.org/)任务队列