Django組件之Form表單


一、Django中的Form表單介紹

我們之前在HTML頁面中利用form表單向后端提交數據時,都會寫一些獲取用戶輸入的標簽並且用form標簽把它們包起來。

與此同時我們在好多場景下都需要對用戶的輸入做校驗,比如校驗用戶是否輸入,輸入的長度和格式等正不正確。如果用戶輸入的內容有錯誤就需要在頁面上相應的位置顯示對應的錯誤信息.。

Django form組件就實現了上面所述的功能:
    生成頁面可用的HTML標簽
    對用戶提交的數據進行校驗
    保留上次輸入內容

 

二、普通方式的form表單注冊

def register(request):
    error_msg = ""
    if request.method == "POST":
        username = request.POST.get("username")
        pwd = request.POST.get("pwd")
        # 對注冊信息做校驗
        if len(username) < 6:
            # 用戶長度小於6位
            error_msg = "用戶名長度不能小於6位"
        else:
            # 將用戶名和密碼存到數據庫
            UserInfo.objects.create(username='username', password='pwd')
            return redirect("/login/")
    return render(request, "register.html", {"error_msg": error_msg})
1.views.py
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta http-equiv="content-type" charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>注冊頁面</title>
</head>
<body>
<form action="/register/" method="post">
    {% csrf_token %}
    <p>
        <label for="t1">用戶名</label>
        <input type="text" name="username" id="t1">
    </p>
    <p>
        <label for="p1">密碼</label>
        <input type="password" name="pwd" id="p1">
    </p>
    <p>
        <input type="submit" value="注冊">
        <p style="color: red">{{ error_msg }}</p>
    </p>
</form>
</body>
</html>
2.regirest.html

 

三、使用form組件實現注冊功能

數據庫
class UserInfo(models.Model):
    username = models.CharField(max_length=12)
    password = models.CharField(max_length=20)


1、views.py
1.先定義好一個RegForm類
from django import forms

# 按照Django form組件的要求自己寫一個類
class RegForm(forms.Form):
    name = forms.CharField(max_length=12, label="用戶名")
    pwd = forms.CharField(min_length=6, max_length=18, label="密碼")


2.再寫一個視圖函數
# 使用form組件實現注冊方式
def register(request):
    form_obj = RegForm()
    if request.method == "POST":
        # 實例化form對象的時候,把post提交過來的數據直接傳進去
        form_obj = RegForm(request.POST)
        # 調用form_obj校驗數據的方法is_valid
        if form_obj.is_valid():
            username = form_obj.cleaned_data.get('name')  # cleaned_data會自動把form提交的數據提取出來形成一個字典
            pwd = form_obj.cleaned_data.get('pwd')
            UserInfo.objects.create(username=username, password=pwd)
            return redirect("/login/")
        else:
            # 如果數據有問題
            return render(request, "register.html", {'form_obj': form_obj})
    return render(request, "register.html", {"form_obj": form_obj})
View Code
 
         

 

2、regirest.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta http-equiv="content-type" charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>注冊頁面</title>
<body>
    <form action="/register/" method="post" novalidate autocomplete="off">
        {% csrf_token %}
        <div>
            <label for="{{ form_obj.name.id_for_label }}">{{ form_obj.name.label }}</label>
            {{ form_obj.name }} <span>{{ form_obj.name.errors.0 }}</span>
        </div>
        <div>
            <label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label>
            {{ form_obj.pwd }} <span>{{ form_obj.pwd.errors.0 }}</span>
        </div>
        <div>
            <input type="submit" class="btn btn-success" value="注冊">
        </div>
    </form>
</body>
</html>
View Code
 
         
3、分析
form的功能:
前端頁面是使用Django模板語言和form類的對象生成的                     -->生成HTML標簽功能
當用戶名和密碼輸入為空或輸錯form組件會默認為我們設置錯誤信息          -->用戶提交校驗功能
當用戶輸錯之后仍保留着上次輸入的內容在input框                        -->保留上次輸入內容

簡析后端:
form_obj = RegForm(request.POST)    把POST提交的數據存到RegForm進行實例化
form_obj.is_valid()                 自動幫我們去校驗form_obj接收到的數據
form_obj.cleaned_data               如果校驗通過,會把信息存到cleaned_data,在后端cleaned_data其實是一個字典
                                    取值:form_obj.cleaned_data['name']或者form_obj.cleaned_data.get('name')
form_obj.errors                     如果校驗不通過,會把錯誤信息存到errors,在后端errors其實是一個字典
                                    取值:form_obj.errors['name']或者form_obj.errors.get('name')

簡析_HTML:
name字段
{{ form_obj.name }}                 自動生成一個input框,屬性就是我們在views.py里面設置的form類的屬性
{{ form_obj.name.id_for_label }}    關聯自動生成的input框
{{ form_obj.name.label }}           form對象的name的label屬性的值
{{ form_obj.name.errors.0 }}        錯誤信息



pwd字段
{{ form_obj.pwd }}
{{ form_obj.pwd.id_for_label }}
{{ form_obj.pwd.label }}   
{{ form_obj.pwd.errors.0 }} 




也可以使用循環
{% for field in form_obj %}
    {{ field }}
    {{ field.id_for_label }}
    {{ field.label }}
    {{ field.errors.0 }}
{% endfor %}

 

四、Form常用字段與插件

創建Form類時,主要涉及到 【字段】 和 【插件】,字段用於對用戶請求數據的驗證,插件用於自動生成HTML;


1、required
設置這個字段必須要填,默認也是True必填的

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用戶名",
        required=False  # 設置成不是必須要填
    )
    pwd = forms.CharField(min_length=6, label="密碼")


2、initial
設置input框里面的初始值。

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用戶名",
        initial="張三"  # 設置默認值
    )
    pwd = forms.CharField(min_length=6, label="密碼")


3、error_messages
1.局部重寫錯誤信息,哪個字段需要重寫錯誤信息就在哪個字段設置

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.設置全局錯誤信息(把默認的錯誤信息由英文改成中文)
在settings.py里面設置
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-hans'  # 把語言改成中文(漢字)

# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'  # 時區改成亞洲上海


4、widget
設置input框的type類型,默認類型是text,密碼框的type應該設置為password
還可以設置input框的屬性,比如class的樣式

class LoginForm(forms.Form):
    ...
    pwd = forms.CharField(
        min_length=6,
        label="密碼",
        # 把密碼框設置為password類型,並指定class為c1和c2的樣式類
        # 當出現出錯時,其他類型的input框默認是保留你寫的內容,只有密碼框不會保存
        # 想要密碼框錯誤時也保留內容,就設置render_value=True
        widget=forms.widgets.PasswordInput(attrs={'class': 'c1 c2'}, render_value=True)  
    )


5、選擇字段:ChoiceField和MultipleChoiceField
注意:在選擇字段中
     單選字段使用:forms.fields.ChoiceField 也可以寫成:forms.ChoiceField
     多選字段使用:forms.fields.MultipleChoiceField 也可以寫成:forms.MultipleChoiceField


1.單選框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()
    )


2.單選下拉框select
class LoginForm(forms.Form):
    ...
    hobby = forms.fields.ChoiceField(
        choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ),
        label="愛好",
        initial=3,
        widget=forms.widgets.Select()
    )


3.單選checkbox
class LoginForm(forms.Form):
    ...
    keep = forms.fields.ChoiceField(
        choices=((True, ""), (False, ""))
        label="是否記住密碼",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )


4.多選select
class LoginForm(forms.Form):
    ...
    hobby = forms.fields.MultipleChoiceField(
        choices=((1, "籃球"), (2, "足球"), (3, "雙色球"), ),
        label="愛好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )


5.多選checkbox
class LoginForm(forms.Form):
    ...
    hobby = forms.fields.MultipleChoiceField(
        choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),),
        label="愛好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )

 

五、choice字段注意事項

在使用選擇標簽時,需要注意choices的選項可以配置從數據庫中獲取,但是由於Form是懶加載模式的,獲取的值無法實時更新,
就是說,你創建Form的時候從數據庫加載到的值是什么,它就只顯示那些值,即使數據庫更新了,它也不會自動更新,如果需要實現實時更新,
需要重寫構造方法從而實現choice實時更新。

1、方式一
重寫Form類的__init__,使其每次初始化都去查一次數據庫
class RegForm(forms.Form):
    hobby = forms.fields.MultipleChoiceField(
        choices=Hobby.objects.all().values_list('id', 'name'),
        label='愛好',
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )
    def __init__(self):
        super(RegForm, self).__init__()
        self.fields['hobby'].choices=Hobby.objects.all().values_list('id', 'name')



2、方式二
使用From里面的models模塊,這個模塊是依賴於APP里面的models的(注意:兩個models不是同一個),得到的是一個個對象,
所有還要要操作APP里面的models,使其顯示出具體的值。
1.APP下的models.py
from django.db import models


class Hobby(models.Model):
    name = models.CharField(max_length=12)

    def __str__(self):
        return self.name


2.views.py
from django import forms


class RegForm(forms.Form):
    hobby2 = forms.models.ModelMultipleChoiceField(
        queryset=Hobby.objects.all(),
        label='第二愛好',
        initial=[1, 2],
        widget=forms.widgets.CheckboxSelectMultiple()
    )

 

六、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類型
View Code

 

七、Form校驗規則

1、使用RegexValidator驗證器(正則表達式)
from django.core.validators import RegexValidator  # 導入使用正則表達的模塊

class RegForm(forms.Form):

    phone = forms.CharField(
        label='手機號',
        widget=forms.widgets.TextInput(attrs={'class': 'form-control'}),
        validators=[RegexValidator(r'^1[3-9]\d{9}$', '手機號碼不正確')]
    )

validators=一個可迭代對象
可迭代對象里面調用RegexValidator方法
RegexValidator方法有兩個參數,一個是用於校驗的正則表達式,另一個是匹配失敗時的錯誤提示



2、自定義校驗規則
from django.core.exceptions import ValidationError

# 專門屏蔽'JPM'的校驗規則
def check_jpm(value):
    # value就是用戶輸入的值
    if 'JPM' in value:
        raise ValidationError('包含敏感詞匯')

class RegForm(forms.Form):
    name = forms.CharField(
        min_length=2,
        max_length=12,
        label="用戶名",
        widget=forms.widgets.TextInput(),
        validators=[check_jpm]  # 直接調用自定義的函數
    )


3、使用RegexField字段
class RegForm(forms.Form):
    phone = forms.RegexField(
        label='手機號碼',
        regex=r'^1[3-9]\d{9}$',
        widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
    )

 

八、鈎子(Hook)函數

1、介紹
局部鈎子函數:
    在Form類里面實現以clean_開頭,字段結尾的函數,例如clean_name,
    在實例化對象調用is_valid()方法進行校驗的時候,會自動執行clean_方法。 

    局部鈎子函數:在實例化對象的時候,數據已經存在cleaned_data里面了
    但是局部鈎子函數是循環每一個具體的字段(例如:name, pwd等),所以在局部鈎子函數只能取到它本身的值
    如果拋出錯誤,則把錯誤信息添加進errors字典里面
    如果沒有拋出錯誤,則把這個字段對應的cleaned_data的值修改成return返回的值(一般直接返回原本的值,不做修改)


全局鈎子函數:
    在Form類里面實現以clean函數,
    在實例化對象調用is_valid()方法進行校驗的時候,會自動執行clean方法。

    全局鈎子:可以取到cleaned_data所有的數據
    如果沒有拋出錯誤,把返回值直接賦值給cleaned_data這個變量
    如果拋出錯誤,需要手動調用add_error()方法,明確指定給哪個字段增加錯誤信息
    注意:局部鈎子函數沒有拋出錯誤是修改cleaned_data字典里面的某個值
    而全局鈎子沒有拋出錯誤是直接修改cleaned_data這個變量,可以把cleaned_data改成列表、數字等
    一般沒有拋出錯誤就直接把原來的cleaned_data字典重新賦值給cleaned_data變量

2、示例
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError


class RegForm(forms.Form):
    name = forms.CharField(
        min_length=2,
        max_length=12,
        label="用戶名",
        # 利用error_messages自定義錯誤信息
        error_messages={'min_length': '用戶名至少2位', 'max_length': '用戶名最多12位'},
        widget=forms.widgets.TextInput(attrs={'class': 'form-control'}),
    )
    pwd = forms.CharField(
        min_length=6,
        max_length=18,
        label="密碼",
        # 把密碼框設置為password類型,並指定class為form-control樣式類,render_value=True設置 密碼錯誤時,把密碼回寫到輸入框里(即密碼錯誤時也保存密碼)
        widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
    )
    re_pwd = forms.CharField(
        min_length=6,
        max_length=18,
        label="確認密碼",
        required=False,  # 設置成不是必須要填的字段
        widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
    )
    phone = forms.CharField(
        label='手機號',
        widget=forms.widgets.TextInput(attrs={'class': 'form-control'}),
        validators=[RegexValidator(r'^1[3-9]\d{9}$', '手機號碼不正確')]
    )

    # 局部鈎子函數:在實例化對象的時候,數據已經存在cleaned_data里面了
    # 但是局部鈎子函數是循環每一個具體的字段(例如:name, pwd等),所以在局部鈎子函數只能取到它本身的值
    # 如果拋出錯誤,則把錯誤信息添加進errors字典里面
    # 如果沒有拋出錯誤,則把這個字段對應的cleaned_data的值修改成return返回的值(一般直接返回原本的值,不做修改)
    def clean_name(self):
        # 局部鈎子只能cleaned_data.get('自己的值')
        # cleaned_data.get('phone') 取不到
        value = self.cleaned_data.get('name')
        if 'JPM' in value:
            raise ValidationError('太sq了,不能填')
        else:
            return value

    # 全局鈎子:可以取到cleaned_data所有的數據
    # 如果沒有拋出錯誤,把返回值直接賦值給cleaned_data這個變量
    # 如果拋出錯誤,需要手動調用add_error()方法,明確指定給哪個字段增加錯誤信息
    # 注意:局部鈎子函數沒有拋出錯誤是修改cleaned_data字典里面的某個值
    # 而全局鈎子沒有拋出錯誤是直接修改cleaned_data這個變量,可以把cleaned_data改成列表、數字等
    # 一般沒有拋出錯誤就直接把原來的cleaned_data字典重新賦值給cleaned_data變量
    def clean(self):
        pwd = self.cleaned_data.get('pwd')
        re_pwd = self.cleaned_data.get('re_pwd')
        if pwd == re_pwd:
            return self.cleaned_data
        else:
            self.add_error('re_pwd', '兩次密碼不一致')
            raise ValidationError('兩次密碼不一致')
View Code
 
        

 

九、在Form組件應用Bootstrap樣式

1、可通過重寫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'
            })

    # 重寫方式二:
    def __init__(self, *args, **kwargs):
        super(LoginForm, self).__init__(*args, **kwargs)
        # self.fields是一個大字典,key是字段名,value是字段對象
        for field in self.fields.values():
            field.widget.attrs.update({'class': 'form-control'})

 

十、Form組件使用ajax提交示例

1、register.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>注冊</title>
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7/css/bootstrap.css' %}">
</head>
<body>

<div class="container">
    <div class="row">
        <div class="col-md-4 col-md-offset-4" style="margin-top: 70px">
            <form novalidate id="f1">
                {% csrf_token %}
                {% for field in form_obj %}
                    <div class="form-group">
                        <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                        {{ field }}
                        <span class="help-block"></span>
                    </div>
                {% endfor %}
                <div>
                    <button type="button" id="b1" class="btn btn-success">注冊</button>
                </div>
            </form>
        </div>
    </div>
</div>
<script src="{% static 'jquery.js' %}"></script>
<script>
    $('#b1').click(function () {
        // 自定義一個對象用來存儲表單的提交信息
        var dataObj = {};
        $('input').each(function () {
            dataObj[$(this).attr('name')] = $(this).val();
        });
        console.log(dataObj);
        // 取到input標簽的值
        $.ajax({
            url: '/register/',
            type: 'post',
            data: dataObj,
            success: function (res) {
                console.log(res);
                // 把所有的錯誤提示信息展示到頁面的指定位置
                if (res.code === 0){
                    // 沒錯
                    location.href = res.url;
                }else {
                    $.each(res.error_msg, function (k, v) {
                        // 根據k找到對應的input框,把v中第一個字符串顯示出來
                        $('#id_'+k).next('span').text(v[0]).parent().addClass('has-error');
                    })
                }
            }
        })
    });
    // input標簽獲取焦點之后清除之前的錯誤提示
    $('form input').focus(function () {
        $(this).next('span').text('').parent().removeClass('has-error');
    })
</script>
</body>
</html>
View Code
 
         

 

2、models.py
from django.db import models

# Create your models here.

GENDER_CHOICES = ((0, ''), (1, ''), (2, '保密'))
DEFAULT_GENDER = 2


class UserInfo(models.Model):
    username = models.CharField(max_length=12, unique=True)
    password = models.CharField(max_length=20)
    phone = models.CharField(max_length=11, unique=True)
    email = models.EmailField()
    gender = models.PositiveIntegerField(choices=GENDER_CHOICES, default=DEFAULT_GENDER)


    def __str__(self):
        return self.username
View Code
 
         

3、Form組件單獨寫一個py文件 forms.py
from django import forms
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from app01.models import GENDER_CHOICES, DEFAULT_GENDER, UserInfo


class RegForm(forms.Form):
    username = forms.CharField(
        max_length=12,
        min_length=2,
        label='用戶名',
        widget=forms.widgets.TextInput()
    )
    password = forms.CharField(
        max_length=20,
        min_length=6,
        label='密碼',
        widget=forms.widgets.PasswordInput()
    )
    re_password = forms.CharField(
        max_length=20,
        min_length=6,
        label='確認密碼',
        widget=forms.widgets.PasswordInput()
    )
    phone = forms.RegexField(
        regex=r'^1[3-9]\d{9}$',
        label='手機號碼',
        max_length=11,
        min_length=11,
        widget=forms.widgets.TextInput(),

    )
    email = forms.CharField(
        label='郵箱',
        widget=forms.widgets.EmailInput(),
        validators=[RegexValidator(r'^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$', '郵箱格式不正確'), ]
    )
    gender = forms.ChoiceField(
        label='性別',
        widget=forms.widgets.RadioSelect(),
        choices=GENDER_CHOICES,
        initial=DEFAULT_GENDER,
    )

    # 寫一個局部鈎子方法,校驗用戶名是否已被注冊
    def clean_name(self):
        name_value = self.cleaned_data.get('name')
        is_exist = UserInfo.objects.filter(name=name_value)
        if is_exist:
            raise ValidationError('用戶名已存在')
        else:
            return name_value

    # 寫一個局部鈎子方法,校驗手機號是否已被注冊
    def clean_phone(self):
        phone_value = self.cleaned_data.get('phone')
        is_exist = UserInfo.objects.filter(phone=phone_value)
        if is_exist:
            raise ValidationError('手機號已存在')
        else:
            return phone_value

    def clean(self):
        # 判斷密碼和確認密碼是否相等
        # self.cleaned_data  --> 所有經過校驗的數據
        pwd = self.cleaned_data.get('password')
        re_pwd = self.cleaned_data.get('re_password')
        if pwd == re_pwd:
            return self.cleaned_data
        else:
            self.add_error('re_password', '兩次密碼不一致')
            raise ValidationError('兩次密碼不一致')

    def __init__(self, *args, **kwargs):
        super(RegForm, self).__init__(*args, **kwargs)
        for field in iter(self.fields):
            if field == 'gender':
                continue
            self.fields[field].widget.attrs.update({
                'class': 'form-control'
            })
View Code
 
         

4、views.py
from django.shortcuts import render, HttpResponse, redirect
from app01.forms import RegForm
from app01.models import UserInfo
from django.http import JsonResponse
# Create your views here.


# 登錄
def login(request):
    return HttpResponse('login')


# 注冊
def register(request):
    if request.method == 'POST':
        res = {'code': 0}
        # 利用post提交的數據實例化form類
        form_obj = RegForm(request.POST)
        # 校驗數據的有效性
        if form_obj.is_valid():  # form_obj2.cleaned_data
            form_obj.cleaned_data.pop('re_password')
            UserInfo.objects.create(**form_obj.cleaned_data)
            res['url'] = '/login/'
        else:
            # 數據有問題
            res['code'] = 1
            res['error_msg'] = form_obj.errors
        return JsonResponse(res)

    form_obj = RegForm()
    return render(request, 'register.html', {'form_obj': form_obj})


# 首頁
def index(request):
    return HttpResponse('index')
View Code

 

 

十一、ModelForm

1、介紹
我們在設計Form表單的時候,一般都會把字段名設置成和數據庫(ORM)的字段名一模一樣,因為這樣設計的話,你后面設計的邏輯中,如果需要創建用戶,
那么可以從表單的數據中提取出來,用關鍵字參數**kwargs直接創建,如果Form表單的字段和ORM的不一樣,那么創建用戶的時候就需要一個個指定,
比如username=name,password=pwd等。

那么問題來了,Form表單的字段和ORM的字段一樣,那么寫Fomr表單的時候不就等於再寫一次ORM的字段嗎,有簡單的方法嗎?

顯然是有的,Django給我們考慮到了。

用法是:
建Form表單類的時候繼承ModelForm,在Meta類里面指定繼承ORM的哪些字段,也可以新增你要的字段。

2、示例
from django import forms
from crm.models import UserProfile  # 導入UserProfile這張ORM表


class RegForm(forms.ModelForm):  # 繼承ModelForm
    # 新增的字段(UserProfile中並沒有這個字段的)
    re_password = forms.CharField(
        label='確認密碼',
        widget=forms.widgets.PasswordInput()
    )

    class Meta:
        model = UserProfile  # 表明使用UserProfile這張表作為模型
        # fields = '__all__'  # 展示UserProfile的所有字段
        fields = ['email', 'password', 're_password', 'name', 'mobile']  # 按照順序展示列表中的字段
        # exclude = ['']  # 把不需要的字段排查,其他字段展示出來
        labels = {
            'email': '郵箱'
        }

        error_messages = {
            'email': {
                'max_length': '郵箱長度過長',
                'unique': '郵箱不能為空'
            }
        }

        widgets = {
            'password': forms.widgets.PasswordInput()
        }

    # 重寫init方法批量應用bootstrap樣式
    def __init__(self):
        super().__init__()
        # self.fields是一個大字典,key是字段名,value是字段對象
        for field in self.fields.values():
            field.widget.attrs.update({'class': 'form-control'})


3、class Meta下的配置
1. model = 'ORM類名'

2. 字段
    1. fields = '__all__'  # 展示ORM表的所有字段
    2. fields = ['要展示的字段1', '字段2']  # 指定的字段顯示出來,其他字段不顯示
    3. exclude = ['需要排除的字段1', ...]  # 指定的字段不顯示,其他字段都顯示出來

3. labels = {
      '字段名1': 'label名1',
      '字段名2': 'label名2',
   }

4. error_messages = {
      '字段名': {
        'min_length': '最小長度不能小於xx位',
        'max_length': '最大長度不能超過xx位',
        'required': '這個字段是必填項'
      }
   }

5. widgets = {
        'password': forms.widgets.PasswordInput()
   }

6.help_texts = None  # 幫助提示信息

7. 校驗
    1. 可以在model的字段中配置validators
    2. 自己在Form類中重寫字段,定義validators配置
    3. 局部鈎子方法和全局鈎子方法 同Form的用法

注意:modelform中的字段的label默認就是對應的model表的verbose_name,若表中的字段沒有定義verbose_name,那么頁面顯示的label就是字段名,當然也可以直接在modelform中定義label


4、ModelForm中含有外鍵字段
創建ModelForm的時候,如果model中有含有外鍵字段,
那么這個外鍵字段在ModelForm默認是單選的select字段,label默認是model的verbose_name的值,如果沒有則顯示字段名,
或者可以在modelform的Meta里面通過labels設置,choices默認是這個外鍵關聯的所有數據,HTML上的select標簽的value默認這個外鍵關聯的表對應的主鍵,
顯示的文本默認是外鍵關聯的表對應的對象。
注意:如果是多對多字段,則生成多選的select字段

例如:
1.models.py
class ConsultRecord(models.Model):
    customer = models.ForeignKey('Customer', verbose_name="所咨詢客戶")



2.modelsform
class ConsultRecordForm(forms.ModelForm):

    class Meta:
        model = ConsultRecord
        fields = '__all__'

上面的modelsform字段customer實際上等於下面這個普通的form
class ConsultRecordForm(forms.Form):

    customer = forms.fields.ChoiceField(
        choices=Customer.objects.all().values_list('id', 'name'),
        label="所咨詢客戶",  # verbose_name
        widget=forms.widgets.Select()
    )


5、save()方法
每個ModelForm還具有一個save()方法。 這個方法根據表單綁定的數據創建並保存數據庫對象。 
ModelForm的子類可以接受現有的模型實例作為關鍵字參數instance的值,如果提供instance參數,則save()將更新該實例。
如果沒有提供,save()將創建模型的一個新實例。
注意:instance接收的是一個實例化對象,普通的Form並沒有instance參數,且當modelform中含有多對多字段時,調用save方法后,
會自動幫你更新自己的表和多對多的第三張表,否則,你得自己手動更新自己的表,然后再通過多對多字段找到第三張表再次更新。

1.沒有提供參數instance,save()創建新的實例
def reg(request):
    if request.method == 'POST':
        form_obj = RegForm(request.POST)
        # form_obj是一個ModelForm對象,它和數據庫的Model類對應
        form_obj.save()  # 相當於UserProfile.objects.create(**form_obj.cleaned_data)
        return redirect('/login/')


2.提供參數instance,save()跟新這個實例
1.models.py
from django.db import models

# Create your models here.


class Publisher(models.Model):
    name = models.CharField(max_length=12)
    addr = models.CharField(max_length=255)


2.forms.py
from django import forms
from myapp.models import Publisher


class PublisherForm(forms.ModelForm):

    class Meta:
        model = Publisher
        fields = '__all__'


3.views.py
from django.shortcuts import render, redirect
from myapp.models import Publisher
from myapp.forms import PublisherForm
# Create your views here.


def publisher_list(request):
    data = Publisher.objects.all()
    return render(request, 'publisher_list.html', {'publisher_list': data})


def edit_publisher(request):
    edit_id = request.GET.get('id')
    publisher_obj = Publisher.objects.filter(id=edit_id).first()
    # instance=某個對象:把這個對象的原始數據填到PublisherForm這個表單里面
    form_obj = PublisherForm(instance=publisher_obj)
    if request.method == 'POST':
        # 把request.POST的數據更新到instance=這個對象里面
        form_obj = PublisherForm(request.POST, instance=publisher_obj)  # DRF框架的時候也會用到
        if form_obj.is_valid():
            # 去數據庫編輯
            # 方法一:
            # new_name = form_obj.cleaned_data.get('name')
            # new_addr = form_obj.cleaned_data.get('addr')
            # publisher_obj.name = new_name
            # publisher_obj.addr = new_addr
            # publisher_obj.save()

            # 方法二:
            form_obj.save()
            return redirect('/publisher_list/')
    return render(request, 'edit_publisher.html', {'form_obj': form_obj})

 

十二、formset_factory的使用

1.介紹
    from django.forms import formset_factory
    對於繼承forms.Form的Form類,我們可以使用formset_factory。
    注意:modelform類其實也繼承了forms.Form的父類,因此modelform也可使用formset_factory

2. 必填參數
    1. form      --> 繼承了forms.form的form類

3. 額外參數
    extra:想要生成的空表單的數量
    max_num:最多可以展示的表單數量
    initial: 初始化formset的默認值(實例化formset時候的參數)
    注意: max_num優先級高於extra,比如,我們想要顯示5個空表單(extra=5),但max_num=2,最后只會顯示2個空表單

4. 示例
        FormSet = formset_factory(CourseForm)  # 實例化一個formset_factory
        formset_obj = FormSet(initial=[{}, {}...])  # formset_obj就可以跟form表單一樣使用了

5. 注意事項
    1. 生成的FormSet在html頁面中使用的時候,每一個form對象必須帶上自己的form.id標識這是哪個對象的form,頁面是會默認hidden這個參數(添加的時候不需要,編輯的時候需要)
    2. 如果要提交這些form對象,則需要在html中的form表單為formset設置一個參數:{{ formset_obj.management_form }},頁面是會默認hidden這個參數
       是告訴后端我這個formset有多少個小form
    3. FormSet默認會給你多生成一個form對象,可以通過參數 extra 設置額外加多少個,0代表不額外加form對象
    4. FormSet其實就是批量生成Form對象的操作,注意是Form對象
    6. formset_factory和form一樣,沒有instance參數,所以也沒有queryset參數
    7. 其他操作和單個form操作是一樣的,只是這個formset等於批量操作而已,一樣有is_valid等(沒有save,因為form是沒有save的,modelform才有)
        1. form表單
        class CourseForm(forms.Form):
            title = forms.CharField(max_length=32, label="課程名稱")
            score = forms.IntegerField(max_value=100, label="分數")
         
        2. views.py 
        from django.forms import formset_factory


        def course(request):
            FormSet = formset_factory(CourseForm, extra=1)
            formset_obj = FormSet(initial=[{"title": "語文"}, {"title": "數學", "score": 59}])
            if request.method == 'POST':
                formset_obj = FormSet(request.POST)
                if formset_obj.is_valid():
                    # 手動創建Course
                    objs = (Course(**item) for item in formset_obj.cleaned_data)
                    Course.objects.bulk_create(objs)
                    return redirect("/index/")
                return render(request, "xx.html", {"formset_obj": formset_obj})
            return render(request, 'xx.html', {'formset_obj': formset_obj})
        
        
        3.html
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
            {% load my_filter%}
        </head>
        <body>
         <form action="" method="post" novalidate>
                    {% csrf_token %}
                    {{ formset_obj.management_form }}  <!--提交formset時必須要添加的參數-->
                    <div class="col-md-12">
                        <table class="table table-striped table-bordered">
                            <thead>
                                <tr>
                                    <th>#</th>
                                    <th>課程名稱</th>
                                    <th>分數</th>
                                </tr>
                            </thead>
                            <tbody>
                                {% for form_obj in formset_obj %}
                                    <tr>
                                    <!--每一個form對象必須帶上自己的form.id標識這是哪個對象的form -->
                                        {{ form_obj.id }}
                                        <td>{{ forloop.counter }}</td>
                                        <td>{{ form_obj.title }}</td>
                                        <td>{{ form_obj.score }}</td>
                                    </tr>
                                {% endfor %}
                            </tbody>
                        </table>
                        <button class="btn btn-success pull-right" type="submit">提交</button>
                    </div>
                </form>
        </body>
        </html>

 

十三、modelformset_factory的使用

1. 介紹
    from django.forms import modelformset_factory
    對於繼承forms.ModelForm的Form類,我們可以使用modelformset_factory

2. 必填參數
    1. model      --> 數據庫中的某個表
    2. modelform  --> 繼承了forms.ModelForm的類

3. 其他參數
    extra:想要生成的空表單的數量
    max_num:最多可以展示的表單數量
    queryset:初始化modelformset(實例化modelformset時候的參數)

4. 示例
    # 生成一個formset類
    FormSet = modelformset_factory(Book, BookForm, extra=0)  # extra=0默認不給我生成額外的form對象
    # 拿到所有書的實例(多個Book實例組成的query_set)
    query_set = Book.objects.all()
    # 從query_set中取出每一個Book實例生成form表單,存到formset里面
    formset_obj = FormSet(queryset=query_set)  # formset_obj是一個可迭代對象,里面是一個個小form

5. 注意事項
    1. 生成的FormSet在html頁面中使用的時候,每一個modelform對象必須帶上自己的modelform.id標識這是哪個對象的modelform,頁面是會默認hidden這個參數(添加的時候不需要,編輯的時候需要)
    2. 如果要提交這些modelform對象,則需要在html中的modelform表單為formset設置一個參數:{{ formset_obj.management_form }},頁面是會默認hidden這個參數
       是告訴后端我這個modelformset有多少個小modelform
    3. FormSet默認會給你多生成一個modelform對象,可以通過參數 extra 設置額外加多少個,0代表不額外加modelform對象
    4. FormSet其實就是批量生成ModelForm對象的操作,注意是ModelForm對象
    5. modelformset_factory實例設置默認值用queryset而不是instance,queryset相當於為每個modelform設置instance
    6. 在html頁面仍然可以使用具體的某個modelform對象的instance參數拿到傳進來的對象,但是如果使用的不是自己的modelform對象的字段,提交數據時並不生效
    7. 其他操作和單個modelform操作是一樣的,只是這個modelformset等於批量操作而已,一樣有is_valid,save等
    8. 示例
        1. modelform
        class BookForm(forms.ModelForm):
    
            class Meta:
                model = Book
                fields = ["title", "price", "publisher"]

        
        2. views.py
        from django.forms import modelformset_factory


        def book(request):
            # 生成一個formset類
            FormSet = modelformset_factory(Book, BookForm, extra=0)  # extra=0默認不給我生成額外的form對象
            # 拿到所有書的實例(多個Book實例組成的query_set)
            query_set = Book.objects.all()
            # 從query_set中取出每一個Book實例生成form表單,存到formset里面
            formset_obj = FormSet(queryset=query_set)  # formset_obj是一個可迭代對象,里面是一個個小form
            # 上面的代碼相當於:
            # form_obj1 = BookForm(instance=query_set[0])
            # form_obj2 = BookForm(instance=query_set[1])  ...
            # 然后把這些form_obj存到formset_obj里面
            if request.method == 'POST':
                formset_obj = FormSet(request.POST, queryset=query_set)
                if formset_obj.is_valid():
                    # formset_obj.save()
                    return redirect('/index/')
                return render(request, 'tem1.html', {'formset_obj': formset_obj})
            return render(request, 'tem1.html', {'formset_obj': formset_obj})


        3. html代碼
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
            {% load my_filter%}
        </head>
        <body>
         <form action="" method="post" novalidate>
                    {% csrf_token %}
                    {{ formset_obj.management_form }}  <!--提交formset時必須要添加的參數-->
                    <div class="col-md-12">
                        <table class="table table-striped table-bordered">
                            <thead>
                                <tr>
                                    <th>#</th>
                                    <th>書名</th>
                                    <th>價格</th>
                                    <th>出版社</th>
                                </tr>
                            </thead>
                            <tbody>
                                {% for form_obj in formset_obj %}
                                    <tr>
                                    <!--每一個form對象必須帶上自己的form.id標識這是哪個對象的form -->
                                        {{ form_obj.id }}
                                        <td>{{ forloop.counter }}</td>
                                        <td>{{ form_obj.title }}</td>
                                        <td>{{ form_obj.price }}</td>
                                        <td>{{ form_obj.publisher }}</td>
                                    </tr>
                                {% endfor %}
                            </tbody>
                        </table>
                        <button class="btn btn-success pull-right" type="submit">提交</button>
                    </div>
                </form>
        </body>
        </html>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM