在 HTML 页面里,我们需要编写表单来获取用户输入。一个典型的表单如下所示:

<form method="post">  <!-- 指定提交方法为 POST --><label for="name">名字</label><input type="text" name="name" id="name"><br>  <!-- 文本输入框 --><label for="occupation">职业</label><input type="text" name="occupation" id="occupation"><br>  <!-- 文本输入框 --><input type="submit" name="submit" value="登录">  <!-- 提交按钮 -->
</form>

编写表单的 HTML 代码有下面几点需要注意:

  • <form> 标签里使用 method 属性将提交表单数据的 HTTP 请求方法指定为 POST。如果不指定,则会默认使用 GET 方法,这会将表单数据通过 URL 提交,容易导致数据泄露,而且不适用于包含大量数据的情况。
  • <input> 元素必须要指定 name 属性,否则无法提交数据,在服务器端,我们也需要通过这个 name 属性值来获取对应字段的数据。

提示 填写输入框标签文字的 <label> 元素不是必须的,只是为了辅助鼠标用户。当使用鼠标点击标签文字时,会自动激活对应的输入框,这对复选框来说比较有用。for 属性填入要绑定的 <input> 元素的 id 属性值。

创建新条目

创建新条目可以放到一个新的页面来实现,也可以直接在主页实现。这里我们采用后者,首先在主页模板里添加一个表单:

templates/index.html:添加创建新条目表单

<p>{{ movies|length }} Titles</p>
<form method="post">Name <input type="text" name="title" autocomplete="off" required>Year <input type="text" name="year" autocomplete="off" required><input class="btn" type="submit" name="submit" value="Add">
</form>

在这两个输入字段中,autocomplete 属性设为 off 来关闭自动完成(按下输入框不显示历史输入记录);另外还添加了 required 标志属性,如果用户没有输入内容就按下了提交按钮,浏览器会显示错误提示。

两个输入框和提交按钮相关的 CSS 定义如下:

/* 覆盖某些浏览器对 input 元素定义的字体 */
input[type=submit] {font-family: inherit;
}input[type=text] {border: 1px solid #ddd;
}input[name=year] {width: 50px;
}.btn {font-size: 12px;padding: 3px 5px;text-decoration: none;cursor: pointer;background-color: white;color: black;border: 1px solid #555555;border-radius: 5px;
}.btn:hover {text-decoration: none;background-color: black;color: white;border: 1px solid black;
}

接下来,我们需要考虑如何获取提交的表单数据。

处理表单数据

默认情况下,当表单中的提交按钮被按下,浏览器会创建一个新的请求,默认发往当前 URL(在 <form> 元素使用 action 属性可以自定义目标 URL)。

因为我们在模板里为表单定义了 POST 方法,当你输入数据,按下提交按钮,一个携带输入信息的 POST 请求会发往根地址。接着,你会看到一个 405 Method Not Allowed 错误提示。这是因为处理根地址请求的 index 视图默认只接受 GET 请求。

提示 在 HTTP 中,GET 和 POST 是两种最常见的请求方法,其中 GET 请求用来获取资源,而 POST 则用来创建 / 更新资源。我们访问一个链接时会发送 GET 请求,而提交表单通常会发送 POST 请求。

为了能够处理 POST 请求,我们需要修改一下视图函数:

@app.route('/', methods=['GET', 'POST'])

app.route() 装饰器里,我们可以用 methods 关键字传递一个包含 HTTP 方法字符串的列表,表示这个视图函数处理哪种方法类型的请求。默认只接受 GET 请求,上面的写法表示同时接受 GET 和 POST 请求。

两种方法的请求有不同的处理逻辑:对于 GET 请求,返回渲染后的页面;对于 POST 请求,则获取提交的表单数据并保存。为了在函数内加以区分,我们添加一个 if 判断:

app.py:创建电影条目

from flask import request, url_for, redirect, flash# ...@app.route('/', methods=['GET', 'POST'])
def index():if request.method == 'POST':  # 判断是否是 POST 请求# 获取表单数据title = request.form.get('title')  # 传入表单对应输入字段的 name 值year = request.form.get('year')# 验证数据if not title or not year or len(year) > 4 or len(title) > 60:flash('Invalid input.')  # 显示错误提示return redirect(url_for('index'))  # 重定向回主页# 保存表单数据到数据库movie = Movie(title=title, year=year)  # 创建记录db.session.add(movie)  # 添加到数据库会话db.session.commit()  # 提交数据库会话flash('Item created.')  # 显示成功创建的提示return redirect(url_for('index'))  # 重定向回主页user = User.query.first()movies = Movie.query.all()return render_template('index.html', user=user, movies=movies)

if 语句内,我们编写了处理表单数据的代码,其中涉及 3 个新的知识点,下面来一一了解。

请求对象

Flask 会在请求触发后把请求信息放到 request 对象里,你可以从 flask 包导入它:

from flask import request

因为它在请求触发时才会包含数据,所以你只能在视图函数内部调用它。它包含请求相关的所有信息,比如请求的路径(request.path)、请求的方法(request.method)、表单数据(request.form)、查询字符串(request.args)等等。

在上面的 if 语句中,我们首先通过 request.method 的值来判断请求方法。在 if 语句内,我们通过 request.form 来获取表单数据。request.form 是一个特殊的字典,用表单字段的 name 属性值可以获取用户填入的对应数据:

if request.method == 'POST':title = request.form.get('title')year = request.form.get('year')

flash 消息

在用户执行某些动作后,我们通常在页面上显示一个提示消息。最简单的实现就是在视图函数里定义一个包含消息内容的变量,传入模板,然后在模板里渲染显示它。因为这个需求很常用,Flask 内置了相关的函数。其中 flash() 函数用来在视图函数里向模板传递提示消息,get_flashed_messages() 函数则用来在模板中获取提示消息。

flash() 的用法很简单,首先从 flask 包导入 flash 函数:

from flask import flash

然后在视图函数里调用,传入要显示的消息内容:

flash('Item created.')

flash() 函数在内部会把消息存储到 Flask 提供的 session 对象里。session 用来在请求间存储数据,它会把数据签名后存储到浏览器的 Cookie 中,所以我们需要设置签名所需的密钥:

app.config['SECRET_KEY'] = 'dev'  # 等同于 app.secret_key = 'dev'

提示 这个密钥的值在开发时可以随便设置。基于安全的考虑,在部署时应该设置为随机字符,且不应该明文写在代码里, 在部署章节会详细介绍。

下面在基模板(base.html)里使用 get_flashed_messages() 函数获取提示消息并显示:

<!-- 插入到页面标题上方 -->
{% for message in get_flashed_messages() %}<div class="alert">{{ message }}</div>
{% endfor %}
<h2>...</h2>

alert 类为提示消息增加样式:

.alert {position: relative;padding: 7px;margin: 7px 0;border: 1px solid transparent;color: #004085;background-color: #cce5ff;border-color: #b8daff;border-radius: 5px;
}

通过在 <input> 元素内添加 required 属性实现的验证(客户端验证)并不完全可靠,我们还要在服务器端追加验证:

if not title or not year or len(year) > 4 or len(title) > 60:flash('Invalid input.')  # 显示错误提示return redirect(url_for('index'))
# ...
flash('Item created.')  # 显示成功创建的提示

提示 在真实世界里,你会进行更严苛的验证,比如对数据去除首尾的空格。一般情况下,我们会使用第三方库(比如 WTForms)来实现表单数据的验证工作。

如果输入的某个数据为空,或是长度不符合要求,就显示错误提示“Invalid input.”,否则显示成功创建的提示“Item Created.”。

重定向响应

重定向响应是一类特殊的响应,它会返回一个新的 URL,浏览器在接受到这样的响应后会向这个新 URL 再次发起一个新的请求。Flask 提供了 redirect() 函数来快捷生成这种响应,传入重定向的目标 URL 作为参数,比如 redirect('http://helloflask.com')

根据验证情况,我们发送不同的提示消息,最后都把页面重定向到主页,这里的主页 URL 均使用 url_for() 函数生成:

if not title or not year or len(year) > 4 or len(title) > 60:flash('Invalid title or year!')  return redirect(url_for('index'))  # 重定向回主页
flash('Item created.')
return redirect(url_for('index'))  # 重定向回主页

编辑条目

编辑的实现和创建类似,我们先创建一个用于显示编辑页面和处理编辑表单提交请求的视图函数:

app.py:编辑电影条目

@app.route('/movie/edit/<int:movie_id>', methods=['GET', 'POST'])
def edit(movie_id):movie = Movie.query.get_or_404(movie_id)if request.method == 'POST':  # 处理编辑表单的提交请求title = request.form['title']year = request.form['year']if not title or not year or len(year) > 4 or len(title) > 60:flash('Invalid input.')return redirect(url_for('edit', movie_id=movie_id))  # 重定向回对应的编辑页面movie.title = title  # 更新标题movie.year = year  # 更新年份db.session.commit()  # 提交数据库会话flash('Item updated.')return redirect(url_for('index'))  # 重定向回主页return render_template('edit.html', movie=movie)  # 传入被编辑的电影记录

这个视图函数的 URL 规则有一些特殊,如果你还有印象的话,我们在第 2 章的《实验时间》部分曾介绍过这种 URL 规则,其中的 <int:movie_id> 部分表示 URL 变量,而 int 则是将变量转换成整型的 URL 变量转换器。在生成这个视图的 URL 时,我们也需要传入对应的变量,比如 url_for('edit', movie_id=2) 会生成 /movie/edit/2。

movie_id 变量是电影条目记录在数据库中的主键值,这个值用来在视图函数里查询到对应的电影记录。查询的时候,我们使用了 get_or_404() 方法,它会返回对应主键的记录,如果没有找到,则返回 404 错误响应。

为什么要在最后把电影记录传入模板?既然我们要编辑某个条目,那么必然要在输入框里提前把对应的数据放进去,以便于进行更新。在模板里,通过表单 <input> 元素的 value 属性即可将它们提前写到输入框里。完整的编辑页面模板如下所示:

templates/edit.html:编辑页面模板

{% extends 'base.html' %}{% block content %}
<h3>Edit item</h3>
<form method="post">Name <input type="text" name="title" autocomplete="off" required value="{{ movie.title }}">Year <input type="text" name="year" autocomplete="off" required value="{{ movie.year }}"><input class="btn" type="submit" name="submit" value="Update">
</form>
{% endblock %}

最后在主页每一个电影条目右侧都添加一个指向该条目编辑页面的链接:

index.html:编辑电影条目的链接

<span class="float-right"><a class="btn" href="{{ url_for('edit', movie_id=movie.id) }}">Edit</a>...
</span>

点击某一个电影条目的编辑按钮打开的编辑页面如下图所示:

删除条目

因为不涉及数据的传递,删除条目的实现更加简单。首先创建一个视图函数执行删除操作,如下所示:

app.py:删除电影条目

@app.route('/movie/delete/<int:movie_id>', methods=['POST'])  # 限定只接受 POST 请求
def delete(movie_id):movie = Movie.query.get_or_404(movie_id)  # 获取电影记录db.session.delete(movie)  # 删除对应的记录db.session.commit()  # 提交数据库会话flash('Item deleted.')return redirect(url_for('index'))  # 重定向回主页

为了安全的考虑,我们一般会使用 POST 请求来提交删除请求,也就是使用表单来实现(而不是创建删除链接):

index.html:删除电影条目表单

<span class="float-right">...<form class="inline-form" method="post" action="{{ url_for('delete', movie_id=movie.id) }}"><input class="btn" type="submit" name="delete" value="Delete" onclick="return confirm('Are you sure?')"></form>...
</span>

为了让表单中的删除按钮和旁边的编辑链接排成一行,我们为表单元素添加了下面的 CSS 定义:

.inline-form {display: inline;
}

最终的程序主页如下图所示:

本章小结

本章我们完成了程序的主要功能:添加、编辑和删除电影条目。结束前,让我们提交代码:

$ git add .
$ git commit -m "Create, edit and delete item by form"
$ git push

提示 你可以在 GitHub 上查看本书示例程序的对应 commit:84e766f。在后续的 commit 里,我们为另外两个常见的 HTTP 错误:400(Bad Request) 和 500(Internal Server Error) 错误编写了错误处理函数和对应的模板,前者会在请求格式不符要求时返回,后者则会在程序内部出现任意错误时返回(关闭调试模式的情况下)。

进阶提示

  • 从上面的代码可以看出,手动验证表单数据既麻烦又不可靠。对于复杂的程序,我们一般会使用集成了 WTForms 的扩展 Flask-WTF 来简化表单处理。通过编写表单类,定义表单字段和验证器,它可以自动生成表单对应的 HTML 代码,并在表单提交时验证表单数据,返回对应的错误消息。更重要的,它还内置了 CSRF(跨站请求伪造) 保护功能。你可以阅读 Flask-WTF 文档和 Hello, Flask! 专栏上的表单系列文章了解具体用法。
  • CSRF 是一种常见的攻击手段。以我们的删除表单为例,某恶意网站的页面中内嵌了一段代码,访问时会自动发送一个删除某个电影条目的 POST 请求到我们的程序。如果我们访问了这个恶意网站,就会导致电影条目被删除,因为我们的程序没法分辨请求发自哪里。解决方法通常是在表单里添加一个包含随机字符串的隐藏字段,在提交时通过对比这个字段的值来判断是否是用户自己发送的请求。在我们的程序中没有实现 CSRF 保护。
  • 使用 Flask-WTF 时,表单类在模板中的渲染代码基本相同,你可以编写宏来渲染表单字段。如果你使用 Bootstap,那么扩展 Bootstrap-Flask 内置了多个表单相关的宏,可以简化渲染工作。
  • 你可以把删除按钮的行内 JavaScript 代码改为事件监听函数,写到单独的 JavaScript 文件里。
  • 《Flask Web 开发实战》第 4 章介绍了表单处理的各个方面,包括表单类的编写和渲染、错误消息显示、自定义错误消息语言、文件和多文件上传、富文本编辑器等等。
  • 本书主页 & 相关资源索引:http://helloflask.com/tutorial。

basefont.createfont设置表单字体_《Flask 入门教程》第 7 章:表单相关推荐

  1. wxpython按钮形状如何修改_Python图形化界面入门教程 - 使用wxPython自定义表

    原标题:Python图形化界面入门教程 - 使用wxPython自定义表 来自: Linux迷 网址:https://www.linuxmi.com/python-gui-wxpython-zidin ...

  2. WMI 使用教程_.NET 入门教程

    WMI 使用教程_.NET 入门教程 先介绍一下WMI 相关知识:  什么是WMI 呢? Windows 管理规范 (Windows Management Instrumentation ),它的主要 ...

  3. OCC-7.6.0 + MFC单文档应用入门教程

    OCC-7.6.0 + MFC单文档应用 入门教程 磨刀不误砍柴工,如果你对MFC没任何基础,请先读这篇MFC的入门基础,它会降低你对这篇文章的恐惧感 开发环境说明 win7+ opencascade ...

  4. 关于flask入门教程-ajax+echarts实现大屏展示

    陆陆续续写了一个系列的flask入门教程了,最后以一个半成品大屏做个了结,也算是一段时间的成果吧,毕竟不是专业码农,只是爱好而已,还有很多其他的事情等待探索. 大屏用到的技术主要包括标准的HTML.C ...

  5. web字体设置成平方字体_如何托管自己的Web字体

    web字体设置成平方字体 字体通常是许多计算机用户的谜. 例如,您是否设计了一个精美的传单,并且将文件放在某个地方进行打印时,发现Arial中呈现的所有标题是因为打印机没有您在设计中使用的精美字体? ...

  6. web字体设置成平方字体_探索免费和开放的Web字体

    web字体设置成平方字体 毫无疑问,近年来,开源字体已经改变了网络的面貌. 在2010年之前,您可能会在网络浏览器中看到的唯一字体是Microsoft的通用"网络安全" 核心字体 ...

  7. java带头结点的单链表_自己实现集合框架 (五): 带头结点单链表的实现

    这是系列文章,每篇文章末尾均附有源代码地址.目的是通过模拟集合框架的简单实现,从而对常用的数据结构和java集合有个大概的了解.当然实现没有java集合的实现那么复杂,功能也没有那么强大,但是可以通过 ...

  8. 计算机桌面字体调大,桌面字体怎么设置-调整电脑字体大小的方法教程

    如何调整电脑字体大小 调整电脑字体大小的方法教程 来源:www.bigbaicai.com 发布时间:2018-12-18 13:15 在使用电脑的过程中,经常遇到这样那样的问题.例如我们在打开一个网 ...

  9. java带头节点的单链表_自己实现集合框架(五):带头结点单链表的实现

    这是系列文章,每篇文章末尾均附有源代码地址.目的是通过模拟集合框架的简单实现,从而对常用的数据结构和java集合有个大概的了解.当然实现没有java集合的实现那么复杂,功能也没有那么强大,但是可以通过 ...

最新文章

  1. InnoDB调优-索引优化策略
  2. springboot 没有跳转到指定页面
  3. 踩坑 :vue2 ajax异步请求数据,层数太多,页面无法渲染
  4. kali linux 编码,Kali Linux 2019.4解决中文乱码问题
  5. [LeetCode] Length of Last Word - 最后一个单词的长度
  6. Jndroid——用应用开发的思路来开发 Web
  7. 【模型训练-loss】模型训练过程中train, test loss的关系及原因
  8. php 网站域名怎么更换,教你如何快速给网站更换域名,简单粗暴!
  9. Python企业微信机器人
  10. IT运维管理必备工具大全,看完还敢称自己是高手吗?
  11. Windows Server 2003 系统安装
  12. 机器学习-样本集(包括训练集及测试集)的选取
  13. 如何删除Linux一个目录下部分类型之外的所有文件的三种方法
  14. mysql加begin报错,MySQL存储过程例子,不能在if else里面用begin end否则会报错Error Code:1064解决...
  15. 北斗系统基础知识2(北斗一代定位原理详述)
  16. 操作系统银行家算法计算机四级,【NCRE四级网络工程师】操作系统多选题
  17. 鏡頭上的 F 與 f 之區別
  18. “Mayday!我们的站点又不能访问啦!”之DNS污染篇
  19. flannel和calcio_Calcio是什么意思
  20. reac-hook的使用

热门文章

  1. 按模板批量生成Word文件(上)
  2. 一文读懂自动驾驶汽车:软硬结合 造就未来出行体验(上篇)
  3. 我的世界服务器修改飞行速度,我的世界怎么修改飞行的速度 我的世界飞行的速度怎么改...
  4. mupdf添加图片水印_如何在图片上加上水印
  5. 前端最常用的几个线上设计网站
  6. Ubuntu下查看进程PID 终止进程方法汇总
  7. SqlServer索引介绍
  8. 费马小定理与欧拉定理 原理与证明
  9. ofo CTO 童长飚:7周完成小黄车“出海” 背后有朵阿里云
  10. 31 给华为P10做个宣传海报 33 认识图标