第三十篇,Django表單與模型

什麼是表單:
表單是蒐集用戶數據信息的各種表單元素的集合,作用是實現網頁上的數據交互,用戶在網站輸入信息,然後提交到網站服務器端進行處理(如數據錄入和用戶登錄、註冊等)。
用戶表單是web開發的一項基本功能,Django的表單功能有Form類實現,主要分爲兩種:django.forms.Form和django.forms.ModelForm。前者是一個基礎的表單功能,後者是在前者的基礎上結合模型所生成的數據表單。

表單

傳統的表單生成方式是在模板文件總編寫HTML代碼實現,在HTML語言中,表單由標籤實現。表單生成方式如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>表單</title>
</head>
<body>
<form>
 First name:<br>
<input type="text" name="firstname">
<br>
 Last name:<br>
<input type="text" name="lastname">
</form> 
</body>
</html>

瀏覽器顯示:
在這裏插入圖片描述
這裏我就要強推w3school,這個網址學前端必備啊,這裏不多說感興趣可以自己去看,也有python都有案例的。
一個完整的表單主要有4個組成部分:提交地址、請求方式、元素控件和提交按鈕。其說明如下:
1、提交地址用於設置用戶提交的表單數據應有那個URL接收和處理,由控件的屬性action決定。當用戶向服務器提交數據時,若屬性action爲空,則提交的數據應有當前的URL來接收和處理,否則網頁會跳轉到屬性action所指向的URL地址。
2、請求方式用於設置表單的提交方式,通常是GET請求或POST請求,由控件的屬性method決定。
3、元素控件是供用戶輸入數據信息的輸入框。由HTML的控件實現,其控件屬性type用於設置輸入框的類型,常用的輸入框類型有文本框、下拉框和複選框等。
4、提交按鈕供用戶數據到服務器,該按鈕也是由HTML的控件實現的。但該按鈕具有一定的特殊性,因此不歸納到元素控件的範圍內。
在模板中直接寫表單文件是一個最簡單最直接的方法,如果表單元素較多,會在無形之中增加模板的代碼量,這樣對日後的維護和更新造成極大的不便。爲了簡化表單的實現過程和提高表單的靈活性,Django提供了完善的表單功能。
在MyDjango的index中添加了空白文件form.py,該文件主要用於編寫表單的實現功能,文件夾可自行命名;同時在文件夾templates中添加模板文件data_form.html,該文件用於將表單的數據顯示到網頁上。最後在文件form.py、views.py和data_form.html中分別添加一下代碼

在這裏插入圖片描述

index(App)下的form.py,沒有這個文件就新建

定義ProductForm表單對象
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 = [(i+1,v['type_name']) for i,v in enumerate(Type.objects.values('type_name'))]
    type = forms.ChoiceField(choices=choices_list, label='產品類型')

index(App)下的view.py

將表單ProductForm實例化並將其傳遞到模板中生成網頁內容
from .form import *
def index(request):
    product = ProductForm()
    return render(request, 'data_form.html', locals())

index下的templates下的html文件data_form.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% if product.errors %}
    <p>
        數據出錯啦,錯誤信息:{{ product.errors }}
    </p>
{% else %}
    <form action="" method="post">
        {% csrf_token %}
        <table>
            {{ product.as_table }}
        </table>
        <input type="submit" value="提交">
    </form>
{% endif %}
</body>
</html>

上述代碼演示了Django內置表單功能的使用方法,主要由form.py、views.py和data_form.html共同實現,說明如下:
1、在form.py中定義表單ProductForm,表單以類的形式表示。在表單中定義了不同類型的類屬性,這些屬性在表單中稱爲表單字段,每個表單字段代表HTML裏的一個控件,這是表單的基本組成單位。
2、在views.py中導入form.py所定義的ProductForm類,在視圖函數index中對ProductForm實例化生成對象product,再將對象product傳遞給模板data_form.html。
3、模板data_form.html將對象product以HTML的<table的形式展現在網頁上。

表單的定義

Django的表單功能主要是通過定義表單類,再由類的實例化生成HTML的表單元素控件,這樣可以在模板中減少HTML的硬編碼。每個HTML的表單元素控件由表單字段來決定,代碼如下:

表單類ProductForm的表單字段name
name = forms.CharField(max_length=20, label='名字',)

表單字段name所生成的HTML元素控件
<tr>
    <th><label for="id_name">名字:</label></th>
    <td><input type="text" name="name" id="id_name" required maxlength="20"></td>
</tr>

從表單字段轉換HTML元素控件可以發現:
1、字段name的參數label轉換成HTML的標籤

字段 說明
BooleanField 複選框,如果字段帶有required=True,複選框被勾選上
CharField 文本框,參數max_length和min_length分別設置輸入長度
ChoiceField 下拉框,參數choices設置數據內容
TypedChoiceField 與ChoiceField相似,但比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庫可識別的圖像格式
ImtegerField 驗證數據是否爲整型
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 自定義數據驗證規則。以列表格式表示,列表元素爲函數命名
Localize 值爲True/False,是否支持本地和,如不同時區顯示相應的時間
Disabled 值爲True/False,是否可以編輯
label_suffix Label內容後綴,在Label後添加內容

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

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': 'c1'}),
                           error_messages={'required': '名字不能爲空'},)
    #使用自定義數據驗證函數
    weight = forms.CharField(max_length=50, 
                             label='重量',
                             validators=[weight_validate])
    size = forms.CharField(max_length=50, label='尺寸')
    #獲取數據庫數據
    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':'4'}),
                             choices=choices_list, label='產品類型')

優化的代碼分別使用了參數widget、label、error_messages和validators,這4個參數是實際開發中常用的參數,參數說明如下:
1、參數widget是一個forms.widgets對象,其作用是設置表單字段的CSS樣式。widget的對象類型應與表單字段類型相符合,如果字段類型爲CharField和widget的對象類型爲forms.widgets.TextInput,這兩者的含義與作用是一致的,都代表文本輸入框;如果字段類型爲ChoiceField和widget的對象類型爲forms.widgets.TextInput,前者是下拉選擇框,後者是文本輸入框,那麼在網頁上就會優先顯示爲文本輸入框。
2、參數label會轉換成HTML的標籤

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的數據
            #方法一
            name = product['name']
            #方法二
            #cleaned_data將控件name的數據進行清洗,轉換成Python數據類型
            cname = product.cleaned_data['name']
            return HttpResponse('提交成功')
        else:
            #將錯誤信息輸出,error_msg是將錯誤信息以json格式輸出
            error_msg = product.errors.as_json()
            print(error_msg)
            return render(request, 'data_form.html', locals())

1、首先判斷用戶的請求方式,不同的請求方式執行不同的程序處理。函數index分別對GET和POST請求做了不同的響應處理。
2、用戶在瀏覽器中訪問http://127.0.0.1:8000/index/,等同於向MyDjango發送一個GET請求,函數index將表單ProductForm實例化並傳遞給模板,由模板引擎生成HTML表單返回給用戶。
3、當用戶在網頁上輸入相關信息後單擊"提交"按鈕,等同於向MyDjango發送一個POST請求,函數index首先獲取表單數據對象product,然後由is_valid()對數據對象product進行數據驗證。
4、如果驗證成功,可以使用product[‘name’]或product.cleaned_data[‘name’]方法來獲取用戶在某個控件上的輸入值。只要將獲取到的輸入值和模型相互使用,就可以實現表單與模型的信息交互。
5、如果驗證失敗,使用errors.as_json()方法獲取驗證失敗的信息,然後將驗證失敗的信息通過模板返回給用戶。
模板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的元素控件,這是日常開發中常用的表單之一。

#forms.py裏面的數據庫表單
class ProductModelForm(forms.ModelForm):
    #添加模型外的表單字段
    productId = forms.CharField(max_length=20, label='產品序號')
    #模型與表單設置
    class Meta:
        #綁定模型
        model = Product
        #fields屬性用於設置轉換字段,’__all__'是將全部模型字段轉換成表單字段
        #fields = '__all__'
        fields = ['name','weight','size','type']
        #exclude用於禁止模型字段轉換表單字段
        exclude = []
        #labels設置HTML元素控件的label標籤
        labels = {
            'name': '產品名稱',
            'weight': '重量',
            'size': '尺寸',
            'type': '產品類型'
        }
        #定義widgets,設置表單字段的CSS樣式
        widgets = {
            'name': forms.widgets.TextInput(attrs={'class': 'cl'}),
        }
        #定義字段的類型,一般情況下模型的字段會自動轉換成表單字段
        field_classes = {
            'name': forms.CharField
        }
        #幫助提示信息
        help_texts = {
            'name': ''
        }
        #自定義錯誤信息
        error_messages = {
            #__all__設置全部錯誤信息
            '__all__': {'required': '請輸入內容',
                        'invalid': '請檢查輸入內容'},
            #設置某個字段的錯誤信息
            'weight': {'required': '請輸入重複數值',
                       'invalid': '請檢查數值是否正確'}
        }
#自定義表單字段weight的數據清洗
def clean_weight(self):
    #獲取字段weight的值
    data = self.cleaned_data['weight']
    return data+'g'

上述代碼中,表單類ProductModelForm可分爲三大部分:添加模型外的表單字段、模型與表單設置和自定義表單字段weight的數據清洗函數,說明如下:
1、添加模型外的表單字段是在模型已有的字段下添加額外的表單字段。
2、模型與表單設置是將模型的字段轉換成表單字段,由類Meta的屬性實現兩者的字段轉換。
3、自定義表單字段weight的數據清洗函數只只是用於字段weight的數據清洗。
模型字段轉換成表單字段主要在類Meta中實現。

屬性 說明
Model 必需屬性,用於綁定Model對象
Fields 必須屬性,設置模型內那些字段轉換成表單字段。屬性值爲’all’代表整個模型的字段,若設置一個或多個,使用列表或元組的數據格式表示,列表或元組裏的元素是模型的字段名
Exclude 可選屬性,與fields相反,禁止模型內那些字段轉換成表單字段。屬性值以列表或元組表示,若設置了該屬性,則屬性fields可以不用設置
Labels 可選屬性,設置表單字段裏的參數label。屬性值以字典表示,字典裏的鍵是模型的字段
Widgets 可選屬性,設置表單字段裏的參數widget
field_classes 可選屬性,將模型的字段類型重新定義爲表單字段類型,默認情況下,模型字段類型會字段轉換爲表單字段類型
help_texts 可選屬性,設置表單字段裏的參數
error_messages 可選屬性,設置表單字段裏的參數error_messages

一些較爲特殊的模型字段在轉換表單時會有不同的處理方式。例如模型字段的類型爲AutoField,該字段在表單中不存在對應的表單字段;模型字段類型爲ForeignKey和ManyToManyField,在表單中對應的表單字段爲ModelChoiceField和ModelMultipleChoiceField。
自定義表單字段weight的數據清洗函數是在視圖函數中使用cleaned_data方法時,首先判斷當前清洗的表單字段是否已定義數據清洗函數。例如上述的clean_weight函數,在清洗表單字段weight的數據時會自動執行該自定義函數。在自定義數據清洗函數時,必須以"clean_字段名"的格式作爲函數名,而且函數必須有return返回值。如果在函數中設置ValidationError了異常拋出,那麼該函數可視爲帶有數據驗證的清洗函數。

數據表單的使用

現在使用前面的data_form.html,在MyDjango的url.py和views.py中分別定義新的URL地址和視圖函數:
url.py添加代碼如下:

path('<int:id>.html', views.models_index),

views.py添加代碼如下:

def models_index(request, id):
    if request.method == 'GET':
        instance = Product.objects.filter(id=id)
        # 判斷數據是否存在
        if instance:
            product = ProductModelForm(instance=instance[0])
        else:
            product = ProductModelForm()
        return render(request, 'data_form.html', locals())
    else:
        product = ProductModelForm(request.POST)
        if product.is_valid():
            #獲取weight的數據,並通過clean_weight進行清洗,轉換成Python數據類型
            weight = product.cleaned_data['weight']
            #數據保存方法一
            #直接將數據保存到數據庫
            #product.save()
            #save方法設置commit=False,將生成數據庫對象product_db,然後對該對象的屬性值修改並保存
            product_db = product.save(commit=False)
            product_db.name = '我的iPhone'
            product_db.save()
            #數據保存方法三
            #save_m2m()方法用於保存ManyToMany的數據模型
            #product.save_m2m()
            return HttpResponse('提交成功!weight清洗後的數據爲:'+weight)
        else:
            #將錯誤信息輸出,error_msg是將錯誤信息以json格式輸出
            error_msg = product.errors.as_json()
            print(error_msg)
            return render(request, 'data_form.html', locals())

1、首先判斷用戶的請求方式,不同的請求方式執行不同的處理程序。代碼分別對GET和POST請求做了不同的響應處理。
2、若當前請求爲GET請求,函數根據URL傳遞的變量id來查找模型Product的數據,如果數據存在,模型的數據以參數的形式傳遞給表單ProductModelForm的參數instance,在生成網頁時,模型數據會填充到對應的元素控件上。
3、若當前請求爲POST請求,函數首先對錶單數據進行驗證,若驗證失敗,則返回失敗信息;若驗證成功,則使用cleaned_data方法對字段weight進行清洗,字段weight清洗由自定義函數clean_weight完成,最後將表單數據保存到數據庫,保存數據有三種方式,具體說明可看代碼註釋,運行結果如下:
在這裏插入圖片描述
這個是我數據庫裏的表:
我們試着提交一下,輸入產品序號,
在這裏插入圖片描述
在這裏插入圖片描述
表單的初始化有4種方法,每一種方法都有自己的使用方法:
(1) 在視圖函數中對錶單類進行實例化,可以設置實例化對象的參數initial。例如ProductModelForm(initital={‘name’:value}),參數值以字典的格式表示,字典的鍵爲表單的字段名,這種方法適用於所有表單類。
(2) 在表單類中進行實例化,如果初始化的數據是一個模型對象的數據,可以設置參數instanse,這種方法只是用於ModelForm,如ProductModelForm(instanse=instanse)。
(3) 定義表單字段時,可以對錶單字段設置初始化參數initial,此方法不適用於ModelForm,如name=forms.CharField(initial=value)。
(4) 重寫表單類的初始化函數__init__(),適用於所有表單類,如在初始化函數__init__()中設置self.fields[‘name’].initial=value。
下拉框的數據是一個模型Type對象,而下拉框是由模型Product的外鍵type所生成的,外鍵type指向模型Type。因此,要解決下拉框的數據問題,可以從定義模型或者定義表單這兩方面解決。
定義模型是在定義模型Type時,設置該模型的返回值。當有外鍵指向模型Type時,模型Type會將返回值返回給外鍵。在模型中通過重寫__str__函數可以設置模型的返回值:

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

如果存在多個下拉框,而且每個下拉框的數據分別取不同一個模型的不同字段,那麼重寫__str__函數可能不太可行。遇到這種情況,可以在定義表單類的時候重寫初始化函數__init__():

class ProductModelForm(forms.ModelForm):
    #方法四:重寫ProductModelForm類的初始化函數__init__
    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 = '我的手機'

實質上數據保存只有save()和save_m2m()方法實現,在上述代碼中所演示的三種保存方式,前兩者是save()的參數commit的不同而導致保存方式有所不同。如果參數commit爲True,直接將表單數據保存到數據庫;如果參數commit爲False,這時將生成一個數據庫對象,然後可以對該對象進行增刪改查等數據操作,再將修改後的數據保存到數據庫中。注意的是save()只適合於將數據保存在非多對多數據關係的數據表,而save_m2m()只適合將數據保存在多對多數據關係的數據表。
本篇博客參考Django的建站基礎表單與模型

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章