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


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




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

虽然简单的网站(Flask+Python+SAE)已经上线,但只是入门。开发大型网站,系统地学习一遍还是有必要的。
20161018: 实际应用网站上线,实现天涯VIP功能:http://tianya.heroku.com

1 虚拟环境


2016-6-8
书上介绍了 virtualenv,每个venv都会拷贝一份packages到项目 /venv目录。
virtualenv venv
venv\Scripts\activate.bat
(venv) $ pip freeze >requirements.txt
(venv) $ pip install -r requirements.txt
pip list --outdated
pip install --upgarade <Package1> <PackageN>
比较了一下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
特点:
-> new 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也有此功能)
python manage.py runserver -p 7777
# 有些端口不能用,查询已占用的端口: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 %}


2016-10-20: 以上本地加速证明是有误的!会重复下载本地和官方Bootstrap 的 css/js

解决:

/app/__init__.py


from flask_bootstrap import Bootstrap, WebCDN, ConditionalCDN, BOOTSTRAP_VERSION, JQUERY_VERSION, HTML5SHIV_VERSION, RESPONDJS_VERSION

def create_app(config_name):

	app = Flask(__name__)
	app.config.from_object(config[config_name])
	config[config_name].init_app(app)

	bootstrap.init_app(app)
	def change_cdn_domestic(tar_app):
		static = tar_app.extensions['bootstrap']['cdns']['static']
		local = tar_app.extensions['bootstrap']['cdns']['local']

		def change_one(tar_lib, tar_ver, fallback):
			tar_js = ConditionalCDN('BOOTSTRAP_SERVE_LOCAL', fallback,
									WebCDN('//cdn.bootcss.com/' + tar_lib + '/' + tar_ver + '/'))
			tar_app.extensions['bootstrap']['cdns'][tar_lib] = tar_js

		libs = {'jquery': {'ver': JQUERY_VERSION, 'fallback': local},
				'bootstrap': {'ver': BOOTSTRAP_VERSION, 'fallback': local},
				'html5shiv': {'ver': HTML5SHIV_VERSION, 'fallback': static},
				'respond.js': {'ver': RESPONDJS_VERSION, 'fallback': static}}
		for lib, par in libs.items():
			change_one(lib, par['ver'], par['fallback'])
	change_cdn_domestic(app)

# 。。。

	return app

外:本地加速 moment.js

这个文件也在国外服务器,访问很慢,而且 size=160KB

/app/templates/base.html

{% block scripts %}
{{ super() }}
{{ moment.include_moment(local_js="/static/moment-with-locales.min.js") }}
{{ moment.lang('zh-CN') }}
{% endblock %}

需要单独下载 https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.3/moment-with-locales.min.js 到本地目录 /app/static/


自定义错误页面

@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...

Placeholder提示:
password = PasswordField('Password', validators=[DataRequired()], render_kw={"placeholder": u"密码"})

重定向和用户会话
刷新页面后会再次提交表单。大多数情况下,这并不是理想的处理方式。
很多用户都不理解浏览器发出的这个警告。 基于这个原因,最好别让 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 文档
SQLAlchemy engine设置编码,防止中文乱码:
engine = create_engine('sqlite:///C:\\Temp\\testsqlalchemy.db', encoding='utf8', convert_unicode=True, echo=True)
# echo:显示出内部过程及SQL语句。debug或学习时打开
# encoding:防止乱码
'mysql://uid:pwd@localhost/mydb?charset=utf8'



集成Python shell

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


新数据库迁移 flask-migrate

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


MySQL Workbench 自动产生EER,可以清楚地看到各个表格之间关系:一对多 Foreign_Key、Index等

1) config.py: 

class DevelopmentConfig(Config):
    DEBUG = True
    # SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or 'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
    SQLALCHEMY_DATABASE_URI = 'mysql://USER:PASSWORD@localhost:3306/flaskr'
2) python manage.py deploy

3) MySQL Workbench: Database -> Reverse Engineer -> 选择 connection -> database -> 一路 Next




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>
所有的在cmd命令行下对环境变量的修改只对 当前窗口 有效,不是永久性的修改。也就是说当关闭此cmd命令行窗口后,将不再起作用。永久性修改环境变量的方法有两种:一种是直接修改注册表(overkill python script),另一种是通过我的电脑-〉属性-〉高级,来设置系统的环境变量


异步发送电子邮件
为了避免处理请求过程中不必要的延迟,我们可以把发送电子邮件的函数移到后台线程(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
用户资料页面


资料编辑器

用户头像

国内 gravatar.com被墙,改用其它方法:静态jpg头像

目录:c:\git\flasky\app\static\avatar\001.jpg ~ XXX.jpg

base.html:
<li class="dropdown">
    <a href="#" class="dropdown-toggle" data-toggle="dropdown">
        <img height="24px" src="{{ url_for('static', filename='avatar/')}}{{ current_user.avatar_hash }}">

models.py:
def gravatar(self, size=100, default='identicon', rating='g'):
    import random
    return  '%.3d.jpg' % random.randint(1, XXX)

user.html:
<div class="page-header">
    <img class="img-rounded profile-thumbnail" src="{{ url_for('static', filename='avatar/')}}{{ current_user.avatar_hash }}"">

_posts.html:
<li class="post">
    <div class="post-thumbnail">
        <a href="{{ url_for('.user', username=post.author.username) }}">
            <img height="40px" class="img-rounded profile-thumbnail" src="{{ url_for('static', filename='avatar/')}}{{ post.author.avatar_hash }}">

_commments.html:
<div class="comment-thumbnail">
    <a href="{{ url_for('.user', username=comment.author.username) }}">
        <img height="40px' class="img-rounded profile-thumbnail" src="{{ url_for('static', filename='avatar/')}}{{ post.author.avatar_hash }}">

效果:


扩展TODO:加入性别、用户自选头像。。。

11 Blog articles 博客文章


2016-6-12
实现 功能,即允许用户阅读、撰写博客文章。本章新技术:重用模板、分页显示长列表以及处理富文本。

生成虚拟信息用于测试,其中功能相对完善的是ForgeryPy
python manage.py shell
>>> User.generate_fake()
>>> Post.generate_fake(200)



添加分页导航
paginate() 方法的返回值是一个 Pagination 类对象,这个类在 Flask-SQLAlchemy 中定义。这个对象包含很多属性, 用于在模板中生成分页链接


使用MarkdownFlask-PageDown支持富文本文章
• PageDown: 使用JavaScript实现的客户端MarkdownHTML的转换程序。
• Flask-PageDown: 为Flask包装的PageDown,把PageDown集成到Flask-WTF表单中。
• Markdown: 使用Python实现的服务器端MarkdownHTML的转换程序。
• Bleach: 使用Python实现的HTML清理器。

博客文章的固定链接

博客文章编辑器




12 followers 关注者


2016-6-12
再论数据库关系
一对多关系:是最常用的关系类型,它把一个记录和一组相关的记录联系在一起。实现这种关系时,要在“多”这一侧加入一个外键,指向“一”这一侧联接的记录。

多对多关系: 这种问题的解决方法是添加第三张表, 这个表称为关联表。多对多关系可以分解成原表和关联表之间的两个一对多关系

若想显示所关注用户发布的所有文章,第一步显然先要获取这些用户,然后获取各用户的文章,再按一定顺序排列,写入单独列表。可是这种方式的伸缩性不好,随着数据库不断变大,生成这个列表的工作量也不断增长,而且分页等操作也无法高效率完成。获取博客文章的高效方式是只用一次查询。
完成这个操作的数据库操作称为联结。联结操作用到两个或更多的数据表, 在其中查找满足指定条件的记录组合, 再把记录组合插入一个临时表中,这个临时表就是联结查询的结果。


创建函数更新数据库这一技术经常用来更新已部署的程序,因为运行脚本更新比手动更新数据库更少出错。




13 User comments 评论


评论属于某篇博客文章,因此定义了一个从posts表到comments表的一对多关系。使用这个关系可以获取某篇特定博客文章的评论列表。
comments 表还和 users 表之间有一对多关系。通过这个关系可以获取用户发表的所有评论,还能间接知道用户发表了多少篇评论

管理评论,我们要在导航条中添加一个链接,具有权限的用户才能看到。



14 RIA (API, REST)


2016-6-12
资源就是一切
资源REST架构方式的核心概念。在REST架构中, 资源是程序中你要着重关注的事物。例如,在博客程序中,用户、博客文章和评论都是资源。

15 Testing


16 Performance 性能


17 Deploy 部署


创建新的Shell命令 deploy:
@manager.command
def deploy():
    """Run deployment tasks."""
    from flask.ext.migrate import upgrade
    from app.models import Role, User
    # migrate database to latest revision
    upgrade()
    # create user roles
    Role.insert_roles()
    # create self-follows for all users
    User.add_self_follows()

# 测试:User.generate_fake(100)
# Post.generate_fake(500)



Flask自带的开发Web服务器表现很差,因为它不是为生产环境设计的服务器。有两个可以在生产环境中使用、性能良好且支持Flask程序的服务器,分别是  Gunicorn 和  uWSGI
manage:app参数冒号左边的部分表示定义程序的包或者模块,冒号右边的部分表示包中程序实例的名字。注意,Gunicor默认使用端口8000,而Flask默认使用5000。

添加ProxyFix等WSGI中间件的方法是包装WSGI程序。收到请求时,中间件有机会审查环境,在处理请求之前做些修改。不仅Heroku需要使用ProxyFix中间件,任何使用反向代理的部署环境都需要。

架设服务器
在能够托管程序之前,服务器必须完成多项管理任务。
  • 安装数据库服务器,例如MySQL或Postgres。也可使用SQLite数据库,但由于其自身 •的种种限制,不建议用于生产服务器。
  • 安装邮件传输代理(Mail Transport Agent,MTA),例如Sendmail,用于向用户发送邮件。 
  • 安装适用于生产环境的Web服务器,例如Gunicorn或uWSGI。 
  • 为了启用安全HTTP,购买、安装并配置SSL证书。 
  • (可选,但强烈推荐)安装前端反向代理服务器,例如nginx或Apache。反向代理服务器能直接服务于静态文件,而把其他请求转发给程序使用的Web服务器。Web服务器
  • 监听localhost中的一个私有端口。
  • 强化服务器。这一过程包含多项任务,目标在于降低服务器被攻击的可能性,例如安装防火墙以及删除不用的软件和服务等






其它:


 
Flask-Cache插件为你提供一组装饰器来实现多种方式的缓存

开发自定义视图装饰器来帮助我们组织自己的代码

自定义的URL转换器将会让你很嗨地玩转URL:https://spacewander.github.io/explore-flask-zh/6-advanced_patterns_for_views_and_routing.html

Write a Tumblelog Application with Flask and MongoEngine

这是MongoDB官方文档中的一个教程,也是学习Flask开发的一个很好案例,尤其适合Flask+MongoDB开发的应用场景


The Hitchhiker’s Guide to Python!

这个资料虽然不直接与Flask有关,但对初学者,绝对有学习的价值


GitHub - humiaozuzu/awesome-flask: A curated list of awesome Flask resources and plugins


作者早些时候写的网上教程:

大多数内容是本书上写的更加 advanced,但以下话题只在“Mega Tutorial”里提到:






查找Flask扩展

一些值得研究的包。
  • Flask-Babel(https://pythonhosted.org/Flask-Babel/):提供国际化和本地化支持。 
  • FLask-RESTful(http://flask-restful.readthedocs.org/en/latest/):开发REST API的工具。 
  • Celery(http://docs.celeryproject.org/en/latest/):处理后台作业的任务队列。 •
  • Frozen-Flask(https://pythonhosted.org/Frozen-Flask/):把Flask程序转换成静态网站。 
  • Flask-DebugToolbar:在浏览器中使用的调试工具。
  • Flask-Assets(https://github.com/miracle2k/flask-assets):用于合并、压缩、编译CSS和JavaScript静态资源文件。
  • Flask-OAuth(http://pythonhosted.org/Flask-OAuth/):使用OAuth服务进行认证。
  • Flask-OpenID(http://pythonhosted.org/Flask-OpenID/):使用OpenID服务进行认证。
  • Flask-WhooshAlchemy: 使 用Whoosh实现Flask-SQLAlchemy模型的全文搜索。
  • Flask-KVsession(http://flask-kvsession.readthedocs.org/en/latest/):使用服务器端存储实现的另一种用户会话。
Flask官方扩展网站

PEP & Import

PEP 8: 每级缩进使用4个空格。不要使用tab
PEP 257: docstrings""" docstrings """
用了相对形式的import 点标记法:第一个.来表示当前目录,之后的每一个.表示下一个父目录。
from .models import User

什么时候会用到蓝图?

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_debugtoolbar


修改文件:/app/__init__.py
from flask_debugtoolbar import DebugToolbarExtension
toolbar = DebugToolbarExtension()
toolbar.init_app(app)







已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页