码迷,mamicode.com
首页 > 编程语言 > 详细

Flask之旅《Flask Web开发:基于Python的Web应用开发实战》学习笔记

时间:2016-06-12 03:29:47      阅读:323      评论:0      收藏:0      [点我收藏+]

标签:


《Flask Web开发:基于Python的Web应用开发实战》




点击上方的“目录”快速到达哦!

虽然简单的网站(Flask+Python+SAE)已经上线,但只是入门。开发大型网站,系统地学习一遍还是有必要的。


1 虚拟环境


2016-6-8
书上介绍了 virtualenv,每个venv都会拷贝一份packages到项目 /venv目录。

比较了一下conda管理环境,可能conda更胜一筹:点击打开链接

或者用 virtualenvwrapper: 点击打开链接

git tag 列出所有打tag的分支
git checkout <tag_name> 切换到tag
git reset --hard 不保留修改
.gitignore:指定哪些文件或目录不作同步,比如 ./venv/,*.pyc,数据库文件.sqlite3, .mysql

推荐IDE: PyCharm 2016.1
导入已有的virtualenv: File -> Setting -> Project Interprater -> 选择项目目录下的/venv/Python
-> Flask Project
-> jump between View funcion and Templates
-> Git


2 基本结构


初始化:
# -*- coding: utf-8 -*-
from flask import Flask
    app = Flask(__name__)
Flask类的构造函数只有一个必须指定的参数,即程序主模块或包的名字。在大多数程序中,Python的__name__变量就是所需的值。Flask用这个参数决定程序的根目录,以便稍后能够找到相对于程序根目录的资源文件位置

路由 (route)和视图函数 (view function):
定义路由的最简便方式,是使用程序实例提供的app.route修饰器,把修饰的函数注册为路由
@app.route('/')
def index():
    return '<h1>Hello World!</h1>'
修饰器是Python语言的标准特性,可以使用不同的方式修改函数的行为。惯常用法是使用修饰器把函数注册为事件的处理程序。

动态路由:地址中可以包含可变部分,Flask支持在路由中使用int、float和path类型。path类型也是字符串,但不把斜线视作分隔符
@app.route('/user/<name>')
def user(name):
    return '<h1>Hello, %s!</h1>' %name
@app.route('/user/<int:id>') # 不能有空格!
def ...
默认端口是5000,可以改成其它的(flask_script.Manager也有此功能)
# 有些端口不能用,查询已占用的端口:netstat -ano;netstat -aon|findstr "6000";tasklist|findstr "<PID>";taskkill /f /t /im XXX.exe
app.run(debug=True, port=7777) 
  1. 公认端口(Well Known Ports):从0到1023,紧密绑定(binding)于一些服务。通常这些端口的通讯明确表明了某种服务协议。80端口实际上总是HTTP通讯。
  2. 注册端口(Registered Ports):从1024到49151。它们松散地绑定于一些服务。这些端口同样用于许多其它目的。例如:许多系统处理动态端口从1024左右开始。
  3. 动态和/或私有端口(Dynamic and/or Private Ports):从49152到65535。理论上,不应为服务分配这些端口。实际上,机器通常从1024起分配动态端口。

请求-响应循环

Context 上下文全局变量:
  • current_app 程序上下文 当前激活程序的程序实例
  • g 程序上下文 处理请求时用作临时存储的对象。每次请求都会重设这个变量
  • request 请求上下文 请求对象,封装了客户端发出的HTTP请求中的内容
  • session 请求上下文 用户会话,用于存储请求之间需要“记住”的值的词典
URL映射是URL和视图函数之间的对应关系。Flask使用app.route修饰器或者非修饰器形式的app.add_url_rule()生成映射。
app.url_map
Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>,
<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/user/<name>' (HEAD, OPTIONS, GET) -> user>])
HEAD、Options、GET是请求方法,由路由进行处理。Flask为每个路由都指定了请求方法,这样不同的请求方法发送到相同的URL上时,会使用不同的视图函数进行处理。HEAD和OPTIONS方法由Flask自动处理

请求 Hook: 在请求钩子函数和视图函数之间共享数据一般使用上下文全局变量g
  1. before_first_request ?  :注册一个函数,在处理第一个请求之前运行。
  2. before_request ?  :注册一个函数,在每次请求之前运行。
  3. after_request ?  :注册一个函数,如果没有未处理的异常抛出,在每次请求之后运行。
  4. teardown_request ?  :注册一个函数,即使有未处理的异常抛出,也在每次请求之后运行
响应(视图函数返回)
  • make_response()函数可接受1~3个参数 (html, 状态码,header)
response = make_response('<h1>This document carries a cookie!</h1>', 200)
response.set_cookie('answer', '42')
return response
  • 重定向的特殊响应类型,302:return redirect(‘http://www.example.com‘)
  • 特殊的响应由abort函数生成,用于处理错误:abort(404)
Flask扩展
原书更正: Importing flask.ext.script is deprecated, use flask_script instead.


3 模板 template


业务逻辑和表现逻辑 要分开
按功能分,或按Division分

Jinja2模板引擎
模板是一个包含响应文本的文件,其中包含用占位变量表示的动态部分,其具体值只在请求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程称为渲染。
@app.route('/user/<name>')
def user(name):
    return render_template('user.html', name=name)
模板变量
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>
可以使用过滤器修改变量。千万别在不可信的值上使用safe过滤器,例如用户在表单中输入的文本
Hello, {{ name|capitalize }
  • safe 渲染值时不转义。默认情况下,出于安全考虑,Jinja2会转义所有变量
  • capitalize 把值的首字母转换成大写,其他字母转换成小写
  • lower 把值转换成小写形式
  • upper 把值转换成大写形式
  • title 把值中每个单词的首字母都转换成大写
  • trim 把值的首尾空格去掉
  • striptags 渲染之前把值中所有的HTML标签都删掉
控制结构,可用来改变模板的渲染流程 if, for, macro, import, include

需要在多处重复使用的模板代码片段可以写入单独的文件,再包含 {%include ‘common.html‘ %} 在所有模板中

另一种重复使用代码的强大方式是模板继承,block标签定义的元素可在衍生模板中修改
<html>
<head>
  {%block head %}
  <title>{%block title %}{%endblock %} - My Application</title>
  {%endblock %}
</head>
<body>
  {%block body %}
  {%endblock %}
</body>
</html>
extends指令声明这个模板衍生自base.html。在extends指令之后,基模板中的3个块被重新定义,模板引擎会将其插入适当的位置。注意新定义的head块,在基模板中其内容不是空的,所以使用super()获取原来的内容(向已经有内容的块中添加新内容)。
{%extends "base.html" %}
{%block title %}Index{%endblock %}
{%block head %}
  {{ super() }}
  <style>
  </style>
{%endblock %}
{%block body %}
  <h1>Hello, World!</h1>
{%endblock %}
使用Flask-Bootstrap
Bootstrap是客户端框架,因此不会直接涉及服务器。服务器需要做的只是提供引用了Bootstrap层 叠样式表(CSS)和JavaScript文 件的HTML响 应,并在HTML、CSS和
JavaScript代码中实例化所需组件。这些操作最理想的执行场所就是模板。
Flask-Bootstrap基模板中定义的块:doc, head, title, styles, body, navbar, content, scripts
Bootstrap官方文档

CDN本地加速:
修改 Base.html,引用本地的css 文件,里面元素跟Bootstrap 重名的,则会覆盖官方里相同元素
{% 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">
  <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css">
  <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap-theme.min.css">
{% endblock %}
。。。
{% block scripts %}
  {{super()}}
  <script src="//cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
  <script src="//cdn.bootcss.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
  {{ moment.include_moment() }}
  {{ moment.lang("zh-CN") }} 
{% endblock %}

自定义错误页面

@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404
url_for() 链接辅助函数
使用url_for()生成动态地址时,将动态部分作为关键字参数传入。例如,
url_for(‘user‘, name=‘john‘, _external=True)的返回结果是http://localhost:5000/user/john

使用Flask-Moment本地化日期和时间查阅文档
{%block scripts %}
  {{ super() }}
  {{ moment.include_moment() }}
  <!--使用中文,默认是英语的-->
  {{ moment.lang("zh-CN") }} 
{%endblock %}

4 Web Form表单


app.config字典可用来存储框架、扩展和程序本身的配置变量。使用标准的字典句法就能把配置值添加到app.config对象中。这个对象还提供了一些方法,可以从文件或环境中导入配置值。

Form基类由Flask-WTF扩展定义,所以从flask.ext.wtf中导入。字段和验证函数 可以直接从WTForms包中导入。
from flask_wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
class NameForm(Form):
    name = StringField('What is your name?', validators=[DataRequired()])
    submit = SubmitField('Submit')

WTForms支持的HTML标准字段:StringField,TextAreaField,PasswordField。。。
WTForms内建的验证函数:Email,DataRequired...

重定向和用户会话
刷新页面后会再次提交表单。大多数情况下,这并不是理想的处理方式。
很多用户都不理解浏览器发出的这个警告。 基于这个原因,最好别让 Web 程序把 POST 求作为浏览器发送的最后一个请求。
这个技巧称为Post/重定向/Get模式

@app.route('/', methods=['GET', 'POST'])
def index():
  form = NameForm()
  if form.validate_on_submit():
    session['name'] = form.name.data
    return redirect(url_for('index'))
  return render_template('index.html', form=form, name=session.get('name'))

使用get()获取字典中键对应的值以避免未找到键的异常情况,因为对于不存在的键,get()会返回默认值None

Flash消息
仅调用flash()函数并不能把消息显示出来,程序使用的模板要渲染这些消息。最好在base.html 中渲染Flash消息,因为这样所有页面都能使用这些消息。Flaskget_flashed_messages()函数开放给模板,用来获取并渲染消息


5 数据库


2016-6-9
使用SQL还是NoSQL
SQL 数据库擅于用高效且紧凑的形式存储结构化数据。这种数据库需要花费大量精力保证数据的一致性。NoSQL 数据库放宽了对这种一致性的要求,从而获得性能上的优势。

MySQL Q&A:
安装MySQL in Windows 报错:需要预先把Windows Defender 打开,或者configure mysql server时,不要勾选“Windows Firewall”
本地安装MySQLdb:  pip install mysql-python
Window7 64位下安装可能还会报 cl.exe错
workaround: 先conda install mysql-python,再手动复制以下目录及文件到 venv\Lib\Site-packages下:
Anaconda2\Lib\site-packages\MySQLdb
Anaconda2\Lib\site-packages\MySQL_python-1.2.5.dist-info
Anaconda2\Lib\site-packages\_mysql*
MySQL创建connection之后,还需要创建“schema” --> 对应SQLAlchemy里的“database”

MySQLdb 中文乱码的处理:

conn = MySQLdb.connect(host=‘localhost‘, user=‘root‘, passwd=‘XXX‘, db=‘app_englishgo‘, charset = ‘utf8‘)

显示:title.encode(‘gbk‘)

接收输入:unicode(request.form[‘title‘])


SQLAlchemy 和MongoEngine:数据库抽象层代码包(ORM、ODM),你可以使用这些抽象包直接处理高等级的 Python 对象,而不用处理如表、文档或查询语言此类的数据库实体。

使用Flask-SQLAlchemy管理数据库
MySQL mysql://username:password@hostname:port/database
SQLite(Windows) sqlite:///c:/absolute/path/to/database

Relationship 关系型数据库

class Role(db.Model):
# ...
  users = db.relationship('User', backref='role') # 面向对象视角
class User(db.Model):
# ...
  role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) # 定义外键

SQLAlchemy 完整的命令、过滤器、查询执行 列表参见 SQLAlchemy 文档

集成Python shell

每次启动 shell 会话都要导入数据库实例和模型。为避免重复导入,我们可以做些配置,让 Flask-Script 的 shell 命令自动导入特定的对象。为 shell 命令注册一个 make_context 回调函数


新数据库迁移 flask-migrate

由于模型中经常会新加一行或几行column (比如用来保存账户的确认状态),此时要修改 models.py,并执行一次新数据库迁移


6 E-mail


使用Flask-Mail提供电子邮件支持
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')
app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]'
app.config['FLASKY_MAIL_SENDER'] = 'Flasky Admin <flasky@example.com>'
app.config['FLASKY_ADMIN'] = os.environ.get('FLASKY_ADMIN')

mail = Mail(app)

def sendmail(mail):
    msg = Message('test subject', sender='ezhqing@163.com', recipients = ['XXX@qq.com'])
    msg.body = 'text body'
    msg.html = '<b>HTML</b> body'
    with app.app_context():
        mail.send(msg)
千万不要把账户密令直接写入脚本,特别是当你计划开源自己的作品时。让脚本从本机环境中导入敏感信息
Windows 用户可按照下面的方式设定环境变量:
(venv) $ set MAIL_USERNAME=<your mail username>
(venv) $ set MAIL_PASSWORD=<mail password>

异步发送电子邮件
为了避免处理请求过程中不必要的延迟,我们可以把发送电子邮件的函数移到后台线程(Threading)中
很多 Flask 扩展都假设已经存在激活的程序上下文和请求上下文。Flask-Mail 中的 send() 函数使用 current_app ,因此必须激活程序上下文。不过,在不同线程中执行 mail.send() 函数时,程序上下文要使用 app.app_context() 人工创建。


7 大型程序的结构


项目结构
|-flasky
  |-app/	Flask 程序一般都保存在名为 app 的程序包中
    |-templates/	templates 和 static 文件夹是程序包的一部分
    |-static/
    |-main/	程序包中创建了一个子包,用于保存蓝本。/main/__init__.py脚本的末尾导入views.py & errors.py,避免循环导入依赖
    |-__init__.py	程序工厂函数 create_app(),注册蓝本
    |-errors.py 错误处理路由. 注册程序全局的错误处理程序,必须使用 app_errorhandler
    |-forms.py  表单对象
    |-views.py  路由。蓝本中的全部端点会加上一个命名空间,如 url_for('main.index')
  |-__init__.py
  |-email.py	电子邮件支持函数
  |-models.py	数据库模型
  |-migrations/	数据库迁移脚本
  |-tests/	单元测试
    |-__init__.py  文件可以为空,因为 unittest 包会扫描所有模块并查找测试
    |-test*.py
  |-venv/ 虚拟环境
  |-requirements.txt	列出了所有依赖包,便于在其他电脑中重新生成相同的虚拟环境
  |-config.py	存储配置。开发、测试和生产环境要使用不同的数据库
  |-manage.py	用于启动程序以及其他的程序任务

requirements.txt 文件,用于记录所有依赖包及其精确的版本号。以便要在另一台电脑上重新生成虚拟环境
创建:(venv) $ pip freeze > requirements.txt
恢复:(venv) $ pip install -r requirements.txt

重组后的程序和单脚本版本使用不同的数据库
可使用如下命令创建数据表或者升级到最新修订版本:(venv) $ python manage.py db upgrade


8 用户认证


Flask的认证扩展
Flask-Login:管理已登录用户的用户会话。 
Werkzeug:计算密码散列值并进行核对。 
itsdangerous:生成并核对加密安全令牌。 

创建认证蓝本
对于不同的程序功能,我们要使用不同的蓝本(main, auth),这是保持代码整齐有序的好方法
因为 Flask 认为模板的路径是相对于程序模板文件夹而言的。为避免与 main 蓝本和后续添加的蓝本发生模板命名冲突,可以把蓝本使用的模板保存在单独的文件夹中

使用Flask-Login认证用户
LoginManager 对象的 session_protection 属性可以设为 None 、 ‘basic‘ 或 ‘strong‘ ,以提供不同的安全等级防止用户会话遭篡改。设为 ‘strong‘ 时,Flask-Login 会记录客户端 IP地址和浏览器的用户代理信息,如果发现异动就登出用户。

为了保护路由只让认证用户访问,Flask-Login 提供了一个 login_required 修饰器
current_user 由 Flask-Login 定义,且在视图函数和模板中自动可用

模板中加入用户登录后的信息和提示效果 base.html:
技术分享
            <ul class="nav navbar-nav navbar-right">
                {% if current_user.is_authenticated %}
                <li><a href="{{ url_for('auth.logout') }}" title = "邮件 {{ current_user.email[:9]+'...' }}">Log Out {{ current_user.username }}</a></li>
                {% else %}
                <li><a href="{{ url_for('auth.login') }}">Log In</a></li>
                {% endif %}
            </ul>

按照第 4 章介绍的“Post/ 重定向 /Get 模式”,Login的 POST 请求最后也做了重定向,不过目标 URL 有两种可能。用户访问未授权的 URL 时会显示登录表单,Flask-Login 会把原地址保存在查询字符串的next参数中,这个参数可从 request.args 字典中读取。如果查询字符串中没有 next 参数,则重定向到首页
app/auth/views.py
        user = User.query.filter_by(email=form.email.data).first()
        if user is not None and user.verify_password(form.password.data):
            login_user(user, form.remember_me.data)
            return redirect(request.args.get('next') or url_for('main.index'))

用户注册表单 app/auth/forms.py
这个表单使用 WTForms 提供的 Regexp 验证函数,确保 username 字段只包含字母、数字、下划线和点号。
密码要输入两次。此时要验证两个密码字段中的值是否一致,这种验证可使用WTForms 提供的另一验证函数实现,即 EqualTo
如果表单类中定义了以validate_ 开头且后面跟着字段名的方法,这个方法就和常规的验证函数一起调用

发送确认邮件
使用itsdangerous生成确认令牌
>>> from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
>>> s = Serializer(app.config['SECRET_KEY'], expires_in = 3600)
>>> token = s.dumps({ 'confirm': 23 })
>>> token
'eyJhbGciOiJIUzI1NiIsImV4cCI6MTM4MTcxODU1OCwiaWF0IjoxMzgxNzE0OTU4fQ.ey ...'
>>> data = s.loads(token)
>>> data
{u'confirm': 23}

对蓝本来说, before_request 钩子只能应用到属于蓝本的请求上。若想在蓝本中使用针对程序全局请求的钩子,必须使用 before_app_request 修饰器



9 User Role 角色


2016-6-10
角色在数据库中的表示

赋予角色

角色验证







10 User Profile 资料


2016-6-10
用户资料页面
技术分享

资料编辑器





11 Blog articles









12 followers 关注者









13 User comments 评论








14 RIA (API, REST)







15 Testing







16 Performance 性能








17 Deploy







其它:

PEP & Import

PEP 8: 每级缩进使用4个空格。不要使用tab
PEP 257: docstrings""" docstrings """
用了相对形式的import 点标记法:第一个.来表示当前目录,之后的每一个.表示下一个父目录。
from .modelsimport User
Flask blueprint
From: https://spacewander.github.io/explore-flask-zh/7-blueprints.html

我什么时候会用到蓝图?

什么是蓝图?

一个蓝图定义了可用于单个应用的视图,模板,静态文件等等的集合。举个例子,想象一下我们有一个用于管理面板的蓝图。这个蓝图将定义像/admin/login和/admin/dashboard这样的路由的视图。它可能还包括所需的模板和静态文件。你可以把这个蓝图当做你的应用的管理面板,管它是宇航员的交友网站,还是火箭推销员的CRM系统。

蓝图的杀手锏是将你的应用组织成不同的组件。假如我们有一个微博客,我们可能需要有一个蓝图用于网站页面,比如index.html和about.html。然后我们还需要一个用于在登录面板中展示最新消息的蓝图,以及另外一个用于管理员面板的蓝图。站点中每一个独立的区域也可以在代码上隔绝开来。最终你将能够把你的应用依据许多能完成单一任务的小应用组织起来。


  • 一个蓝图包括了可以作为独立应用的视图,模板,静态文件和其他插件。
  • 蓝图是组织你的应用的好办法。
  • 在分区式架构下,每个蓝图对应你的应用的一个部分。
  • 在功能式架构下,每个蓝图就只是视图的集合。所有的模板和静态文件都放在一块。
  • 要使用蓝图,你需要定义它,并在应用中用Flask.register_blueprint()注册它。
  • 你可以给一个蓝图中的所有路由定义一个动态URL前缀。
  • 你也可以给蓝图中的所有路由定义一个动态子域名。
  • 仅需五步走,你可以用蓝图重构一个应用。

flask-bootstrap 前端插件

pip install flask-sqlalchemy

pip install flask-login

...

from flask.ext.bootstrap import Bootstrap

bootstrap = Bootstrap(app)

然后 /template/目录下创建自己的 html,{% extends "bootstrap/base.html" %}

模板:\Lib\site-packages\flask_bootstrap\templates\bootstrap\

Bootstrap 导航栏 自定义配色:

{% block styles %}
{{super()}}
<link rel=stylesheet type=text/css href="{{ url_for(‘static‘, filename=‘style.css‘) }}">
{% endblock %}
其中 style.css 可以包含自定义的 .navbar-kevin {。。。} 
配色方案 http://work.smarchal.com/twbscolor/






Flask之旅《Flask Web开发:基于Python的Web应用开发实战》学习笔记

标签:

原文地址:http://blog.csdn.net/kevin_qq/article/details/51594639

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!