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

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

20161018: 实际应用网站上线,实现天涯VIP功能:http://tianya.heroku.com

1 虚拟环境

2016-6-8

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

[plain] view plain copy

  1. virtualenv venv
  2. venv\Scripts\activate.bat
  3. (venv) $ pip freeze >requirements.txt
  4. (venv) $ pip install -r requirements.txt
  5. pip list --outdated
  6. pip install --upgarade <Package1> <PackageN>

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

或者用 virtualenvwrapper: 点击打开链接

[plain] view plain copy

  1. git tag 列出所有打tag的分支
  2. git checkout <tag_name> 切换到tag
  3. 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 基本结构

初始化:

[python] view plain copy

  1. # -*- coding: utf-8 -*-
  2. from flask import Flask
  3. app = Flask(__name__)

Flask类的构造函数只有一个必须指定的参数,即程序主模块或包的名字。在大多数程序中,Python的__name__变量就是所需的值。Flask用这个参数决定程序的根目录,以便稍后能够找到相对于程序根目录的资源文件位置

路由 (route)和视图函数 (view function):

定义路由的最简便方式,是使用程序实例提供的app.route修饰器,把修饰的函数注册为路由

[python] view plain copy

  1. @app.route('/')
  2. def index():
  3. return '<h1>Hello World!</h1>'

修饰器是Python语言的标准特性,可以使用不同的方式修改函数的行为。惯常用法是使用修饰器把函数注册为事件的处理程序。

动态路由:地址中可以包含可变部分,Flask支持在路由中使用int、float和path类型。path类型也是字符串,但不把斜线视作分隔符

[python] view plain copy

  1. @app.route('/user/<name>')
  2. def user(name):
  3. return '<h1>Hello, %s!</h1>' %name
  4. @app.route('/user/<int:id>') # 不能有空格!
  5. 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

[python] view plain copy

  1. 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()生成映射。

[python] view plain copy

  1. app.url_map
  2. Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>,
  3. <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
  4. <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)

[python] view plain copy

  1. response = make_response('<h1>This document carries a cookie!</h1>', 200)
  2. response.set_cookie('answer', '42')
  3. 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模板引擎

模板是一个包含响应文本的文件,其中包含用占位变量{{...}}表示的动态部分,其具体值只在请求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程称为渲染。

[python] view plain copy

  1. @app.route('/user/<name>')
  2. def user(name):
  3. return render_template('user.html', name=name)

模板变量

Jinja2能识别所有类型的变量,甚至是一些复杂的类型,例如列表、字典和对象

[html] view plain copy

  1. <p>A value from a dictionary: {{ mydict['key'] }}.</p>
  2. <p>A value from a list: {{ mylist[3] }}.</p>
  3. <p>A value from a list, with a variable index: {{ mylist[myintvar] }}.</p>
  4. <p>A value from an object's method: {{ myobj.somemethod() }}.</p>

可以使用过滤器修改变量。千万别在不可信的值上使用safe过滤器,例如用户在表单中输入的文本

[html] view plain copy

  1. Hello, {{ name|capitalize }

完整的过滤器列表

  • safe 渲染值时不转义。默认情况下,出于安全考虑,Jinja2会转义所有变量
  • capitalize 把值的首字母转换成大写,其他字母转换成小写
  • lower 把值转换成小写形式
  • upper 把值转换成大写形式
  • title 把值中每个单词的首字母都转换成大写
  • trim 把值的首尾空格去掉
  • striptags 渲染之前把值中所有的HTML标签都删掉

控制结构{%...%},可用来改变模板的渲染流程 if, for, macro, import, include

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

另一种重复使用代码的强大方式是模板继承,block标签定义的元素可在衍生模板中修改

[html] view plain copy

  1. <html>
  2. <head>
  3. {%block head %}
  4. <title>{%block title %}{%endblock %} - My Application</title>
  5. {%endblock %}
  6. </head>
  7. <body>
  8. {%block body %}
  9. {%endblock %}
  10. </body>
  11. </html>

extends指令声明这个模板衍生自base.html。在extends指令之后,基模板中的3个块被重新定义,模板引擎会将其插入适当的位置。注意新定义的head块,在基模板中其内容不是空的,所以使用super()获取原来的内容(向已经有内容的块中添加新内容)。

[html] view plain copy

  1. {%extends "base.html" %}
  2. {%block title %}Index{%endblock %}
  3. {%block head %}
  4. {{ super() }}
  5. <style>
  6. </style>
  7. {%endblock %}
  8. {%block body %}
  9. <h1>Hello, World!</h1>
  10. {%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 重名的,则会覆盖官方里相同元素

[html] view plain copy

  1. {% block head %}
  2. {{super()}}
  3. <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
  4. <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
  5. <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css">
  6. <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap-theme.min.css">
  7. {% endblock %}
  8. 。。。
  9. {% block scripts %}
  10. {{super()}}
  11. <script src="//cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
  12. <script src="//cdn.bootcss.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
  13. {{ moment.include_moment() }}
  14. {{ moment.lang("zh-CN") }}
  15. {% 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):

[python] view plain copy

  1. app = Flask(__name__)
  2. app.config.from_object(config[config_name])
  3. config[config_name].init_app(app)
  4. bootstrap.init_app(app)
  5. def change_cdn_domestic(tar_app):
  6. static = tar_app.extensions['bootstrap']['cdns']['static']
  7. local = tar_app.extensions['bootstrap']['cdns']['local']
  8. def change_one(tar_lib, tar_ver, fallback):
  9. tar_js = ConditionalCDN('BOOTSTRAP_SERVE_LOCAL', fallback,
  10. WebCDN('//cdn.bootcss.com/' + tar_lib + '/' + tar_ver + '/'))
  11. tar_app.extensions['bootstrap']['cdns'][tar_lib] = tar_js
  12. libs = {'jquery': {'ver': JQUERY_VERSION, 'fallback': local},
  13. 'bootstrap': {'ver': BOOTSTRAP_VERSION, 'fallback': local},
  14. 'html5shiv': {'ver': HTML5SHIV_VERSION, 'fallback': static},
  15. 'respond.js': {'ver': RESPONDJS_VERSION, 'fallback': static}}
  16. for lib, par in libs.items():
  17. change_one(lib, par['ver'], par['fallback'])
  18. change_cdn_domestic(app)
  19. # 。。。
  20. 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/

自定义错误页面

[python] view plain copy

  1. @app.errorhandler(404)
  2. def page_not_found(e):
  3. return render_template('404.html'), 404

url_for() 链接辅助函数

使用url_for()生成动态地址时,将动态部分作为关键字参数传入。例如,

url_for('user', name='john', _external=True)的返回结果是http://localhost:5000/user/john

使用Flask-Moment本地化日期和时间。查阅文档

[html] view plain copy

  1. {%block scripts %}
  2. {{ super() }}
  3. {{ moment.include_moment() }}
  4. <!--使用中文,默认是英语的-->
  5. {{ moment.lang("zh-CN") }}
  6. {%endblock %}

4 Web Form表单

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

Form基类由Flask-WTF扩展定义,所以从flask.ext.wtf中导入。字段和验证函数 可以直接从WTForms包中导入。

[python] view plain copy

  1. from flask_wtf import Form
  2. from wtforms import StringField, SubmitField
  3. from wtforms.validators import DataRequired
  4. class NameForm(Form):
  5. name = StringField('What is your name?', validators=[DataRequired()])
  6. submit = SubmitField('Submit')

WTForms支持的HTML标准字段:StringField,TextAreaField,PasswordField。。。

WTForms内建的验证函数:Email,DataRequired...

Placeholder提示:

[python] view plain copy

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

重定向和用户会话

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

这个技巧称为Post/重定向/Get模式。

[python] view plain copy

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

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

Flash消息

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

5 数据库

2016-6-9

使用SQL还是NoSQL

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

MySQL Q&A:

安装MySQL in Windows 报错:需要预先把Windows Defender 打开,或者configure mysql server时,不要勾选“Windows Firewall”

可能要先安装:Microsoft Visual C++ Compiler for Python 2.7

本地安装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 关系型数据库

[python] view plain copy

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

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

SQLAlchemy engine设置编码,防止中文乱码:

[python] view plain copy

  1. engine = create_engine('sqlite:///C:\\Temp\\testsqlalchemy.db', encoding='utf8', convert_unicode=True, echo=True)
  2. # echo:显示出内部过程及SQL语句。debug或学习时打开
  3. # encoding:防止乱码

[python] view plain copy

  1. '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:

[python] view plain copy

  1. class DevelopmentConfig(Config):
  2. DEBUG = True
  3. # SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or 'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
  4. 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提供电子邮件支持

[python] view plain copy

  1. app.config['MAIL_SERVER'] = 'smtp.163.com'
  2. app.config['MAIL_PORT'] = 25
  3. app.config['MAIL_USE_TLS'] = True
  4. app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
  5. app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
  6. app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]'
  7. app.config['FLASKY_MAIL_SENDER'] = 'Flasky Admin <flasky@example.com>'
  8. app.config['FLASKY_ADMIN'] = os.environ.get('FLASKY_ADMIN')
  9. mail = Mail(app)
  10. def sendmail(mail):
  11. msg = Message('test subject', sender='ezhqing@163.com', recipients = ['XXX@qq.com'])
  12. msg.body = 'text body'
  13. msg.html = '<b>HTML</b> body'
  14. with app.app_context():
  15. mail.send(msg)

千万不要把账户密令直接写入脚本,特别是当你计划开源自己的作品时。让脚本从本机环境中导入敏感信息Windows 用户可按照下面的方式设定环境变量:

[plain] view plain copy

  1. (venv) $ set MAIL_USERNAME=<your mail username>
  2. (venv) $ set MAIL_PASSWORD=<mail password>

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

异步发送电子邮件

为了避免处理请求过程中不必要的延迟,我们可以把发送电子邮件的函数移到后台线程(Threading)中

很多 Flask 扩展都假设已经存在激活的程序上下文和请求上下文。Flask-Mail 中的 send() 函数使用 current_app ,因此必须激活程序上下文。不过,在不同线程中执行 mail.send() 函数时,程序上下文要使用 app.app_context() 人工创建。

7 大型程序的结构

项目结构

[plain] view plain copy

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

[html] view plain copy

  1. <ul class="nav navbar-nav navbar-right">
  2. {% if current_user.is_authenticated %}
  3. <li><a href="{{ url_for('auth.logout') }}" title = "邮件 {{ current_user.email[:9]+'...' }}">Log Out {{ current_user.username }}</a></li>
  4. {% else %}
  5. <li><a href="{{ url_for('auth.login') }}">Log In</a></li>
  6. {% endif %}
  7. </ul>

按照第 4 章介绍的“Post/ 重定向 /Get 模式”,Login的 POST 请求最后也做了重定向,不过目标 URL 有两种可能。用户访问未授权的 URL 时会显示登录表单,Flask-Login 会把原地址保存在查询字符串的next参数中,这个参数可从 request.args 字典中读取。如果查询字符串中没有 next 参数,则重定向到首页

app/auth/views.py

[python] view plain copy

  1. user = User.query.filter_by(email=form.email.data).first()
  2. if user is not None and user.verify_password(form.password.data):
  3. login_user(user, form.remember_me.data)
  4. return redirect(request.args.get('next') or url_for('main.index'))

用户注册表单 app/auth/forms.py

这个表单使用 WTForms 提供的 Regexp 验证函数,确保 username 字段只包含字母、数字、下划线和点号。

密码要输入两次。此时要验证两个密码字段中的值是否一致,这种验证可使用WTForms 提供的另一验证函数实现,即 EqualTo

如果表单类中定义了以validate_ 开头且后面跟着字段名的方法,这个方法就和常规的验证函数一起调用

发送确认邮件

使用itsdangerous生成确认令牌

[python] view plain copy

  1. >>> from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
  2. >>> s = Serializer(app.config['SECRET_KEY'], expires_in = 3600)
  3. >>> token = s.dumps({ 'confirm': 23 })
  4. >>> token
  5. 'eyJhbGciOiJIUzI1NiIsImV4cCI6MTM4MTcxODU1OCwiaWF0IjoxMzgxNzE0OTU4fQ.ey ...'
  6. >>> data = s.loads(token)
  7. >>> data
  8. {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:

[html] view plain copy

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

models.py:

[python] view plain copy

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

user.html:

[html] view plain copy

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

_posts.html:

[html] view plain copy

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

_commments.html:

[html] view plain copy

  1. <div class="comment-thumbnail">
  2. <a href="{{ url_for('.user', username=comment.author.username) }}">
  3. <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] view plain copy

  1. python manage.py shell
  2. >>> User.generate_fake()
  3. >>> Post.generate_fake(200)

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

使用Markdown和Flask-PageDown支持富文本文章
• PageDown: 使用JavaScript实现的客户端Markdown到HTML的转换程序。
• Flask-PageDown: 为Flask包装的PageDown,把PageDown集成到Flask-WTF表单中。
• Markdown: 使用Python实现的服务器端Markdown到HTML的转换程序。
• 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:

[python] view plain copy

  1. @manager.command
  2. def deploy():
  3. """Run deployment tasks."""
  4. from flask.ext.migrate import upgrade
  5. from app.models import Role, User
  6. # migrate database to latest revision
  7. upgrade()
  8. # create user roles
  9. Role.insert_roles()
  10. # create self-follows for all users
  11. User.add_self_follows()
  12. # 测试:User.generate_fake(100)
  13. # 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

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

The Flask Mega-Tutorial, Part I: Hello, World! - miguelgrinberg.com

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

  • 全文检索:
  • 国际化:I18n and L10n
  • Ajax
  • Debug:pdb

查找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 .modelsimport 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

[python] view plain copy

  1. from flask_debugtoolbar import DebugToolbarExtension
  2. toolbar = DebugToolbarExtension()
  3. toolbar.init_app(app)

文章标签: pythonwebflaskweb框架

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

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

20161018: 实际应用网站上线,实现天涯VIP功能:http://tianya.heroku.com

1 虚拟环境

2016-6-8

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

[plain] view plain copy

  1. virtualenv venv
  2. venv\Scripts\activate.bat
  3. (venv) $ pip freeze >requirements.txt
  4. (venv) $ pip install -r requirements.txt
  5. pip list --outdated
  6. pip install --upgarade <Package1> <PackageN>

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

或者用 virtualenvwrapper: 点击打开链接

[plain] view plain copy

  1. git tag 列出所有打tag的分支
  2. git checkout <tag_name> 切换到tag
  3. 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 基本结构

初始化:

[python] view plain copy

  1. # -*- coding: utf-8 -*-
  2. from flask import Flask
  3. app = Flask(__name__)

Flask类的构造函数只有一个必须指定的参数,即程序主模块或包的名字。在大多数程序中,Python的__name__变量就是所需的值。Flask用这个参数决定程序的根目录,以便稍后能够找到相对于程序根目录的资源文件位置

路由 (route)和视图函数 (view function):

定义路由的最简便方式,是使用程序实例提供的app.route修饰器,把修饰的函数注册为路由

[python] view plain copy

  1. @app.route('/')
  2. def index():
  3. return '<h1>Hello World!</h1>'

修饰器是Python语言的标准特性,可以使用不同的方式修改函数的行为。惯常用法是使用修饰器把函数注册为事件的处理程序。

动态路由:地址中可以包含可变部分,Flask支持在路由中使用int、float和path类型。path类型也是字符串,但不把斜线视作分隔符

[python] view plain copy

  1. @app.route('/user/<name>')
  2. def user(name):
  3. return '<h1>Hello, %s!</h1>' %name
  4. @app.route('/user/<int:id>') # 不能有空格!
  5. 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

[python] view plain copy

  1. 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()生成映射。

[python] view plain copy

  1. app.url_map
  2. Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>,
  3. <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
  4. <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)

[python] view plain copy

  1. response = make_response('<h1>This document carries a cookie!</h1>', 200)
  2. response.set_cookie('answer', '42')
  3. 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模板引擎

模板是一个包含响应文本的文件,其中包含用占位变量{{...}}表示的动态部分,其具体值只在请求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程称为渲染。

[python] view plain copy

  1. @app.route('/user/<name>')
  2. def user(name):
  3. return render_template('user.html', name=name)

模板变量

Jinja2能识别所有类型的变量,甚至是一些复杂的类型,例如列表、字典和对象

[html] view plain copy

  1. <p>A value from a dictionary: {{ mydict['key'] }}.</p>
  2. <p>A value from a list: {{ mylist[3] }}.</p>
  3. <p>A value from a list, with a variable index: {{ mylist[myintvar] }}.</p>
  4. <p>A value from an object's method: {{ myobj.somemethod() }}.</p>

可以使用过滤器修改变量。千万别在不可信的值上使用safe过滤器,例如用户在表单中输入的文本

[html] view plain copy

  1. Hello, {{ name|capitalize }

完整的过滤器列表

  • safe 渲染值时不转义。默认情况下,出于安全考虑,Jinja2会转义所有变量
  • capitalize 把值的首字母转换成大写,其他字母转换成小写
  • lower 把值转换成小写形式
  • upper 把值转换成大写形式
  • title 把值中每个单词的首字母都转换成大写
  • trim 把值的首尾空格去掉
  • striptags 渲染之前把值中所有的HTML标签都删掉

控制结构{%...%},可用来改变模板的渲染流程 if, for, macro, import, include

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

另一种重复使用代码的强大方式是模板继承,block标签定义的元素可在衍生模板中修改

[html] view plain copy

  1. <html>
  2. <head>
  3. {%block head %}
  4. <title>{%block title %}{%endblock %} - My Application</title>
  5. {%endblock %}
  6. </head>
  7. <body>
  8. {%block body %}
  9. {%endblock %}
  10. </body>
  11. </html>

extends指令声明这个模板衍生自base.html。在extends指令之后,基模板中的3个块被重新定义,模板引擎会将其插入适当的位置。注意新定义的head块,在基模板中其内容不是空的,所以使用super()获取原来的内容(向已经有内容的块中添加新内容)。

[html] view plain copy

  1. {%extends "base.html" %}
  2. {%block title %}Index{%endblock %}
  3. {%block head %}
  4. {{ super() }}
  5. <style>
  6. </style>
  7. {%endblock %}
  8. {%block body %}
  9. <h1>Hello, World!</h1>
  10. {%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 重名的,则会覆盖官方里相同元素

[html] view plain copy

  1. {% block head %}
  2. {{super()}}
  3. <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
  4. <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
  5. <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css">
  6. <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap-theme.min.css">
  7. {% endblock %}
  8. 。。。
  9. {% block scripts %}
  10. {{super()}}
  11. <script src="//cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
  12. <script src="//cdn.bootcss.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
  13. {{ moment.include_moment() }}
  14. {{ moment.lang("zh-CN") }}
  15. {% 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):

[python] view plain copy

  1. app = Flask(__name__)
  2. app.config.from_object(config[config_name])
  3. config[config_name].init_app(app)
  4. bootstrap.init_app(app)
  5. def change_cdn_domestic(tar_app):
  6. static = tar_app.extensions['bootstrap']['cdns']['static']
  7. local = tar_app.extensions['bootstrap']['cdns']['local']
  8. def change_one(tar_lib, tar_ver, fallback):
  9. tar_js = ConditionalCDN('BOOTSTRAP_SERVE_LOCAL', fallback,
  10. WebCDN('//cdn.bootcss.com/' + tar_lib + '/' + tar_ver + '/'))
  11. tar_app.extensions['bootstrap']['cdns'][tar_lib] = tar_js
  12. libs = {'jquery': {'ver': JQUERY_VERSION, 'fallback': local},
  13. 'bootstrap': {'ver': BOOTSTRAP_VERSION, 'fallback': local},
  14. 'html5shiv': {'ver': HTML5SHIV_VERSION, 'fallback': static},
  15. 'respond.js': {'ver': RESPONDJS_VERSION, 'fallback': static}}
  16. for lib, par in libs.items():
  17. change_one(lib, par['ver'], par['fallback'])
  18. change_cdn_domestic(app)
  19. # 。。。
  20. 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/

自定义错误页面

[python] view plain copy

  1. @app.errorhandler(404)
  2. def page_not_found(e):
  3. return render_template('404.html'), 404

url_for() 链接辅助函数

使用url_for()生成动态地址时,将动态部分作为关键字参数传入。例如,

url_for('user', name='john', _external=True)的返回结果是http://localhost:5000/user/john

使用Flask-Moment本地化日期和时间。查阅文档

[html] view plain copy

  1. {%block scripts %}
  2. {{ super() }}
  3. {{ moment.include_moment() }}
  4. <!--使用中文,默认是英语的-->
  5. {{ moment.lang("zh-CN") }}
  6. {%endblock %}

4 Web Form表单

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

Form基类由Flask-WTF扩展定义,所以从flask.ext.wtf中导入。字段和验证函数 可以直接从WTForms包中导入。

[python] view plain copy

  1. from flask_wtf import Form
  2. from wtforms import StringField, SubmitField
  3. from wtforms.validators import DataRequired
  4. class NameForm(Form):
  5. name = StringField('What is your name?', validators=[DataRequired()])
  6. submit = SubmitField('Submit')

WTForms支持的HTML标准字段:StringField,TextAreaField,PasswordField。。。

WTForms内建的验证函数:Email,DataRequired...

Placeholder提示:

[python] view plain copy

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

重定向和用户会话

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

这个技巧称为Post/重定向/Get模式。

[python] view plain copy

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

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

Flash消息

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

5 数据库

2016-6-9

使用SQL还是NoSQL

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

MySQL Q&A:

安装MySQL in Windows 报错:需要预先把Windows Defender 打开,或者configure mysql server时,不要勾选“Windows Firewall”

可能要先安装:Microsoft Visual C++ Compiler for Python 2.7

本地安装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 关系型数据库

[python] view plain copy

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

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

SQLAlchemy engine设置编码,防止中文乱码:

[python] view plain copy

  1. engine = create_engine('sqlite:///C:\\Temp\\testsqlalchemy.db', encoding='utf8', convert_unicode=True, echo=True)
  2. # echo:显示出内部过程及SQL语句。debug或学习时打开
  3. # encoding:防止乱码

[python] view plain copy

  1. '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:

[python] view plain copy

  1. class DevelopmentConfig(Config):
  2. DEBUG = True
  3. # SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or 'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
  4. 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提供电子邮件支持

[python] view plain copy

  1. app.config['MAIL_SERVER'] = 'smtp.163.com'
  2. app.config['MAIL_PORT'] = 25
  3. app.config['MAIL_USE_TLS'] = True
  4. app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
  5. app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
  6. app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]'
  7. app.config['FLASKY_MAIL_SENDER'] = 'Flasky Admin <flasky@example.com>'
  8. app.config['FLASKY_ADMIN'] = os.environ.get('FLASKY_ADMIN')
  9. mail = Mail(app)
  10. def sendmail(mail):
  11. msg = Message('test subject', sender='ezhqing@163.com', recipients = ['XXX@qq.com'])
  12. msg.body = 'text body'
  13. msg.html = '<b>HTML</b> body'
  14. with app.app_context():
  15. mail.send(msg)

千万不要把账户密令直接写入脚本,特别是当你计划开源自己的作品时。让脚本从本机环境中导入敏感信息Windows 用户可按照下面的方式设定环境变量:

[plain] view plain copy

  1. (venv) $ set MAIL_USERNAME=<your mail username>
  2. (venv) $ set MAIL_PASSWORD=<mail password>

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

异步发送电子邮件

为了避免处理请求过程中不必要的延迟,我们可以把发送电子邮件的函数移到后台线程(Threading)中

很多 Flask 扩展都假设已经存在激活的程序上下文和请求上下文。Flask-Mail 中的 send() 函数使用 current_app ,因此必须激活程序上下文。不过,在不同线程中执行 mail.send() 函数时,程序上下文要使用 app.app_context() 人工创建。

7 大型程序的结构

项目结构

[plain] view plain copy

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

[html] view plain copy

  1. <ul class="nav navbar-nav navbar-right">
  2. {% if current_user.is_authenticated %}
  3. <li><a href="{{ url_for('auth.logout') }}" title = "邮件 {{ current_user.email[:9]+'...' }}">Log Out {{ current_user.username }}</a></li>
  4. {% else %}
  5. <li><a href="{{ url_for('auth.login') }}">Log In</a></li>
  6. {% endif %}
  7. </ul>

按照第 4 章介绍的“Post/ 重定向 /Get 模式”,Login的 POST 请求最后也做了重定向,不过目标 URL 有两种可能。用户访问未授权的 URL 时会显示登录表单,Flask-Login 会把原地址保存在查询字符串的next参数中,这个参数可从 request.args 字典中读取。如果查询字符串中没有 next 参数,则重定向到首页

app/auth/views.py

[python] view plain copy

  1. user = User.query.filter_by(email=form.email.data).first()
  2. if user is not None and user.verify_password(form.password.data):
  3. login_user(user, form.remember_me.data)
  4. return redirect(request.args.get('next') or url_for('main.index'))

用户注册表单 app/auth/forms.py

这个表单使用 WTForms 提供的 Regexp 验证函数,确保 username 字段只包含字母、数字、下划线和点号。

密码要输入两次。此时要验证两个密码字段中的值是否一致,这种验证可使用WTForms 提供的另一验证函数实现,即 EqualTo

如果表单类中定义了以validate_ 开头且后面跟着字段名的方法,这个方法就和常规的验证函数一起调用

发送确认邮件

使用itsdangerous生成确认令牌

[python] view plain copy

  1. >>> from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
  2. >>> s = Serializer(app.config['SECRET_KEY'], expires_in = 3600)
  3. >>> token = s.dumps({ 'confirm': 23 })
  4. >>> token
  5. 'eyJhbGciOiJIUzI1NiIsImV4cCI6MTM4MTcxODU1OCwiaWF0IjoxMzgxNzE0OTU4fQ.ey ...'
  6. >>> data = s.loads(token)
  7. >>> data
  8. {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:

[html] view plain copy

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

models.py:

[python] view plain copy

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

user.html:

[html] view plain copy

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

_posts.html:

[html] view plain copy

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

_commments.html:

[html] view plain copy

  1. <div class="comment-thumbnail">
  2. <a href="{{ url_for('.user', username=comment.author.username) }}">
  3. <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] view plain copy

  1. python manage.py shell
  2. >>> User.generate_fake()
  3. >>> Post.generate_fake(200)

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

使用Markdown和Flask-PageDown支持富文本文章
• PageDown: 使用JavaScript实现的客户端Markdown到HTML的转换程序。
• Flask-PageDown: 为Flask包装的PageDown,把PageDown集成到Flask-WTF表单中。
• Markdown: 使用Python实现的服务器端Markdown到HTML的转换程序。
• 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:

[python] view plain copy

  1. @manager.command
  2. def deploy():
  3. """Run deployment tasks."""
  4. from flask.ext.migrate import upgrade
  5. from app.models import Role, User
  6. # migrate database to latest revision
  7. upgrade()
  8. # create user roles
  9. Role.insert_roles()
  10. # create self-follows for all users
  11. User.add_self_follows()
  12. # 测试:User.generate_fake(100)
  13. # 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

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

The Flask Mega-Tutorial, Part I: Hello, World! - miguelgrinberg.com

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

  • 全文检索:
  • 国际化:I18n and L10n
  • Ajax
  • Debug:pdb

查找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 .modelsimport 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

[python] view plain copy

  1. from flask_debugtoolbar import DebugToolbarExtension
  2. toolbar = DebugToolbarExtension()
  3. toolbar.init_app(app)

文章标签: pythonwebflaskweb框架

Flask Web开发:基于Python的Web应用开发实战相关推荐

  1. 《Flask Web开发——基于Python的Web应用开发实践》一字一句上机实践(上)

    目录 前言 第1章 安装 第2章 程序的基本结构 第3章 模板 第4章 Web表单 第5章 数据库 第6章 电子邮件 第7章 大型程序的结构 前言 学习Python也有一个半月时间了,学到现在感觉还是 ...

  2. 《Flask Web开发——基于Python的Web应用开发实践》一字一句上机实践(下)

    目录 前言 第8章 用户认证 第9章 用户角色 第10章 用户资料 第11章 博客文章 第12章 关注者 第13章 用户评论 第14章 应用编程接口   前言 第1章-第7章学习实践记录请参见:< ...

  3. flask web开发:基于python的web应用开发实战_在知乎上学 Python Web 开发篇

    通知: 1. 最近我们将进行2期学习小组,面向完全零基础的Python入门学习小组已经开始,第一次任务的讨论将于明晚(15号)进行,现在上车还来得及 2. 另有一期数据可视化小组将于年前启动,第一次讨 ...

  4. 《Flask Web开发——基于Python的Web应用开发实践》代码使用方法

    git clone https://github.com/miguelgrinberg/flasky.git 然后书上遍地的git checkout 1a这样的命令,怎么回事呢? 先把上面的命令在一个 ...

  5. CANanlystII 基于python的二次开发实践

    前期,我已经编写过一篇<CANanlystII 基于linux的二次开发实践>这篇博客承接上一篇博客,所以背景知识和测试场景,就不再赘述. 背景知识和测试场景,可以查阅如下: CANanl ...

  6. 学习《Flask Web开发:基于Python的Web应用开发实战》分享

    学习<Flask Web开发:基于Python的Web应用开发实战>分享一直在说学习Python,对同事,对朋友,都说我正在学习Python,这无形给自己一定的压力,促使自己要去学习,进步 ...

  7. 《Flask Web开发:基于Python的Web应用开发实战》笔记(原创)

    内容提要 在学习"狗书"<Flask Web开发:基于Python的Web应用开发实战>的过程中,一直遇到各种各样的坑.该书的第一部分是"Flask简介&qu ...

  8. 《FlaskWeb开发:基于Python的Web应用开发实战》笔记

    开源库的cdn加速 可以在这里直接搜索复制script链接 https://www.bootcdn.cn/ requirements.txt文件的生成与使用 生成requirements文件:$ pi ...

  9. w3af 基于Python的Web应用扫描器

    全称"Web Application Attack and Audit Framework"--Web应用程序攻击审计框架.W3af是一个基于Python的Web应用扫描器.W3a ...

最新文章

  1. 前端的各种各样的面试题大全
  2. vs社区版到期离线激活_vs2019离线安装包
  3. Android ListView常用用法
  4. 在 Mac 安装Docker
  5. S3C2410中断系统
  6. C++Template 模版的本质
  7. web后端学习过程中技巧总结(持续更新。。。)
  8. android h5使用缓存_Android SDK 的 H5 打通方案演进 | 数据采集
  9. 前端学习(3093):vue+element今日头条管理-反馈
  10. jQuery学习之三---工具
  11. 【数据分析学习】Pandas学习记录
  12. NOIP 2011 Day 1
  13. qt编写的android菜单,Qt for Android实现与webview的交互
  14. 这样保养让你皮肤变水嫩 - 生活至上,美容至尚!
  15. Javascript 检查对象是否含有这个属性
  16. 【Unity】对接Steam
  17. 中国大学MOOC保险学试题及答案
  18. c语言中文叫什么意思,c语言中“||”是什么意思?
  19. 前端国际化如何对中文——>英文自动化翻译小demo
  20. 【漏洞学习——沙盒跳出】————7、某银行自助查询终端可绕过权限控制

热门文章

  1. 点要素生成面要素(Arcgis实操系列)
  2. gcc官网以及各个版本下载地址
  3. excel 撤销工作表保护
  4. 怎么将微信小程序项目转为uniapp项目
  5. sybase数据库环境搭建教程
  6. SAP ODATA 开发教程
  7. Android自制弹幕
  8. Nginx分布式与集群概述
  9. UDS诊断系列介绍06-22服务
  10. (全程图解)RP8.0安装教程