117 表單
1冬三,HTML中的表單
單純從前端的html來說,表單是用來提交數(shù)據(jù)給服務(wù)器的,不管后臺(tái)的服務(wù)器用的是Django還是PHP語言還是其他語言钢悲。只要把input標(biāo)簽放在form標(biāo)簽中蓬抄,然后再添加一個(gè)提交按鈕,那么以后點(diǎn)擊提交按鈕履羞,就可以將input標(biāo)簽中對(duì)應(yīng)的值提交給服務(wù)器了。
2屡久,Django中的表單
Django中的表單豐富了傳統(tǒng)的HTML語言中的表單忆首。在Django中的表單,主要做以下兩件事:
1被环,渲染表單模板糙及。
2,表單驗(yàn)證數(shù)據(jù)是否合法筛欢。
3浸锨,Django中表單使用流程
在講解Django表單的具體每部分的細(xì)節(jié)之前唇聘。我們首先先來看下整體的使用流程。這里以一個(gè)做一個(gè)留言板為例柱搜。首先我們?cè)诤笈_(tái)服務(wù)器定義一個(gè)表單類迟郎,繼承自django.forms.Form。示例代碼如下:
# forms.py
class MessageBoardForm(forms.Form):
title = forms.CharField(max_length=3,label='標(biāo)題',min_length=2,<br>error_messages={"min_length":'標(biāo)題字符段不符合要求聪蘸!'})
content = forms.CharField(widget=forms.Textarea,label='內(nèi)容')
email = forms.EmailField(label='郵箱')
reply = forms.BooleanField(required=False,label='回復(fù)')
然后在視圖中宪肖,根據(jù)是GET還是POST請(qǐng)求來做相應(yīng)的操作。如果是GET請(qǐng)求健爬,那么返回一個(gè)空的表單控乾,如果是POST請(qǐng)求,那么將提交上來的數(shù)據(jù)進(jìn)行校驗(yàn)浑劳。示例代碼如下:
# views.py
class IndexView(View):
def get(self,request):
form = MessageBoardForm()
return render(request,'index.html',{'form':form})
def post(self,request):
form = MessageBoardForm(request.POST)
if form.is_valid():
title = form.cleaned_data.get('title')
content = form.cleaned_data.get('content')
email = form.cleaned_data.get('email')
reply = form.cleaned_data.get('reply')
在使用GET請(qǐng)求的時(shí)候阱持,我們傳了一個(gè)form給模板,那么以后模板就可以使用form來生成一個(gè)表單的html代碼魔熏。在使用POST請(qǐng)求的時(shí)候,我們根據(jù)前端上傳上來的數(shù)據(jù)鸽扁,構(gòu)建一個(gè)新的表單蒜绽,這個(gè)表單是用來驗(yàn)證數(shù)據(jù)是否合法的,如果數(shù)據(jù)都驗(yàn)證通過了桶现,那么我們可以通過cleaned_data來獲取相應(yīng)的數(shù)據(jù)躲雅。在模板中渲染表單的HTML代碼如下:
<form action="" method="post">
<table>
<tr>
<td></td>
<td><input type="submit" value="提交"></td>
</tr>
</table>
</form>
我們?cè)谧钔饷娼o了一個(gè)form標(biāo)簽,然后在里面使用了table標(biāo)簽來進(jìn)行美化骡和,在使用form對(duì)象渲染的時(shí)候相赁,使用的是table的方式,當(dāng)然還可以使用ul的方式(as_ul)慰于,也可以使用p標(biāo)簽的方式(as_p)钮科,并且在后面我們還加上了一個(gè)提交按鈕。這樣就可以生成一個(gè)表單了
118用表單驗(yàn)證數(shù)據(jù)是否合法
常用的Field:
使用 Field 可以是對(duì)數(shù)據(jù)驗(yàn)證的第一步婆赠。你期望這個(gè)提交上來的數(shù)據(jù)是什么類型绵脯,那么就使用什 么類型的 Field 。
CharField:
用來接收文本休里。
參數(shù):
max_length:這個(gè)字段值的最大長(zhǎng)度蛆挫。 min_length:這個(gè)字段值的最小長(zhǎng)度。 required:這個(gè)字段是否是必須的妙黍。默認(rèn)是必須的悴侵。 error_messages:在某個(gè)條件驗(yàn)證失敗的時(shí)候,給出錯(cuò)誤信息拭嫁。
EmailField:
用來接收郵件可免,會(huì)自動(dòng)驗(yàn)證郵件是否合法抓于。
錯(cuò)誤信息的key: required 、 invalid 巴元。
FloatField:
用來接收浮點(diǎn)類型毡咏,并且如果驗(yàn)證通過后,會(huì)將這個(gè)字段的值轉(zhuǎn)換為浮點(diǎn)類型逮刨。
參數(shù):
max_value:最大的值呕缭。 min_value:最小的值。
錯(cuò)誤信息的key: required 修己、 invalid 恢总、 max_value 、 min_value 睬愤。
IntegerField:
用來接收整形片仿,并且驗(yàn)證通過后,會(huì)將這個(gè)字段的值轉(zhuǎn)換為整形尤辱。
參數(shù):
max_value:最大的值砂豌。 min_value:最小的值。
錯(cuò)誤信息的key: required 光督、 invalid 阳距、 max_value 、 min_value 结借。
URLField:
用來接收 url 格式的字符串筐摘。
錯(cuò)誤信息的key: required 、 invalid 船老。
119表單中常用的驗(yàn)證器
常用的驗(yàn)證器:
在驗(yàn)證某個(gè)字段的時(shí)候咖熟,可以傳遞一個(gè) validators 參數(shù)用來指定驗(yàn)證器,進(jìn)一步對(duì)數(shù)據(jù)進(jìn)行過 濾柳畔。驗(yàn)證器有很多馍管,但是很多驗(yàn)證器我們其實(shí)已經(jīng)通過這個(gè) Field 或者一些參數(shù)就可以指定了。 比如 EmailValidator 荸镊,我們可以通過 EmailField 來指定咽斧,比如 MaxValueValidator ,我們可以 通過 max_value 參數(shù)來指定躬存。以下是一些常用的驗(yàn)證器:
MaxValueValidator :驗(yàn)證最大值张惹。
MinValueValidator :驗(yàn)證最小值。
MinLengthValidator :驗(yàn)證最小長(zhǎng)度岭洲。
MaxLengthValidator :驗(yàn)證最大長(zhǎng)度宛逗。
EmailValidator :驗(yàn)證是否是郵箱格式。
URLValidator :驗(yàn)證是否是 URL 格式盾剩。
RegexValidator :如果還需要更加復(fù)雜的驗(yàn)證雷激,那么我們可以通過正則表達(dá)式的驗(yàn)證 器: RegexValidator 替蔬。比如現(xiàn)在要驗(yàn)證手機(jī)號(hào)碼是否合格,那么我們可以通過以下代碼實(shí) 現(xiàn):
class MyForm(forms.Form):
telephone =forms.CharField(validators[validators.RegexValidator("1[345678]\d {9}",message='請(qǐng)輸入正確格式的手機(jī)號(hào)碼屎暇!')]
120 自定義驗(yàn)證
自定義驗(yàn)證:
有時(shí)候?qū)σ粋€(gè)字段驗(yàn)證承桥,不是一個(gè)長(zhǎng)度,一個(gè)正則表達(dá)式能夠?qū)懬宄母浚€需要一些其他復(fù)雜的邏輯凶异,那么我們可以對(duì)某個(gè)字段,進(jìn)行自定義的驗(yàn)證挤巡。比如在注冊(cè)的表單驗(yàn)證中剩彬,我們想要驗(yàn)證手機(jī)號(hào)碼是否已經(jīng)被注冊(cè)過了,那么這時(shí)候就需要在數(shù)據(jù)庫中進(jìn)行判斷才知道矿卑。對(duì)某個(gè)字段進(jìn)行自定義的驗(yàn)證方式是喉恋,定義一個(gè)方法,這個(gè)方法的名字定義規(guī)則是: clean_fieldname 母廷。如果驗(yàn)證失敗轻黑,那么就拋出一個(gè)驗(yàn)證錯(cuò)誤。比如要驗(yàn)證用戶表中手機(jī)號(hào)碼之前是否在數(shù)據(jù)庫中存在琴昆,那么可以通過以下代碼實(shí)現(xiàn):
class MyForm(forms.Form):
telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",message='請(qǐng)輸入正確格式的手機(jī)號(hào)碼苔悦!')])
def clean_telephone(self):
telephone = self.cleaned_data.get('telephone')
exists = User.objects.filter(telephone=telephone).exists()
if exists:
raise forms.ValidationError("手機(jī)號(hào)碼已經(jīng)存在!")
else:
return telephone
以上是對(duì)某個(gè)字段進(jìn)行驗(yàn)證椎咧,如果驗(yàn)證數(shù)據(jù)的時(shí)候,需要針對(duì)多個(gè)字段進(jìn)行驗(yàn)證把介,那么可以重寫 clean 方法勤讽。比如要在注冊(cè)的時(shí)候,要判斷提交的兩個(gè)密碼是否相等拗踢。那么可以使用以下代碼來完成:
class MyForm(forms.Form):
telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",message='請(qǐng)輸入正確格式的手機(jī)號(hào)碼脚牍!')])
pwd1 = forms.CharField(max_length=12)
pwd2 = forms.CharField(max_length=12)
def clean(self):
cleaned_data = super().clean()
pwd1 = cleaned_data.get('pwd1')
pwd2 = cleaned_data.get('pwd2')
if pwd1 != pwd2:
raise forms.ValidationError('兩個(gè)密碼不一致!')
121
提取錯(cuò)誤信息:
如果驗(yàn)證失敗了巢墅,那么有一些錯(cuò)誤信息是我們需要傳給前端的诸狭。這時(shí)候我們可以通過以下屬性來獲取:
form.errors :這個(gè)屬性獲取的錯(cuò)誤信息是一個(gè)包含了 html 標(biāo)簽的錯(cuò)誤信息君纫。
form.errors.get_json_data() :這個(gè)方法獲取到的是一個(gè)字典類型的錯(cuò)誤信息驯遇。將某個(gè)字段的名字作為 key ,錯(cuò)誤信息作為值的一個(gè)字典蓄髓。
form.as_json() :這個(gè)方法是將 form.get_json_data() 返回的字典 dump 成 json 格式的字符串叉庐,方便進(jìn)行傳輸。
上述方法獲取的字段的錯(cuò)誤值会喝,都是一個(gè)比較復(fù)雜的數(shù)據(jù)陡叠。比如以下:
{'username': [{'message': 'Enter a valid URL.', 'code': 'invalid'}, {'message': 'Ensurethis value has at most 4 characters (it has 22).', 'code': 'max_length'}]}
那么如果我只想把錯(cuò)誤信息放在一個(gè)列表中玩郊,而不要再放在一個(gè)字典中。這時(shí)候我們可以定義一個(gè)方法枉阵,把這個(gè)數(shù)據(jù)重新整理一份译红。實(shí)例代碼如下:
class MyForm(forms.Form):
username = forms.URLField(max_length=4)
def get_errors(self):
errors = self.errors.get_json_data()
new_errors = {}
for key,message_dicts in errors.items():
messages = []
for message in message_dicts:
messages.append(message['message'])
new_errors[key] = messages
return new_errors
這樣就可以把某個(gè)字段所有的錯(cuò)誤信息直接放在這個(gè)列表中。
122ModelForm用法講解
大家在寫表單的時(shí)候兴溜,會(huì)發(fā)現(xiàn)表單中的Field和模型中的Field基本上是一模一樣的侦厚,而且表單中需要驗(yàn)證的數(shù)據(jù),也就是我們模型中需要保存的昵慌。那么這時(shí)候我們就可以將模型中的字段和表單中的字段進(jìn)行綁定假夺。
比如現(xiàn)在有個(gè)Article的模型。示例代碼如下:
from django.db import models
from django.core import validators
class Article(models.Model):
title = models.CharField(max_length=10,validators=[validators.MinLengthValidator(limit_value=3)])
content = models.TextField()
author = models.CharField(max_length=100)
category = models.CharField(max_length=100)
create_time = models.DateTimeField(auto_now_add=True)
那么在寫表單的時(shí)候斋攀,就不需要把Article模型中所有的字段都一個(gè)個(gè)重復(fù)寫一遍了已卷。示例代碼如下:
from django import forms
class MyForm(forms.ModelForm):
class Meta:
model = Article
fields = "__all__"
MyForm是繼承自forms.ModelForm,然后在表單中定義了一個(gè)Meta類淳蔼,在Meta類中指定了model=Article侧蘸,以及fields="all",這樣就可以將Article模型中所有的字段都復(fù)制過來鹉梨,進(jìn)行驗(yàn)證讳癌。如果只想針對(duì)其中幾個(gè)字段進(jìn)行驗(yàn)證,那么可以給fields指定一個(gè)列表存皂,將需要的字段寫進(jìn)去晌坤。比如只想驗(yàn)證title和content,那么可以使用以下代碼實(shí)現(xiàn):
from django import forms
class MyForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title','content']
如果要驗(yàn)證的字段比較多旦袋,只是除了少數(shù)幾個(gè)字段不需要驗(yàn)證骤菠,那么可以使用exclude來代替fields。比如我不想驗(yàn)證category疤孕,那么示例代碼如下:
lass MyForm(forms.ModelForm):
class Meta:
model = Article
exclude = ['category']
1商乎,自定義錯(cuò)誤消息
使用ModelForm,因?yàn)樽侄味疾皇窃诒韱沃卸x的祭阀,而是在模型中定義的鹉戚,因此一些錯(cuò)誤消息無法在字段中定義。那么這時(shí)候可以在Meta類中专控,定義error_messages抹凳,然后把相應(yīng)的錯(cuò)誤消息寫到里面去。示例代碼如下:
class MyForm(forms.ModelForm):
class Meta:
model = Article
exclude = ['category']
error_messages ={
'title':{
'max_length': '最多不能超過10個(gè)字符踩官!',
'min_length': '最少不能少于3個(gè)字符却桶!'
},
'content': {
'required': '必須輸入content!',
}
}
123
2,save方法
ModelForm還有save方法颖系,可以在驗(yàn)證完成后直接調(diào)用save方法嗅剖,就可以將這個(gè)數(shù)據(jù)保存到數(shù)據(jù)庫中了。示例代碼如下:
form = MyForm(request.POST)
if form.is_valid():
form.save()
return HttpResponse('succes')
else:
print(form.get_errors())
return HttpResponse('fail')
這個(gè)方法必須要在clean沒有問題后才能使用嘁扼,如果在clean之前使用信粮,會(huì)拋出異常。另外趁啸,我們?cè)谡{(diào)用save方法的時(shí)候强缘,如果傳入一個(gè)commit=False,那么只會(huì)生成這個(gè)模型的對(duì)象不傅,而不會(huì)把這個(gè)對(duì)象真正的插入到數(shù)據(jù)庫中旅掂。比如表單上驗(yàn)證的字段沒有包含模型中所有的字段,這時(shí)候就可以先創(chuàng)建對(duì)象访娶,再根據(jù)填充其他字段商虐,把所有字段的值都補(bǔ)充完成后,再保存到數(shù)據(jù)庫中崖疤。示例代碼如下:
form = MyForm(request.POST)
if form.is_valid():
article = form.save(commit=False)
article.category = 'Python'
article.save()
return HttpResponse('succes')
else:
print(form.get_errors())
return HttpResponse('fail')
124文件上傳
文件上傳是網(wǎng)站開發(fā)中非常常見的功能秘车。這里詳細(xì)講述如何在Django中實(shí)現(xiàn)文件的上傳功能。
1劫哼,前端HTML代碼實(shí)現(xiàn)
1叮趴,在前端中,我們需要填入一個(gè)form標(biāo)簽权烧,然后在這個(gè)form標(biāo)簽中指定enctype="multipart/form-data"眯亦,不然就不能上傳文件。
2般码,在form標(biāo)簽中添加一個(gè)input標(biāo)簽搔驼,然后指定input標(biāo)簽的name,以及type="file"侈询。
以上兩步的示例代碼如下:
<form action="" method="post" enctype="multipart/form-data">
<input type="file" name="myfile">
</form>
2,后端的代碼實(shí)現(xiàn)
后端的主要工作是接收文件糯耍。然后存儲(chǔ)文件扔字。接收文件的方式跟接收POST的方式是一樣的,只不過是通過FILES來實(shí)現(xiàn)温技。示例代碼如下:
def save_file(file):
with open('somefile.txt','wb') as fp:
for chunk in file.chunks():
fp.write(chunk)
def index(request):
if request.method == 'GET':
form = MyForm()
return render(request,'index.html',{'form':form})
else:
myfile = request.FILES.get('myfile')
save_file(myfile)
return HttpResponse('success')
以上代碼通過request.FILES接收到文件后革为,再寫入到指定的地方。這樣就可以完成一個(gè)文件的上傳功能了舵鳞。
3震檩,使用模型來處理上傳的文件
在定義模型的時(shí)候,我們可以給存儲(chǔ)文件的字段指定為FileField,這個(gè)Field可以傳遞一個(gè)upload_to參數(shù)抛虏,用來指定上傳上來的文件保存到哪里。比如我們讓他保存到項(xiàng)目的files文件夾下迂猴,那么示例代碼如下:
# models.py
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
thumbnail = models.FileField(upload_to="files")
# views.py
def index(request):
if request.method == 'GET':
return render(request,'index.html')
else:
title = request.POST.get('title')
content = request.POST.get('content')
thumbnail = request.FILES.get('thumbnail')
調(diào)用完article.save()方法慕淡,就會(huì)把文件保存到files下面,并且會(huì)將這個(gè)文件的路徑存儲(chǔ)到數(shù)據(jù)庫中沸毁。
4峰髓,指定MEDIA_ROOT和MEDIA_URL
以上我們是使用了upload_to來指定上傳的文件的目錄。我們也可以指定MEDIA_ROOT息尺,就不需要在FielField中指定upload_to携兵,他會(huì)自動(dòng)的將文件上傳到MEDIA_ROOT的目錄下。
MEDIA_ROOT = os.path.join(BASE_DIR,'media')
MEDIA_URL = '/media/'
然后我們可以在urls.py中添加MEDIA_ROOT目錄下的訪問路徑搂誉。示例代碼如下:
from django.urls import path
from front import views
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('', views.index),
] + static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
如果我們同時(shí)指定MEDIA_ROOT和upload_to徐紧,那么會(huì)將文件上傳到MEDIA_ROOT下的upload_to文件夾中。示例代碼如下:
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
thumbnail = models.FileField(upload_to="%Y/%m/%d/")
限制上傳的文件拓展名:
如果想要限制上傳的文件的拓展名勒葱,那么我們就需要用到表單來進(jìn)行限制浪汪。我們可以使用普通的Form表單,也可以使用ModelForm凛虽,直接從模型中讀取字段死遭。示例代碼如下:
# models.py
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
thumbnial = models.FileField(upload_to='%Y/%m/%d/',validators=[validators.FileExtensionValidator(['txt','pdf'])])
# forms.py
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = "__all__"
5,上傳圖片
上傳圖片跟上傳普通文件是一樣的凯旋。只不過是上傳圖片的時(shí)候Django會(huì)判斷上傳的文件是否是圖片的格式(除了判斷后綴名呀潭,還會(huì)判斷是否是可用的圖片)。如果不是至非,那么就會(huì)驗(yàn)證失敗钠署。我們首先先來定義一個(gè)包含ImageField的模型。示例代碼如下:
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
thumbnail = models.ImageField(upload_to="%Y/%m/%d/")
因?yàn)橐?yàn)證是否是合格的圖片荒椭,因此我們還需要用一個(gè)表單來進(jìn)行驗(yàn)證谐鼎。表單我們直接就使用ModelForm就可以了。示例代碼如下:
class MyForm(forms.ModelForm):
class Meta:
model = Article
fields = "__all__"
注意:使用ImageField趣惠,必須要先安裝Pillow庫:pip install pillow