Django組件之Form表單

1、Django中的Form表單介紹

咱們以前在HTML頁面中利用form表單向後端提交數據時,都會寫一些獲取用戶輸入的標籤而且用form標籤把它們包起來。

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

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

 

2、普通方式的form表單註冊

1、views.py
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})
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>
</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>
View Code

 

 

3、使用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 %}

 

4、Form經常使用字段與插件

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


0、require
設置這個字段必需要填,默認也是True必填的

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


1、initial
設置input框裏面的初始值。

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


2、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'  # 時區改爲亞洲上海


3、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)  
    )



注意:在選擇字段中
     單選字段使用forms.fields.ChoiceField
     也能夠寫成forms.ChoiceField
     多選字段使用forms.fields.MultipleChoiceField


4、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()
    )


5、單選Select

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


6、多選Select

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


7、單選checkbox

class LoginForm(forms.Form):
    ...
    keep = forms.fields.ChoiceField(
        label="是否記住密碼",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )


8、多選checkbox

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

 

5、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,使其顯示出具體的值
APP下的models.py
class Hobby(models.Model):
    name = models.CharField(max_length=12)

    def __str__(self):
        return self.name


views.py
from django.forms import models as f_models

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

 

6、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

 

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

 

8、鉤子(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
 

 

9、在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'})

 

10、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

 

 

11、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'})


三、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的用法




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

 

12、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>

 

十3、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>
相關文章
相關標籤/搜索