Python后端开发flask—基础

最小框架

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'


if __name__ == '__main__':
    app.run(debug=False, port=5000)

项目配置

开启DEBUG

为什么要开启

  • 如果开启DEBUG模式,那么在代码中抛出异常,在浏览器的页面中可以看到具体的错误信息及具体的错误位置,方便开发者调试。但如果想在页面上调试,应该输入控制台的PIN码。
  • 如果开启,那么以后在python代码中修改了任何代码,只要CTRL+S保存代码,flask就会重新记载整个网站,不需要手动点击重新运行。

4种开启方式

  • app.run()传参debug=True;
  • 增加语句app.debug = True;
  • 配置参数: app.config.update(DEBUG=True)
  • 配置文件(以后项目使用这种): app.config.from_object(config)

示例

from flask import Flask

app = Flask(__name__)
# 方式2
# app.debug = True
# 方式3
# app.config.update(DEBUG=True)
# 方式4(真正项目采用这种配置文件方式)
# app.config.from_pyfile('config.py')


@app.route('/')
def hello_world():
	return 'Hello World!'

if __name__ == '__main__':
	# 方式1
	app.run(debug=True)

配置config

from_object(obj)方式

  • 使用:

    import config
    app.config.from_object(config)
    

from_pyfile(path[,slient=False])方式

app.config.from_pyfile("./config.py")
  • 此方式不局限于.py文件,也可以用.txt文件…
  • silent: False:如果找不到文件报错反之True不报错

url请求

url唯一

  • 在定义url的时候,在最后加一个/

  • 原因:

    • 如果不加,那么浏览器访问这个url的时候,用户一旦加入斜杠,就访问不到。
    • 搜索引擎会将不加和加/的是为两个不同的url,而其实它们代表同一个,那么就会给搜索引擎造成误解。加了斜杠,就不会出现没有斜杠的情况。
    # 一定要在定义url时最后加一个'/'
    @app.route('/user/')
    def user():
        return 'User...'
    

Http请求

GET和POST

  • GET: 把参数放到url中,通过?key=value的形式传递,因为会把参数放到url中,所以某些视力好的人一眼就能看到用户密码,这样就不太安全。

  • POST: 把参数放到Form Data中,这样就避免了被偷窥的风险,但是别人想获取密码,可以采用抓包的形式。同时,因为POST请求可以提交一些文件、数据,那么增加了让黑客恶意发送病毒文件的风险,所以也不是绝对安全

设置请求方式

  • 默认只用GET方式,设置请求方式:app.route(url, methods)
@app.route('/user/',methods=['GET'])
def get_user():
	return 'Get User...'

@app.route('/user/',methods=['POST'])
def add_user():
	return 'Add User...'

@app.route('/user/',methods=['PUT'])
def update_user():
	return 'Update User...'

@app.route('/user/',methods=['DELETE'])
def delete_user():
	return 'Delete User...'

获取请求参数

[GET]路径参数

  • 参数传递/<参数名>,然后在视图函数也要定义同名的参数。
  • 限定数据类型:<数据类型:变量名>,数据类型:
    • string: 默认类型,但不接受/,\
    • path: 能接受/,\
    • int: 整型
    • float: 浮点型
    • uuid: 接受唯一的uuid字符串,uuid是个全宇宙唯一的字符串,可用来作为表的主键
    • any: 可以在url中指定多种路径
    @app.route('/p/<path:path>')
    def param_path(path):
        return 'path:{}'.format(path)
    
    @app.route('/s/<string:string>')
    def param_string(string):
        return 'string:{}'.format(string)
    
    @app.route('/<int:int>')
    def param_int(int):
        return 'int:{}'.format(int)
    
    @app.route('/<float:float>')
    def param_float(float):
        return 'float:{}'.format(float)
    
    @app.route('/<uuid:uuid>')
    def param_uuid(uuid):
        return 'uuid:{}'.format(uuid)
    
    @app.route('/<any(blog,qq):which>/<id>')
    def param_any(which,id):
        return '{}:{}'.format(which,id)
    

[GET]查询参数

  • 使用path形式将参数嵌入到路径中

  • 使用查询字符串, 即通过?key=value的形式传递,要引入request模块:

    @app.route('/query')
    def query():
    	account = request.args.get('account')
    	password = request.args.get('password')
    	return 'User(account={},password={})'.format(account, password)
    

    在这里插入图片描述

  • 默认情况下,上述取参方式得到的都是str类型,想要直接得到int,则:

    @app.route('/list')
    def list_user_page(): 
    	current = request.args.get('current', type=int)
    	size = request.args.get('size', type=int)
    	return 'UserList(current={},size={})'.format(current, size)
    
  • 适应性:若页面想要做SEO优化, 那么使用path的形式;如果不在乎搜索引擎的优化,那么使用查询字符串更好。

[POST]表单参数

  • POST传参可分为两大类,一类是在form传参,另一类是请求体传参body。
  • flask对这两类参数要采用不同的方式接收
@app.route('/login/form', methods=['POST'])
def login_form():
    """ 可接受form和urlencode """
    account = request.form.get('account')
    password = request.form.get('password')
    ok = account == '90217' and password == '123abc'
    return '登录成功' if ok else '登陆失败'

@app.route('/login/json', methods=['POST'])
def login_json():
    """ 可接受json表单 """
    account = request.json.get('account')
    password = request.json.get('password')
    ok = account == '90217' and password == '123abc'
    return '登录成功' if ok else '登陆失败'

注意

  • 获取请求参数,其实也可以request.args[key],request.form[key],request.json[key]的形式,但以后要禁止使用,因为如果key不存在会报错(500服务器内部错误影响用户体验)。以后都用dict的get方法获取参数
  • 对于get查询参数,使用request.args.get('xxx',type=int)时指定type属性来确定参数的数据类型。但是对于post表单参数来说,无法指定type属性只能使用get('xxx'),即request.json.get('xxx', type=int)是错误的

URL与视图函数的映射

url_for

  • 一般通过一个url就可执行到某一函数。反过来,我们知道一个函数,该如何去获取url呢?

  • 语法: url_for(func[,**kwargs])

    • func: 视图函数的名字。
    • **kwargs: 传递给url,如果传递的参数在之前的url已定义, 那么这个参数会以path的形式传递给url;若没定义,那么将以查询字符串?key=value的形式放到url
    @app.route('/urlfor/')
    def urlfor():
    	return url_for('url_target', q='shuang')
    
    # 此方式可以一行代码不用改,就改变url
    @app.route('/urltarget/<path:q>')
    # 此处的函数名和url_for里的相同
    def url_target(q):
    	return 'q:{}'.format(q)
    
  • 优点:

    • 将来如果修改了url,但没有修改url对应的函数名,就不用到处替换url
    • url_for()会自动处理那些特殊的字符,无需手动处理。

自定义url转换器

  • 刚刚在url映射的时候,我们看到了flask内置了几种数据类型的转换器,比如int/string。但如果它们不能满足你的需求时,可以自定义转换器。

  • 步骤:

    1. 导入模块:from werkzeug.routing import BaseConverter
    2. 写一个自定义类,继承BaseConverter,在自定义类中,重写regex
    3. 写一个转换器类,继承BaseConverter,
    • 在转换器类中实现to_python(self,value):这个方法返回值将会传递到view函数作为参数
    • 在转换器类中,实现to_url(self,values):这个方法的返回值,将会在调用url_for函数的时候生成符合要求的url形式
    1. 将这两个类, 映射到app.url_map.converters
    from werkzeug.routing import BaseConverter
    
    # url含有手机号的变量,必须限定这个变量的格式满足手机号码格式
    class TelConverter(BaseConverter):
      '''自定义URL转换器'''
    regex = r'1[34578]\d{9}'
    app.url_map.converters['tel'] = TelConverter
    
    class ListConverter(BaseConverter):
    def to_python(self, value):
    	return value.split("+")
    def to_url(self, value):
    	return '+'.join(value)
    app.url_map.converters['list'] = ListConverter
    
    
    @app.route('/tel/<tel:telNum>')
    def tel(telNum):
    return "您的手机号:{}".format(telNum)
    
    @app.route('/scripts/<list:lang>')
    def scripts(lang):
    return "选择的语言为:{}".format(lang)
    
    if __name__ == '__main__':
        app.run(debug=True)
    

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

重定向和跳转

  • 语法:redirect(path[,code=302])
    • code:重定向的种类,默认302时临时性重定向,也可传301实现永久性重定向
    from flask import request, redirect
    
    @app.route('/userinfo')
    def userinfo():
    	account = request.args.get('account')
    	if account:
    		return "用户{}的个人中心".format(account)
    	else:	# 如果未登录,就临时性重定向到登陆界面
    		return redirect('/login/', code=302)
    
    
    @app.route('/login/')
    def login():
    	return "Login..."
    

url响应

常见响应结果

string

@app.route('/')
def hello_world():
    return 'Hello World!'

json

from flask import jsonify

@app.route('/')
def hello_world():
    return jsonify(status=1, message='Hello World!', data=None)

Response

关于响应

  • 视图函数的返回值会自动转换成一个响应对象,flask转换逻辑如下:
    • 如果是一个合法的响应对象,直接返回;
    • 如果是字符串,flask会重新创建一个werkzeug.wrappers.Response对象,Response将该字符串作为主体,状态码为200,MIME类型为text/html,然后返回该Response对象。
    • 返回元组(response,status,headers),其中status值会覆盖默认的200状态码
    • headers可以是一个列表或字典,作为额外的消息头。
    • 如果以上条件都不满足,flask会假设返回值是一个合法的WSGI_t应用程序,并通过
      Response.force_type(rv,request.environ)转换作为一个请求对象。
  • 注意: 可以使用make_response方法来创建Response对象,这个方法可以设置如cookies,headers等信息。

视图函数中可以返回的值

  • 字符串,返回的字符串其实底层将这个字符串包装成了一个Response对象。
  • 元组,元组的形式是(响应体,状态码,头部信息),也不一定3个都要写,写两个也是可以的;其实在底层也将元组包装成了一个Response对象
  • Response及子类.

自定义响应

  1. 继承Response
  2. 实现方法:force_type(cls,rv,environ=None)
  3. 指定app.response_class为程序员自定义的对象.
    • 条件:视图函数返回的数据既不是字符串,又不是元组,也不是Response对象
    • 注:如果满足条件,就将返回值传给force_type,然后再将force_type的返回值返回给前端
  • jsonify除了将字典转换成json对象,还将该对象包装成了Response对象

示例

from flask import Flask, Response
from flask import jsonify

app = Flask(__name__)

# 将视图函数中返回的字典,转换成json再返回
class JsonResponse(Response):
    '''视图函数Response返回值详解'''
    @classmethod
    def force_type(cls, response, environ=None):
        """只有返回非字符串/非元组/非Response对象才会调用"""
        # response是视图函数的返回值
        if isinstance(response, dict):  # 如果返回字典,改成json对象
            response = jsonify(response)
        return super(JsonResponse, cls).force_type(response, environ)
app.response_class = JsonResponse


@app.route('/demo01')
def demo01():
    """如果是一个合法的响应对象,直接返回"""
    return Response('test1')

@app.route('/demo02')
def demo02():
    """返回元组(response,status,headers)"""
    return ('test2', 200, {'username': 'Azir'})  # 设置头部信息,在响应头

@app.route('/demo03')
def demo03():
    """如果以上条件都不满足,会假设返回值是一个合法的WSGI_t,并通过Response转换"""
    return {'username': 'LZC', 'age': 20}

@app.route('/demo04')
def demo04():
    """Response及子类"""
    res = Response('test4')
    res.set_cookie('goddess', 'zhengshuang')  # 可以设置cookie等参数
    return res


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

模板

介绍

  • 在之前的章节中,视图函数只是直接返回文本,而在实际生产中很少这样用。因为实际的页面大多是带有样式和复杂逻辑的HTML代码,这可以让浏览器渲染出非常漂亮的页面。目前市场上有非常多的模板系统,其中最知名最好用的就是Jinja2和Mako。我们先来看一下这两个模板的特点和不同:
  • Jinja2:Jinja是日本寺庙的意思,并且寺庙的英文是temple和模板的英文template的发音类似。Jinjia2是默认的仿Django模板的一个模板引擎,由Flask的作者开发。它速度快,被广泛使用,并且提供了可选的沙箱模板来保证执行环境的安全。它有以下优点:
    • 让前端和后端开发者分离;
    • 减少flask项目代码的耦合性,页面逻辑放在模板中,业务逻辑放在视图函数中,将页面逻辑和业务逻辑解耦有利于代码的维护;
    • 提供控制语句、继承等高级功能,降低开发的复杂度。
  • Marko:它从Django、Jinja2等模板借鉴很多语法和API,它有以下优点:
    • 性能和Jinja2相近。
    • 有大型网站在使用,有成功的案例,如Reddit和豆瓣都在使用;
    • 有知名的web框架支持。Pylons和Pyramid这两个web框架内置模板就是Mako;
    • 支持在模板中写几乎原生的Python语法,对Python工程师比较友好,开发效率高;
    • 自带完整的缓存系统,也提供了非常好的扩展接口,很容易切换成其他的缓存系统

模板规则

模板查找路径

  • 渲染模板时,默认从项目根目录下的templates查找html文件
  • 更换目录: app = Flask(__name__[,template_folder])方法传入这个可选参数。

模版传参及其技巧

  • 在使用render_template可以直接传递参数。
  • 若参数过多,可将参数放入字典中,然后向render_template()传参的时候,使用**context传递动态参数;如果字典中还嵌套字典,在html页面可以用母参数.key母参数['key'] 的方式传递二级参数。

模版中使用url_for

  • 使用起来跟我们后台视图函数中的url_for()基本相同,也是传递视图函数的名字,也可以传递参数。
  • 例子: {{ url_for('函数名',**kwargs) }}

过滤器

什么是过滤器

  • 定义: 过滤器是通过管道符号 | 进行使用的。例如{{ name | length }}将返回name的长度。过滤器相当于一个函数,把当前的变量传入到过滤器中,然后过滤器根据自己的功能,再返回相应的值,之后再将结果渲染到页面中。
  • 语法: {{ 变量 | 过滤器名字 }}

常用过滤器

  • 默认值过滤器value | default('默认值'[,boolean=False]):参数boolean
    • False:如果value根本不存在,就会显示默认值;如果value存在、即使为False类型,也不会使用默认值;
    • True:如果value结果为False(不存在、None、空字符串、空字典、空列表…),就会返回默认值。
    • 可使用{{value or '默认值'}}来代替{{value | default('默认值',boolean=True)}}
  • 转义过滤器: {{ value | safe }},开启转义,但可以不设置,默认就是自动转义,将<,>等字符转化为HTML符号。
    • 可以关闭字符的自动转义;{{ value | escape }}
      {% autoescape off/on %}	# 可以利用此方法代替上面过滤器来开启
      		<>代码块</>			# 或关闭自动转义
      {% endautoescape %}
      
  • 首尾过滤器: {{ list | first/last }}返回序列的第一个或最后1个元素。
  • 格式化过滤器: {{ "你好,%s" | format("Unity") }}
  • 替代过滤器: {{ value | replace('要替换的','替换后的' }}
  • join(value,d=u'):将一个序列用d这个参数的值拼接字符串
  • int/float/string(value):将值转换为指定的类型。
  • lower/upper(value): 对字符串大小写
  • trim:截取字符串前边和后边的空白字符
  • wordcount(s):计算一个长字符串中单词的个数。

语句

条件语句

  • 和python类似可使用>,<,<=,>=,==,and,or,not,()

    {% if 条件0  %}
    	语句0
    {% else %}
    	语句1
    {% endif %}
    

循环语句

  • 和python相似可遍历所有的序列及迭代器

    {% for . in ...  %}
    	语句0
    {% else %}
    	语句1
    {% endfor %}
    
  • 获取遍历状态:

    • loop.index: 当前迭代的序号
    • loop.index0: 当前迭代的索引
    • loop.first: 判断是否为第一次迭代
    • loop.last: 判断是否为最后一次迭代
    • loop.length: 序列的长度
  • 注意:无breakcontinue语句

概述

  • 模板中的宏跟python中的函数相似,可传递参数,但不能有返回值,可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量。使用宏时,参数可为默认值

导入宏

  • 语法
    • {% imprt '宏文件路径' as xxx %}
    • {% from '宏文件路径' import '宏名称' (as xxx) %}
  • 宏文件路径不要以相对路径,都要以templates作为绝对路径寻找。
  • 如果想在导入宏的时候,就把当前模板的一些参数传给宏所在的模板,那么应该在导入时使用{% imprt '宏文件路径' as xxx with context %}

include标签

  • 这个标签相当于直接将指定的模板中的代码复制粘贴到当前位置。
  • 如果想要使用父模板中的变量,直接用即可,不需要使用... with context
  • 路径也要用绝对路径。

set语句

  • 在模板中可以使用set语句定义变量,一旦定义了这个变量,在后面的代码中都可以使用这个变量

  • 语法: {% set a = '哈哈哈' %}, 此时a相当于一个全局变量

with语句

  • 定义的变量,只能在with语句中执行,超过这个代码块就不能使用,相当于一个局部变量
    {% with class_='数媒1902' %}
    <p>班级:{{ class_ }}</p>
    {% endwith %}						<!-- 不要忘记endwith -->
    <p>超过作用域的班级:{{ class_ }}</p>   <!-- 无效 -->
    
  • 注意with语句也不一定要跟一个变量,可以定义一个空的{% with %},以后在它当中通过set语句定义变量,这样的效果是不变的

加载静态文件

  • 语法: {{ url_for('static',filename='路径') }} ,在html标签中传递这个属性即可

模板继承

  • 可以把一些公用的代码单独取出来放到一个父模板中,以后子模版直接继承就可以使用,减少了代码的复用,以后修改起来也非常方便。
<!-- 父模板 -->
{% block body_block %}	
{% endblock %}
    
<!-- 子模版 -->
{% extends '文件路径' %}
{% block body_block %}
	子模版内容
{% endblock %}
  • 子模版调用父模板block中的代码:默认情况下,子模版如果调用父模板的block,父模板block语句中的代码会被覆盖。如果想保留,可以在子模版block语句中使用语句{{ super() }}语句
  • 子模版一个block调用另一个block中的代码:{{ self.其他block名称() }}
  • 子模版中代码第一行必须为继承语句: {% extends '文件路径' %}
  • 子模版中,如果想要在浏览器页面渲染代码,必须放在block语句中,否则不会被渲染。如果想写其他代码,必须在父模板中写。

视图

映射

  • 添加映射:添加url与视图函数的映射
    • 语法:add_url_rule(rule[,endpoint=None,view_func=None])endpoint参数:
      • 如果传入那么默认会使用view_func的名字作为endpoint,以后在使用url_for()时,就要看在映射的时候有没有传递endpoint参数:如果传了,那么就应该使用endpoint指定的字符串;
      • 如果没有传递,应该使用view_func的名字
  • 装饰器@app.route(rule,**options):这个装饰器的底层,其实也是用add_url_rule()来实现url与视图函数映射的。

类视图

标准类视图及场景

  • 标准类视图,必须继承flask.views.View
  • 必须实现dipatch_request()方法,以后请求过来后,都会执行这个方法,这个方法的返回值相当于之前的视图函数,也必须返回Response对象或者子类的对象,或者是字符串、元组。
  • 须通过app.add_url_rule(rule,endpoint,view_func=ListView.as_view('list'))来做url与视图的映射。view_func参数必须使用类视图下的as_view类方法来转换。
  • 如果指定endpoint,那么在使用url_for()反转时必须使用endpoint指定的名字;如果未指定,那么就可以使用as_view(视图名字)中指定的视图名字作为反转。
  • 类试图的的好处:可以继承,把一些公共的东西抽取出来放到父类中,子视图直接拿来用即可。但是不是说所有的视图都要使用类试图,这个视情况而定。

基于请求方法的类视图

  • 基于方法的类试图,是根据请求的method来执行不同的方法的。如果用户发送get请求,会执行get方法。如果发送post请求,执行post方法。其他方法也类似,如delete和put。
  • 优点: 让代码简洁,所有get请求相关的代码都放在get()中, 所有post请求相关的代码都放在post()中,无需通过每个函数下的if request.method == 'GET'就可处理所有请求

类视图中使用装饰器

  • 如果使用的是函数视图,那么自己定义的装饰器必须放在@app.route()下面,否则这个装饰器不起任何作用。
  • 类视图的装饰器,需要重写类视图的一个类属性decoractors=[],它是一个列表或元组,里面装的是所有装饰器的名称。

模块化路由(蓝图)

蓝图

基本使用

# 蓝图文件
from flask import Blueprint
user_bp = Blueprint('user', __name__)

# 主文件
from 蓝图文件名.py文件名 import user_bp
app.regist_blueprint(user_bp)
  • 如果想在某个蓝图下的所有url都有一个url前缀,如在有关用户的url前面都加一个/user,可在定义蓝图时指定url_prefix='/user'。但在定义时注意后面的斜杠, 如果加上, 以后在定义url视图函数的时候就不要再url前边加斜杠了。语法如下:
    user_bp = Blueprint('user', __name__,url_prefix='/user')

  • 优点:让Flask项目更加模块化,结构更清晰,可以将相同模块的视图函数放在同一个蓝图下、同一个文件中,方便管理。

模版文件寻找规则

  • 如果项目中的templates文件夹中有相应的模板文件,就直接优先使用。
  • 如果没有,那么就到在定义蓝图时指定的路径中寻找。该路径可为绝对路径,也可为相对路径。相对路径是相对当前这个蓝图py文件所在的目录。
  • 语法user_bp=Blueprint('user', __name__, template_folder='mods') , 因为这个蓝图文件是在blueprint/user.py,那么就会到blueprint/mods直接查找模板。
蓝图中静态文件寻找规则
  • 在模板文件中,加载静态文件,如果使用url_for('static'),那么就只会在app默认的static文件夹目录下查找静态文件
  • 如果在加载静态文件时,指定蓝图的名字,如url_for('user.static'),那么就会到这个蓝图指定的static_folder下查找静态文件。
  • py主文件:
    # 这个蓝图文件是在blueprint/user.py,那么到blueprint/statics直接查找模板
    user_bp = Blueprint('user', __name__, static_folder='statics')
    
  • HTML模板文件:
    <!-- url_for()第1个参数传入蓝图名字user,而不是py文件名 -->
    <link rel="stylesheet" href="{{ url_for('user.static',filename='用户.css') }}">
    

url_for反转蓝图

  • 使用蓝图,以后想反转蓝图中的视图函数为url,就应该在使用url_for()时指定这个蓝图的名字,比如url_for('user.login'),否则就找不到这个endpoint
  • 在html模板中的url_for()同样也要这么写。
  • 即使在同一个蓝图本身反转url,也要这么写,指定蓝图的名字。

蓝图实现子域名

  • 创建蓝图对象时,可传递subdomain参数指定子域名的前缀:
  user_bp = Blueprint('user', __name__, subdomain='lzc')
  • 需要在主app文件中配置app.config参数,注意IP和localhost中都不能有子域名:
    app.config['SERVER_NAME']='jd.com:5000'
    
  • C:\Windows\System32\drivers\etc找到hosts文件,添加域名和本机的映射:
    # 注意: 域名,子域名都需要做映射
    127.0.0.1  jd.com
    127.0.0.1  lzc.jd.com
    

路由模块化

创建路由模块(蓝图)

  1. 在根目录(与app.python同级目录)下创建blueprint文件夹
  2. 假设要创建user路由模块,则在blueprint文件夹下创建user.py
  3. user.py中创建bp对象,并编写路由处理函数
from flask import Blueprint

# url_prefix: 公共路由前缀
bp_user = Blueprint('user', __name__, url_prefix='/user')

@bp_user.route('/login', methods=['POST'])
def login():
    """ 用户登录 """
    return 'logining..'

app导入及注册路由模块

from flask import Flask

from blueprint.user import bp_user

app = Flask(__name__)
# 模块化路由
app.register_blueprint(bp_user)

@app.route('/')
def hello_world():
    return 'Hello World!'

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

WTForms

表单验证

两个作用

  1. 做表单验证,用户提交上来的数据验证是否合法
  2. 模板渲染

步骤

  1. 自定义一个类,继承自wtforms.Form
  2. 定义好需验证的字段,字段名必须和模板中需要验证的input标签name属性保持一致
  3. 在需要验证的字段上,指定验证器
  4. 以后在视图中,就只需要使用这个表单类的对象,把需要验证的数据request.form传给这个表单类,以后调用form.validate()方法
    • 如果返回True,代表用户输入的数据合法,否则不合法
    • 如果验证失败,可通过form.errors来获取具体的错误信息

示例

from wtforms import Form, StringField, IntegerField, BooleanField, SelectField
from wtforms.validators import *


class RegisterForm(Form):
    '''注册表单验证'''
    username = StringField(validators=[Length(min=4, max=18, message='用户名长度必须在4-18位之间')])
    passward = StringField(validators=[Length(min=6, max=10)])
    passward_repeat = StringField(validators=[EqualTo('passward')])


class LoginForm(Form):
    '''登录表单验证'''
    # pip install -i https://pypi.douban.com/simple email-validator
    email = StringField(validators=[Email()])
    str = StringField(validators=[InputRequired()])
    age = IntegerField(validators=[NumberRange(18, 88)])
    phone_num = StringField(validators=[Regexp(r'1[34578]\d{9}')])
    url = StringField(validators=[URL()])
    uuid = StringField(validators=[UUID()])
    vcode = StringField(validators=[Length(min=4, max=4)])

    def validate_vcode(self, field):
        """自定义验证"""
        if field.data != '6666':
            raise ValidationError('验证码错误!')


class SettingsForm(Form):
    """ 设置表单验证 """
    username = StringField('用户名:', validators=[InputRequired()])
    age = IntegerField('年龄:', validators=[NumberRange(18, 88)])
    remember = BooleanField('记住我: ')
    tags = SelectField('选择', choices=[(1, 'JavaWEB'), (2, 'NodeJs')])

系统验证器

  • Email():验证是否为邮箱
  • EqualTo():验证上传的数据是否和指定字段相等,常用于注册时检验’密码’和’确认密码’操作
  • InputRequired():必须输入内容,不能不输入。
  • Length(min=,max=):长度限制
  • NumberRange(a,b):数字区间。
  • Regexp:使用正则表达式验证。
  • URL:验证是否为url。
  • UUID:验证uuid数据。

自定义验证器

步骤

  1. 在类里定义一个方法,命名规则为validate_字段名(self,field)
  2. 在方法中,field.data可以获取到这个字段的值
  3. 如果数据满足条件,可以pass;如果验证失败, 应该抛出一个validators.ValidationError的异常 , 并把这个验证失败的值传到这个异常类中

示例

class LoginForm(Form):
    '''登录表单验证'''
    # pip install -i https://pypi.douban.com/simple email-validator
    email = StringField(validators=[Email()])
    str = StringField(validators=[InputRequired()])
    age = IntegerField(validators=[NumberRange(18, 88)])
    phone_num = StringField(validators=[Regexp(r'1[34578]\d{9}')])
    url = StringField(validators=[URL()])
    uuid = StringField(validators=[UUID()])
    vcode = StringField(validators=[Length(min=4, max=4)])

    def validate_vcode(self, field):
        """自定义验证"""
        if field.data != '6666':
            raise ValidationError('验证码错误!')

文件上传及资源开放

static资源开放

**开放static文件夹下的资源:**在定义app变量时,指定static_folder参数为static文件夹根目录即可,注意相对路径

# 这里static文件夹和app.py在同一路径
app = Flask(__name__, static_folder='./static')

访问:格式为http://虚拟根目录/static_folder最后一层目录/文件路径

  • 注意不能省略/static,如http://localhost:5000/static/parser/Drain_result/1_00e355a5-22a7-4c48-9c0b-5b75c7b690e5.csv
    在这里插入图片描述
    在这里插入图片描述

  • 注意如果static_folder是多层目录,那么中间要添加最后一层目录。例如app = Flask(__name__, static_folder='./results/data/'),那么要访问下面的资源,访问的url为http://127.0.0.1:9000/data/origin/01.png,注意不是http://127.0.0.1:9000/results/data/origin/01.png!!!
    在这里插入图片描述

代码调用:

filename1 = "meituan/meituan_hongbao/meituanbg.jpg"
meituanbg = url_for('static', filename=filename1, _external=True)
# 效果: meituanbg = http://localhost:5000/static/meituan/meituan_hongbao/meituanbg.jpg

下载资源

如果出于安全因素不希望用户访问static路径下的所有文件,而是提供一个公共接口让用户传参访问获取,则可采用此方式。

后端:有两种方式,尽量不要使用send_file(path),而使用send_from_directory(directory,path),后者限定了文件夹更安全。

  • 后者传入directory和path,当然path不一定非得是末层文件(如a.txt),是路径也可以(如a/b/c.txt,下面例子就是这种方式)
  • flask返回文件流,不要直接return open(path, 'wb'),而是用上面两个接口,具体原因不详
import os
from flask import Flask, request, send_from_directory, send_file

UPLOAD_PATH = os.path.join(os.path.dirname(__file__), 'upload')

app = Flask(__name__)

@app.route('/file/<user_id>/<method>')
def download_file_01(user_id, method):
    filename = '{}/{}.csv'.format(user_id, method)
	return send_from_directory(UPLOAD_PATH, filename)

@app.route('/download', methods=['POST'])
def download_file_02():
    user_id = request.json['user_id']
    method = request.json['method']
    template_path = './static/parser/{}/{}.csv'.format(user_id, method)
    # 文件存在性和合法性校验
    if not os.path.exists(template_path) or not os.path.isfile(template_path):
        return jsonify(status=0, message='未找到此文件', data=None)
    return send_file(os.path.abspath(template_path))

前端:假设要下载的文件在服务器中存储在/static/parser/2/Drain.csv

  • static资源开放的方式访问:
    url = 'http://localhost:5000/static/parser/2/Drain.csv'
    response = requests.get(url)
    with open('./test.csv', 'wb') as fp:
        fp.write(response.content)
    
  • download_file_01方式访问:
    url = 'http://127.0.0.1:5000/file/2/Drain'
    response = requests.get(url)
    with open('./test.csv', 'wb') as fp:
        fp.write(response.content)
    
  • download_file_02方式访问:
    url = 'http://127.0.0.1:5000/download'
    body = {
        'user_id': 2,
        'method': 'Drain',
    }
    response = requests.post(url, json=body)
    with open('./test.csv', 'wb') as fp:
        fp.write(response.content)
    

上传及访问

import os
from flask import Flask, request, render_template

app = Flask(__name__)

UPLOAD_PATH = os.path.join(os.path.dirname(__file__), 'upload')

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'GET':
		return render_template('fileupload.html')
	else:
        file_avatar = request.files['avatar']
        filename = secure_filename(file_avatar.filename)
        file_avatar.save(os.path.join(UPLOAD_PATH, filename))
        return '文件上传成功'

上传时添加校验

步骤

  1. 在模板form表单中,必须指定encotype='multipart/form-data'
  2. 后台使用request.files.get('avatar')来获取文件数据
  3. 保存文件前,先要使用werk.utils.secure_filename来对上传上来的文件名进行过滤,这保证不会有安全问题
  4. 获取到上传来的文件后,调用avatar.save(path)保存文件
  5. 从服务器上读取文件,应该定义一个url和视图函数,来获取指定的文件。在这个视图函数中,使用send_from_directory(path,filename)来获取。

示例

import os
from flask import Flask
from flask import request, render_template, send_from_directory
from werkzeug.utils import secure_filename
from werkzeug.datastructures import CombinedMultiDict
from forms import UploadForm

app = Flask(__name__)

paths = os.path.join(os.path.dirname(__file__), 'upload')


@app.route('/')
def hello_world():
	return 'Hello World!'


@app.route('/upload', methods=['GET', 'POST'])
def upload():
	if request.method == 'GET':
		return render_template('fileupload.html')
	else:
		# form是不可变类型,无法使用form.update()来更新files和form
		form = UploadForm(CombinedMultiDict([request.form, request.files]))
		if form.validate():
			# 获取描述信息:有两种方法
			# desc = request.form.get('desc')
			print(form.desc.data)
			# avatar = request.files.get('avatar')
			avatar = form.avatar.data
			filename = secure_filename(avatar.filename)
			avatar.save(os.path.join(paths, filename))
			return '文件上传成功!!'
		else:
			return form.errors

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

验证文件

步骤

  1. 定义表单的时候,对文件的字段,需要采用FileField类型
  2. 验证器应该从flask_wtf.file中导入,
    • flask_wtf.file.FileRequired验证上传文件是否为空;
    • flask_wtf.file.FileAllowed验证文件后缀名是否正确。
  3. 视图函数中,使用from werkzeug.datastructures import CombinedMultDict来把request.formrequest.files合并,再传给表单来验证。

获取描述信息

  • desc = request.form.get('desc')
  • desc = form.desc.data

示例

  • form文件
    from wtforms import Form,FileField
    from wtforms.validators import InputRequired
    from flask_wtf.file import FileRequired,FileAllowed
    
    class UploadForm(Form):
        avatar = FileField(validators=[FileRequired(),FileAllowed(['jpg','png'])])
        desc = StringField(validators=[InputRequired()])
    
  • 主文件:
    from werkzeug.datastructures import CombinedMultiDict
    from forms import UploadForm
    
    # form是不可变类型,无法使用form.update()来更新files和form
    form = UploadForm(CombinedMultiDict([request.form,request.files]))
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值