[本文总结自Flask-Microblog教程] https://github.com/luhuisicnu/The-Flask-Mega-Tutorial-zh
1 输入exit()
并回车来退出交互式解释器,Linux和Mac OS X操作系统上,按下快捷键Ctrl-D也可以快速退出交互式解释器。在Windows操作系统上,则是通过按下Ctrl-Z后跟上Enter快捷键来快速退出。
2 为了解决维护不同应用程序对应不同版本的问题,Python使用了虚拟环境的概念。
3 在Python中,包含__init__.py
文件的子目录被视为一个可导入的包。 当你导入一个包时,__init__.py
会执行并定义这个包暴露给外界的属性。
4 导入类Flask
; __name__
变量表示当前调用它的模块的名字,使用这个位置作为起点来计算绝对路径; 在Flask中,应用程序路由的处理逻辑被编写为Python函数,称为视图函数。 视图函数被映射到一个或多个路由URL.
from flask import Flask
app = Flask(__name__)
from app import routes
4 示例视图函数代码, 装饰器的常见模式是使用它们将函数注册为某些事件的回调函数。
from app import app
@app.route('/')
@app.route('/index')
def index():
return "Hello, World!"
5 Flask 允许设置只会在运行flask
命令时自动注册生效的环境变量,要实现这点,需要安装 python-dotenv
新建.flaskenv
文件
FLASK_APP=microblog.py
6 创建模拟对象是一项实用的技术,它可以让你专注于应用程序的一部分,而无需为系统中尚不存在的其他部分分心。
7 模板有助于实现页面展现和业务逻辑之间的分离。 在Flask中,模板被编写为单独的文件,存储在应用程序包内的templates文件夹中。
<html>
<head>
<title>{{ title }} - Microblog</title>
</head>
<body>
<h1>Hello, {{ user.username }}!</h1>
</body>
</html>
{{ ... }}
包含的内容是动态的,只有在运行时才知道具体表示成什么样子。
8 将模板转换为完整的HTML页面的操作称为渲染。render_template()
函数需要传入模板文件名和模板参数的变量列表,并返回模板中所有占位符都用实际变量值替换后的字符串结果。其调用Flask框架原生依赖的Jinja2模板引擎。
return render_template('index.html', title='Home', user=user)
9 模板支持在{%...%}
块内使用控制语句。
{% if title %}
<title>{{ title }} - Microblog</title>
{% else %}
<title>Welcome to Microblog!</title>
{% endif %}
{% for post in posts %}
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %}
10 Jinja2有一个模板继承特性。使用block
控制语句来定义派生模板可以插入代码的位置。 block被赋予一个唯一的名称,派生的模板可以在提供其内容时进行引用。extends
语句用来建立了两个模板之间的继承关系。
base.html
{% block content %}{% endblock %}
index.html
{% extends "base.html" %}
{% block content %}
<h1>Hi, {{ user.username }}!</h1>
{% for post in posts %}
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %}
{% endblock %}
11 Web表单接受用户的输入。 Flask-WTF插件处理本应用中的Web表单。Flask故计为只包含核心功能以保持代码的整洁,并暴露接口以对接解决不同问题的插件。
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
remember_me = BooleanField('Remember Me')
submit = SubmitField('Sign In')
从flask_wtf
导入了名为FlaskForm
的基类。四个表示表单字段的类,每个字段类都接受一个描述或别名作为第一个参数,并生成一个实例来作为LoginForm
的类属性。
12 将这个配置类存储到单独的Python模块,以保持良好的组织结构。
import os
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
小写的“config”是Python模块config.py的名字,另一个含有大写“C”的是类。
from config import Config
app.config.from_object(Config)
13 表单模板-登录
{% extends "base.html" %}
{% block content %}
<h1>Sign In</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}
</p>
<p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
form来自于LoginForm
类的实例化; form.hidden_tag()
模板参数生成了一个隐藏字段,其中包含一个用于保护表单免受CSRF攻击的token
。 .
from app.forms import LoginForm
‘’‘
form = LoginForm()
14
-
form.validate_on_submit() 实例方法会执行form校验的工作。当浏览器发起
GET
请求的时候,它返回False
。并且会获取到所有的数据,运行字段各自的验证器,全部通过之后就会返回True
,这表示数据有效。不过,一旦有任意一个字段未通过验证,这个实例方法就会返回False
。 -
flash()
函数是向用户显示消息的有效途径。一旦通过get_flashed_messages
函数请求了一次,它们就会从消息列表中移除,所以在调用flash()
函数后它们只会出现一次。 -
redirect()`函数指引浏览器自动重定向到它的参数所关联的URL。
-
url_for()
函数使用URL到视图函数的内部映射关系来生成URL。例如,url_for('login')
返回/login
。url_for()
的参数是endpoint名称,也就是视图函数的名字。
15 关系数据库更适合具有结构化数据的应用程序,例如用户列表,用户动态等,而NoSQL数据库往往更适合非结构化数据。
Flask-SQLAlchemy,这个插件为流行的SQLAlchemy包做了一层封装以便在Flask中调用更方便,类似SQLAlchemy这样的包叫做Object Relational Mapper,简称ORM。 ORM允许应用程序使用高级实体(如类,对象和方法)而不是表和SQL来管理数据库。 ORM的工作就是将高级操作转换成数据库命令。
Flask-Migrate。 这个插件是Alembic的一个Flask封装,是SQLAlchemy的一个数据库迁移框架。
16 models
的模块用来定义数据库结构。
id
字段通常存在于所有模型并用作主键。
from app import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
def __repr__(self):
return '<User {}>'.format(self.username)
该类的__repr__
方法用于在调试时打印用户实例。
17 flask
命令依赖于FLASK_APP
环境变量来知道Flask应用入口在哪里。
18 post
表将具有必须的id
、用户动态的body
和timestamp
字段。 除了这些预期的字段之外,还添加了一个user_id
字段,将该用户动态链接到其作者。 你已经看到所有用户都有一个唯一的id
主键, 将用户动态链接到其作者的方法是添加对用户id
的引用,这正是user_id
字段所在的位置。 这个user_id
字段被称为外键。
class User(db.Model):
‘’‘
posts = db.relationship('Post', backref='author', lazy='dynamic')
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return '<Post {}>'.format(self.body)
19 以通过db.session
进行访问验证,db.session.commit()
来以原子方式写入所有更改。只有在调用db.session.commit()
时才会将更改写入数据库。所有模型都有一个query
属性,它是运行数据库查询的入口。
>>> u = User(username='john', email='john@example.com')
>>> db.session.add(u)
>>> db.session.commit()
users = User.query.all()
>>> u = User.query.get(1)
>>> p = Post(body='my first post!', author=u)
>>> db.session.add(p)
>>> db.session.commit()
20 生成与验证密码
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model):
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
21 Flask-Login插件管理用户登录状态,以便用户可以登录到应用,然后用户在导航到该应用的其他页面时,应用会“记得”该用户已经登录。它还提供了“记住我”的功能,允许用户在关闭浏览器窗口后再次访问应用时保持登录状态。
from flask_login import LoginManager
app = Flask(__name__)
login = LoginManager(app)
22 以下可以通过UserMin类提供:
-
is_authenticated
: 一个用来表示用户是否通过登录认证的属性,用True
和False
表示。 -
is_active
: 如果用户账户是活跃的,那么这个属性是True
,否则就是False
(译者注:活跃用户的定义是该用户的登录状态是否通过用户名密码登录,通过“记住我”功能保持登录状态的用户是非活跃的)。 -
is_anonymous
: 常规用户的该属性是False
,对特定的匿名用户是True
。 -
get_id()
: 返回用户的唯一id的方法,返回值类型是字符串(Python 2下返回unicode字符串).用户会话是Flask分配给每个连接到应用的用户的存储空间,Flask-Login通过在用户会话中存储其唯一标识符来跟踪登录用户。每当已登录的用户导航到新页面时,Flask-Login将从会话中检索用户的ID,然后将该用户实例加载到内存中。
23 current_user
变量来自Flask-Login,可以在处理过程中的任何时候调用以获取用户对象。 这个变量的值可以是数据库中的一个用户对象(Flask-Login通过我上面提供的用户加载函数回调读取),或者如果用户还没有登录,则是一个特殊的匿名用户对象。
注册: forms.py 注册 class RegistrationForm(FlaskForm) register.html <p>{{ form.submit() }}</p>
routes.py form = RegistrationForm() db.session.commit()
24 User
类新增的avatar()
方法需要传入需求头像的像素大小,并返回用户头像图片的URL。
from hashlib import md5
class User(UserMixin, db.Model):
def avatar(self, size):
digest = md5(self.email.lower().encode('utf-8')).hexdigest()
return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format(
digest, size)
渲染一条用户动态的子模板_post.html
<table>
<tr valign="top">
<td><img src="{{ post.author.avatar(36) }}"></td>
<td>{{ post.author.username }} says:<br>{{ post.body }}</td>
</tr>
</table>
{% for post in posts %}
{% include '_post.html' %}
{% endfor %}
25 数据库迁移脚本
flask db migrate -m "new fields in user model"
flask db upgrade
26 Flask中的@before_request
装饰器注册在视图函数之前执行的函数。
一旦某个用户向服务器发送请求,就将当前时间写入到这个字段。
from datetime import datetime
@app.before_request
def before_request():
if current_user.is_authenticated:
current_user.last_seen = datetime.utcnow()
db.session.commit()
27 调试模式,它是Flask在浏览器上直接运行一个友好调试器的模式。
(venv) $ export FLASK_DEBUG=1
永远不要在生产服务器上以调试模式运行Flask应用.
使用@errorhandler
装饰器来声明一个自定义的错误处理器。/errors.py
from flask import render_template
from app import app, db
@app.errorhandler(404)
def not_found_error(error):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback() # 为了确保任何失败的数据库会话不会干扰模板触发的其他数据库访问,我执行会话回滚来将会话重置为干净的状态。
return render_template('500.html'), 500
28 Flask使用Python的logging
包来写它的日志,而且这个包能够通过电子邮件发送日志。需要为Flask的日志对象app.logger
添加一个SMTPHandler的实例。
import logging
from logging.handlers import SMTPHandler
Python的SMTP调试服务器。 这是一个模拟的电子邮件服务器.
(venv) $ python -m smtpd -n -c DebuggingServer localhost:8025
需要设置MAIL_SERVER=localhost和MAIL_PORT=8025
29 基于文件类型RotatingFileHandler的日志记录器。
from logging.handlers import RotatingFileHandler
import os
if not app.debug:
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/microblog.log', maxBytes=10240,
backupCount=10)
#RotatingFileHandler类非常棒,因为它可以切割和清理日志文件,以确保日志文件在应用运行很长时间时不会变得太大。 本处,我将日志文件的大小限制为10KB,并只保留最后的十个日志文件作为备份。
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Microblog startup')
日志级别:严重程度递增的顺序分别是DEBUG
、INFO
、WARNING
、ERROR
和CRITICAL
。
30 一个类的实例被关联到同一个类的其他实例的关系被称为自引用关系。
Python包含一个非常有用的unittest
包,可以轻松编写和执行单元测试。
31 Flask有一个名为Flask-Mail的流行插件,通过app.config
对象来配置。
32 Python实际上有多种方式支持运行异步任务,threading
和multiprocessing
模块都可以做到这一点。
from threading import Thread
# ...
def send_async_email(app, msg):
with app.app_context():
mail.send(msg)
def send_email(subject, sender, recipients, text_body, html_body):
msg = Message(subject, sender=sender, recipients=recipients)
msg.body = text_body
msg.html = html_body
Thread(target=send_async_email, args=(app, msg)).start()
注意:
1 若更改多次代码结果仍无变化,请检查是否更改后重启了flask