1. 程式人生 > 實用技巧 >django基礎之Django表單

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()返回的字典dumpjson格式的字串,方便進行傳輸。

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指定一個列表,將需要的欄位寫進去。比如只想驗證titlecontent,那麼可以使用以下程式碼實現:

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')