(8)Django - 表單

表單是搜集用戶數據信息,實現網頁數據交互的關鍵。Django的表單功能由Form類實現,主要分兩種:django.forms.Formdjango.forms.ModelForm。前者是一個基礎的表單功能,后者是在前者的基礎上結合模型所生成的數據表單。
雖然在模板文件中直接編寫表單是一種較為簡單的實現方法,但如果表單元素較多,會在無形之中增加模板的代碼量,對日后維護和更新造成極大的不便。為了簡化表單的實現過程和提高表單的靈活性,Django提供了完善的表單功能。先來看個簡單的例子。
在mysite的index中添加一個form.py用于編寫表單的實現功能,然后在templates文件夾中添加模板data_form.html,用于將表單數據顯示到網頁上。
首先在form.py中添加以下代碼:

#index 的 form.py
from django import forms
from .models import *

class ProductForm(forms.Form):
    name = forms.CharField(max_length=20, label='名字')
    weight = forms.CharField(max_length=50, label='重量')
    size = forms.CharField(max_length=50, label='尺寸')
    #設置下拉框的值
##    choices_list = [(1,'手機'),(2, '平板')]
    choices_list = [(i+1, v['type_name']) for i,v in enumerate(Type.objects.values('type_name'))]
    type = forms.ChoiceField(choices=choices_list, label='產品類型')

在form.py中定義表單類ProductForm,類屬性就是表單字段,對應HTML里的每一個控件。
然后在視圖函數中導入form.py所定義的ProductForm類,在index函數中實例化生成對象,再將對象傳遞給模板文件。

#index 的 views.py
from django.shortcuts import render
from .form import *

def index(request):
    product = ProductForm()
    return render(request, 'data_form.html', locals())

最后在模板文件data_form.html中將對象product以HTML的<table>的形式展現在網頁上。

templates 下的 data_form.html
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post">
    <table>
        {{ product.as_table }}
    </table>
    <input type="submit" value="提交">
</form>
</body>
</html>

保存后運行結果如下:


image.png

表單的定義

Django的表單功能主要是通過定義表單類,再由類的實例化生成HTML的表單元素控件,以減少再模板中的硬編碼。上例中的<table>源代碼如下:

<table>
<tr><th><label for="id_name">名字:</label></th><td><input type="text" name="name" maxlength="20" required id="id_name"></td></tr>
<tr><th><label for="id_weight">重量:</label></th><td><input type="text" name="weight" maxlength="50" required id="id_weight"></td></tr>
<tr><th><label for="id_size">尺寸:</label></th><td><input type="text" name="size" maxlength="50" required id="id_size"></td></tr>
<tr><th><label for="id_type">產品類型:</label></th><td>
<select name="type" id="id_type">
  <option value="1">手機</option>
  <option value="2">平板電腦</option>
  <option value="3">智能穿戴</option>
  <option value="4">通用配件</option>
</select></td></tr>
</table>

通過對比,可以很容易發現HTML源代碼和定義的ProductForm類中的字段和參數的對應關系。
除了上述表單字段外,Django還提供多種內置的表單字段:

字段 說明
BooleanField 復選框,如果字段帶有initial=True, 復選框被勾上
CharField 文本框,參數max_length和min_length
ChoiceField 下拉框,參數choices設置數據內容
TypedChoiceField 與ChoiceField相似,但多出參數coerce和empty_value,分別代表強制轉換數據類型和用于表示空值,默認為空字符串
DateField 文本框,具有驗證日期格式的功能,參數input_formats設置日期格式
EmailField 文本框,驗證輸入數據是否符合郵箱格式,可選參數max_length和min_length
FileField 文件上傳功能,參數max_length和allow_empty_file分別設置最大長度和文件內容是否為空
FilePathField 在特定的目錄選擇并上傳文件,參數path是必需參數,參數recursive、match、allow_files和allow_folders為可選參數
FloatField 驗證數據是否為浮點數
ImageField 驗證文件是否為Pillow庫可識別的圖像格式
IntegerField 驗證數據是否為整型
GenericIPAddressField 驗證數據是否為有效數值
SlugField 驗證數據是否只包含字母、數字、下劃線及連字符
TimeField 驗證數據是否為datetime.time或指定特定時間格式的字符串
URLField 驗證數據是否為有效的URL地址

表單字段除了轉換HTML控件之外,還具有一定的數據格式規范,規范主要由字段類型和字段共同實現。每個不同類型的表單字段都由一些自己特殊的參數,但每個表單字段都具有以下的共同參數:

參數 說明
Required 輸入數據是否為空,默認為True
Widget 設置HTML控件的樣式
Label 生成Label標簽或顯示內容
Initial 設置初始值
help_text 設置幫助提示信息
error_messages 設置錯誤信息,以字典格式表示:{‘required’:'不能為空','invalid':'格式錯誤'}
show_hidden_initial 值為True或False,是否在當前控件后面再加一個 隱藏的且具有默認值的控件(可用于檢驗兩次輸入值是否一致)
Validators 自定義數據驗證規則。以列表格式表示,列表元素為函數名
Lacalize 值為True/False,是否支持本地化,如不同時區顯示相應的時間
Disabled 值為True/False,是否可以編輯
label_suffix Label內容后綴,在Label后添加內容

根據以上參數說明,對form.py的表單ProductForm的字段進行優化:

#form.py
from django import forms
from .models import *
from django.core.exceptions import ValidationError

#自定義數據驗證函數
def weight_validate(value):
    if not str(value).isdigit():
        raise ValidationError('請輸入正確的重量 ')
    

class ProductForm(forms.Form):
    #設置錯誤信息并設置樣式
    name = forms.CharField(max_length=20, label='名字',
                           widget=forms.widgets.TextInput(attrs={'class':'c2'}),
                           error_messages={'required':'名字不能為空'})
    #使用自定義數據驗證函數
    weight = forms.CharField(max_length=50, label='重量',
                             validators=[weight_validate])
    size = forms.CharField(max_length=50, label='尺寸')
    #設置下拉框的值
##    choices_list = [(1,'手機'),(2, '平板')]
    choices_list = [(i+1, v['type_name']) for i,v in enumerate(Type.objects.values('type_name'))]
    #設置CSS樣式
    type = forms.ChoiceField(widget=forms.widgets.Select(attrs={'class':'type','size':3}),
                             choices=choices_list, label='產品類型')

優化的代碼分別使用了參數widget、label、error_messages和validators,這4個參數是實際開發中常用的參數。為了進一步驗證優化后的表單是否正確運行,還要對views.py的視圖函數index代碼進行優化

#views.py 的 index 函數
from django.http import HttpResponse
from django.shortcuts import render
from .form import *

def index(request):
    #GET請求
    if request.method == 'GET':
        product = ProductForm()
        return render(request, 'data_form.html', locals())
    #POST請求
    else:
        product = ProductForm(request.POST)
        if product.is_valid():
            #獲取網頁控件name的數據
            #方法1
##            name = product['name']
            #方法2
            #cleaned_data將控件name的數據進行清洗,轉換成Python數據類型
            name = product.cleaned_data['name']
            return HttpResponse('提交成功')
        else:
            #將錯誤信息輸出,error_msg是將錯誤信息以json格式輸出
            error_msg = product.errors.as_json()
            return render(request, 'data_form.html', locals())

上述代碼,首先判斷了用戶的請求方式,index函數對GET和POST請求做了不同的響應處理。

  • 用戶首次訪問url地址時,等于向項目發送一個GET請求,函數index將表單ProductForm實例化并傳遞給模板,由模板引擎生成HTML表單返回給用戶。
  • 當用戶在表單中輸入相關信息并提交時,等于向項目發送一個POST請求,函數index首先獲取表單數據對象product,然后由is_valid()方法對數據對象product進行數據驗證。
  • 如果驗證成功,可以使用product['name']或prodcut.cleaned_data['name']方法來獲取用戶在某個控件上的輸入值,實現表單和模型的信息交互。
    下面來驗證一下是否能正常工作,在姓名欄輸入了空格


    image.png

    點擊提交按鈕后,


    image.png

    驗證正常工作了。
    在上述例子中,模板data_form.html的表單是使用HTML的<table>標簽展現在網頁上,除此之外,表單還可以用其他HTML標簽展現,只需將模板data_form.html的對象product使用以下方法即可生成其他HTML標簽:

將表單生成HTML的 ul 標簽
{{ product.as_ul }}
將表單生成HTML的 p 標簽
{{ product.as_p }}
生成單個HTML元素控件
{{ product.type }}
獲取表單字段的參數 label 屬性值
{{ product.type.label }}

模型與表單

Django的表單分兩種:基礎表單django.forms.Form和數據表單django.forms.ModelForm。數據表單是將模型的字段轉換成表單的字段,再從表單的字段生成HTML的元素控件。Django通過ModelForm表單功能模塊實現了表單數據與模型數據之間的交互開發。
首先再form.py中定義表單ProductModelForm。該類可分為三大部分:添加模型外的表單字段、模型與表單設置和自定義表單字段的數據清洗。

from django import forms
from .models import *
from django.core.exceptions import ValidationError

#數據庫表單
class ProductModelForm(forms.ModelForm):
    #步驟1:添加模型外的表單字段
    productId = forms.CharField(max_length=20, label='產品序號')

    #步驟2:模型與表單設置
    class Meta:
        #綁定模型,必選
        model = Product
        #設置轉換字段,必選,屬性值為'__all__'時全部轉換
        #fields = '__all__'
        fields = ['name','weight','size','type']
        #禁止模型轉換的字段,可選,若設置了該屬性,fields則可以不設置
        exclude = []
        #設置HTML元素控件的label標簽,可選
        labels = {'name':'產品名稱',
                  'weight':'重量',
                  'size':'尺寸',
                  'type':'產品類型',
            }
        #設置表單字段的CSS樣式,可選
        widgets = {'name':forms.widgets.TextInput(attrs={'class':'c1'}),
            }
        #定義字段的類型,可選,默認時自動轉換的
        field_classes = {
            'name': forms.CharField,
            }
        #設置提示信息
        help_texts = {
            'name':'',
            }
        #自定義錯誤信息
        error_messages = {
            #設置全部錯誤信息
            '__all__':{'required':'請輸入內容',
                       'invalid':'請檢查輸入內容'},
            #設置某個字段的錯誤信息
            'weight':{'required':'請輸入重量數值',
                      'invalid':'請檢查數值是否正確'},
            }

    #步驟3: 自定義表單字段的數據清洗
    def clean_weight(self):
        #獲取字段weight的值
        data = self.cleaned_data['weight']
        return data + 'g'

模型字段轉換成表單字段主要在類Meta中實現,由類Meta的屬性實現兩者之間的轉換,其屬性說明如下:

屬性 說明
model 必需屬性,用于綁定Model對象
fields 必需屬性,設置模型內哪些字段轉換成表單字段。屬性值為"all"時表示整個模型的字段,若設置一個或多個,使用列表或元組表示,列表或元組里的元素是模型的字段名
exclude 可選屬性,與fields相反,表示禁止模型哪些字段轉換成表單字段。若設置了該屬性,則屬性fields可以不用設置
labels 可選屬性,設置表單字段里的參數label。屬性值以字典表示,字典的鍵是模型的字段
widgets 可選屬性,設置表單字段里的參數widget
field_classes 可選,將模型的字段類型重新定義為表單字段類型,默認模型字段類型會自動轉換為表單字段類型
help_texts 可選,設置表單字段里的參數help_text
error_messages 可選,設置表單字段里的參數error_messages

需要注意的是,一些較為特殊的模型字段在轉換表單時會有不同的處理方式。例如模型字段的類型為AutoField,該字段在表單中不存在對應的表單字段;模型字段類型為ForeignKey和ManyToManyField,在表單中對應的表單字段為ModelChoiceField和ModelMultipleChoiceField。
在自定義數據清洗函數時,必須以“clean_字段名”的格式作為函數名,而且函數必須有return返回值。如果在函數中設置了ValidationError異常拋出,那么該函數可視為帶有數據驗證的清洗函數。
通過定義表單類ProductModelForm將模型Product與表單相互結合起來后,還要通過視圖函數來使用和展現表單,繼續沿用前面的模板data_form.html,在項目的urls.py和views.py中分別定義新的URL地址和視圖函數:

#urls.py 
path('<int:id>.html', views.model_index),

#views.py 的視圖函數 model_index
def model_index(request, id):
    if request.method == 'GET':
        instance = Product.objects.filter(id=id)
        #判斷數據是否存在
        if instance:#如果存在,將數據傳遞給參數instance
            product = ProductModelForm(instance=instance[0])
        else:#如果不存在,為name字段設置一個默認值
            product = ProductModelForm(initial={'name':'iphone XS'})
        return render(request, 'data_form.html',locals())
    else:
        product = ProductModelForm(request.POST)
        if product.is_valid():
            #獲取并清洗weight的數據
            weight = product.cleaned_data['weight']
            #數據保存方法1:直接保存到數據庫
##            product.save()
            #數據保存方法2:save方法設置commit=False,將生成數據庫對象product_db,
            #然后對該對象的屬性值修改并保存
            product_db = product.save(commit=False)
            product_db.name = '我的 iphone'
            product_db.save()
            #數據保存方法3:save_m2m()保存ManyTOMany的數據模型
##            product.save_m2m()
            return HttpResponse('提交成功!weight清洗后的數據是:'+weight)
        else:
            #將錯誤信息以json格式輸出
            error_msg = product.errors.as_json()
            print(error_msg)
            return render(request, 'data_form.html', locals())

函數model_index的處理邏輯和上面的index函數大致相同:

  • 首先判斷用戶的請求方式,不同的請求方式做不同的處理。
  • 若是GET請求,函數根據URL傳遞的變量id來查找模型Product的數據,如果數據存在,以參數的形式傳遞給表單ProductModelForm的參數instance,在生成網頁時,模型數據會填充到對應的元素控件上,如下圖:
    image.png
  • 若時POST請求,函數首先對表單數據進行驗證,如果驗證失敗,返回失敗信息;驗證成功,則使用cleaned_data方法對字段weight進行清洗,最后將表單數據保存到數據庫。
    在以上代碼中,還存在一點小問題,比如產品類型的下拉框數據是模型Type對象,如果要將模型Type的字段type_name的數據在下拉框中展示,可以通過定義模型或定義表單兩方面解決:
定義模型是在定義模型Type時,設置該模型的返回值。
#models.py
class Type(models.Model):
    id = models.AutoField(primary_key=True)
    type_name = models.CharField(max_length=20)
    #設置返回值,若不設置,則默認返回Type對象
    def __str__(self):
        return self.type_name

再運行一次剛才的網址,可以看到產品類型已經變成了名稱。

image.png

如果存在多個下拉框,而且每個下拉框的數據分別取同一個模型的不同字段,那么可以在定義表單類的時候重寫初始化函數__init__()

#forms.py 
class ProductModelForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(ProductModelForm, self).__init__(*args, **kwargs)
        #設置下拉框的數據
        type_obj = Type.objects.values('type_name')
        choices_list = [(i+1, v['type_name']) for i,v in enumerate(Type.objects.values('type_name'))]
        self.fields['type'].choices = choices_list
        #初始化字段name
        self.fields['name'].initial = '我的手機'
表單初始化的方法有以下幾種:
  • 在視圖函數中對表單實例化時,設置實例化對象的參數initial。如product = ProductModelForm(initial={'name':'iphone XS'}),參數值以字典格式表示,字典鍵為表單的字段名,該方法適用于所有表單類。
  • 表單類中進行實例化時,如果初始化的數據是一個模型對象的數據,可設置參數instance,如product = ProductModelForm(instance=instance[0]),該方法只適用于ModelForm.
  • 定義表單字段時,對表單字段設置初始化參數initial,此方法不適用于ModelForm,如name = forms.CharField(initial='我的手機')。
  • 重寫表單類的初始化函數 __init__,適用于所有表單類,如在初始化函數中設置slef.fields['name'].initial='我的手機'.

數據庫的保存實際上只有save()和save_m2m()方法實現。save()只適合將數據保存在非多對多關系的數據表,而save_m2m只適合將數據保存在多對多關系的數據表。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容