django基礎之Django表單
表單概述
HTML中的表單
單純從前端的html
來說,表單是用來提交資料給伺服器的,不管後臺的伺服器用的是Django
還是PHP
語言還是其他語言。只要把input
標籤放在form
標籤中,然後再新增一個提交按鈕,那麼以後點選提交按鈕,就可以將input
標籤中對應的值提交給伺服器了。
Django中的表單
Django
中的表單豐富了傳統的HTML
語言中的表單。在Django
中的表單,主要做以下兩件事:
1. 渲染表單模板
2. 表單驗證資料是否合法
Django中表單使用流程
在講解Django
表單的具體每部分的細節之前。我們首先先來看下整體的使用流程。這裡以一個做一個留言板為例。首先我們在後臺伺服器定義一個表單類,繼承自django.forms.Form
# forms.py class MessageBoardForm(forms.Form): title = forms.CharField(max_length=3,label='標題',min_length=2,error_messages={"min_length":'標題字元段不符合要求!'}) content = forms.CharField(widget=forms.Textarea,label='內容') email = forms.EmailField(label='郵箱') reply = forms.BooleanField(required=False,label='回覆')
然後在檢視中,根據是GET
還是POST
請求來做相應的操作。如果是GET
請求,那麼返回一個空的表單,如果是POST
請求,那麼將提交上來的資料進行校驗。示例程式碼如下:
# 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') return HttpResponse('success') else: print(form.errors) return HttpResponse('fail')
在使用GET
請求的時候,我們傳了一個form
給模板,那麼以後模板就可以使用form
來生成一個表單的html
程式碼。在使用POST
請求的時候,我們根據前端上傳上來的資料,構建一個新的表單,這個表單是用來驗證資料是否合法的,如果資料都驗證通過了,那麼我們可以通過cleaned_data
來獲取相應的資料。在模板中渲染表單的HTML
程式碼如下:
<form action="" method="post" novalidate> {# <p>第一種渲染方式: 程式碼書寫極少, 封裝程度太高, 不便於擴充套件, 一般情況下只在本地測試使用</p>#} {# {{ form.as_p }}#} {# {{ form.as_ul }}#} {# {{ form.as_table }}#} {# <p>第二種渲染方式: 可擴充套件性很強, 但是書寫的程式碼太多, 一般情況下不使用</p>#} {# <p>{{ form.username.label }}: {{ form.username }}</p>#} {# <p>{{ form.password.label }}: {{ form.password }}</p>#} {# <p>{{ form.email.label }}: {{ form.email }}</p>#} <p>第三種渲染方式: 推薦使用, 程式碼書寫簡單, 並且擴充套件性也高</p> {% for form_obj in form %} <p>{{ form_obj.label }}: {{ form_obj }}</p> <span style="color: red">{{ form_obj.errors.0}}</span> {% endfor %} <button style="color: red;">提交</button> </form>
Form表單的基本使用
Form表單的內建屬性與方法
from app import forms # 1. 將帶校驗的資料組成成字典的形式傳入即可 form_obj = forms.MyForm({'username':'json', 'password':'123', 'email':'123'}) # 2. 判斷資料是否合法, 注意該方法只有在所有的資料全部合法的情況下才返回True form_obj.is_valid() False # 3. 檢視所有校驗通過的資料 form_obj.cleaned_data {'username': 'json', 'password': '123'} # 4. 檢視所有不符合校驗規則的欄位以及不符合的原因 form_obj.errors {'email': ['Enter a valid email address.']} # 5. 校驗資料只校驗類中出現的欄位, 多傳不影響, 多傳的欄位直接忽略 form_obj = forms.MyForm({'username':'json', 'password':'123', 'email':'[email protected]', 'hobby': 'read'}) form_obj.is_valid() True # 6. 校驗資料預設情況下, 類裡面所有的欄位都必須傳值 form_obj = forms.MyForm({'username':'json', 'password':'123'}) form_obj.is_valid() False """ 校驗資料的時候, 預設情況下資料可以多傳但是絕不能少傳 """
渲染標籤
""" forms元件只會自動幫你渲染獲取使用者輸入的標籤(input select radio checkbox), 不會幫你渲染提交按鈕 """ def index(request): # 1. 先生成一個空物件 form_obj = MyForm() # 2. 直接將該空物件傳遞給html頁面 return render(request, 'app02/index.html', locals()) <form action="" method="post"> {# <p>第一種渲染方式: 程式碼書寫極少, 封裝程度太高, 不便於擴充套件, 一般情況下只在本地測試使用</p>#} {# {{ form_obj.as_p }}#} {# {{ form_obj.as_ul }}#} {# {{ form_obj.as_table }}#} {# <p>第二種渲染方式: 可擴充套件性很強, 但是書寫的程式碼太多, 一般情況下不使用</p>#} {# <p>{{ form_obj.username.label }}: {{ form_obj.username }}</p>#} {# <p>{{ form_obj.password.label }}: {{ form_obj.password }}</p>#} {# <p>{{ form_obj.email.label }}: {{ form_obj.email }}</p>#} <p>第三種渲染方式: 推薦使用, 程式碼書寫簡單, 並且擴充套件性也高</p> {% for form in form_obj %} <p>{{ form.label }}: {{ form }}</p> {% endfor %} </form> """ lable屬性預設展示的是類中定義的欄位的首字母大寫的形式, 也可以進行修改, 直接給欄位物件家lable屬性即可 username = forms.CharField(min_length=3, max_length=8, label='使用者名稱') """
展示提示資訊
""" 瀏覽器會自動幫你校驗資料, 但是前端的校驗弱不禁風, 如何讓瀏覽器不做校驗 <form action="" method="post" novalidate> """ class MyForm(forms.Form): # username字串型別最少3位, 最多8位 username = forms.CharField(min_length=3, max_length=8, label='使用者名稱', error_messages={ 'min_length': '使用者名稱最少3位', 'max_length': '使用者名稱最多8位', 'required': '使用者名稱不能為空' }) # username字串型別最少3位, 最多8位 password = forms.CharField(min_length=3, max_length=8, label='密碼', error_messages={ 'min_length': '密碼最少3位', 'max_length': '密碼最多8位', 'required': '密碼不能為空' }) # email欄位必須符合郵箱格式 [email protected] email = forms.EmailField(label='郵箱', error_messages={ 'invalid': '郵箱格式不正確', 'required': '郵箱不能為空' }) {% for form in form_obj %} <p>{{ form.label }}: {{ form }}</p> <span style="color: red">{{ form.errors.0}}</span> {% endfor %}
Form常用欄位與外掛
建立Form類時,主要涉及到 【欄位】 和 【外掛】,欄位用於對使用者請求資料的驗證,外掛用於自動生成HTML;
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, 最小值 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型別 Django Form內建欄位View Code
Django Form常用欄位
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) )
radioSelect
單radio值為字串
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="密碼") gender = forms.fields.ChoiceField( choices=((1, "男"), (2, "女"), (3, "保密")), label="性別", initial=3, widget=forms.widgets.RadioSelect() )
單選Select
class LoginForm(forms.Form): ... hobby = forms.ChoiceField( choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ), label="愛好", initial=3, widget=forms.widgets.Select() )
多選Select
class LoginForm(forms.Form): ... hobby = forms.MultipleChoiceField( choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ), label="愛好", initial=[1, 3], widget=forms.widgets.SelectMultiple() )
單選checkbox
class LoginForm(forms.Form): ... hobby = forms.MultipleChoiceField( choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),), label="愛好", initial=[1, 3], widget=forms.widgets.CheckboxSelectMultiple() )
多選checkbox
class LoginForm(forms.Form): ... hobby = forms.MultipleChoiceField( choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),), label="愛好", initial=[1, 3], widget=forms.widgets.CheckboxSelectMultiple() )
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) # 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 = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多選 # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 單選
Form表單驗證資料
常用驗證器
在驗證某個欄位的時候,可以傳遞一個validators
引數用來指定驗證器,進一步對資料進行過濾。驗證器有很多,但是很多驗證器我們其實已經通過這個Field
或者一些引數就可以指定了。比如EmailValidator
,我們可以通過EmailField
來指定,比如MaxValueValidator
,我們可以通過max_value
引數來指定。以下是一些常用的驗證器:
1. MaxValueValidator
:驗證最大值。
2. MinValueValidator
:驗證最小值。
3. MinLengthValidator
:驗證最小長度。
4. MaxLengthValidator
:驗證最大長度。
5. EmailValidator
:驗證是否是郵箱格式。
6. URLValidator
:驗證是否是URL
格式。
7. RegexValidator
:如果還需要更加複雜的驗證,那麼我們可以通過正則表示式的驗證器:RegexValidator
。比如現在要驗證手機號碼是否合格,那麼我們可以通過以下程式碼實現:
class MyForm(forms.Form): telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",message='請輸入正確格式的手機號碼!')])
自定義校驗函式
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'郵箱'}))
鉤子函式
鉤子函式即在特定的節點自動觸發完成響應操作,鉤子函式在forms元件中就類似於第二道關卡, 能夠讓我們自定義校驗規則
在forms元件中有兩類鉤子
1. 區域性鉤子:當你需要給單個欄位增加校驗規則的時候可以使用
2. 全域性鉤子:當你需要給多個欄位增加校驗規則的時候可以使用
區域性鉤子函式
有時候對一個欄位驗證,不是一個長度,一個正則表示式能夠寫清楚的,還需要一些其他複雜的邏輯,那麼我們可以對某個欄位,進行自定義的驗證。比如在註冊的表單驗證中,我們想要驗證手機號碼是否已經被註冊過了,那麼這時候就需要在資料庫中進行判斷才知道。對某個欄位進行自定義的驗證方式是,定義一個方法,這個方法的名字定義規則是:clean_fieldname
。如果驗證失敗,那麼就丟擲一個驗證錯誤。比如要驗證使用者表中手機號碼之前是否在資料庫中存在,那麼可以通過以下程式碼實現:
class MyForm(forms.Form): telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",message='請輸入正確格式的手機號碼!')]) def clean_telephone(self): telephone = self.cleaned_data.get('telephone') exists = User.objects.filter(telephone=telephone).exists() if exists: raise forms.ValidationError("手機號碼已經存在!") return telephone
全域性鉤子函式
如果驗證資料的時候,需要針對多個欄位進行驗證,那麼可以重寫clean
方法。比如要在註冊的時候,要判斷提交的兩個密碼是否相等。那麼可以使用以下程式碼來完成:
class MyForm(forms.Form): telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",message='請輸入正確格式的手機號碼!')]) 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('兩個密碼不一致!')
提取錯誤資訊
如果驗證失敗了,那麼有一些錯誤資訊是我們需要傳給前端的。這時候我們可以通過以下屬性來獲取:
1. form.errors
:這個屬性獲取的錯誤資訊是一個包含了html
標籤的錯誤資訊。
2. form.errors.get_json_data()
:這個方法獲取到的是一個字典型別的錯誤資訊。將某個欄位的名字作為key
,錯誤資訊作為值的一個字典。
3. form.as_json()
:這個方法是將form.get_json_data()
返回的字典dump
成json
格式的字串,方便進行傳輸。
4. 上述方法獲取的欄位的錯誤值,都是一個比較複雜的資料。比如以下:
{'username': [{'message': 'Enter a valid URL.', 'code': 'invalid'}, {'message': 'Ensure this value'}]
那麼如果我只想把錯誤資訊放在一個列表中,而不要再放在一個字典中。這時候我們可以定義一個方法,把這個資料重新整理一份。例項程式碼如下:
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
這樣就可以把某個欄位所有的錯誤資訊直接放在這個列表中
ModelForm
在寫表單的時候,會發現表單中的Field
和模型中的Field
基本上是一模一樣的,而且表單中需要驗證的資料,也就是我們模型中需要儲存的。那麼這時候我們就可以將模型中的欄位和表單中的欄位進行繫結。 比如現在有個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)
那麼在寫表單的時候,就不需要把Article
模型中所有的欄位都一個個重複寫一遍了。示例程式碼如下
from django import forms class MyForm(forms.ModelForm): class Meta: model = Article fields = "__all__"
MyForm
是繼承自forms.ModelForm
,然後在表單中定義了一個Meta
類,在Meta
類中指定了model=Article
,以及fields="__all__"
,這樣就可以將Article
模型中所有的欄位都複製過來,進行驗證。如果只想針對其中幾個欄位進行驗證,那麼可以給fields
指定一個列表,將需要的欄位寫進去。比如只想驗證title
和content
,那麼可以使用以下程式碼實現:
from django import forms class MyForm(forms.ModelForm): class Meta: model = Article fields = ['title','content']
如果要驗證的欄位比較多,只是除了少數幾個欄位不需要驗證,那麼可以使用exclude
來代替fields
。比如我不想驗證category
,那麼示例程式碼如下:
class MyForm(forms.ModelForm): class Meta: model = Article exclude = ['category']
自定義錯誤訊息
使用ModelForm
,因為欄位都不是在表單中定義的,而是在模型中定義的,因此一些錯誤訊息無法在欄位中定義。那麼這時候可以在Meta
類中,定義error_messages
,然後把相應的錯誤訊息寫到裡面去。示例程式碼如下:
class MyForm(forms.ModelForm): class Meta: model = Article exclude = ['category'] error_messages ={ 'title':{ 'max_length': '最多不能超過10個字元!', 'min_length': '最少不能少於3個字元!' }, 'content': { 'required': '必須輸入content!', } }
save方法
ModelForm
還有save
方法,可以在驗證完成後直接呼叫save
方法,就可以將這個資料儲存到資料庫中了。示例程式碼如下:
form = MyForm(request.POST) if form.is_valid(): form.save() return HttpResponse('succes') else: print(form.get_errors()) return HttpResponse('fail')
這個方法必須要在clean
沒有問題後才能使用,如果在clean
之前使用,會丟擲異常。另外,我們在呼叫save
方法的時候,如果傳入一個commit=False
,那麼只會生成這個模型的物件,而不會把這個物件真正的插入到資料庫中。比如表單上驗證的欄位沒有包含模型中所有的欄位,這時候就可以先建立物件,再根據填充其他欄位,把所有欄位的值都補充完成後,再儲存到資料庫中。示例程式碼如下
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')