Form
局部鈎子、全局鈎子
ModelForm
兩種的區別
Form介紹
form組件的主要功能如下:
- 生成頁面可用的HTML標簽
- 對用戶提交的數據進行校驗
- 保留上次輸入內容
使用form組件實現書籍的增加功能
首先定義一個類

在views.py中定義一個BookForm的類 from django import forms #自定義一個form類 class BookForm(forms.Form): title = forms.CharField( max_length = 12, max_length = 6, label = "書名", #頁面 label標簽顯示的中文名 widget = forms.widgets.TextInput(attrs={"class":"form-control"}) #widget 插件 ) publish_date = forms.DateField( label = "出版日期" widget = forms.widgets.DateInput(attrs={"type":"date","class":"form-control"}) ) phone = forms.CharField( label = "手機號", max_length = 11, required = True, #是否允許為空 widget = forms.widgets.TextInput(attrs={"class":"form-control"}) ) publisher = forms.ModelChoiceField( queryset = models.Publisher.objects.all(), #queryset:查詢數據庫中的數據 widget = forms.widgets.Select(attrs = {"class":"form-control}) ) authors = forms.ModelMultipleChoiceField( queryset = models.Author.objects.all(), widget = forms.widgets.SelectMultiple(attrs={"class":"form-control"}) )
對應的html操作

<form action="" method="post" novalidate autocomplete="off"> {%csrf_token%} {% for field in form_obj %} <div class="form-group"> <label for="{{ field.id_for_label }}">{{ field.label }}</label> {{ field }} <p> {{ field.errors.0 }}</p> </div> {% endfor %} <input type="submit" class="btn btn-info"> </form>
添加書籍函數

def addbook(request): form_obj = BookForm() if request.POST: #做校驗 form_obj = BookForm(request.POST) if form_obj.is_valid(): #做數據有效性的校驗 # 因為有多對多的字段,需要額外處理 authors = form_obj.cleaned_data.pop("authors") # 創建新書籍對象 book_obj = models.Book.objects.create(**form_obj.cleaned_data) # 將書籍對象和作者建立關聯 book_obj.authors.add(*authors) return redirect('/book_list/') return render(request,'addbook.html',locals())
Form常用字段與插件
創建Form類時,主要涉及到【字段】和【插件】,字段用於對用戶請求數據的驗證,插件用於自動生成HTML
initial
初始值,input框里面的初始值
class LoginForm(forms.Form): username = forms.CharField( min_length = 8, label = "用戶名" initial = "張三" #設置默認值 ) pwd = forms.CharField(min_length=6,label="密碼")
error_messages
重寫錯誤信息
class LoginForm(forms.Form): username = forms.CharField( min_length = 8, label = "用戶名", initial = "張三", error_messages = { "required": "不能為空", "invalid": "格式錯誤", "min_length": "用戶名最短8位" ) pwd = forms.CharField(min_length=6,label="密碼")
radioSelect
單radio值為字符串,choices不建議固定,具體方法下方有說明
class LoginForm(forms.Form): ... ... gender = forms.fields.ChoiceField( choices = ((1,'男'), (2,'女'), (3,'保密')), label = "性別", initial = 3, widget = forms.widgets.RadioSelect() )
單選select
class LoginForm(forms.Form): ... hobby = forms.fields.ChoiceField( choice = ((1, "籃球"), (2, "足球"), (3, "雙色球"), ), label = "愛好", initial = 3, widget = forms.widgets.Select() )
多選select
class LoginForm(forms.Form): ... hobby = forms.fields.MultipleChoiceField( choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ), label="愛好", initial=[1, 3], widget = forms.widgets.SelectMultiple() )
單選CheckBox
class Login(forms.Form): ... keep = forms.fields.ChoiceField( label="是否記住密碼", initial="checked", widget = forms.widgets.CheckboxInput() )
多選checkbox
class LoginForm(forms.Form): ... hobby = forms.fields.MultipleChoiceField( choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),), label="愛好", initial=[1, 3], widget = forms.widgets.CheckboxSelectMultiple() )
關於choice的注意事項:
在使用選擇標簽時,需要注意choices的選項可以從數據庫中獲取,但是由於是靜態字段,**獲取的值無法實時更新**,那么需要自定義構造方法從而達到此目的
具體方法:
class FInfo(forms.Form): ... publisher = forms.ModelChoiceField( queryset=models.Publisher.objects.all(), widget=forms.widgets.Select(attrs={"class": "form-control"}) )
Django Form所有內置字段

Field required=True 是否運行為空 widget=None, HTML插件 label=None, 用於生成label標簽或顯示內容 initial=None, 初始值 help_text=' ', 幫助信息(在標簽旁顯示) error_messages=None 錯誤信息{'required': '不能為空', 'invalid': '格式錯誤'} 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, 最小值 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 RegexField(CharField) regex, 自定制正則表達式 max_length=None, 最大長度 min_length=None, 最小長度 error_message=None, 忽略,錯誤信息使用 error_messages={'invalid': '...'} FileField(Field) allow_empty_file=False 是否允許空文件 ImageField(FileField) ... 注:需要PIL模塊,pip3 install Pillow 以上兩個字典使用時,需要注意兩點: - form表單中 enctype="multipart/form-data" - view函數中 obj = MyForm(request.POST, request.FILES) 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= '' 空值的默認值 TypedMultipleChoiceField(MultipleChoiceField) coerce = lambda val: val 對選中的每一個值進行一次轉換 empty_value= '' 空值的默認值 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類型
自定義校驗規則
1、使用內置的正則校驗器: validators=[]
from django.core.validators import RegexValidator class BookForm(forms.Form): phone = forms.CharField( max_length=11, validators=[RegexValidator(r'^1[35789]\d{9}$',"手機號格式不正確")] )
2、手寫一個校驗函數
from django.core.exceptions import ValidationError # 自定義一個字段的校驗規則函數 def phone_validate(value): # 拿用戶填寫的手機號去數據庫中校驗 is_exits = models.Book.objects.filter(phone=value) if is_exits: #如果手機號被注冊,不能再注冊 raise ValidationError("該手機號已經被注冊") else: return value # 第二步在自定義的BookForm類中phone字段添加phone_validate from django.core.validators import RegexValidator class BookForm(forms.Form): ... phone = forms.CharField( max_length=11, validators = [RegexValidator(r'^1[356789]\d{9}$','手機號格式不正確'),phone_validate] )
3、使用鈎子函數做校驗
局部鈎子(hook)
from django.core.exceptions import ValidationError black_list = ["alex",] class BookForm(forms.Form): ... # 定義一個局部鈎子,對title字段做有效性驗證 def clean_title(self): value = self.cleand_data.get("title") #判斷有沒有敏感詞 #第一種方法: #if "alex" in value: # raise ValidationError("alex已被和諧") #else: # return value #第二種方法: for v in black_list: if v in value: raise ValidationError("{}為敏感詞".format(v)) return value
全局鈎子 (兩個字段值做比較)
#全局鈎子,在form類內部定義一個 clean(self) 方法 black_list = ["alex",] class BookForm(forms.Form): def clean(self): #取到所有字段的數據 # self.add_error("字段","密碼和確認密碼不一致") title = self.cleand_data.get("title") for v in black_list: if v in title: self.add_error("title","包含敏感詞{}".format(v)) return title
form組件給html標簽設置默認值
# 以修改書籍功能為例: def editbook(request,edit_id): book_obj = models.Book.objects.filter(id=edit_id).first() # 把ORM中一個對象快速轉換成字典格式 from django.forms import model_to_dict book_dict = model_to_dict(book_obj) book_dict["publish_date"] = book_obj.publish_date.strftime("%Y-%m-%d") form_obj = BookForm(book_dict) if request.POST: # 從用戶提交過來的數據中取數據 form_obj = BookForm(request.POST) if form_obj.is_valid(): #去數據庫更新數據 book_obj.title = form_obj.cleaned_data.get("title") book_obj.publish_date = form_obj.cleaned_data.get("publish_date") book_obj.publisher_id = form_obj.cleaned_data.get("publisher") book_obj.save() book_obj.authors.set(form_obj.cleaned_data.get("authors")) return redirect('/book_list/') return render(request, 'editbook.html', locals())
ORM中把一個對象轉換成字典格式
from django.forms import model_to_dict book_obj = models.Book.objects.filter(id=edit_id).first() book_dict = model_to_dict(book_obj)
補充進階
批量添加樣式
可通過重寫form類的init方法來實現
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用戶名",
initial="張三",
error_messages={
"required": "不能為空",
"invalid": "格式錯誤",
"min_length": "用戶名最短8位"
}
...
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
for field in iter(self.fields):
self.fields[field].widget.attrs.update({
'class': 'form-control'
})
ModelForm
form與model的終極結合,具有以下功能:驗證、數據庫操作。
# 寫一個和Model類一一對應的form
from django import forms
class BookModelForm(forms.ModelForm): class Meta: model = models.Book #對應的model中的類 fields = "__all__" # model類里所有的字段都展示 # fields = ["title",] # 指定展示某些字段 exclude = None #除了指定字段,其他字段都顯示 labels = { #設置label標簽名 "title":"書名", "phone":"手機號", "publish_date":"出版日期", "publisher":"出版社", "authors":"作者", } widgets = { #設置每個字段的插件信息 "title":forms.widgets.TextInput(attrs={"class":"form-control"}), "phone": forms.widgets.TextInput(attrs={"class": "form-control"}), "publish_date":forms.widgets.DateInput(attrs={"type":"date","class":"form-control"}), "publisher": forms.widgets.Select(attrs={"class": "form-control"}), "authors": forms.widgets.SelectMultiple(attrs={"class": "form-control"}), } error_messages = { #設置每個字段的報錯提示信息 "publisher":{ "required":"必須選一個出版社!" } }
class Meta: 常用參數 model = models.Student # 對應的Model中的類 fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段 exclude = None # 排除的字段 labels = None # 提示信息 help_texts = None # 幫助提示信息 widgets = None # 自定義插件 error_messages = None # 自定義錯誤信息
instance = book_obj #實例名

def editbook(request,edit_id): book_obj = models.Book.objects.filter(id=edit_id).first() form_obj = BookModelForm(instance=book_obj) if request.POST: # 從用戶提交過來的數據中取數據 form_obj = BookModelForm(request.POST,instance=book_obj) if form_obj.is_valid(): #去數據庫更新數據 form_obj.save() return redirect('/book_list/') return render(request, 'editbook.html', locals())

def addbook(request): form_obj = BookModelForm() if request.POST: #做校驗 form_obj = BookModelForm(request.POST) if form_obj.is_valid(): #做數據有效性的校驗 form_obj.save() return redirect('/book_list/') # 實例化BookForm return render(request,'addbook.html',locals())

class Meta: model, #對應model的類 fields #字段 exclude=None #排查的字段 labels=None #提示信息 widgets=None #自定義插件 error_messages=None #自定義錯誤信息 field_classes=None #自定義字段類 驗證執行過程: is_valid -> full_clean --> 鈎子 --> 整體錯誤 字典字段驗證: def clean_字段名(self): return "新值" 驗證: model_form_obj.is_valid() model_form_obj.clean() model_form_obj.cleand_data 用於創建: model_form_obj = XXOOModelForm(request.POST) 用於更新和初始化 obj = model.tb.objects.get(id=1) model_form_obj = XXOOModelForm(request.POST,instance=obj)
Form和ModelForm區別
model可以操作數據庫字段,form驗證也有那幾個字段,雖然耦合度很低,但是代碼還是有重復的。如果使用ModelForm,可以利用model
里的字段,form里的字段就可以不用寫了。