欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

《Flaskweb开发从入门到精通》读书笔记(上)

时间:2023-05-23
《Flask web 开发从入门到精通》

前言

官方文档代码资源python -m参数virtualenvwrapperMVC随记 第1章 配置Flask

1.2 处理基本的配置问题1.4 组织静态文件1.5 实例文件夹1.6 视图和模型 第2章 基于jinja2的模板机制

2.1 引导推荐的布局2.2 实现块组合和继承布局2.3 自定义上下文处理器2.4 创建自定义 jinja2 过滤器2.5 为表单创建自定义宏2.6 高级日期和时间格式 第3章 Flask中的数据模型

3.1 创建SQLAchemy DB 实例3.2 创建基本的模型3.3 创建关系分类模型3.4 利用 Alembic 和 Flask-Migrate 迁移数据库3.5~3.6 Redis 和MongoDB 第 4 章 与视图协同工作

4.1 编写基于函数的视图和URL路由4.2 编写基于类的视图4.3 实现URL路由机制和基于产品的分页机制4.4 渲染至模板4.5 处理XHR请求4.6 使用装饰器处理请求4.7 处理自定义的404和500处理程序4.8 闪动消息以获得更好的用户反馈4.9 实现基于SQL的搜索机制 第 5 章 WTForms

5.1 将SQLAlchemy 模型数据表示为表单5.2 验证服务器端上的字段5.3 创建公共表单集合5.4 创建自定义字段和验证5.5 创建自定义Widget5.6 通过表单上传文件5.7 保护应用程序免受跨站点请求伪造(CSRF) 前言 官方文档

http://flask.pocoo.org/docs/0.12/ 英文
http://docs.jinkan.org/docs/flask/ 中文

代码资源 python -m参数

1,python xxx.py2,python -m xxx.py

这是两种加载py文件的方式:
1叫做直接运行
2把模块当作脚本来启动(注意:但是__name__的值为’main’ )

不能将应用程序文件保存为flask.py,否则 ,在导入时将与Flask 发生冲突 virtualenvwrapper

Python3.4以上版本不需要额外安装virtualenv安装包了,直接使用python -m venv env1即可创建虚拟环境

virtualenvwrapper 时一个基于virtualenv之上的工具,它将所欲的虚拟环境统一管理。

安装如下命令:

$ sudo pip install virtualenvwrapper

virtualenvwrapper默认将所有的虚拟环境放在~/.virtualenvs目录下管理,可以修改环境变量WORKON_HOME来指定虚拟环境 的保存目录。

使用如下命令来启动virtualenvwrapper:

$ source /usr/local/bin/virtualenvwrapper.sh

还可以将该命令添加到/.bashrc或/.profie等shell启动文件中,以便登陆shell后可直接使用virtualenvwrapper提供的命令。

参考这儿

MVC

为方便后续理解flask中的视图,模型等概念,这儿引入MVC

MVC框架的核心思想是:解耦,让不同的代码块之间降低耦合,增强代码的可扩展性和可移植性,实现向后兼容。

当前主流的开发语言如Java、PHP、Python中都有MVC框架。

Web MVC各部分的功能
M:全拼为Model,主要封装对数据库层的访问,对数据库中的数据进行增、删、改、查操作。

V:全拼为View,用于封装结果,生成页面展示的html内容。

C:全拼为Controller,用于接收请求,处理业务逻辑,与Model和View交互,返回结果。

随记

1,route
route方法必须传入一个字符串形式的url路径,路径必须以斜线开始

url可以重复吗?视图函数可以重复吗?

url可以重复,url可以指定不同的请求方式

url 查找视图 从上往下执行,如果找到,不会继续匹配

视图函数不能重复,函数只允许有一个返回值

在flask中路由分发1、如果路由名字后面没有写/ 那么请求的路径后面就不可以写/2、如果路由名字后面写了/ 那么请求的路径后面怎么写都可以# 所以在route()中路由名字一般情况下 /路由名字/@app.route('/index/')# 路由的名字和视图函数的名字 一般情况下一致,这个是开发的经验;def index(): return 'index'

2,返回JSON

在使用 Flask 写一个接口时候需要给客户端返回 JSON 数据,在 Flask 中可以直接使用 jsonify 生成一个 JSON 的响应

@app.route('/demo4')def exp4(): json_dict = { "user_id": 110, "user_name": "kenan" } return jsonify(json_dict)

不推荐使用 json.dumps 转成 JSON 字符串直接返回,因为返回的数据要符合 HTTP 协议规范,如果是 JSON 需要指定 content-type:application/json

第1章 配置Flask 1.2 处理基本的配置问题

配置设置方式
方法一

app = Flask(__name__)app.debug=True

方法二

app.config["DEBUG"] = True

方法三

if __name__=='__main__': app.run(debug=True)

方法四 环境变量

$ export FLASK_DEBUG=1

方法五 多种方式

Flask可以自动选取大写形式的配置变量

from flask import Flaskapp = Flask(__name__)DEBUG = TrueTESTING = True

另外还可以基于类的继承,在不通的环境(开发环境,测试环境,生产环境)中继承基类并覆写配置变量,基类配置不随环境改变的配置变量

密钥存储在单独的文件中,不属于版本控制系统的一部分

1.4 组织静态文件

文件目录

my_app/- app,py- config.py- __init__.py- static/- css/- js/- images/-logo.png

引用模板时

app = Flask(__name__,static_folder="/path/to/static/folder")

1.5 实例文件夹

my_app/- app.py- instance/- config.cfg

app = Flask(__name__,instance_path='instance/' instance_relative_config=True)app.config.from_pyfile('config.cfg',silent=True)

所谓实例文件夹,是指和flaskr同级的一个名字为instance的文件夹,适合存放私有配置的秘钥或者本地数据库等不需要上传到Git的文件,可以通过Flask.instance_path获取完整路径。

1.6 视图和模型

这一节主要讨论怎么模块实现web的代码
具体代码就不贴了,主要记录以下各个文件完成的功能
文件组织目录

flask_app/

- run.py # app.run()- my_app/- __init__.py # 创建app,注册register_blueprint(xxx)- hello/- __init__.py- models.py # 数据封装,数据库的增删改查- views.py # 封装结果生成页面展示内容生成 blueprint

通过app.env="development可以取消下面的注释

第2章 基于jinja2的模板机制 2.1 引导推荐的布局

flask_app/

- run.py # app.run()- my_app/- __init__.py # 创建app,注册register_blueprint(xxx)- hello/- __init__.py- views.py

view中的代码

from flask import render_template,request@hello.route('/')@hello.route('/home')def hello_world():user = request.args.get('user','Shala') return render_template('index.html', user=user)

- templates/ 放模板文件- index.html

当用户访问http://127.0.0.1:5000/hello?user=John,通过request.args.get('user','Shala')从request中获取然后随render_template 传递到渲染的模板上下文中

文档,Jinja2是Flask作者开发的模板系统。

2.2 实现块组合和继承布局

大型应用程序会有不同的界面,但界面的页眉,页脚相同
针对这种会有一个基本模板。
本书使用Bootstrap框架实现模板的简约设计

Bootstrap,来自 Twitter,是目前最受欢迎的前端框架。Bootstrap 是基于 HTML、CSS、JAVAscript 的,它简洁灵活,使得 Web 开发更加快捷。

{% extends 'base.html' %}{% block container %} {% for id, product in products.items() %}

{{ product['name'] }} $ {{ product['price'] }}

{% endfor %} {% endblock %}

url_for() 与蓝图结合使用
url_for() 的第一个参数代表product蓝图下的product函数,
url_for() 的第二个参数 key=id ,是指:product 蓝图下的 product 函数的参数 key参数 。

参考如下:

from werkzeug.exceptions import abortfrom flask import render_template,Blueprintfrom my_app.product.models import PRODUCTS#注册一个名字为product的蓝图product_blueprint = Blueprint('product', __name__)@product_blueprint.route('/product/')def product(key): product = PRODUCTS.get(key) if not product: abort(404) return render_template('product.html', product=product)

2.3 自定义上下文处理器

逻辑处理行为(比如某些数值计算)应在视图中完成,以此保持模板的整洁
上下文处理器可以将数值传递至某个方法中,并处理后返回值

@product_blueprint.context_processordef some_processor(): def full_name(product): return f"{product['category']} / {product['name']}" return {'full_name': full_name}

html中使用,按照{{ full_name(product) }} 这种方式使用上下文处理器

{% extends 'home.html' %}{% block container %}

{{ full_name(product) }}

{{ product['name'] }} {{ product['category'] }}

{{ product['price']|format_currency }}

{% endblock %}

2.4 创建自定义 jinja2 过滤器

在blueprint 级别创建过滤器app_template_filter

@product_blueprint.app_template_filter('full_name')def full_name_filter(product): return f"{product['category']} / {product['name']}"

html中使用

{{product | full_name}}

在应用程序级别创建过滤器template_filter

import ccyfrom flask import Flask, requestfrom my_app.product.views import product_blueprintapp = Flask(__name__)app.register_blueprint(product_blueprint)@app.template_filter('format_currency')def format_currency_filter(amount):currency_code = ccy.countryccy(request.accept_languages.best[-2:]) or 'USD'return '{0} {1}'.format(currency_code, amount)

accept_languages返回浏览器的语言环境,是一个字符串
html中使用

{{ product['price']|format_currency }}

2.5 为表单创建自定义宏

部分内容来自此处
Jinja2中的宏功能有些类似于传统程序语言中的函数,跟python中的函数类似,可以传递参数,但不能有返回值,有声明和调用两部分。让我们先声明一个宏:
在_helper.html 中输入以下内容

{% macro input(name, type='text', value='') -%} {%- endmacro %}

%之前和之后的-号将去除这些块之前和之后的空格

上面的代码定义了一个宏,宏定义要加macro,宏定义结束要加endmacro标志。宏的名称就是input,它有3个参数,分别是name、type和value,后两个参数有默认值。调用时用下面这个表达式:
宏被导入文件

{% from '_helper.html' import input %}

以下代码调用宏

用户名:{{ input('username') }}

密 码:{{ input('password', type='password') }}

如果要编写一个无法从当前文件外部访问的私有宏,可在该宏的名称前加上下划线(_)

2.6 高级日期和时间格式

Javascript 日期处理类库Moment.js-中文
Moment.js-英文
下载moment.min.js后放在static/js文件夹中
引用

但是看到了这个Moment.js 宣布停止开发,现在该用什么?

第3章 Flask中的数据模型 3.1 创建SQLAchemy DB 实例

pip install flask-sqlalchemy

关于连接数据库的URL,可以访问-英文
关于连接数据库的URL,可以访问-中文

需要对特殊字符(如密码中可能使用的字符)进行URL编码才能正确解析。 。以下是包含密码的URL示例 “kx%jj5/g” ,其中%和/字符表示为 %25 和 %2F ,分别为:

postgresql+pg8000://dbuser:kx%25jj5%2Fg@pghost10/appdb

可以使用以下命令生成上述密码的编码 urllib.parse

>>>import urllib.parse>>>urllib.parse.quote_plus("kx%jj5/g")>>>'kx%25jj5%2Fg'

在上述连接 介绍MYSQL 的部分,有提到数据库断开的问题,这个当年做项目也是有遇到过,很头大,当时百度也不知道怎么解决,现在这儿有遇到

不希望db实例绑定到单个应用程序上,希望在多个应用程序间加以使用或者采用动态的创建应用程序。

from flask import Flaskfrom flask_sqlalchemy import SQLAlchemydb = SQLAlchemy()def creat_app(): app = Flask(__name__) db.init_app(app) return app

3.2 创建基本的模型

创建数据库模型Product 类似下面这样

class Product(db.Model): id = db.Column(db.Integer,primary_key=True) name = db.Column(db.String(255)) price = db.Column(db.Float) def __init__(self,name,price): self.name = name self.price = price def __repr__(self): return '' % self.id

使用方式一

@catalog.route('/product/')def product(key): product = Product.objects.get_or_404(key=key) return f"Product - {product.name}, ${product.price}"

JSON返回

@catalog.route('/products')def products(): products = Product.objects.all() res = {} for product in products: res[product.key] = { 'name': product.name, 'price': str(product.price), } return jsonify(res)

3.3 创建关系分类模型

这一届主要理解db.relationship

这个函数有点难用,一是因为它的有几个参数不太好理解,二是因为它的参数非常丰富,让人望而却步。下面通过一对多、多对一、多对多几个场景下 relationship 的使用,来一步步熟悉它的用法。

参考这儿

3.4 利用 Alembic 和 Flask-Migrate 迁移数据库

安装Alembic

pip install Flask-Migrate

具体使用可以参考下面的文章,先收藏,用的时候再看
参考1
参考2

3.5~3.6 Redis 和MongoDB

pip install redispip install flask-mongoengine

指定使用“my_catalog”表

app = Flask(__name__)app.config['MONGODB_SETTINGS'] = {'DB': 'my_catalog'}app.debug = Truedb = MongoEngine(app)

对于SQLAlchemy 对应的类为db.Model,对于MongoDB 对应的类则为db.document

class Product(db.document): created_at = db.DateTimeField( default=datetime.datetime.now, required=True ) key = db.StringField(max_length=255, required=True) name = db.StringField(max_length=255, required=True) price = db.DecimalField() def __repr__(self): return '' % self.id

使用

@catalog.route('/product-create', methods=['POST',])def create_product(): name = request.form.get('name') key = request.form.get('key') price = request.form.get('price') product = Product( name=name, key=key, price=Decimal(price) ) product.save() return 'Product created.'

第 4 章 与视图协同工作 4.1 编写基于函数的视图和URL路由

1,GET请求(默认)

@app.route('/category-create')def create_category(): name = request.args.get('name')

2, POST请求,需要指定methods,通过form获取参数,因为POST请求假设数据是以表单的方式提交的

@catalog.route('/category-create', methods=['POST',])def create_category(): name = request.form.get('name')

3,合并的请求

@catalog.route('/category-create', methods=['GET','POST',])def create_category():if request.method == 'GET': name = request.args.get('name') else: name = request.form.get('name')

如果尝试向仅支持POST请求的方法中发送GET请求,请求将失败,并生成405 HTTP错误,反过来也一样

add_url_rule使用

def get_request():bar = request.args.get('foo','bar')return 'xxxxx'app = Flask(__name__)app.add_url_rule('/a-get-request',view_func=get_request)

4.2 编写基于类的视图

参考自此链接
flask提供了一个名为View的类,可以继承该类进而添加自定义的行为

之前我们接触的视图都是函数,所以一般简称视图函数。其实视图也可以基于类来实现,类视图的好处是支持继承,但是类视图不能跟函数视图一样,写完类视图还需要通过app.add_url_rule(url_rule,view_func来进行注册。以下将对两种类视图进行讲解

标准类视图是继承自flask.views.View,并且在子类中必须实现dispatch_request方法,这个方法类似于视图函数,也要返回一个基于Response或者其子类的对象。以下将用一个例子进行讲解:

from flask.views import Viewclass PersonalView(View): def dispatch_request(self): return "知了课堂"# 类视图通过add_url_rule方法和url做映射app.add_url_rule('/users/',view_func=PersonalView.as_view('personalview'))

Flask还为我们提供了另外一种类视图flask.views.MethodView,对每个HTTP方法执行不同的函数(映射到对应方法的小写的同名方法上),以下将用一个例子来进行讲解

class LoginView(views.MethodView): # 当客户端通过get方法进行访问的时候执行的函数 def get(self): return render_template("login.html") # 当客户端通过post方法进行访问的时候执行的函数 def post(self): email = request.form.get("email") password = request.form.get("password") if email == 'xx@qq.com' and password == '111111': return "登录成功!" else: return "用户名或密码错误!"# 通过add_url_rule添加类视图和url的映射,并且在as_view方法中指定该url的名称,方便url_for函数调用app.add_url_rule('/myuser/',view_func=LoginView.as_view('loginview'))

4.3 实现URL路由机制和基于产品的分页机制

URL路由的转换

@qpp.rout('/test/')def get_name(name):return name

包含特定长度的字符串

@qpp.rout('/test/')def get_name(code):return code

解析整数,或者指定所接收的最小和最大值,也可以用float代替int

@app.rout('test/int(min=18,max=99):age')@qpp.rout('/test/')def get_name(age):return str(age)

分页。返回每页前10件产品

@catalog.route('/products')@catalog.route('/products/')def products(page=1): products = Product.query.paginate(page, 10) return render_template('products.html', products=products)

Flask-SQLAlchemy 提供的 paginate 方法。页数是 paginate() 方法的第一个参数,也是唯一必需的参数。可选参数 per_page 用来指定 每页显示的记录数量;如果没有指定,则默认显示 20 个记录。另一个可选参数为 error_out,当其设为 True 时(默认值),如果请求的页数超出了范围,则会返回 404 错误;如果 设为 False,页数超出范围时会返回一个空列表。

4.4 渲染至模板

这一节中提到了request.endpoint ,有必要了解一下

4.5 处理XHR请求

异步Javascript XMLHttpRequest (XHR) 也称作Ajax

if request.is_xhr:xxxxreturn jsonify(xxx)

4.6 使用装饰器处理请求

书上这儿的示例代码缩进有问题,看了半天看不懂参考此链接

from functools import wrapsdef template_or_json(template=None): """Return a dict from your view and this will either pass it to a template or render json、Use like: @template_or_json('template.html') """ def decorated(f): @wraps(f) def decorated_fn(*args, **kwargs): ctx = f(*args, **kwargs) if request.is_xhr or not template: return jsonify(ctx) else: return render_template(template, **ctx) return decorated_fn return decorated

这个装饰器做的就是之前小节中我们对 XHR 的处理,即检查请求是否是 XHR,根据结果是否决定是渲染模板还是返回 JSON 数据。
使用

@app.route('/')@app.route('/home')@template_or_json('home.html')def home(): products = Product.query.all() return {'count': len(products)}

4.7 处理自定义的404和500处理程序

Flask 对象 app 有一个叫做 errorhandler()的方法,这使得处理应用程序错误的方式更加美观和高效。

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

出现报错 将看到渲染后的模板

4.8 闪动消息以获得更好的用户反馈

案例:当用户创建完产品并被重定向至新生成的产品时,较好的方法是通知用户该产品已经被创建完毕
会话依赖于密钥,因此先添加密钥

app.secret_key = 'some_random_key'

使用
注意 flash 消息,它提醒用户一个商品创建成功了。flash()的第一个参数是要被显示的消息,第二个参数是消息的类型。

from flask import flash,redirect@catalog.route('/product-create', methods=['GET', 'POST'])def create_product(): if request.method == "POST": name = request.form.get('name') price = request.form.get('price') categ_name = request.form.get('category') category = Category.query.filter_by(name=categ_name).first() if not category: category = Category(categ_name) product = Product(name, price, category) flash('The product %s has been created' % name, 'success') return redirect(url_for('catalog.product', id=product.id)) return render_template('product-create.html')

当然对应的模板中也要修改,以适应闪动消息
最后的效果类似于

4.9 实现基于SQL的搜索机制

join 数据库表的连接查询

from sqlalchemy.orm.util import join@catalog.route('/product-search')@catalog.route('/product-search/')def product_search(page=1): name = request.args.get('name') price = request.args.get('price') company = request.args.get('company') category = request.args.get('category') products = Product.query if name: products = products.filter(Product.name.like('%' + name + '%')) if price: products = products.filter(Product.price == price) if company: products = products.filter(Product.company.like('%' + company + '%')) if category: products = products.select_from(join(Product, Category)).filter(Category.name.like('%' + category + '%')) return render_template( 'products.html', products=products.paginate(page, 10) )

第 5 章 WTForms

WTForms 为许多字段提供了服务器端验证,从而提高了开发速度并减少了所需的总体工作量

$ pip install Flask-WTF

5.1 将SQLAlchemy 模型数据表示为表单

文档

from flask_wtf import FlaskFormfrom wtforms import StringField, DecimalField, SelectFieldclass ProductForm(FlaskForm): name = StringField('Name') price = DecimalField('Price') category = SelectField('Category', coerce=int)

Category字段里有一个叫做 coerce 的参数(表示为可选列表),意味着在任何验证或者处理之前强制转化来自HTML表单的输入为一个整数。在这里,强制仅仅意味着转换,由一个特定数据类型到另一个不同的数据类型。

通过Flask-WTF来保护表单免受CSRF攻击
单个表单禁用:生成表单时加入参数csrf_enabled=False
实现方式

from my_app.catalog.models import ProductForm@catalog.route('/product-create', methods=['GET', 'POST'])def create_product(): form = ProductForm(csrf_enabled=False) categories = [(c.id, c.name) for c in Category.query.all()] form.category.choices = categories if request.method == 'POST': name = form.name.data price = form.price.data category = Category.query.get_or_404( form.category.data ) product = Product(name, price, category) db.session.add(product) db.session.commit() flash('The product %s has been created' % name, 'success') return redirect(url_for('catalog.product', id=product.id)) return render_template('product-create.html', form=form)

5.2 验证服务器端上的字段

在 WTForm 字段中很容易添加验证机制。我们仅仅需要传递一个 validators 参数

from decimal import Decimalfrom wtforms.validators import InputRequired, NumberRangeclass ProductForm(FlaskForm): name = StringField('Name', validators=[InputRequired()]) price = DecimalField('Price', validators=[ InputRequired(), NumberRange(min=Decimal('0.0')) ]) category = SelectField( 'Category', validators=[InputRequired()], coerce=int)

InputRequired意味着字段不可缺少,否则对应表单不会被提交
price 字段设置价格不能小于0

@catalog.route('/product-create', methods=['GET', 'POST'])def create_product(): form = ProductForm(csrf_enabled=False)categories = [(c.id, c.name) for c in Category.query.all()] form.category.choices = categories if request.method == 'POST' and form.validate(): name = form.name.data price = form.price.data category = Category.query.get_or_404(form.category.data ) product = Product(name, price, category, filename) db.session.add(product) db.session.commit() flash('The product %s has been created' % name, 'success') return redirect(url_for('catalog.product', id=product.id)) if form.errors: flash(form.errors, 'danger') return render_template('product-create.html', form=form)

form.errors的闪动行为仅显示JSON对象形式的错误信息

也可以直接用if form.validate_on_submit(): 代替if request.method == 'POST' and form.validate():效果一样

5.3 创建公共表单集合

设置公共表单于随后在需要时加以复用,共有的NameForm单独设置,ProductForm和CategoryForm复用的直接继承

class NameForm(FlaskForm): name = StringField('Name', validators=[InputRequired()])class ProductForm(NameForm): price = DecimalField('Price', validators=[ InputRequired(), NumberRange(min=Decimal('0.0'))]) category = CategoryField( 'Category', validators=[InputRequired()], coerce=int)class CategoryForm(NameForm): pass

5.4 创建自定义字段和验证

这一节实现的功能主要是,有些字段可以自动从数据库中获取并生成可选字段

class CategoryField(SelectField): def iter_choices(self): categories = [(c.id, c.name) for c in Category.query.all()] for value, label in categories: yield (value, label, self.coerce(value) == self.data) def pre_validate(self, form): for v, _ in [(c.id, c.name) for c in Category.query.all()]: if self.data == v: break else: raise ValueError(self.gettext('Not a valid choice'))class ProductForm(NameForm): price = DecimalField('Price', validators=[ InputRequired(), NumberRange(min=Decimal('0.0')) ]) category = CategoryField( 'Category', validators=[InputRequired()], coerce=int ) image = FileField('Product Image', validators=[FileRequired()])

SelectField 中本身有一个iter_choices 方法,这儿覆写iter_choices方法,直接从数据库中获取分类值,无需在每次使用该表单时填写对应的字段


CategoryField 也可通过QuerySlectField予以实现, WTForms 3.0.之后删除了WTForms 扩展,很多扩展变成了单独的库,其中WTForms-SQLAlchemy 中实现了QuerySelectField


因此可以移除用于表单分类的下面2句

categories = [(c.id, c.name) for c in Category.query.all()]form.category.choices = categories

不希望支持重复的分类,可以在表单上使用自定义验证器

def check_duplicate_category(case_sensitive=True): def _check_duplicate(form, field): if case_sensitive: res = Category.query.filter( Category.name.like('%' + field.data + '%') ).first() else: res = Category.query.filter( Category.name.ilike('%' + field.data + '%') ).first() if res: raise ValidationError( 'Category named %s already exists' % field.data ) return _check_duplicate

关于 like和 ilike操作符可以模糊匹配字符串,like是一般用法,ilike匹配时则不区分字符串的大小写

5.5 创建自定义Widget

文档

from wtforms.widgets import html_params, Select, HTMLStringclass CustomCategoryInput(Select): def __call__(self, field, **kwargs): kwargs.setdefault('id', field.id) html = [] for val, label, selected in field.iter_choices(): html.append( ' %s' % ( html_params( name=field.name, value=val, checked=selected, **kwargs ), label ) ) return HTMLString(' '.join(html))class CategoryField(SelectField): widget = CustomCategoryInput() pass

5.6 通过表单上传文件

需要向应用配置提供一个参数:UPLOAD_FOLDER。这个参数告诉 Flask 上传文件被存储的位置。

图片不建议 存储到数据库,应始终将图像和其他上传内容存储在文件系统中,并使用字符串字段将其位置存储在数据库中

import osfrom flask import Flask# 指定接收的文件格式ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])app = Flask(__name__)app.config['UPLOAD_FOLDER'] = os.path.realpath('.') + '/my_app/static/uploads' # 这个路径必须先创建好,否则报错

增加下面内容

from flask_wtf.file import FileField, FileRequiredclass ProductForm(NameForm): image = FileField('Product Image', validators=[FileRequired()])

from my_app import db, app, ALLOWED_EXTENSIONSfrom werkzeug import secure_filenameimport osdef allowed_file(filename): return '.' in filename and filename.lower().rsplit('.', 1)[1] in ALLOWED_EXTENSIONS@catalog.route('/product-create', methods=['GET', 'POST'])def create_product(): form = ProductForm() if form.validate_on_submit(): name = form.name.data price = form.price.data category = Category.query.get_or_404( form.category.data ) image = form.image.data if allowed_file(image.filename): filename = secure_filename(image.filename) image.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) product = Product(name, price, category, filename) db.session.add(product) db.session.commit() flash('The product %s has been created' % name, 'success') return redirect(url_for('catalog.product', id=product.id)) if form.errors: flash(form.errors, 'danger') return render_template('product-create.html', form=form)

secure_filename()函数来检查文件名

要注意的是,secure_filename仅返回ASCII字符。所以, 非ASCII(比如汉字)会被过滤掉,空格会被替换为下划线。你也可以自己处理文件名,或是在使用这个函数前将中文替换为拼音或是英文。

对应的HTML中也要修改,表单应该包含参数enctype="multipart/form-data",以便告诉应用该表单参数含有多个数据。

5.7 保护应用程序免受跨站点请求伪造(CSRF)

Flask 默认不提供任何 CSRF 保护,且需要在表单验证级别上予以处理。当前案例将通过Flask-WTF 扩展处理这些。
Flask-WTF 默认提供CSRF,只需移除相关语句即可开发CSRF防护
form = ProductForm(csrf_enabled=False) 变为form = ProductForm()
相应的需要调整配置

app.config['WTF_CSRF_SECRET_KEY'] = 'random key for form'

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。