一 Form介紹
我們之前在HTML頁面中利用form表單向后端提交數據時,都會寫一些獲取用戶輸入的標簽並且用form標簽把它們包起來。
與此同時我們在好多場景下都需要對用戶的輸入做校驗,比如校驗用戶是否輸入,輸入的長度和格式等正不正確。如果用戶輸入的內容有錯誤就需要在頁面上相應的位置顯示對應的錯誤信息.。
Django form組件就實現了上面所述的功能。
總結一下,其實form組件的主要功能如下:
生成頁面可用的HTML標簽
對用戶提交的數據進行校驗
保留上次輸入內容
普通方式手寫注冊功能
views.py
# 注冊 def register(request): error_msg = "" if request.method == "POST": username = request.POST.get("name") pwd = request.POST.get("pwd") # 對注冊信息做校驗 if len(username) < 6: # 用戶長度小於6位 error_msg = "用戶名長度不能小於6位" else: # 將用戶名和密碼存到數據庫 return HttpResponse("注冊成功") return render(request, "register.html", {"error_msg": error_msg})
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注冊頁面</title> </head> <body> <form action="/reg/" method="post"> {% csrf_token %} <p> 用戶名: <input type="text" name="name"> </p> <p> 密碼: <input type="password" name="pwd"> </p> <p> <input type="submit" value="注冊"> <p style="color: red">{{ error_msg }}</p> </p> </form> </body> </html>
使用form組件實現注冊功能
views.py
先定義好一個RegForm類:
from django import forms # 按照Django form組件的要求自己寫一個類 class RegForm(forms.Form): name = forms.CharField(label="用戶名") #form字段的名稱寫的是什么,那么前端生成input標簽的時候,input標簽的name屬性的值就是什么 pwd = forms.CharField(label="密碼")
再寫一個視圖函數:
# 使用form組件實現注冊方式 def register2(request): form_obj = RegForm() if request.method == "POST": # 實例化form對象的時候,把post提交過來的數據直接傳進去 form_obj = RegForm(data=request.POST) #既然傳過來的input標簽的name屬性值和form類對應的字段名是一樣的,所以接過來后,form就取出對應的form字段名相同的數據進行form校驗 # 調用form_obj校驗數據的方法 if form_obj.is_valid(): return HttpResponse("注冊成功") return render(request, "register2.html", {"form_obj": form_obj})
login2.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注冊2</title> </head> <body> <form action="/reg2/" method="post" novalidate autocomplete="off"> #novalidate 告訴前端form表單,不要對輸入的內容做校驗 {% csrf_token %}
#{{ form_obj.as_p }} 直接寫個這個,下面的用戶名和密碼的標簽不自己寫,你看看效果 <div> <label for="{{ form_obj.name.id_for_label }}">{{ form_obj.name.label }}</label> {{ form_obj.name }} {{ form_obj.name.errors.0 }} #errors是這個字段所有的錯誤,我就用其中一個錯誤提示就可以了,再錯了再提示,並且不是給你生成ul標簽了,單純的是錯誤文本
{{ form_obj.errors }} #這是全局的所有錯誤,找對應字段的錯誤,就要form_obj.字段名 </div> <div> <label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label> {{ form_obj.pwd }} {{ form_obj.pwd.errors.0 }} </div> <div> <input type="submit" class="btn btn-success" value="注冊"> </div> </form> </body> </html>
看網頁效果發現 也驗證了form的功能:
前端頁面是form類的對象生成的 -->生成HTML標簽功能
當用戶名和密碼輸入為空或輸錯之后 頁面都會提示 -->用戶提交校驗功能
當用戶輸錯之后 再次輸入 上次的內容還保留在input框 -->保留上次輸入內容
二 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="密碼")
password
class LoginForm(forms.Form): ... pwd = forms.CharField( min_length=6, label="密碼", widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) #這個密碼字段和其他字段不一樣,默認在前端輸入數據錯誤的時候,點擊提交之后,默認是不保存的原來數據的,但是可以通過這個render_value=True讓這個字段在前端保留用戶輸入的數據 )
radioSelect
單radio值為字符串
class LoginForm(forms.Form): username = forms.CharField( #其他選擇框或者輸入框,基本都是在這個CharField的基礎上通過插件來搞的 min_length=8, label="用戶名", initial="張三", error_messages={ "required": "不能為空", "invalid": "格式錯誤", "min_length": "用戶名最短8位" } ) pwd = forms.CharField(min_length=6, label="密碼") gender = forms.fields.ChoiceField( choices=((1, "男"), (2, "女"), (3, "保密")), label="性別", initial=3, widget=forms.widgets.RadioSelect() )
單選Select
class LoginForm(forms.Form): ... hobby = forms.fields.ChoiceField( #注意,單選框用的是ChoiceField,並且里面的插件是Select,不然驗證的時候會報錯, Select a valid choice的錯誤。 choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ), label="愛好", initial=3, widget=forms.widgets.Select() )
多選Select
class LoginForm(forms.Form): ... hobby = forms.fields.MultipleChoiceField( #多選框的時候用MultipleChoiceField,並且里面的插件用的是SelectMultiple,不然驗證的時候會報錯。 choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ), label="愛好", initial=[1, 3], widget=forms.widgets.SelectMultiple() )
單選checkbox
class LoginForm(forms.Form): ... keep = forms.fields.ChoiceField( label="是否記住密碼", initial="checked", widget=forms.widgets.CheckboxInput() )
單選checkbox示例:
#單選的checkbox class TestForm2(forms.Form): keep = forms.ChoiceField( choices=( ('True',1), ('False',0), ), label="是否7天內自動登錄", initial="1", widget=forms.widgets.CheckboxInput(), ) 選中:'True' #form只是幫我們做校驗,校驗選擇內容的時候,就是看在沒在我們的choices里面,里面有這個值,表示合法,沒有就不合法 沒選中:'False' ---保存到數據庫里面 keep:'True' if keep == 'True': session 設置有效期7天 else: pass
多選checkbox
class LoginForm(forms.Form): ... hobby = forms.fields.MultipleChoiceField( choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),), label="愛好", initial=[1, 3], widget=forms.widgets.CheckboxSelectMultiple() )
date類型
from django import forms from django.forms import widgets class BookForm(forms.Form): date = forms.DateField(widget=widgets.TextInput(attrs={'type':'date'})) #必須指定type,不然不能渲染成選擇時間的input框
choice字段注意事項
在使用選擇標簽時,需要注意choices的選項可以配置從數據庫中獲取,但是由於是靜態字段 獲取的值無法實時更新,需要重寫構造方法從而實現choice實時更新。
方式一:
from django.forms import Form from django.forms import widgets from django.forms import fields class MyForm(Form): user = fields.ChoiceField( # choices=((1, '上海'), (2, '北京'),), initial=2, widget=widgets.Select ) def __init__(self, *args, **kwargs): super(MyForm,self).__init__(*args, **kwargs) #注意重寫init方法的時候,*args和**kwargs一定要給人家寫上,不然會出問題,並且驗證總是不能通過,還不顯示報錯信息 # self.fields['user'].choices = ((1, '上海'), (2, '北京'),) # 或 self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
方式二:
from django import forms from django.forms import fields from django.forms import models as form_model class FInfo(forms.Form):
authors = forms.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多選
#或者下面這種方式,通過forms里面的models中提供的方法也是一樣的。
authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多選 #authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 單選
#或者,forms.ModelChoiceField(queryset=models.Publisth.objects.all(),widget=forms.widgets.Select()) 單選
#
authors = forms.ModelMultipleChoiceField(
queryset=models.Author.objects.all(),
widget = forms.widgets.Select(attrs={'class': 'form-control'}
))
#如果用這種方式,別忘了model表中,NNEWType的__str__方法要寫上,不然選擇框里面是一個個的object對象
三 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, 最小值 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類型 復制代碼 內置字段
四 字段校驗
RegexValidator驗證器
from django.forms import Form from django.forms import widgets from django.forms import fields from django.core.validators import RegexValidator class MyForm(Form): user = fields.CharField( validators=[RegexValidator(r'^[0-9]+$', '請輸入數字'), RegexValidator(r'^159[0-9]+$', '數字必須以159開頭')], )
自定義驗證函數
import re from django.forms import Form from django.forms import widgets from django.forms import fields from django.core.exceptions import ValidationError # 自定義驗證規則 def mobile_validate(value): mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$') if not mobile_re.match(value): raise ValidationError('手機號碼格式錯誤') #自定義驗證規則的時候,如果不符合你的規則,需要自己發起錯誤 class PublishForm(Form): title = fields.CharField(max_length=20, min_length=5, error_messages={'required': '標題不能為空', 'min_length': '標題最少為5個字符', 'max_length': '標題最多為20個字符'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': '標題5-20個字符'})) # 使用自定義驗證規則 phone = fields.CharField(validators=[mobile_validate, ], error_messages={'required': '手機不能為空'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'手機號碼'})) email = fields.EmailField(required=False, error_messages={'required': u'郵箱不能為空','invalid': u'郵箱格式錯誤'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'郵箱'}))
五 Hook鈎子方法
除了上面兩種方式,我們還可以在Form類中定義鈎子函數,來實現自定義的驗證功能。
局部鈎子
我們在Fom類中定義 clean_字段名() 方法,就能夠實現對特定字段進行校驗。
舉個例子:
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用戶名", initial="張三", error_messages={ "required": "不能為空", "invalid": "格式錯誤", "min_length": "用戶名最短8位" }, widget=forms.widgets.TextInput(attrs={"class": "form-control"}) ) ... # 定義局部鈎子,用來校驗username字段,之前的校驗股則還在,給你提供了一個添加一些校驗功能的鈎子 def clean_username(self): value = self.cleaned_data.get("username") if "666" in value: raise ValidationError("光喊666是不行的") else: return value
全局鈎子
我們在Fom類中定義 clean() 方法,就能夠實現對字段進行全局校驗,字段全部驗證完,局部鈎子也全部執行完之后,執行這個全局鈎子校驗。
class LoginForm(forms.Form): ... password = forms.CharField( min_length=6, label="密碼", widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True) ) re_password = forms.CharField( min_length=6, label="確認密碼", widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True) ) ... # 定義全局的鈎子,用來校驗密碼和確認密碼字段是否相同,執行全局鈎子的時候,cleaned_data里面肯定是有了通過前面驗證的所有數據 def clean(self): password_value = self.cleaned_data.get('password') re_password_value = self.cleaned_data.get('re_password') if password_value == re_password_value: return self.cleaned_data #全局鈎子要返回所有的數據 else: self.add_error('re_password', '兩次密碼不一致') #在re_password這個字段的錯誤列表中加上一個錯誤,並且clean_data里面會自動清除這個re_password的值,所以打印clean_data的時候會看不到它 raise ValidationError('兩次密碼不一致')
六 進階補充
應用Bootstrap樣式
Django form應用Bootstrap樣式簡單示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> <title>login</title> </head> <body> <div class="container"> <div class="row"> <form action="/login2/" method="post" novalidate class="form-horizontal"> {% csrf_token %} <div class="form-group"> <label for="{{ form_obj.username.id_for_label }}" class="col-md-2 control-label">{{ form_obj.username.label }}</label> <div class="col-md-10"> {{ form_obj.username }} <span class="help-block">{{ form_obj.username.errors.0 }}</span> </div> </div> <div class="form-group"> <label for="{{ form_obj.pwd.id_for_label }}" class="col-md-2 control-label">{{ form_obj.pwd.label }}</label> <div class="col-md-10"> {{ form_obj.pwd }} <span class="help-block">{{ form_obj.pwd.errors.0 }}</span> </div> </div> <div class="form-group"> <label class="col-md-2 control-label">{{ form_obj.gender.label }}</label> <div class="col-md-10"> <div class="radio"> {% for radio in form_obj.gender %} <label for="{{ radio.id_for_label }}"> {{ radio.tag }}{{ radio.choice_label }} </label> {% endfor %} </div> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <button type="submit" class="btn btn-default">注冊</button> </div> </div> </form> </div> </div> <script src="/static/jquery-3.2.1.min.js"></script> <script src="/static/bootstrap/js/bootstrap.min.js"></script> </body> </html>
批量添加樣式
可通過重寫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
通常在Django項目中,我們編寫的大部分都是與Django 的模型緊密映射的表單。 舉個例子,你也許會有個Book 模型,並且你還想創建一個form表單用來添加和編輯書籍信息到這個模型中。 在這種情況下,在form表單中定義字段將是冗余的,因為我們已經在模型中定義了那些字段。
基於這個原因,Django 提供一個輔助類來讓我們可以從Django 的模型創建Form,這就是ModelForm。
modelForm定義
form與model的終極結合,會根據你model中的字段轉換成對應的form字段,並且並你生成標簽等操作。
比如你的models中的表是下面的內容:
class Book(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField( max_length=32) publishDate=models.DateField() price=models.DecimalField(max_digits=5,decimal_places=2) publish=models.ForeignKey(to="Publish",to_field="nid") authors=models.ManyToManyField(to='Author',) def __str__(self): return self.title
modelform類的寫法。
class BookForm(forms.ModelForm): class Meta: model = models.Book fields = "__all__" labels = { "title": "書名", "price": "價格" } widgets = { "password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
"publishDate": forms.widgets.DateInput(attrs={"type": "date"}),
}
class Meta下常用參數
model = models.Book # 對應的Model中的類 fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段 exclude = None # 排除的字段 labels = None # 提示信息 help_texts = None # 幫助提示信息 widgets = None # 自定義插件 error_messages = None # 自定義錯誤信息
error_messages = {
'title':{'required':'不能為空',...} #每個字段的所有的錯誤都可以寫,...是省略的意思,復制黏貼我代碼的時候別忘了刪了...
}
批量添加樣式:和form的一樣
class BookForm(forms.ModelForm):
#password = forms.CharField(min_length=10) #可以重寫字段,會覆蓋modelform中的這個字段,那么modelform下面關於這個字段的設置就會被覆蓋,比如果設置插件啊,error_messages啊等等,
r_password = forms.CharField() #想多驗證一些字段可以單獨拿出來寫,按照form的寫法,寫在Meta的上面或者下面都可以
class Meta: model = models.Book # fields = ['title','price'] fields = "__all__" #['title,'price'] 指定字段生成form # exclude=['title',] #排除字段 labels = { "title": "書名", "price": "價格" } error_messages = { 'title':{'required':'不能為空',} #每個字段的錯誤都可以寫 } #如果models中的字段和咱們需要驗證的字段對不齊的是,比如注冊時,咱們需要驗證密碼和確認密碼兩個字段數據,但是后端數據庫就保存一個數據就行,那么驗證是兩個,數據保存是一個,就可以再接着寫form字段
r_password = forms.CharField()。
#同樣的,如果想做一些特殊的驗證定制,那么和form一昂,也是那兩個鈎子(全局和局部),寫法也是form那個的寫法,直接在咱們的類里面寫:
#局部鈎子:
def clean_title(self):
pass
#全局鈎子
def clean(self):
pass
def __init__(self,*args,**kwargs): #批量操作 super().__init__(*args,**kwargs) for field in self.fields:
#field.error_messages = {'required':'不能為空'} #批量添加錯誤信息,這是都一樣的錯誤,不一樣的還是要單獨寫。 self.fields[field].widget.attrs.update({'class':'form-control'})
ModelForm的驗證
與普通的Form表單驗證類型類似,ModelForm表單的驗證在調用is_valid() 或訪問errors 屬性時隱式調用。
我們可以像使用Form類一樣自定義局部鈎子方法和全局鈎子方法來實現自定義的校驗規則。
如果我們不重寫具體字段並設置validators屬性的話,ModelForm是按照模型中字段的validators來校驗的。
save()方法
每個ModelForm還具有一個save()方法。 這個方法根據表單綁定的數據創建並保存數據庫對象。 ModelForm的子類可以接受現有的模型實例作為關鍵字參數instance;如果提供此功能,則save()將更新該實例。 如果沒有提供,save() 將創建模型的一個新實例:
>>> from myapp.models import Book >>> from myapp.forms import BookForm # 根據POST數據創建一個新的form對象 >>> form_obj = BookForm(request.POST) # 創建書籍對象 >>> new_ book = form_obj.save() # 基於一個書籍對象創建form對象 >>> edit_obj = Book.objects.get(id=1) # 使用POST提交的數據更新書籍對象 >>> form_obj = BookForm(request.POST, instance=edit_obj) >>> form_obj.save()
之前我們通過form組件來保存書籍表數據的時候的寫法:
def index(request): if request.method == 'GET': form_obj = BookForm() return render(request,'index.html',{'form_obj':form_obj}) else: form_obj = BookForm(request.POST) if form_obj.is_valid(): # authors_obj = form_obj.cleaned_data.pop('authors') # new_book_obj = models.Book.objects.create(**form_obj.cleaned_data) # new_book_obj.authors.add(*authors_obj) form_obj.save() #因為我們再Meta中指定了是哪張表,所以它會自動識別,不管是外鍵還是多對多等,都會自行處理保存,它完成的就是上面三句話做的事情,並且還有就是如果你驗證的數據比你后端數據表中的字段多,那么他自會自動剔除多余的不需要保存的字段,比如那個重復確認密碼就不要保存 return redirect('show') else: print(form_obj.errors) return render(request,'index.html',{'form_obj':form_obj})
八 補充
這是一個神奇的組件,通過名字我們可以看出來,這個組件的功能就是把model和form組合起來,先來一個簡單的例子來看一下這個東西怎么用:比如我們的數據庫中有這樣一張學生表,字段有姓名,年齡,愛好,郵箱,電話,住址,注冊時間等等一大堆信息,現在讓你寫一個創建學生的頁面,你的后台應該怎么寫呢?首先我們會在前端一個一個羅列出這些字段,讓用戶去填寫,然后我們從后天一個一個接收用戶的輸入,創建一個新的學生對象,保存其實,重點不是這些,而是合法性驗證,我們需要在前端判斷用戶輸入是否合法,比如姓名必須在多少字符以內,電話號碼必須是多少位的數字,郵箱必須是郵箱的格式這些當然可以一點一點手動寫限制,各種判斷,這毫無問題,除了麻煩我們現在有個更優雅(以后在Python相關的內容里,要多用“優雅”這個詞,並且養成習慣)的方法:ModelForm先來簡單的,生硬的把它用上,再來加驗證條件。
創建modelform
#首先導入ModelForm from django.forms import ModelForm #在視圖函數中,定義一個類,比如就叫StudentList,這個類要繼承ModelForm,在這個類中再寫一個原類Meta(規定寫法,並注意首字母是大寫的) #在這個原類中,有以下屬性(部分): class StudentList(ModelForm): class Meta: model =Student #對應的Model中的類 fields = "__all__" #字段,如果是__all__,就是表示列出所有的字段 exclude = None #排除的字段 #error_messages用法: error_messages = { 'name':{'required':"用戶名不能為空",}, 'age':{'required':"年齡不能為空",}, } #widgets用法,比如把輸入用戶名的input框給為Textarea #首先得導入模塊 from django.forms import widgets as wid #因為重名,所以起個別名 widgets = { "name":wid.Textarea(attrs={"class":"c1"}) #還可以自定義屬性 } #labels,自定義在前端顯示的名字 labels= { "name":"用戶名" }
然后在url對應的視圖函數中實例化這個類,把這個對象傳給前端
def student(request): if request.method == 'GET': student_list = StudentList() return render(request,'student.html',{'student_list':student_list})
然后前端只需要 {{ student_list.as_p }} 一下,所有的字段就都出來了,可以用as_p顯示全部,也可以通過for循環這
student_list,拿到的是一個個input框,現在我們就不用as_p,手動把這些input框搞出來,as_p拿到的頁面太丑。
首先 for循環這個student_list,拿到student對象,直接在前端打印這個student,是個input框student.label ,拿到數據庫中每個字段的verbose_name ,如果沒有設置這個屬性,拿到的默認就是字段名,還可以通過student.errors.0 拿到錯誤信息有了這些,我們就可以通過bootstrap,自己拼出來想要的樣式了,比如:
<body> <div class="container"> <h1>student</h1> <form method="POST" novalidate> {% csrf_token %} {# {{ student_list.as_p }}#} {% for student in student_list %} <div class="form-group col-md-6"> {# 拿到數據字段的verbose_name,沒有就默認顯示字段名 #} <label class="col-md-3 control-label">{{ student.label }}</label> <div class="col-md-9" style="position: relative;">{{ student }}</div> </div> {% endfor %} <div class="col-md-2 col-md-offset-10"> <input type="submit" value="提交" class="btn-primary"> </div> </form> </div> </body>
現在還缺一個input框的form-contral樣式,可以考慮在后台的widget里面添加
比如這樣:
from django.forms import widgets as wid #因為重名,所以起個別名 widgets = { "name":wid.TextInput(attrs={'class':'form-control'}), "age":wid.NumberInput(attrs={'class':'form-control'}), "email":wid.EmailInput(attrs={'class':'form-control'}) }
當然也可以在js中,找到所有的input框,加上這個樣式,也行。
添加紀錄
保存數據的時候,不用挨個取數據了,只需要save一下
def student(request): if request.method == 'GET': student_list = StudentList() return render(request,'student.html',{'student_list':student_list}) else: student_list = StudentList(request.POST) if student_list.is_valid(): student_list.save() return redirect(request,'student_list.html',{'student_list':student_list})
編輯數據
如果不用ModelForm,編輯的時候得顯示之前的數據吧,還得挨個取一遍值,如果ModelForm,只需要加一個instance=obj(obj是要修改的數據庫的一條數據的對象)就可以得到同樣的效果
保存的時候要注意,一定要注意有這個對象(instance=obj),否則不知道更新哪一個數據
代碼示例:
from django.shortcuts import render,HttpResponse,redirect from django.forms import ModelForm # Create your views here. from app01 import models def test(request): # model_form = models.Student model_form = models.Student.objects.all() return render(request,'test.html',{'model_form':model_form}) class StudentList(ModelForm): class Meta: model = models.Student #對應的Model中的類 fields = "__all__" #字段,如果是__all__,就是表示列出所有的字段 exclude = None #排除的字段 labels = None #提示信息 help_texts = None #幫助提示信息 widgets = None #自定義插件 error_messages = None #自定義錯誤信息 #error_messages用法: error_messages = { 'name':{'required':"用戶名不能為空",}, 'age':{'required':"年齡不能為空",}, } #widgets用法,比如把輸入用戶名的input框給為Textarea #首先得導入模塊 from django.forms import widgets as wid #因為重名,所以起個別名 widgets = { "name":wid.Textarea } #labels,自定義在前端顯示的名字 labels= { "name":"用戶名" } def student(request): if request.method == 'GET': student_list = StudentList() return render(request,'student.html',{'student_list':student_list}) else: student_list = StudentList(request.POST) if student_list.is_valid(): student_list.save() return render(request,'student.html',{'student_list':student_list}) def student_edit(request,pk): obj = models.Student.objects.filter(pk=pk).first() if not obj: return redirect('test') if request.method == "GET": student_list = StudentList(instance=obj) return render(request,'student_edit.html',{'student_list':student_list}) else: student_list = StudentList(request.POST,instance=obj) if student_list.is_valid(): student_list.save() return render(request,'student_edit.html',{'student_list':student_list})
總結: 從上邊可以看到ModelForm用起來是非常方便的,比如增加修改之類的操作。但是也帶來額外不好的地方,model和form之間耦合了。如果不耦合的話,mf.save()方法也無法直接提交保存。 但是耦合的話使用場景通常局限用於小程序,寫大程序就最好不用了。