一、表单和其生成
通过WTForms设置的表单,可以把HTML代码直接封装起来,直接通过它给的类就可以做表单,不需要自己写HTML代码来实现模板的功能。
通常把表单类用一个单独的python文件存起来。
二、表单处理流程
引用书上的流程图:
和模板可以自定义测试器和过滤器一样,表单也可以自定义验证器。
三、文件上传
文件上传主要应该考虑安全问题。
四、富文本编辑器
可以从网络上加载一些富文本编辑器。
五、视图和表单的关系
多表单对应多视图的情况
- 两个表单在一个视图的时候,可设置不同的submit字段,比如submit1和submit2,可以根据submit字段来确定是哪个表单的提交。
- 以上情况也可以在模板里面定向到两个不同的视图函数里面分别解决,那么每个就都可以用名为submit的字段而不用区分。
六、代码及注释
app.py
import os
import uuid
from flask import Flask, render_template, flash, redirect, url_for, request, send_from_directory, session
from flask_ckeditor import CKEditor, upload_success, upload_fail
from flask_dropzone import Dropzone
from flask_wtf.csrf import validate_csrf
from wtforms import ValidationError
from forms import LoginForm, FortyTwoForm, NewPostForm, UploadForm, MultiUploadForm, SigninForm, RegisterForm, SigninForm2, RegisterForm2, RichTextForm
app = Flask(__name__)
app.secret_key = os.getenv('SECRET_KEY', 'secret string')
app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True
# Custom config
app.config['UPLOAD_PATH'] = os.path.join(app.root_path, 'uploads')
if not os.path.exists(app.config['UPLOAD_PATH']):
os.makedirs(app.config['UPLOAD_PATH'])
app.config['ALLOWED_EXTENSIONS'] = ['png', 'jpg', 'jpeg', 'gif']
# Flask config
# set request body's max length
# app.config['MAX_CONTENT_LENGTH'] = 3 * 1024 * 1024 # 3Mb
# Flask-CKEditor config
app.config['CKEDITOR_SERVE_LOCAL'] = True
app.config['CKEDITOR_FILE_UPLOADER'] = 'upload_for_ckeditor'
# Flask-Dropzone config
app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image'
app.config['DROPZONE_MAX_FILE_SIZE'] = 3
app.config['DROPZONE_MAX_FILES'] = 30
ckeditor = CKEditor(app)
dropzone = Dropzone(app)
@app.route('/', methods=['GET', 'POST'])
def index():
return render_template('index.html')
@app.route('/html', methods=['GET', 'POST'])
def html(): #这里的html和下面的basic视图函数比较,HTML这个是直接用的固定的HTML文本,所以没有验证器。下面的用的是表单
form = LoginForm()
if request.method == 'POST':
username = request.form.get('username')
flash('Welcome home, %s!' % username)
return redirect(url_for('index'))
return render_template('pure_html.html')
@app.route('/basic', methods=['GET', 'POST'])
def basic():
form = LoginForm()
if form.validate_on_submit():#这个函数用来确认表单已经提交,且通过验证器验证
username = form.username.data
flash('Welcome home, %s!' % username)
return redirect(url_for('index'))#发送flash然后返回主页
return render_template('basic.html', form=form)#否则就渲染basic,flash出来错误信息,这个错误信息会保存在表单类的errors属性中,注意错误信息可能不止一个,所以要用for循环
@app.route('/bootstrap', methods=['GET', 'POST'])
def bootstrap():
form = LoginForm()
if form.validate_on_submit():
username = form.username.data
flash('Welcome home, %s!' % username)
return redirect(url_for('index'))
return render_template('bootstrap.html', form=form)
@app.route('/custom-validator', methods=['GET', 'POST'])
def custom_validator():
form = FortyTwoForm()
if form.validate_on_submit():
flash('Bingo!')
return redirect(url_for('index'))
return render_template('custom_validator.html', form=form)
@app.route('/uploads/<path:filename>')
def get_file(filename):
return send_from_directory(app.config['UPLOAD_PATH'], filename)
@app.route('/uploaded-images')
def show_images():
return render_template('uploaded.html')#在这个模板里面,再调用get_file,用来在一个新的页面打开图片,即给这个图片一个专门的url
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']
def random_filename(filename):#随机重命名
ext = os.path.splitext(filename)[1]
new_filename = uuid.uuid4().hex + ext #先记住这个写法
return new_filename
@app.route('/upload', methods=['GET', 'POST'])
def upload():
form = UploadForm()
if form.validate_on_submit():
f = form.photo.data
filename = random_filename(f.filename)#将原文件名进行处理,这里是用随机重命名的方法
f.save(os.path.join(app.config['UPLOAD_PATH'], filename))#将文件转移到文件系统中,这里其实还是传到了本地的uploads文件夹下面
#path.join是os下的一个函数,可以进行路径的构造
flash('Upload success.')
session['filenames'] = [filename]
return redirect(url_for('show_images'))#然后重定向到显示图片用的url里(即处理这个操作的视图函数show_images所对应的url)
return render_template('upload.html', form=form) #注意里面有一个csrf_token,是用来防止csrf(跨站请求伪造)的
@app.route('/multi-upload', methods=['GET', 'POST'])
def multi_upload():
form = MultiUploadForm()
if request.method == 'POST':
filenames = []
# check csrf token
#手动验证csrf令牌,先记住这个格式就行了
try:
validate_csrf(form.csrf_token.data)
except ValidationError:
flash('CSRF token error.')
return redirect(url_for('multi_upload'))
photos = request.files.getlist('photo')
# check if user has selected files. If not, the browser
# will submit an empty file part without filename
if not photos[0].filename: #调用的是多文件字段,返回的就是一个list
flash('No selected file.')
return redirect(url_for('multi_upload'))
for f in photos:
# check the file extension
if f and allowed_file(f.filename): #用for循环对每个进行检查,检查成功后改名字,基本上就是单个文件操作套上for循环
filename = random_filename(f.filename)
f.save(os.path.join(
app.config['UPLOAD_PATH'], filename
))
filenames.append(filename)
else:
flash('Invalid file type.')
return redirect(url_for('multi_upload'))
flash('Upload success.')
session['filenames'] = filenames
return redirect(url_for('show_images'))
return render_template('upload.html', form=form)
@app.route('/dropzone-upload', methods=['GET', 'POST'])
def dropzone_upload():
if request.method == 'POST':
# check if the post request has the file part
if 'file' not in request.files:
return 'This field is required.', 400
f = request.files.get('file')
if f and allowed_file(f.filename):
filename = random_filename(f.filename)
f.save(os.path.join(
app.config['UPLOAD_PATH'], filename
))
else:
return 'Invalid file type.', 400
return render_template('dropzone.html')
@app.route('/two-submits', methods=['GET', 'POST'])
def two_submits():
form = NewPostForm()
if form.validate_on_submit():
if form.save.data: #后面要用数据库来处理
# save it...
flash('You click the "Save" button.')
elif form.publish.data:
# publish it...
flash('You click the "Publish" button.')
return redirect(url_for('index'))
return render_template('2submit.html', form=form)
@app.route('/multi-form', methods=['GET', 'POST'])
def multi_form():
signin_form = SigninForm()
register_form = RegisterForm()
if signin_form.submit1.data and signin_form.validate():
username = signin_form.username.data
flash('%s, you just submit the Signin Form.' % username)
return redirect(url_for('index'))
if register_form.submit2.data and register_form.validate():
username = register_form.username.data
flash('%s, you just submit the Register Form.' % username)
return redirect(url_for('index'))
return render_template('2form.html', signin_form=signin_form, register_form=register_form)
@app.route('/multi-form-multi-view')
def multi_form_multi_view():
signin_form = SigninForm2()
register_form = RegisterForm2()
return render_template('2form2view.html', signin_form=signin_form, register_form=register_form)
@app.route('/handle-signin', methods=['POST'])
def handle_signin():
signin_form = SigninForm2()
register_form = RegisterForm2()
if signin_form.validate_on_submit():
username = signin_form.username.data
flash('%s, you just submit the Signin Form.' % username)
return redirect(url_for('index'))
return render_template('2form2view.html', signin_form=signin_form, register_form=register_form)
@app.route('/handle-register', methods=['POST'])
def handle_register():
signin_form = SigninForm2()
register_form = RegisterForm2()
if register_form.validate_on_submit():
username = register_form.username.data
flash('%s, you just submit the Register Form.' % username)
return redirect(url_for('index'))
return render_template('2form2view.html', signin_form=signin_form, register_form=register_form)
@app.route('/ckeditor', methods=['GET', 'POST'])
def integrate_ckeditor(): #还是和前面的结构一样
form = RichTextForm()
if form.validate_on_submit():
title = form.title.data
body = form.body.data
flash('Your post is published!')
return render_template('post.html', title=title, body=body)
return render_template('ckeditor.html', form=form)
# handle image upload for ckeditor
@app.route('/upload-ck', methods=['POST'])
def upload_for_ckeditor():
f = request.files.get('upload')
if not allowed_file(f.filename):
return upload_fail('Image only!')
f.save(os.path.join(app.config['UPLOAD_PATH'], f.filename))
url = url_for('get_file', filename=f.filename)
return upload_success(url, f.filename)
forms.py
from flask_ckeditor import CKEditorField
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileRequired, FileAllowed
from wtforms import StringField, PasswordField, BooleanField, IntegerField, \
TextAreaField, SubmitField, MultipleFileField
from wtforms.validators import DataRequired, Length, ValidationError, Email
# 4.2.1 basic form example
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])#文本字段,带了一个验证器,验证器也是WTForm自带的东西
password = PasswordField('Password', validators=[DataRequired(), Length(8, 128)])#密码文本字段
remember = BooleanField('Remember me')#复选框,这些都对应的是HTML里面的东西
submit = SubmitField('Log in')#提交按钮
# custom validator
#自定义的验证器
class FortyTwoForm(FlaskForm):
answer = IntegerField('The Number')
submit = SubmitField()
def validate_answer(form, field):#上下文分析,第一个参数是表单实例,在外层视图函数中定义,第二个参数是静态的字段对象,用于获取待验证的字段的数据
if field.data != 42:
raise ValidationError('Must be 42.')
# upload form
class UploadForm(FlaskForm):
photo = FileField('Upload Image', validators=[FileRequired(), FileAllowed(['jpg', 'jpeg', 'png', 'gif'])])
submit = SubmitField()
# multiple files upload form
class MultiUploadForm(FlaskForm):#多文件上传也有响应的字段,第一个参数是命名,会直接显示出来,第二个参数是验证器,保证至少有文件包含
photo = MultipleFileField('Upload Image', validators=[DataRequired()])
submit = SubmitField()
# multiple submit button
class NewPostForm(FlaskForm):
title = StringField('Title', validators=[DataRequired(), Length(1, 50)])
body = TextAreaField('Body', validators=[DataRequired()])
save = SubmitField('Save')
publish = SubmitField('Publish')
class SigninForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(1, 20)])
password = PasswordField('Password', validators=[DataRequired(), Length(8, 128)])
submit1 = SubmitField('Sign in')
class RegisterForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(1, 20)])
email = StringField('Email', validators=[DataRequired(), Email(), Length(1, 254)])
password = PasswordField('Password', validators=[DataRequired(), Length(8, 128)])
submit2 = SubmitField('Register')
class SigninForm2(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(1, 24)])
password = PasswordField('Password', validators=[DataRequired(), Length(8, 128)])
submit = SubmitField()
class RegisterForm2(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(1, 24)])
email = StringField('Email', validators=[DataRequired(), Email(), Length(1, 254)])
password = PasswordField('Password', validators=[DataRequired(), Length(8, 128)])
submit = SubmitField()
# CKEditor Form
class RichTextForm(FlaskForm):
title = StringField('Title', validators=[DataRequired(), Length(1, 50)])
body = CKEditorField('Body', validators=[DataRequired()])
submit = SubmitField('Publish')