Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

时间:2022-12-15 18:08:40

1.表单介绍

1.1.表单知识回顾

 

常见的表单元素:

  • 表单标签<form>
    • action:表单提交的URL地址
    • method:表单请求的方式(GET/POSt)
    • enctype:请求内容的形式,如:application/x-www-form-urlencoded、multipart/form-data
  • 单行文本框/多行文本框
    • textarea:多行文本
    • 单行文本(type的不同值),常见的有:text(单行文本)、password(密码)、email(邮箱)、url(URL)、number(数字)、color(颜色)、日期时间等(date、month、week等等)
  • 选择(单选、多选、下拉选择)
    • 单选: <input type="radio"> 
    • 多选: <input type="checkbox"> 
    • 下拉框选择: <select><option></option></select> 
  • 隐藏表单域: <input type="hidden"> 
  • 表单按钮: <input type="button">  <button></button> 
  • 文件上传框: <input type="file"> 
 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>表单知识点回顾</title>
 6 </head>
 7 <body>
 8 <form action="/index" method="post" enctype="multipart/form-data">
 9     <ul>
10         <li>
11             用户地址:
12             <textarea name="" id="" cols="30" rows="10" placeholder="请输入地址"></textarea>
13         </li>
14         <li>
15             用户名:
16             <input type="text" placeholder="请输入用户名">
17         </li>
18         <li>
19             密码:
20             <input type="password" placeholder="请输入密码">
21         </li>
22         <li>
23             用户的年龄:
24             <input type="number">
25         </li>
26         <li>
27             性别:
28             <label><input type="radio" value="男" name="sex"></label>
29             <label><input type="radio" value="女" name="sex"></label>
30         </li>
31         <li>
32             爱好
33             <input id="id-paly-ball" type="checkbox" value="打球">
34             <label for="id-paly-ball">打球</label>
35             <input id="id-paly" type="checkbox" value="玩耍">
36             <label for="id-paly">玩耍</label>
37         </li>
38     </ul>
39 </form>
40 </body>
41 </html>

 

在视图中获取表单值:

  • get请求: request.args.get('name',None) 
  • post请求: request.form.get('name',None) 

 

思考:HTML表单在Flask中如何快速使用?

 

1.2.wtf表单介绍

通过在Flask中写python代码,可以直接生成HTML表单,通过wtf实现。

flask-wtf提供的3个组要功能:

  • 集成wtforms
  • CSRF保护,flask-wtf能保护所有表单免受跨站请求伪造(CSRF)的攻击
  • 与Flask-Uploads一起支持文件上传

安装

  • pip安装: pip install Flask-WTF 
  • 源码安装: python setup.py install 

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

下载好之后如何使用呢?需不需要一些配置呢?它的配置很简单,它配置的目的就是用来做CSRF保护。只需要在Flask app上加上 WTF_CSRF_SECRET_KEY = 'a random string' 就行了,这个key可以随便给一个字符串,没有要求,只是一个随机的串就行了。

1 from flask import Flask, render_template,flash
2 from flask_sqlalchemy import SQLAlchemy
3 
4 app = Flask(__name__)
5 # 配置数据库的连接参数
6 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:@127.0.0.1/test_flask'
7 app.config['SECRET_KEY'] = 'abc'    #消息闪现保护的key,注意当消息闪现的SECRET_KEY配置了,WTF_CSRF_SECRET_KEY也可以不用配置,但是你配置了也没有影响,这个知识点了解一下
8 app.config['WTF_CSRF_SECRET_KEY'] = 'abc1234abc'    #WTF_CSRF_SECRET_KEY配置

 

第一个表单模型

1 from flask_wtf import FlaskForm     #导入flask_wtf的FlaskForm类
2 from wtforms import StringField     #StringField表示的是一个文本的输入框
3 
4 
5 class LoginForm(FlaskForm):     #继承FlaskForm类
6     """ 登录表单的实现 """
7     username = StringField(label='用户名')

 

1.3.表单常用字段类型及渲染

 

表单常用字段类型

  • 文本/字符串
    •  StringField :字符串输入
    •  PasswordField :密码输入
    •  TextAreaField :长文本输入
    •  HiddenField :隐藏表单域
  • 数值(整数,小数)
    •  FloatField :浮点数输入
    •  IntegerField :整数输入
    •  DecimalField :小数输入(更准确)
  • 选择
    •  RadioFied :radio单选
    •  SelectField :下拉单选
    •  SelectMultipleField :下拉多选
    •  BooleanField :勾选(复选框)
  • 日期/时间
    •  DateField :日期选择
    •  DateTimeField :日期时间选择
  • 文件/文件上传
    •  FileField :文件单选
    •  MultipleFileField :文件多选
  • 其他
    •  SubmitField :提交按钮
    •  FieldList :自定义的表单选择列表(如:选择用户对象)
    •  FormField :自定义多个字段构成的选项

表单字段的常用核心参数

  •  lable :lable标签(如:输入框钱的文字描述)
  •  default :表单的默认值
  •  validators :表单验证规则
  •  widget :定制界面显示方式(如:文本框、选择框)
  •  description :帮助文字

表单渲染

使用模板语法渲染表单内容:

  • 表单输入区域: {{form.username}} 
  • 表单label: {{form.username.label}} 

 

实例代码:

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

forms.py:python编写登录表单页面导入FlaskForm,wtfforms实现

1 from flask_wtf import FlaskForm
2 from wtforms import StringField, PasswordField, SubmitField
3 
4 
5 class LoginForm(FlaskForm):
6     """ 登录表单的实现 """
7     username = StringField(label='用户名', default='admin')
8     password = PasswordField(label='密码')
9     submit = SubmitField('登录')

app.py:导入forms文件,将python编写好的HTML页面展示类传递给html文件

 1 from flask import Flask, render_template
 2 
 3 from forms import LoginForm
 4 
 5 app = Flask(__name__)
 6 app.config['WTF_CSRF_SECRET_KEY'] = 'abc1234abc'
 7 app.config['SECRET_KEY'] = 'abc'
 8 
 9 
10 @app.route('/form', methods=['GET', 'POST'])
11 def page_form():
12     """ form 表单练习 """
13     form = LoginForm()
14     return render_template('page_form.html', form=form)

page_form.html

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Flask Form表单练习</title>
 6 </head>
 7 <body>
 8     <h3>欢迎登录</h3>
 9     <form action="" method="post">
10         <p>
11             {{ form.username.label }}
12             {{ form.username }}
13         </p>
14         <p>
15             {{ form.password.label }}
16             {{ form.password }}
17         </p>
18         <p>
19             {{ form.submit }}
20         </p>
21 
22     </form>
23 </body>
24 </html>

 

1.4.通过表单保存数据

保单保存数据步骤:

  • 第一步:检测表单是否已经通过验证
    • form.validate_on_submit()
  • 第二步:获取表单中传递过来的值
    •  form.field_name.data 
  • 第三步:业务逻辑代码编写(结合ORM)

 

表单保存数据的时候会触发CSRF表单保护:

  • 默认模板是开启CSRF保护
  • 关闭单个表单CSRF保护
    •  form = LoginForm(csrf_enabled=False) 
  • 全局关闭(不推荐)
    •  在类上面加上:WTF_CSRF_ENABLED=False 

如果不关闭CSRF保护,如何处理:

同步请求CSRF保护,在模板中添加csrf_token,通过CSRF机制的验证:

  • 方式一:{{ form.csrf_token }}
  • 方式二:<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">

 

实例代码:

新增用户注册表单,对注册的内容进行入库,注册成功后返回index主页面。

app.py

 1 from flask import Flask, render_template, flash, redirect, url_for
 2 from flask_sqlalchemy import SQLAlchemy
 3 
 4 from forms import RegisterForm
 5 
 6 app = Flask(__name__)
 7 # 配置数据库的连接参数
 8 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:@********/test_flask'
 9 app.config['WTF_CSRF_SECRET_KEY'] = 'abc1234abc'
10 app.config['SECRET_KEY'] = 'abc'
11 db = SQLAlchemy(app)
12 
13 
14 class User(db.Model):
15     __tablename__ = 'weibo_user'
16     id = db.Column(db.Integer, primary_key=True)
17     username = db.Column(db.String(64), nullable=False)
18     password = db.Column(db.String(256), nullable=False)
19     birth_date = db.Column(db.Date, nullable=True)
20     age = db.Column(db.Integer, default=0)
21 
22 @app.route('/')
23 def index():
24     """  首页 """
25     return render_template('index.html')
26 
27 @app.route('/user/register', methods=['GET', 'POST'])
28 def page_register():
29     """ 新用户注册 """
30     # csrf_enabled为False表示不做csrf校验
31     # form = RegisterForm(csrf_enabled=False)
32     form = RegisterForm()
33     # 用户在提交表单的时候,会触发validate_on_submit
34     if form.validate_on_submit():
35         # 表单验证通过,接下来处理业务逻辑
36         # 1. 获取表单数据
37         username = form.username.data
38         password = form.password.data
39         birth_date = form.birth_date.data
40         age = form.age.data
41         # 2. 构建用户对象
42         user = User(
43             username=username,
44             password=password,
45             birth_date=birth_date,
46             age=age
47         )
48         # 3. 提交到数据库
49         db.session.add(user)
50         db.session.commit()
51         print('添加成功')
52         # 4. 跳转到登录页面
53         return redirect(url_for('index'))
54     else:
55         # 打印错误信息
56         print(form.errors)
57     return render_template('page_register.html', form=form)

forms.py

 1 from flask_wtf import FlaskForm
 2 from wtforms import StringField, PasswordField, SubmitField, DateField, IntegerField
 3 
 4 
 5 class RegisterForm(FlaskForm):
 6     """ 用户注册表单 """
 7 
 8     # def __init__(self, csrf_enabled, *args, **kwargs):
 9     #     super().__init__(csrf_enabled=csrf_enabled, *args, **kwargs)
10 
11     username = StringField(label='用户名', default='')
12     password = PasswordField(label='密码')
13     birth_date = DateField(label='生日')
14     age = IntegerField(label='年龄')
15     submit = SubmitField('注册')

page_register.html

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>用户注册</title>
 6 </head>
 7 <body>
 8     <h3>用户注册</h3>
 9 {#  todo 注释内容:使用宏来把表单进一步完善 #}
10     <form action="{{ url_for('page_register') }}" method="post">
11         {{ form.csrf_token }}
12         <p>
13             {{ form.username.label }}
14             {{ form.username }}
15         </p>
16         <p>
17             {{ form.password.label }}
18             {{ form.password }}
19         </p>
20         <p>
21             {{ form.birth_date.label }}
22             {{ form.birth_date }}
23         </p>
24         <p>
25             {{ form.age.label }}
26             {{ form.age }}
27         </p>
28         <p>
29             {{ form.submit }}
30         </p>
31 
32     </form>
33 </body>
34 </html>

index.html

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>用户首页</title>
 6 </head>
 7 <body>
 8 注册成功
 9 </body>
10 </html>

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

 Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

 

2.表单验证与图片上传

2.1.表单验证

思考:以手机注册为例,不验证表单会怎么样?

  • 用户输入的可能不是手机号
  • 用户输入的可能不是他的手机号
  • 不断的提交表单

思考:表单验证为了什么?

  • 更好的用户体验
  • 更少的安全隐患
  • 永远不要相信用户的输入

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

内置的表单验证器

  •  DataRequired/InputRequired :必填验证
  •  Email/URL/UUID :电子邮箱/URL/UUID格式验证
  •  Length(min=-1,max=-1,message=None) :长度范围验证
  •  EqualTo(fieldname,message=None) :重复验证,用于密码的二次输入验证和上一次是否一致

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

 

自定义表单验证

  • 场景一:只有本表单使用,在表单类里面创建一个方法,方法名为:validatge_需要验证的变量,在方法中编写验证逻辑
  • 场景二:多个表单中使用,如:验证手机号码,登录/注册/修改用户信息页面均会用到。在表单类外面声明一个验证方法,传入form对象。这个方法命名就没有要求,这个见示例代码。

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

????图为场景一的示例,场景二跟这个差不多;两者的区别主要在于一个放到类里面,一个放到类外面;备注:方法命名的规则暂时这么理解,我学的好像是这样的,我是这么理解的,有知道所以然的可以教下我,谢谢!

 

实例:

场景一(当前表单使用):

 1 import re
 2 
 3 from flask_wtf import FlaskForm
 4 from wtforms import StringField, PasswordField, SubmitField, DateField, IntegerField
 5 from wtforms.validators import DataRequired,ValidationError
 6 
 7 class RegisterForm(FlaskForm):
 8     """ 用户注册表单 """
 9 
10     username = StringField(label='用户名', default='')
11     password = PasswordField(label='密码',validators=[DataRequired("请输入密码")])  #增加必填验证
12     birth_date = DateField(label='生日')
13     age = IntegerField(label='年龄')
14     submit = SubmitField('注册')
15 
16     def validate_username(self,field):  #注意这个方法必须是validate_*****
17         """ 验证用户名 """
18         # 自定义:强制验证用户名为手机号
19         username = field.data
20         pattern = r'^1[0-9]{10}$'   #自定义正则匹配规则
21         if not re.search(pattern,username):
22             raise ValidationError('请输入手机号码')
23         return field

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

 

我们看到报错提示信息是在控制台展示的,那么我们怎么让提示信息展示给用户?

forms.py文件里面自定义了表单验证规则,验证失败抛出form的错误

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

app.py文件无法提交时,将打印form文件抛出的异常.。异常内容为 {'username': ['请输入手机号码']} 是个items结构。

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

前端页面,获取forms的username的报错信息,进行展示

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

场景二(多个表单使用):

????图froms.py:用python编写登录的前端页面、表单自定义的验证、表单字段使用自定义的验证

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

疑问:在类外面自定义的验证方法,为什么要传form,不传行不行;不穿form就会报错,说应该传两个参数,只传了一个,那这是为什么呢?

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

看了系统自带验证规则的源码,发现都是两个形参form和field,就暂时这么理解吧:因为源码有,跟他保持一致,form具体干什么使的后面研究了再说吧!

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

????图app.py启动文件:将登录页面forms.py交给html文件page_form进行渲染展示,同时进行form表单的验证Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

????图:page_form.html+展实效果

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

2.2.图片上传

图片上传主要有两种方式实现

  • 方式一:不使用wtf实现
  • 方式二:使用wtf的FileField并添加类型验证

 

图片上传:不使用wtf

  • 1)设置<form>的enctype, enctype="multipart/form-data" 
  • 2)在视图函数中获取文件对象 request.files 
  • 3)保存文件 f.save(file_path) 

 

文件名称格式化: werkzeug.utils.secure_filename 

 

示例:

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

上传不规则的文件名,secure_filename进行格式化

Python全栈工程师之从网页搭建入门到Flask全栈项目实战(6) - Flask表单的实现

app.py

 1 import os
 2 
 3 from flask import Flask, render_template, redirect, url_for, request
 4 from werkzeug.utils import secure_filename
 5 
 6 from forms import UserAvatarForm
 7 
 8 app = Flask(__name__)
 9 app.config['WTF_CSRF_SECRET_KEY'] = 'abc1234abc'
10 app.config['SECRET_KEY'] = 'abc'
11 # 自定义的配置扩展,表示文件上传的路径
12 app.config['UPLOAD_PATH'] = os.path.join(os.path.dirname(__file__), 'medias')
13 
14 @app.route('/img/upload', methods=['GET', 'POST'])
15 def img_upload():
16     """ 不使用wtf实现的文件上传 """
17     if request.method == 'POST':
18         # 获取文件列表
19         files = request.files
20         file1 = files.get('file1', None)
21         if file1:
22             # 保存文件
23             f_name = secure_filename(file1.filename)
24             print('filename:', f_name)
25             file_name = os.path.join(app.config['UPLOAD_PATH'], f_name)
26             file1.save(file_name)
27             print('保存成功')
28         return redirect(url_for('img_upload'))
29     return render_template('img_upload.html')

img_upload.html

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>文件上传</title>
 6 </head>
 7 <body>
 8 <form action="/img/upload" method="post" enctype="multipart/form-data">
 9     <p>
10         <input type="file" name="file1">
11         <input type="file" name="file2">
12     </p>
13     <p>
14         <input type="submit" value="开始上传">
15     </p>
16 </form>
17 </body>
18 </html>

 

图片上传:验证

  • 文件必须上传: FileRequired 
  • 文件类型验证: FileAllowed 

app.py

 1 import os
 2 
 3 from flask import Flask, render_template, redirect
 4 from werkzeug.utils import secure_filename
 5 
 6 from forms import UserAvatarForm
 7 
 8 app = Flask(__name__)
 9 app.config['WTF_CSRF_SECRET_KEY'] = 'abc1234abc'
10 app.config['SECRET_KEY'] = 'abc'
11 # 自定义的配置扩展,表示文件上传的路径
12 app.config['UPLOAD_PATH'] = os.path.join(os.path.dirname(__file__), 'medias')
13 
14 
15 @app.route('/avatar/upload', methods=['GET', 'POST'])
16 def avatar_upload():
17     """ 头像上传 """
18     form = UserAvatarForm()
19     if form.validate_on_submit():
20         # 获取图片对象
21         img = form.avatar.data
22         f_name = secure_filename(img.filename)
23         file_name = os.path.join(app.config['UPLOAD_PATH'], f_name)
24         img.save(file_name)
25         print('保存成功')
26         return redirect('/')
27     else:
28         print(form.errors)
29     return render_template('avatar_upload.html', form=form)

forms.py

 1 from flask_wtf import FlaskForm
 2 from flask_wtf.file import FileRequired, FileAllowed
 3 from wtforms import FileField
 4 
 5 
 6 class UserAvatarForm(FlaskForm):
 7     """ 用户头像上传 """
 8     avatar = FileField(label='上传头像', validators=[
 9         FileRequired('请选择头像文件'),
10         FileAllowed(['png'], '仅支持PNG图片上传')
11     ])

avatar_upload.html

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>WTF 文件上传</title>
 6 </head>
 7 <body>
 8 <form action="/avatar/upload" method="post" enctype="multipart/form-data">
 9     {{ form.csrf_token }}
10     <p>
11         {{ form.avatar.label }}
12         {{ form.avatar }}
13     </p>
14     <p>
15         <input type="submit" value="开始上传">
16     </p>
17 </form>
18 </body>

 

使用拓展:Flask-Uploads

  • 常用文件类型验证
  • 指定文件上传的目录