TML 表單用于收集不同類型的用戶輸入,它是一個包含表單元素的區域。
表單元素是允許用戶在表單中輸入內容,比如:文本域(textarea)、下拉列表、單選框(radio-buttons)、復選框(checkboxes)等等。今天我們主要來說說文本域和密碼域這兩個部分,希望對大家學習有所幫助喲!
本文福利后臺回復【學習】即可獲得Python、HTML等編程學習資料
HTML 表單
表單使用表單標簽 <form> 來設置:
<form>
First name: <input type="text" name="firstname"><br>
Last name: <input type="text" name="lastname">
</form>
HTML 表單 - 輸入元素
多數情況下被用到的表單標簽是輸入標簽(<input>)。輸入類型是由類型屬性(type)定義的。
如何在 HTML 頁面創建文本域?
用戶可以在文本域中寫入文本,參考代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鳥教程(runoob.com)</title>
</head>
<body>
<form action="">
First name: <input type="text" name="firstname"><br>
Last name: <input type="text" name="lastname">
</form>
<p><b>注意:</b> 表單本身是不可見的。并且注意一個文本字段的默認寬度是20個字符。</p>
</body>
</html>
運行結果為
如何創建 HTML 的密碼域?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鳥教程(runoob.com)</title>
</head>
<body>
<form action="">
Username: <input type="text" name="user"><br>
Password: <input type="password" name="password">
</form>
<p><b>注意:</b> 密碼字段中的字符是隱藏的(顯示為星號或圓圈)。</p>
</body>
</html>
運行結果如下
戳了解更多免費領取HTML試聽課~
什么是表單呢?當前端想要提交數據給后端,怎么搞?那么在前端開發中,表單是常用的手段,比如常見的場景有:登錄框、賬號注冊頁、主機信息錄入CMDB等等場景都是需要表單。那么在本篇中,筆者除了講一些基本的知識點,還會再結合后端的方式來演示如何接收表單提交的數據。希望這些小小的演示可以起到拋磚引玉的效果。
構建表單,主要是通過from元素,我們先來一個最簡單的小栗子,看下面代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<h3>主機信息錄入CMDB</h3>
<form>
<label for="hostname">主機名:</label><br>
<input type="text" id="hostname" name="hostname"><br>
<label for="ipaddr">IP地址:</label><br>
<input type="text" id="ipaddr" name="ipaddr"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
效果如下圖:
通上面的小栗子可以知道,form表單的主要通途是用于收集用戶的輸入。在from表單里面,還包含著各種不同類型的input元素,比如我們上面小栗子中用到的文本(text)、提交按鈕(submit)。
input元素是表單里最重要的元素,它有很多type屬性,下面我們來總結下:
類型描述text文本輸入radio單選按鈕checkbox提交按鈕submit提交按鈕button可單擊按鈕
在上面小栗子中,除了input元素之外,不知道大家注意label元素沒有。label元素的主要用途是為input元素定義標簽,且用for屬性和input元素的id屬性進行綁定呢。
什么是單選按鈕?就是在多個選項中,你只能選其中1個,不能多選。下面我們看個小栗子,看下面代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<h3>問答題1(單選):某站長,工作經驗不足1年,僅從互聯網收集學習資料制定學習路線售賣盈利,從道德層面角度分析是否有問題?</h3>
<form>
<input type="radio" id="i1" name="problem" value="yes">
<label for="i1">有</label>
<input type="radio" id="i2" name="problem" value="no">
<label for="i2">沒有</label>
<input type="radio" id="i3" name="problem" value="not_clear">
<label for="i3">不清楚</label>
</form>
<h3>問答題2(單選):實際工作經驗不足1年的人員折騰的學習資料您覺得是否對你有幫助?</h3>
<form>
<input type="radio" id="w1" name="problem" value="yes">
<label for="w1">有</label>
<input type="radio" id="w2" name="problem" value="no">
<label for="w2">沒有</label>
<input type="radio" id="w3" name="problem" value="not_clear">
<label for="w3">不清楚</label>
</form>
</body>
</html>
效果如下圖:
上面的小栗子中,出了兩道問答題,均為單選題。那么,類似的需求都是可以使用輸入類型為radio來實現需要使用單選按鈕的場景。
什么是復選框?復選框就是可以選擇多個選項,當需要多選的時候,使用復選框輸入類型就對了。看下面代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<h3>問答題1(單選):某站長,工作經驗不足1年,僅從互聯網收集學習資料制定學習路線售賣盈利,從道德層面角度分析是否有問題?</h3>
<form>
<input type="checkbox" id="i1" name="problem" value="yes">
<label for="i1">有</label>
<input type="checkbox" id="i2" name="problem" value="no">
<label for="i2">沒有</label>
<input type="checkbox" id="i3" name="problem" value="not_clear">
<label for="i3">不清楚</label><br>
<input type="submit" value="提交">
</form>
<h3>問答題2(單選):實際工作經驗不足1年的人員折騰的學習資料您覺得是否對你有幫助?</h3>
<form>
<input type="checkbox" id="w1" name="problem" value="yes">
<label for="w1">有</label>
<input type="checkbox" id="w2" name="problem" value="no">
<label for="w2">沒有</label>
<input type="checkbox" id="w3" name="problem" value="not_clear">
<label for="w3">不清楚</label><br>
<input type="submit" value="提交">
</form>
</body>
</html>
效果如下圖:
上面的小栗子中,實現復選框的輸入類型是checkbox,這個是重點哦!
當有數據要提交給后端的時候怎么搞?如果后端是API服務,可以給它提交json。如果是前端頁面,就需要通過構建表單來獲取用戶的輸入。基于表單提交數據給后端,怎么提交?需要一個可以點擊的提交按鈕,那這個按鈕怎么來?先看下面代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<h3>主機信息</h3>
<form>
<label for="ipaddr">IP地址</label>
<input type="text" id="ipaddr" name="ip"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
效果如下圖:
輸入類型為submit,說明它就是提交按鈕,注意上面代碼type="submit"了嗎?
在之前的例子中,前端表單需要將數據提交給后端,除了需要一個提交按鈕外,還需要action屬性。當點擊提交按鈕后,表單的數據該發到后端的哪個url進行處理,就是定義在action屬性中。接下來,我們結合前端和后端直接來個小實戰,后端代碼用Python的Flask框架。
前端代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<h3>主機信息</h3>
<form action="http://192.168.11.10:8088/ttropsstack" target="_blank">
<label for="ipaddr">IP地址</label>
<input type="text" id="ipaddr" name="ip"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
后端代碼:
from flask import Flask, request
webapp = Flask(__name__)
@webapp.route('/ttropsstack', methods=["GET", "POST"])
def ttropsstack():
args = request.args
print 'ip addr is: %s' % args.get('ip')
return 'ok'
if __name__ == '__main__':
webapp.run(host="0.0.0.0", port=8088, debug=True)
前端和后端的代碼寫完后,我們接下來看看效果
輸入IP地址后,點擊提交
這個ok是后端返回的
后端接收到數據后,啥也沒做,只是做了簡單打印
這個小栗子很簡單,通過這個小栗子,是不是對action屬性的用法有了進一步的理解呢?
method屬性的用途是指定提交數據的http方法,通常有2個,get和post。接下來我們在上個小栗子的基礎上做個小改造,看下面代碼
前端代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<h3>主機信息</h3>
<form action="http://192.168.11.10:8088/ttropsstack" target="_blank" method="get">
<label for="ipaddr">IP地址</label>
<input type="text" id="ipaddr" name="ip"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
后端代碼:
from flask import Flask, request
webapp = Flask(__name__)
@webapp.route('/ttropsstack', methods=["POST"])
def ttropsstack():
args = request.args
print 'ip addr is: %s' % args.get('ip')
return 'ok'
if __name__ == '__main__':
webapp.run(host="0.0.0.0", port=8088, debug=True)
當前端輸入數據后,提交表單時,直接告訴你這是不允許的方法
在這個例子中,是因為前端的表單了指定了method為get請求,而后端接收數據的method規定了需要post請求,故所以出現這個問題。
下面我們改造一下后端代碼:
# coding: utf8
from flask import Flask, request
webapp = Flask(__name__)
@webapp.route('/ttropsstack', methods=['GET','POST'])
def ttropsstack():
if request.method == 'POST':
print request.get_data(as_text=True)
return 'ok'
else:
return '提交數據需要post請求'
if __name__ == '__main__':
webapp.run(host="0.0.0.0", port=8088, debug=True)
前端表單中的method還是保持get請求,再次提交,后端的返回如下:
看到了嗎?后端判斷前端過來的請求是get還是post,很顯然,前端過來的請求是get,并且返回了非常友好的提示。
接下來我們繼續改造一下前端的代碼,將請求修改為post,代碼如下:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<h3>主機信息</h3>
<form action="http://192.168.11.10:8088/ttropsstack" target="_blank" method="post">
<label for="ipaddr">IP地址</label>
<input type="text" id="ipaddr" name="ip"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
后端代碼也稍微改造一下,改變接收前端數據的方法
# coding: utf8
from flask import Flask, request
webapp = Flask(__name__)
@webapp.route('/ttropsstack', methods=['GET','POST'])
def ttropsstack():
if request.method == 'POST':
a = request.form
print a.get('ip')
print type(a)
return 'ok'
else:
return '提交數據需要post請求'
if __name__ == '__main__':
webapp.run(host="0.0.0.0", port=8088, debug=True)
輸入IP地址,并點擊提交
提交后,后端給前端返回了ok
接下來看下后端,后端啥也沒做,就獲取到表單的數據,然后打印了數據,并且打印了下數據類型
好了,關于前端的action屬性和Method屬性就講到這里了。為了講解action和method,還結合了后端的一丟丟知識,前端和后端的知識點以后都會慢慢講到哈!
先來個前端代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<form action="http://192.168.11.10:8088/ttropsstack" target="_blank" method="post">
<label for="opslist">運維開發應掌握的技能:</label>
<select id="opslist" name="opslist">
<option value="python">Python語言</option>
<option value="go">Go語言</option>
<option value="shell">Shell語言</option>
<option value="database">數據庫</option>
<option value="frontend">前端</option>
<option value="linux">Linux</option>
<option value="network">網絡</option>
<option value="storage">存儲</option>
</select>
<input type="submit">
</form>
</body>
</html>
后端代碼:
# coding: utf8
from flask import Flask, request
webapp = Flask(__name__)
@webapp.route('/ttropsstack', methods=['GET','POST'])
def ttropsstack():
if request.method == 'POST':
a = request.form
print a.get('opslist')
return 'ok'
else:
return '提交數據需要post請求'
if __name__ == '__main__':
webapp.run(host="0.0.0.0", port=8088, debug=True)
在下拉框中選擇“Go語言”,并提交
后端啥也沒干,就只做了打印
前端代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>彩虹運維技術棧社區主頁</title>
</head>
<body>
<h2>彩虹運維技術棧社區,公眾號ID:TtrOpsStack</h2>
<form action="http://192.168.11.10:8088/ttropsstack" target="_blank" method="post">
<label for="opslist">運維開發應掌握的技能:</label>
<select id="opslist" name="opslist" size="6" multiple>
<option value="python">Python語言</option>
<option value="go">Go語言</option>
<option value="shell">Shell語言</option>
<option value="database">數據庫</option>
<option value="frontend">前端</option>
<option value="linux">Linux</option>
<option value="network">網絡</option>
<option value="storage">存儲</option>
</select>
<input type="submit">
</form>
</body>
</html>
上述前端代碼中,是使用multiple屬性來實現選擇多個值。
后端代碼的打印方式稍微做了些許調整:
# coding: utf8
from flask import Flask, request
webapp = Flask(__name__)
@webapp.route('/ttropsstack', methods=['GET','POST'])
def ttropsstack():
if request.method == 'POST':
data = request.get_data()
print data
return 'ok'
else:
return '提交數據需要post請求'
if __name__ == '__main__':
webapp.run(host="0.0.0.0", port=8088, debug=True)
按住ctrl或者shift鍵可進行多選
后端打印的效果圖下圖:
關于表單基礎知識點的講解就這么愉快的結束了,關于更多表單的元素、輸入屬性、輸入類型可自行查閱和實戰,筆者因時間有限就不一一演示。感謝您的閱讀,望多多關注我們、點贊、轉發!
本文轉載于(喜歡的盆友關注我們):https://mp.weixin.qq.com/s/L-Msv39JrF7AzRx4K1OLjA
一章節我們通過在html中直接編寫表單的方式進行數據傳遞,并且在視圖中對前端傳遞的數據進行了簡單的認證,但是如果把驗證數據的代碼與邏輯混合在一起,將使得視圖的代碼不夠清晰,并且難以維護,稍加疏忽就會產生驗證漏洞,如果細心的同學其實可以發現,在之前的登錄注冊中我們一直沒有對空表單進行驗證,當然這是我故意為之,但如果在生產環境,這將是一個災難的開始,所以,在編程中無論是前端還是后端都要求要對數據進行驗證,作為后端,更要保持一種永遠不相信前端傳遞數據的態度去做數據校驗。
本章節我們將使用Flask官方推薦的Flask-WTF擴展來重構我們的登錄注冊表單!
Flask-WTF是Flask 和 WTForms 的簡單集成,包括 CSRF、文件上傳和 reCAPTCHA。
pip install Flask-WTF
在app/auth/目錄下新建一個forms.py的文件,所有的表單驗證代碼都放到這個文件當中!
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, Length, ValidationError, EqualTo
from werkzeug.security import check_password_hash
from .models import User
class LoginForm(FlaskForm):
# 登錄表單
def qs_username(username):
# 對該字段進行在傳遞之前處理
u = f'{username}123456'
print(u)
return username
username = StringField('username', validators=[
DataRequired(message="不能為空"),
Length(max=32, message="不符合字數要求!")
], filters=(qs_username,))
password = PasswordField('password', validators=[
DataRequired(message="不能為空"),
Length(max=32, message="不符合字數要求!")
])
def validate_username(form, field):
user = User.query.filter_by(username=field.data).first()
if user is None:
error = '該用戶不存在!'
raise ValidationError(error)
elif not check_password_hash(user.password, form.password.data):
raise ValidationError('密碼不正確')
代碼詳解:
class LoginForm(FlaskForm): 創建了一個登錄表單類,繼承了FlaskForm類
StringField, PasswordField
這些都是wtforms內置的字段,負責呈現和數據轉換。
官方文檔:https://wtforms.readthedocs.io/en/3.0.x/fields/
他繼承自Filed的基類,其中有一些比較重要的參數我們大概在這里理解一下!
第一個字符串其實是該類的label參數,字段的標簽,也就是轉換到html中的label!
validators傳入對該字段的一些驗證器,在提交數據之前對數據進行驗證!
filters這個參數比較特殊,官方文檔并沒有對其詳細說明,只說是篩選器,其實怎么說就是在額外的方法中對該字段的值提前處理過濾,元組中的每個值都是一個回調函數,函數不需要傳入括號,但這個回調函數默認有一個參數,這個參數就是本身該字段的值,所以在定義該函數時就必須傳入一個參數!例如:我們定義username之前定義的這個方法!
def qs_username(username):
# 對該字段進行在傳遞之前處理
u = f'{username}123456'
print(u)
return username
備注:必須返回處理后的這個參數,否則會觸發DataRequired驗證器,后端獲取不到該表單的值!
官方文檔: https://wtforms.readthedocs.io/en/3.0.x/validators/#custom-validators
在之前的視圖函數中我們對用戶名和密碼都做了校驗,現在我們需要把驗證的代碼全部移動到表單類中,代碼如下:
def validate_username(form, field):
user = User.query.filter_by(username=field.data).first()
if user is None:
error = '該用戶不存在!'
raise ValidationError(error)
elif not check_password_hash(user.password, form.password.data):
raise ValidationError('密碼不正確')
這個函數的寫法是固定的validate_{filed},validate_后邊的filed是指你需要驗證的某個字段名,比如我們這個驗證,他主要就是對username字段進行驗證,這個函數中參數的filed就是這個字段,通過field.data就可以獲取到usernam的值。form參數則指代的是整個表單,可以用form.{filed}.data的方式獲取表單類中某個具體字段的值!
當了解了登錄表單后,我們完全就可以參照登錄表單去實現注冊表單,代碼如下:
路徑:app/auth/forms.py
class RegisterForm(FlaskForm):
# 注冊表單
username = StringField('username', validators=[
DataRequired(message="不能為空"),
Length(min=2, max=32, message="超過限制字數!")
])
password = PasswordField('password', validators=[
DataRequired(message="不能為空"),
Length(min=2, max=32, message="超過限制字數!"),
EqualTo('password1', message='兩次密碼輸入不一致!')
])
password1 = PasswordField('password1')
def validate_username(form, field):
user = User.query.filter_by(username=field.data).first()
if user is not None:
error = '該用戶名稱已存在!'
raise ValidationError(error)
這里唯一需要注意的是兩次密碼是否輸入一致,我們用了一個內置的驗證器EqualTo,使用方式可完全參照代碼,他會自動校驗password和password1輸入的值是否一致!
from ..forms import LoginForm, RegisterForm
@bp.route('/login', methods=['GET', 'POST'])
def login():
# 登錄視圖
# form = LoginForm(meta={'csrf': False}) # 禁用csrf
form = LoginForm()
if form.validate_on_submit():
user = auth.User.query.filter_by(username=form.username.data).first()
session.clear()
session['user_id'] = user.id
return redirect(url_for('index'))
return render_template('login.html', form=form)
@bp.route('/register', methods=['GET', 'POST'])
def register():
# 注冊視圖
form = RegisterForm()
if form.validate_on_submit():
user = auth.User(username=form.username.data, password=generate_password_hash(form.password.data))
db.session.add(user)
db.session.commit()
session.clear()
session['user_id'] = user.id
return redirect(url_for('index'))
return render_template('register.html', form=form)
1、首先從forms.py中引入了我們定義的登錄(LoginForm)和注冊(RegisterForm)表單類!
2、form = RegisterForm() 實例化表單類
3、if form.validate_on_submit(): 驗證前端傳遞的數據是否有效,并且會自動判斷是POST請求還是GET請求!
4、 數據驗證通過則進入之后的邏輯,未驗證通過則返回我們在表單類中傳入的驗證提示!
我們以調用username字段的驗證提示為例,在模板中加入這段代碼即可獲得錯誤提示!
<!-- 表單驗證 -->
{% if form.username.errors %}
<b-message type="is-danger">
<ul class="errors">
{% for error in form.username.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</b-message>
{% endif %}
路徑:app/auth/templates/login.html 以登陸表單為例,代碼如下:
{% block auth_form %}
<form action="" method="post" style="margin-top: 40%;" class="box">
<div class=" has-text-centered mb-3">
<p class=" subtitle">登錄</p>
<h1 class="title">FlaskBlog</h1>
</div>
{{ form.csrf_token }}
<!-- 消息閃現 -->
{% with messages = get_flashed_messages() %}
<b-message type="is-danger">
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
</b-message>
{% endwith %}
<!-- 表單驗證 -->
{% if form.username.errors %}
<b-message type="is-danger">
<ul class="errors">
{% for error in form.username.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</b-message>
{% endif %}
<div class="field">
<p class="control has-icons-left has-icons-right">
{{ form.username(class='input', placeholder='Username') }}
<span class="icon is-small is-left">
<i class="fas fa-envelope"></i>
</span>
<span class="icon is-small is-right">
<i class="fas fa-check"></i>
</span>
</p>
</div>
<div class="field">
<p class="control has-icons-left">
{{ form.password(class='input', placeholder='Password') }}
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field">
<p class="control">
<input class="button is-success is-fullwidth" type="submit" value="Login">
</p>
</div>
</form>
{% endblock auth_form %}
{{ form.csrf_token }} 隱式的創建一個csrftoken的表單
{{ form.username(class='input', placeholder='Username') }} 這樣就可以直接獲得一個表單html并自動渲染,向該表單增加書香的方式就是像代碼中這樣傳入參數和值即可,當然也可以提前在表單類中定義!
剩下的注冊表單,就當是給大家留作的一個作業,大家自行去參照登錄表單完善重構一下,加油哦!我相信你可以!
到這里我們的表單驗證就大概了解了,之后的章節就是基本的增刪改查以及表單驗證,都是基于我們這些章節學習的知識點,所以之后的章節就不會過多的去講解每行代碼的意思,重心放在邏輯的展示上,如果基礎較差的同學,到這里,可以去反復的把前邊所有章節的內容去練習,寫代碼其實就是寫的多了就會了,也就理解了,練習 練習 再練習!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。