一、Form介紹
我們之前在HTML頁面中利用form表單向后端提交數據時,都會寫一些獲取用戶輸入的標簽並且用form標簽把它們包起來。
與此同時我們在好多場景下都需要對用戶的輸入做校驗,比如校驗用戶是否輸入,輸入的長度和格式等正不正確。如果用戶輸入的內容有錯誤就需要在頁面上相應的位置顯示對應的錯誤信息.。
Django form組件就實現了上面所述的功能。
總結一下,其實form組件的主要功能如下:
-
- 生成頁面可用的HTML標簽
- 對用戶提交的數據進行校驗
- 保留上次輸入內容
1.1 普通方式手寫注冊功能:
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>
1.2 使用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;
2.1 initial:
初始值,input框里面的初始值: class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用戶名", initial="張三" # 設置默認值 ) pwd = forms.CharField(min_length=6, label="密碼")
2.2 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="密碼")
2.3 password:
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讓這個字段在前端保留用戶輸入的數據 )
2.4 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() )
2.5 單選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() )
2.6 多選Select:
class LoginForm(forms.Form): ... hobby = forms.fields.MultipleChoiceField( #多選框的時候用MultipleChoiceField,並且里面的插件用的是SelectMultiple,不然驗證的時候會報錯。 choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ), label="愛好", initial=[1, 3], widget=forms.widgets.SelectMultiple() )
2.7 單選checkbox:
# 單選的checkbox方式一:
class LoginForm(forms.Form): ... keep = forms.fields.CharField( label="是否記住密碼", initial="checked", widget=forms.widgets.CheckboxInput() )
# 單選的checkbox方式二:
class TestForm2(forms.Form):
keep=forms.ChoiceField(
choices=(
("True", 1),
("False", 0),
),
1abe1="是否7天內自動登錄”,
initial = "1",
widget = forms.widgets.checkboxInput()
)
2.8 多選checkbox:
class LoginForm(forms.Form): ... hobby = forms.fields.MultipleChoiceField( choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),), label="愛好", initial=[1, 3], widget=forms.widgets.CheckboxSelectMultiple() )
2.9 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框
2.10 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對象
方式三: models.py文件: class Student(models.Model): name = models.CharField(max_length=12, null=True) sex_choice = ((1, '男'), (2, '女'),) sex = models.IntegerField( choices=sex_choice, ) views.py文件: def test_choice(request): # 插入數據: # models.Student.objects.create(name="egon", sex="1") # models.Student.objects.create(name="egon", sex=1) # 插入數值與字符串是相同效果 new_obj = models.Student.objects.get(name='chao') print(new_obj.sex) # 1 print(new_obj.get_sex_display()) # 男 格式:get_字段名稱_display print(new_obj.name) # chao return render(request, 'testchoice.html', {'new_obj': new_obj})
三、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類型
四、字段校驗
4.1 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開頭')], )
4.2 自定義驗證函數:
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類中定義鈎子函數,來實現自定義的驗證功能。
5.1 局部鈎子:
我們在Fom類中定義 clean_字段名() 方法,就能夠實現對特定字段進行校驗。
舉個例子:
from django.core.exceptions import ValidationError
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
補充:
通過定義局部鈎子,驗證每個字段的合法性:不滿足時返回錯誤
def clean_username(self):
val = self.cleaned_data.get('username')
user_obj = models.UserInfo.objects.filter(username=val).first()
if user_obj:
raise ValidationError('該用戶名已經存在,請換個名字!') # 驗證名字是否存在
else:
return val
def clean_password(self):
val = self.cleaned_data.get('password') # 驗證密碼合法性
if val.isdecimal(): # 這里用isdecimal,建議別用isdigit,不准確
raise ValidationError('密碼不能為純數字')
else:
return val
def clean_email(self):
val = self.cleaned_data.get('email')
if re.search('\w+@163.com$', val):
return val
else:
raise ValidationError('必須是163網易郵箱!') # 指定特定的郵箱格式
5.2 全局鈎子:
我們在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的時候會看不到它,這個錯誤信息放在re_password的局部錯誤中 # raise ValidationError('兩次密碼不一致') # 這個報錯信息放在全局的錯誤中
六 進階補充
6.1 應用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>
6.2 批量添加樣式:
可通過重寫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' })
6.3 項目實例:
簡單寫一個小項目:book表的添加和數據展示

from django.db import models # 作者信息 class Author(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) age = models.IntegerField() authorDetail = models.OneToOneField(to="AuthorDetail", to_field="nid") def __str__(self): return self.name # 作者的詳細信息 class AuthorDetail(models.Model): nid = models.AutoField(primary_key=True) birthday = models.DateField() telephone = models.BigIntegerField() addr = models.CharField(max_length=64) # 出版社信息 class Publish(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) city = models.CharField(max_length=32) email = models.EmailField() def __str__(self): return self.name # 書籍信息 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

from django.shortcuts import render, HttpResponse, redirect from django import forms from app01 import models class MyForm1(forms.Form): # 標題 title = forms.CharField( max_length=32, min_length=2, # 自定義最大長度,最小長度 # required=False, # # initial='用戶名', # 用戶提示信息 help_text='這里是輸入用戶名的地方,別忘了不能小於2位!', # 幫助信息 error_messages={ # 自定義提示信息 "min_length": '長度不能小於2', "required": '該字段不能為空!', "max_length": '字段過長,不能超過32位!' }, label='書名', # 定義標簽名字 widget=forms.widgets.TextInput(attrs={'placeholder': '用戶名'}) # 通過widget自定義用戶提示信息 ) # 價格 price = forms.IntegerField( label='價格', widget=forms.widgets.NumberInput(attrs={'placeholder': '價格'}) ) # 出版社日期 publishDate = forms.DateField( label='出版社日期', # widget=forms.widgets.DateInput(attrs={'class': 'form-control', 'type': 'date'}) widget=forms.widgets.DateInput(attrs={'type': 'date'}) # 必須指定type,不然不能渲染成選擇時間的input框 ) # 出版社 publish = forms.ModelChoiceField( label='出版社名稱', queryset=models.Publish.objects.all(), # 獲取所有的出版社信息 # widget=forms.widgets.Select(attrs={'class': 'form-control'}) # 單一形式添加樣式 widget=forms.widgets.Select() # 單選 ) # 作者 authors = forms.ModelMultipleChoiceField( label='作者', queryset=models.Author.objects.all(), widget=forms.widgets.SelectMultiple() # 多選 ) # 批量添加樣式 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields: self.fields[field].widget.attrs.update({ 'class': 'form-control' }) # 擴展 # 郵箱配置 # email = forms.EmailField( # error_messages={ # 'invalid':'請輸入正確的郵箱格式' # } # ) # 指定choices值 # 這種方式要通過__init__方法來指定choices # publish = forms.ChoiceField() # def __init__(self,*args,**kwargs): # super().__init__(*args,**kwargs) # self.fields['publish'].choices = models.Publish.objects.all().values_list('pk','name') # 單選框 # sex = forms.ChoiceField( # choices=( # ('1', '男'), # ('2', '女'), # ), # # widget=forms.widgets.RadioSelect() # widget=forms.widgets.Select(attrs={'class': 'form-control'}) # ) # 多選框 # author = forms.MultipleChoiceField( # choices=( # ('1', '男'), # ('2', '女'), # ), # widget=forms.widgets.CheckboxSelectMultiple(), # ) def index1(request): if request.method == 'GET': form_obj = MyForm1() # 實例化這個MyForm return render(request, 'index1.html', {'form_obj': form_obj}) else: data = request.POST # print(data) form_obj = MyForm1(data) if form_obj.is_valid(): # 驗證每個字段傳過來的數據是不是正確的,正確返回True,否則返回False data = form_obj.cleaned_data # 驗證后的數據,這里只會驗證MyForm類里面指定的數據 print(data) author_data = data.pop('authors') # 刪除多余字段 print(author_data) book_obj = models.Book.objects.create(**data) book_obj.authors.add(*author_data) return HttpResponse('ok') else: print(form_obj.errors) # 所有的錯誤信息 return render(request, 'index1.html', {'form_obj': form_obj}) # ##################################################################################################################### import re from django.core.exceptions import ValidationError from django.core.validators import RegexValidator # 自定義驗證函數 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 MyForm2(forms.Form): # RegexValidator驗證器 與 自定義驗證函數 name = forms.CharField( label="手機號", max_length=32, # validators=[RegexValidator(r'^a','必須以a開頭'),], # RegexValidator驗證器 validators=[mobile_validate, ], # 自定義驗證函數 widget=forms.widgets.TextInput(attrs={'class': 'form-control'}) ) # 密碼 password = forms.CharField( label="密碼", widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True) # 密碼錯誤時,保存當前錯誤密碼 ) # 確認密碼 r_password = forms.CharField( label="確認密碼", widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True) ) # 局部鈎子 def clean_name(self): # 格式:clean_字段名字 value = self.cleaned_data.get('name') # {'name': '九陰真經'} if '6666' in value: raise ValidationError('手機號不能連續4個數字相同!') else: return value # 必須有個返回值 # 全局鈎子 def clean(self): p1 = self.cleaned_data.get('password') p2 = self.cleaned_data.get('r_password') if p1 == p2: return self.cleaned_data # return所有的cleaned_data else: self.add_error('r_password', '兩次輸入的密碼不一致') # 添加錯誤標簽到當前標簽 # raise ValidationError('兩次輸入的密碼不一致!') # 添加錯誤到全局錯誤列表 def index2(request): if request.method == 'GET': form_obj = MyForm2() return render(request, 'index2.html', {'form_obj': form_obj}) else: data = request.POST # 接收到的所有數據 print(data) form_obj = MyForm2(data) if form_obj.is_valid(): print(form_obj.cleaned_data) else: return render(request, 'index2.html', {'form_obj': form_obj})

{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}" rel="stylesheet"> </head> <body> <h1> 這是添加頁面 </h1> {#{{ form_obj.as_p }} #} {# 標簽自動生成,不需要自己寫 #} {# 方式一: #} {#<div class="container-fluid">#} {# <div class="row">#} {# <div class="col-md-6 col-md-offset-3">#} {##} {# <form action="{% url 'index1' %}" method="post" novalidate> {# novalidate:忽略瀏覽器錯誤 #} {# {{ form_obj.errors }} {# 全局錯誤 #} {# {% csrf_token %}#} {# <div class="form-group">#} {# <label for="{{ form_obj.title.id_for_label }}"> {{ form_obj.title.label }}</label>#} {# {{ form_obj.title }}#} {# </div>#} {##} {# <div class="form-group">#} {# <label for="{{ form_obj.price.id_for_label }}"> {{ form_obj.price.label }}</label>#} {# {{ form_obj.price }}#} {# </div>#} {# <div class="form-group">#} {# <label for="{{ form_obj.publishDate.id_for_label }}"> {{ form_obj.publishDate.label }}</label>#} {# {{ form_obj.publishDate }}#} {# </div>#} {# <div class="form-group">#} {# <label for="{{ form_obj.publish.id_for_label }}"> {{ form_obj.publish.label }}</label>#} {# {{ form_obj.publish }}#} {# </div>#} {# <div class="form-group">#} {# <label for="{{ form_obj.authors.id_for_label }}"> {{ form_obj.authors.label }}</label>#} {# {{ form_obj.authors }}#} {# </div>#} {##} {# <input type="submit" class="btn btn-success pull-right" value="保存">#} {# </form>#} {##} {# </div>#} {# </div>#} {#</div>#} {# 方式二: #} <div class="container-fluid"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <form action="{% url 'index1' %}" method="post" novalidate> {# novalidate:忽略瀏覽器錯誤 #} {# {{ form_obj.errors }} {# 全局錯誤 #} {% csrf_token %} {% for field in form_obj %} <div class="form-group {% if field.errors.0 %} has-error {% endif %}"> <label for="{{ field.id_for_label }}"> {{ field.label }}</label> {{ field }} <span class="text-success">{{ field.help_text }}</span> {# 幫助信息 #} <span class="text-danger">{{ field.errors.0 }}</span> {# 顯示第一條錯誤 #} </div> {% endfor %} <input type="submit" class="btn btn-success pull-right" value="保存"> </form> </div> </div> </div> <script src="{% static 'jquery-3.4.1.js' %}"></script> <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script> </body> </html>

{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}" rel="stylesheet"> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-offset-3 col-md-6"> <form action="{% url 'index2' %}" method="post" novalidate> {% csrf_token %} <div> {# {{ form_obj.errors }}#} </div> {{ form_obj.name.label }} {{ form_obj.name }} {{ form_obj.name.errors.0 }} <div> {{ form_obj.password.label }} {{ form_obj.password }} {{ form_obj.password.errors.0 }} </div> <div> {{ form_obj.r_password.label }} {{ form_obj.r_password }} {{ form_obj.r_password.errors.0 }} </div> <input type="submit"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script> <script> </script> </body> </html>
七、ModelForm
通常在Django項目中,我們編寫的大部分都是與Django 的模型緊密映射的表單。 舉個例子,你也許會有個Book 模型,並且你還想創建一個form表單用來添加和編輯書籍信息到這個模型中。 在這種情況下,在form表單中定義字段將是冗余的,因為我們已經在模型中定義了那些字段。
基於這個原因,Django 提供一個輔助類來讓我們可以從Django 的模型創建Form,這就是ModelForm。
7.1 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): 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'})
7.2 ModelForm的驗證:
與普通的Form表單驗證類型類似,ModelForm表單的驗證在調用is_valid() 或訪問errors 屬性時隱式調用。
我們可以像使用Form類一樣自定義局部鈎子方法和全局鈎子方法來實現自定義的校驗規則。
如果我們不重寫具體字段並設置validators屬性的話,ModelForm是按照模型中字段的validators來校驗的。
每個ModelForm還具有一個save()方法。 這個方法根據表單綁定的數據創建並保存數據庫對象。 ModelForm的子類可以接受現有的模型實例作為關鍵字參數instance;如果提供此功能,則save()將更新該實例。 如果沒有提供,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})
比如說我們圖書管理系統頁面之前是這樣寫的

{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}"> </head> <body> <h1>編輯頁面</h1> <div class="container-fluid"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <form action=""> <div class="form-group"> <label for="title">書名</label> <input type="text" class="form-control" id="title" placeholder="title" value="{{ book_obj.title }}"> </div> <div class="form-group"> <label for="publishDate">出版日期</label> <input type="text" class="form-control" id="publishDate" placeholder="publishDate" value="{{ book_obj.publishDate|date:'Y-m-d' }}"> </div> <div class="form-group"> <label for="price">價格</label> <input type="number" class="form-control" id="price" placeholder="price" value="{{ book_obj.price }}"> </div> <div class="form-group"> <label for="publish">書名</label> <select name="publish" id="publish" class="form-control"> {% for publish in all_publish %} {% if publish == book_obj.publish %} <option value="{{ publish.id }}" selected>{{ publish.name }}</option> {% else %} <option value="{{ publish.id }}">{{ publish.name }}</option> {% endif %} {% endfor %} </select> </div> <div class="form-group"> <label for="authors">書名</label> <select name="authors" id="authors" multiple class="form-control"> {% for author in all_authors %} {% if author in book_obj.authors.all %} <option value="{{ author.id }}" selected>{{ author.name }}</option> {% else %} <option value="{{ author.id }}" >{{ author.name }}</option> {% endif %} {% endfor %} </select> </div> </form> </div> </div> </div> </body> <script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script> <script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script> </html>
views.py是這樣寫的:

def edit_book(request,n): book_obj = models.Book.objects.filter(pk=n).first() if request.method == 'GET': all_authors = models.Author.objects.all() # all_publish = models.Publish.objects.all() return render(request,'edit_book.html',{'book_obj':book_obj,'all_authors':all_authors,'all_publish':all_publish})
改成使用modelform之后,我們這樣寫:

{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}"> </head> <body> <h1>編輯頁面</h1> <div class="container-fluid"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <form action="{% url 'edit_book' n %}" novalidate method="post"> {% csrf_token %} {% for field in form %} <div class="form-group"> <label for="{{ field.id_for_label }}">{{ field.label }}</label> {{ field }} <span class="text-danger">{{ field.errors.0 }}</span> </div> {% endfor %} <div class="form-group"> <input type="submit" class="btn btn-primary pull-right"> </div> </form> </div> </div> </div> </body> <script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script> <script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script> </html>
views.py這樣寫:
def edit_book(request,n): book_obj = models.Book.objects.filter(pk=n).first() if request.method == 'GET': # all_authors = models.Author.objects.all() # # all_publish = models.Publish.objects.all() form = BookForm(instance=book_obj) return render(request,'edit_book.html',{'form':form,'n':n}) #傳遞的這個n參數是給form表單提交數據的是的action的url用的,因為它需要一個參數來識別是更新的哪條記錄 else: form = BookForm(request.POST,instance=book_obj) #必須指定instance,不然我們調用save方法的是又變成了添加操作 if form.is_valid(): form.save() return redirect('show') else: return render(request,'edit_book.html',{'form':form,'n':n})
7.3 ModelForm設置獨立字段:
補充一: 示例: class ConsultRecordModelForm(forms.ModelForm): class Meta: model = models.ConsultRecord fields = "__all__" exclude = ["delete_status", ] def __init__(self, request, *args, **kwargs): super().__init__(*args, **kwargs) # 單獨設置字段 # 查詢當前用戶 self.fields["consultant"].queryset = models.UserInfo.objects.filter(pk=request.user.id) # 當前用戶對應的客戶信息 self.fields["customer"].queryset = models.Customer.objects.filter(consultant=request.user) for field in self.fields: self.fields[field].widget.attrs.update({ "class": "form-control", })
補充二:
示例:
# ModelForm字段:客戶表字段
class CustomerModelForm(forms.ModelForm):
class Meta:
model = models.Customer
fields = "__all__"
def __init__(self, *args, **kwargs):
super(CustomerModelForm, self).__init__(*args, **kwargs)
# print(self.fields)
from multiselectfield.forms.fields import MultiSelectFormField # 對某一字段不設置ModeForm樣式
for field in self.fields:
if not isinstance(self.fields[field], MultiSelectFormField):
self.fields[field].widget.attrs.update({
"class": "form-control",
})
八、modelform實例
這是一個神奇的組件,通過名字我們可以看出來,這個組件的功能就是把model和form組合起來,先來一個簡單的例子來看一下這個東西怎么用:比如我們的數據庫中有這樣一張學生表,字段有姓名,年齡,愛好,郵箱,電話,住址,注冊時間等等一大堆信息,現在讓你寫一個創建學生的頁面,你的后台應該怎么寫呢?首先我們會在前端一個一個羅列出這些字段,讓用戶去填寫,然后我們從后天一個一個接收用戶的輸入,創建一個新的學生對象,保存其實,重點不是這些,而是合法性驗證,我們需要在前端判斷用戶輸入是否合法,比如姓名必須在多少字符以內,電話號碼必須是多少位的數字,郵箱必須是郵箱的格式這些當然可以一點一點手動寫限制,各種判斷,這毫無問題,除了麻煩我們現在有個更優雅(以后在Python相關的內容里,要多用“優雅”這個詞,並且養成習慣)的方法:ModelForm先來簡單的,生硬的把它用上,再來加驗證條件。
8.1 創建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'})
}
8.2 添加紀錄:
# 保存數據的時候,不用挨個取數據了,只需要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})
8.3 編輯數據:
如果不用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()方法也無法直接提交保存。 但是耦合的話使用場景通常局限用於小程序,寫大程序就最好不用了。
九、其他補充(isdigit 與 isdecimal 區別)
isdigit與isdecimel用法比較: a = "1" print(a.isdigit()) # True print(a.isdecimal()) # True a = '1.111' print(a.isdigit()) # False print(a.isdecimal()) # False a = '⑴' print(a.isdigit()) # True print(a.isdecimal()) # False