對Form進行更深入的研究。
使用Form,需要
1、創建一個驗證用戶請求的模板,就是一個類:
from django import forms
class MyForm(forms.Form)
user = forms.CharField(...) # 默認生成 input type=‘text’
email = forms.EmailField(...) # 默認生成input type=‘email’
pwd = forms.PasswordField(...) # 默認生成input type=‘password’
通過這個模板,前端就可以自動生成相應的html標簽,CharField(...)默認是生成一個input的text標簽等等。類就是一個模板,裡面有幾個字段,就驗證幾個字段
2、類中要有相應的字段:user = forms.CharField(...)
字段是繼承了Field類的對象,主要是用來驗證輸入的某個字段的數據的合法性,如可以在參數中定義長度等,這個類主要是封裝了一些正則表達式。
當obj = MyForm(req.POST)
obj.is_valid() # 這個方法就是循環執行類中每個字段的驗證規則,如這裡是3個字段,如果有一個字段的驗證結果為False,結果就為False,全正確,才為True。
3、插件:
對於字段,在前端生成HTML標簽時,如果沒有其他配置,會生成默認的標簽的,就如CharField(...)默認是生成一個input的text標簽。那是否可以改變這個默認生成的標簽呢?可以使用插件來改變,就是在參數中配置“widget=”選項。如下形式
user = forms.CharField(...,widget=Input框)
對於CharField類,看其源代碼如下
在CharField中沒有widget,在其父類Field中定義了widget=TextInput
對於其他的字段類,如下:
基本上都是在本類中定義了默認的widget。
django提供的字段類列表如下:
所以,在定義不同的字段時,是可以通過widget修改不同的插件來生成不同的標簽,fields.py中的這些類都是插件。
如user = forms.CharField(widget=forms.PasswordField),生成的就是密碼輸入框,而不是默認的文本輸入框。
還可以給生成的標簽添加屬性:
user = forms.CharField(widget=forms.TextField(attrs={‘class’:‘c1’,‘placeholder’:‘用戶名’}))
測試:
模板類:
from django import forms
class MyForm(forms.Form):
user = forms.CharField()
user1 = forms.CharField(widget=forms.PasswordInput)
user2 = forms.CharField(widget=forms.TextInput(attrs={'class':'cl1','placeholder':'用戶名'}))
user3 = forms.ChoiceField(choices=[(1,'足球'),(2,'籃球'),(3,'排球'),(4,'乒乓球')])
視圖函數:
def detail(req):
obj = myviews.MyForm()
return render(req,'detail.html',{'obj':obj})
前端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>user:{
{ obj.user }}</p>
<p>user1:{
{ obj.user1 }}</p>
<p>user2:{
{ obj.user2 }}</p>
<p>user3:{
{ obj.user3 }}</p>
</body>
</html>
結果:
模板的字段還具有自動類型轉換的功能,如:
user4 = forms.IntegerField(),前端傳來的是字符串,到了這裡,會自動轉換為整型數。
字段的參數:
Field
required=True, 是否必須輸入值,即不能為空,默認設置
widget=None, HTML插件
label=None, 用於生成Label標簽或顯示內容
initial=None, 初始值
help_text='', 幫助信息(在標簽旁邊顯示)
error_messages=None, 錯誤信息 {'required': '不能為空', 'invalid': '格式錯誤'}
show_hidden_initial=False, 是否在當前插件後面再加一個隱藏的且具有默認值的插件(可用於檢驗兩次輸入是否一直)
validators=[], 自定義驗證規則
localize=False, 是否支持本地化
disabled=False, 是否可以編輯
label_suffix=None Label內容後綴
CharField(Field)
max_length=None, 最大長度
min_length=None, 最小長度
strip=True 是否移除用戶輸入空白
IntegerField(Field)
max_value=None, 最大值
min_value=None, 最小值
FloatField(IntegerField)
...
DecimalField(IntegerField)
max_value=None, 最大值
min_value=None, 最小值
max_digits=None, 總長度
decimal_places=None, 小數位長度
BaseTemporalField(Field)
input_formats=None 時間格式化
DateField(BaseTemporalField) 格式:2015-09-01
TimeField(BaseTemporalField) 格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
DurationField(Field) 時間間隔:%d %H:%M:%S.%f
...
RegexField(CharField)
regex, 自定制正則表達式
max_length=None, 最大長度
min_length=None, 最小長度
error_message=None, 忽略,錯誤信息使用 error_messages={'invalid': '...'}
EmailField(CharField)
...
FileField(Field)
allow_empty_file=False 是否允許空文件
ImageField(FileField)
...
注:需要PIL模塊,pip3 install Pillow
以上兩個字典使用時,需要注意兩點:
- form表單中 enctype="multipart/form-data"
- view函數中 obj = MyForm(request.POST, request.FILES)
URLField(Field)
...
BooleanField(Field)
...
NullBooleanField(BooleanField)
...
ChoiceField(Field)
...
choices=(), 選項,如:choices = ((0,'上海'),(1,'北京'),)
required=True, 是否必填
widget=None, 插件,默認select插件
label=None, Label內容
initial=None, 初始值
help_text='', 幫助提示
ModelChoiceField(ChoiceField)
... django.forms.models.ModelChoiceField
queryset, # 查詢數據庫中的數據
empty_label="---------", # 默認空顯示內容
to_field_name=None, # HTML中value的值對應的字段
limit_choices_to=None # ModelForm中對queryset二次篩選
ModelMultipleChoiceField(ModelChoiceField)
... django.forms.models.ModelMultipleChoiceField
TypedChoiceField(ChoiceField)
coerce = lambda val: val 對選中的值進行一次轉換
empty_value= '' 空值的默認值
MultipleChoiceField(ChoiceField)
...
TypedMultipleChoiceField(MultipleChoiceField)
coerce = lambda val: val 對選中的每一個值進行一次轉換
empty_value= '' 空值的默認值
ComboField(Field)
fields=() 使用多個驗證,如下:即驗證最大長度20,又驗證郵箱格式
fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
MultiValueField(Field)
PS: 抽象類,子類中可以實現聚合多個字典去匹配一個值,要配合MultiWidget使用
SplitDateTimeField(MultiValueField)
input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
FilePathField(ChoiceField) 文件選項,目錄下文件顯示在頁面中
path, 文件夾路徑
match=None, 正則匹配
recursive=False, 遞歸下面的文件夾
allow_files=True, 允許文件
allow_folders=False, 允許文件夾
required=True,
widget=None,
label=None,
initial=None,
help_text=''
GenericIPAddressField
protocol='both', both,ipv4,ipv6支持的IP格式
unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1時候,可解析為192.0.2.1, PS:protocol必須為both才能啟用
SlugField(CharField) 數字,字母,下劃線,減號(連字符)
...
UUIDField(CharField) uuid類型
...
測試:
後端:
from django.shortcuts import render,HttpResponse,redirect,HttpResponseRedirect
from myadminzdy import models
from django import forms
from django.core.validators import RegexValidator
class MyForm(forms.Form):
# user = forms.CharField()
# user1 = forms.CharField(widget=forms.PasswordInput)
# user2 = forms.CharField(widget=forms.TextInput(attrs={'class':'cl1','placeholder':'用戶名'}))
# user3 = forms.ChoiceField(choices=[(1,'足球'),(2,'籃球'),(3,'排球'),(4,'乒乓球')],)
f1 = forms.CharField() #默認字段不能為空,即required=True
f2 = forms.CharField(required=False) # 字段可以為空
f3 = forms.CharField(label="f3的label") # 前端可以使用.label、.id_for_label、.label_tag
f4 = forms.CharField(initial='初始值') # 生成的標簽有初始值
f5 = forms.CharField(initial='初始值11111',show_hidden_initial=True) #在生成一個顯示的標簽外,還生成一個隱藏的標簽,其值保持原始值,可用於標簽值的比對
f6 =forms.CharField(validators=[RegexValidator(r'^[0-9]+$','jiaoyan1:quanshuzi'),RegexValidator(r'^135[0-9]+$','jiaoyan2:135kaishi')])
# 添加自定義的校驗規則,使用validators=參數,規則是RegexValidator的對象,這裡添加兩條,它的兩個參數,一個是規則,一個是錯誤信息
# 打印的錯誤信息:{"f6": [{"message": "jiaoyan1:quanshuzi", "code": "invalid"}, {"message": "jiaoyan2:135kaishi", "code": "invalid"}]}
f7 = forms.CharField(validators=[RegexValidator(r'^[0-9]+$', 'jiaoyan1:quanshuzi'),
RegexValidator(r'^135[0-9]+$', 'jiaoyan2:135kaishi')],
error_messages={'required':'111-buweikong','invalid':'222-geshicuowu'})
# 測試error_message參數
# {"f6": [{"message": "jiaoyan1:quanshuzi", "code": "invalid"}, {"message": "jiaoyan2:135kaishi", "code": "invalid"}],
# "f7": [{"message": "222-geshicuowu", "code": "invalid"}, {"message": "222-geshicuowu", "code": "invalid"}]}
# 可以看到,設置error_message後,最後的錯誤信息以error_message設置的為主
# 對於RegexValidator,還可以第三個參數:code,來定義不同的類型,如系統中的required、invalid等
f8 = forms.CharField(validators=[RegexValidator(r'^[0-9]+$', 'jiaoyan1:quanshuzi',code='f1'),
RegexValidator(r'^135[0-9]+$', 'jiaoyan2:135kaishi',code='f2')],
error_messages={'required':'111-buweikong','invalid':'222-geshicuowu'})
# {"f6": [{"message": "jiaoyan1:quanshuzi", "code": "invalid"}, {"message": "jiaoyan2:135kaishi", "code": "invalid"}],
# "f7": [{"message": "222-geshicuowu", "code": "invalid"}, {"message": "222-geshicuowu", "code": "invalid"}],
# "f8": [{"message": "jiaoyan1:quanshuzi", "code": "f1"}, {"message": "jiaoyan2:135kaishi", "code": "f2"}]}
# error_message優先級高,根據code進行覆蓋,想要修改原生的錯誤信息,可以設置error_message。
f9 = forms.RegexField(r'^135[0-9]+$') # 自定義正則表達式,自定義校驗規則的字段,前端默認生成文本輸入框
f10 = forms.FileField() # 上傳文件,生成input type為file的標簽,
# clean()中顯示的:'f10': <InMemoryUploadedFile: 1.jpg (image/jpeg)>
f11 = forms.ImageField() # 類似FileField,生成標簽多了accept="image/*"
f12 = forms.ChoiceField(
choices=[(1,'羽毛球'),(2,'乒乓球'),(3,'藍球'),(4,'排球')],
initial=3
)
# 下列選擇框,值是字符串
f13 = forms.TypedChoiceField(
coerce=lambda x:int(x), #類型轉換
choices=[(1, '羽毛球'), (2, '乒乓球'), (3, '藍球'), (4, '排球')],
initial=3
)
#f12和f13結果進行比較:'f12': '3', 'f13': 3,一個是字符串,一個是整型
f14 = forms.MultipleChoiceField(
choices=[(1, '羽毛球'), (2, '乒乓球'), (3, '藍球'), (4, '排球')],
initial=[1,3]
)
# 多選框,要注意初始值是列表。
f15 = forms.FilePathField(path='myadminzdy/',allow_folders=True,allow_files=True,recursive=True)
# 下拉選擇框,內容是對應路徑下的文件
def detail(req):
if req.method == "GET":
obj = MyForm()
return render(req,'detail.html',{'obj':obj})
else:
obj = MyForm(req.POST,req.FILES)
# 如果要接收文件,參數中增加req.FILES,因為上傳的文件是保存在FILES中的
obj.is_valid()
print(obj.clean())
print(obj.errors.as_json())
return render(req,'detail.html',{'obj':obj})
前端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<script src="/static/jquery-3.6.0.js"></script>
<script>
$('#id_f5').attr(value)
</script>
<body>
<form action="detail.html" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>f1:{
{ obj.f1 }}</p>
<p>f2:{
{ obj.f2 }}</p>
<!-- <p>{
{ obj.f3.label }}:{
{ obj.f3 }}</p> 這種寫法標簽只是一個文本 -->
<!--<p>{
{ obj.f3.id_for_label }}{
{ obj.f3 }}</p> --> <!-- 還有一個屬性id_for_label,將id值做標簽 -->
<!-- <p><label for="{
{ obj.f3.id_for_label }}">{
{ obj.f3.label }}</label>{
{ obj.f3 }}</p>
這種寫法,點擊標簽,焦點進入相應的輸入框 -->
{
{ obj.f3.label_tag }}{
{ obj.f3 }} <!-- 上面寫法可以這樣實現 -->
<p>測試初始值{
{ obj.f4 }}</p>
<p>測試生成隱藏標簽{
{ obj.f5 }}</p>
<!-- <input type="text" name="f5" value="初始值11111" id="id_f5">
<input type="hidden" name="initial-f5" value="初始值11111" id="initial-id_f5">
-->
<p>自定義校驗規則 : {
{ obj.f6 }}</p>
<p>自定義校驗規則 : {
{ obj.f7 }}</p>
<p>自定義校驗規則 : {
{ obj.f8 }}</p>
<p>自定義校驗規則 : {
{ obj.f9 }}</p>
<p>文件上傳: {
{ obj.f10 }}</p>
<p>tuoian上傳: {
{ obj.f11 }}</p>
<p>下拉選擇框: {
{ obj.f12 }}</p>
<p>下拉選擇框類型轉換: {
{ obj.f13 }}</p>
<p>下拉多選選擇框: {
{ obj.f14 }}</p>
<p>下拉選擇框-文件列表: {
{ obj.f15 }}</p>
<p><input type="submit" value="提交"></p>
</form>
</body>
</html>
django提供的widget插件:
TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget
# 單radio,值為字符串
# user = fields.CharField(
# initial=2,
# widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
# )
# 單radio,值為字符串
# user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.RadioSelect
# )
# 單select,值為字符串
# user = fields.CharField(
# initial=2,
# widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
# )
# 單select,值為字符串
# user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.Select
# )
# 多選select,值為列表
# user = fields.MultipleChoiceField(
# choices=((1,'上海'),(2,'北京'),),
# initial=[1,],
# widget=widgets.SelectMultiple
# )
# 單checkbox
# user = fields.CharField(
# widget=widgets.CheckboxInput()
# )
# 多選checkbox,值為列表
# user = fields.MultipleChoiceField(
# initial=[2, ],
# choices=((1, '上海'), (2, '北京'),),
# widget=widgets.CheckboxSelectMultiple
# )
對於字段,主要是進行驗證的,對於widget插件,主要是定義前端生成的標簽類型,如果字段定義的是CharField,而插件使用的是MultipleSelect,前端返回的是一個列表,但是驗證是按字符串進行驗證,就失去驗證的意義,所以要做好兩者的匹配。
下拉單選框,通過數據庫動態獲取數據:
頁面:
後端代碼:
# models中代碼,生成數據庫
class UserType(models.Model):
caption = models.CharField(max_length=32)
# 視圖函數中
# 定義模板類
from myadminzdy import models
class MyFormDb(forms.Form):
host = forms.CharField()
host_type = forms.IntegerField(
# widget=forms.Select(choices=[(1,'BJ'),(2,'SH')]) # 靜態的獲取
# widget = forms.Select(choices=models.UserType.objects.all().values_list('id','caption'))
# 從數據庫動態獲取,django啟動時獲取,只執行一次
widget=forms.Select(choices=[])
)
# 為了在數據庫記錄改變時,前端動態獲取到最新的數據,需要每次生成對象時執行一次獲取數據
def __init__(self,*args,**kwargs):
super(MyFormDb,self).__init__(*args,**kwargs)
self.fields['host_type'].widget.choices = models.UserType.objects.all().values_list('id','caption')
# 每次實例化都會重新獲取數據庫數據並賦值給choices。
#視圖函數
def db(req):
if req.method == "GET":
obj = MyFormDb()
return render(req, 'db.html', {'obj': obj})
else:
obj = MyForm(req.POST, req.FILES)
# 如果要接收文件,參數中增加req.FILES,因為上傳的文件是保存在FILES中的
obj.is_valid()
print(obj.clean())
print(obj.errors.as_json())
return render(req, 'db.html', {'obj': obj})
前端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{
{ obj.host }}
{
{ obj.host_type }}
</body>
</html>
關鍵點是,在定義模板類時,按照以前的寫法,只定義字段,在定義字段時設置choices獲取數據庫數據,測試時,在後台數據庫改變時,前端數據沒有改變,因為字段的choices值只在第一次加載時執行了一次,以後都是這個加載值的拷貝,所以需要定義__init__(),在每次實例化時都執行一遍查詢數據庫,並賦值給choices,這樣就實現數據的實時更新。
注意這一句:
self.fields['host_type'].widget.choices = models.UserType.objects.all().values_list('id','caption')
通過這個推斷,模板類中有一個字典類型字段fields,我們定義的字段作為其中的鍵值對,所以可以使用fields['host_type']獲取到forms.IntegerField(widget=forms.Select(choices=[])),這又是一個字典結構,可以通過.widget獲取到forms.Select(choices=[]),又是一個字典結構,在.choices獲取到choices。
定義一個用戶表:
class UserType(models.Model):
caption = models.CharField(max_length=32)
class User(models.Model):
username = models.CharField(max_length=32)
user_type = models.ForeignKey('UserType',on_delete=models.DO_NOTHING)
前台請求傳遞一個用戶id,返回其用戶名和類型:
def db(req):
if req.method == "GET":
nid = req.GET.get('nid')
m = models.User.objects.filter(id=nid).first()
dic = {'host':m.username,'host_type':m.user_type_id}
obj = MyFormDb(dic)
return render(req, 'db.html', {'obj': obj})
只需要形成一個驗證模板類字段的字典格式數據,這裡就是dic ={'host':'','host_type':''},傳遞給生成的模板對象就可以了。