django之--forms組件

forms組件

前戲

"""
寫一個註冊功能
    獲取用戶名和密碼 利用form表單提交數據
    在後端判斷用戶名和密碼是否符合一定的條件
        用戶名中不能含有666
        密碼不能少於三位
    
    如何符合條件需要你將提示信息展示到前端頁面
"""
def ab_form(request):
    back_dic = {'username':'','password':''}
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if '666' in username:
            back_dic['username'] = '不能666'
        if len(password) < 4:
            back_dic['password'] = '密碼不能少於4位'
    """
    無論是post請求還是get請求
    頁面都能夠獲取到字典 只不過get請求來的時候 字典值都是空的
    而post請求來之後 字典可能有值
    """
    return render(request,'ab_form.html',locals())

<form action="" method="post">
    <p>username:
        <input type="text" name="username">
        <span style="color: red">{{ back_dic.username }}</span>
    </p>
    <p>password:
        <input type="text" name="password">
        <span style="color: red">{{ back_dic.password }}</span>
    </p>
    <input type="submit" class="btn btn-info">
</form>


"""
1.手動書寫前端獲取用戶數據的html代碼                       渲染html代碼
2.後端對用戶數據進行校驗                                 校驗數據
3.對不符合要求的數據進行前端提示                            展示提示信息

forms組件能夠完成的事情
            1.渲染html代碼
            2.校驗數據
            3.展示提示信息

爲什麼數據校驗非要去後端 不能在前端利用js直接完成呢?
    因爲前端校驗不安全,可以直接修改,爬蟲程序也可以繞過前端頁面直接朝後端提交數據
    
    購物網站    
        選取了貨物之後 會計算一個價格發送給後端 如果後端不做價格的校驗
        
        實際是獲取到用戶選擇的所有商品的主鍵值
        然後在後端查詢出所有商品的價格 再次計算一遍
        如果跟前端一致 那麼完成支付如果不一致直接拒絕
"""

基本使用

 

from django import forms


class MyForm(forms.Form):
    # username字符串類型最小3位最大8位
    username = forms.CharField(min_length=3,max_length=8)
    # password字符串類型最小3位最大8位
    password = forms.CharField(min_length=3,max_length=8)
    # email字段必須符合郵箱格式  [email protected]
    email = forms.EmailField()

 

校驗數據

"""
1.測試環境的準備 可以自己拷貝代碼準備
2.其實在pycharm裏面已經幫你準備一個測試環境
    python console
    
基本方法:
    1、is_valid():判斷是否合法,只有所有都合法才返回true
    2、errors:查看所有不符合校驗規則以及不符合的原因
    3、cleaned_data:查看所有校驗通過的數據
"""
from app01 import views
# 1 將帶校驗的數據組織成字典的形式傳入即可
form_obj = views.MyForm({'username':'jason','password':'123','email':'123'})
# 2 判斷數據是否合法        注意該方法只有在所有的數據全部合法的情況下才會返回True
form_obj.is_valid()
False
# 3 查看所有校驗通過的數據
form_obj.cleaned_data
{'username': 'jason', 'password': '123'}
# 4 查看所有不符合校驗規則以及不符合的原因
form_obj.errors
{
  'email': ['Enter a valid email address.']
}
# 5 校驗數據只校驗類中出現的字段 多傳不影響 多傳的字段直接忽略
form_obj = views.MyForm({'username':'jason','password':'123','email':'[email protected]','hobby':'study'})
form_obj.is_valid()
True
# 6 校驗數據 默認情況下 類裏面所有的字段都必須傳值
form_obj = views.MyForm({'username':'jason','password':'123'})
form_obj.is_valid()
False
"""
也就意味着校驗數據的時候 默認情況下數據可以多傳但是絕不可能少傳
"""

字段校驗

RegexValidator驗證器

from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
 
class MyForm(Form):
    user = fields.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '請輸入數字'), RegexValidator(r'^159[0-9]+$', '數字必須以159開頭')],
    )

自定義驗證函數

import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.exceptions import ValidationError
 
 
# 自定義驗證規則
def mobile_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value):
        raise ValidationError('手機號碼格式錯誤')
 
 
class PublishForm(Form):
 
 
    title = fields.CharField(max_length=20,
                            min_length=5,
                            error_messages={'required': '標題不能爲空',
                                            'min_length': '標題最少爲5個字符',
                                            'max_length': '標題最多爲20個字符'},
                            widget=widgets.TextInput(attrs={'class': "form-control",
                                                          'placeholder': '標題5-20個字符'}))
 
 
    # 使用自定義驗證規則
    phone = fields.CharField(validators=[mobile_validate, ],
                            error_messages={'required': '手機不能爲空'},
                            widget=widgets.TextInput(attrs={'class': "form-control",
                                                          'placeholder': u'手機號碼'}))
 
    email = fields.EmailField(required=False,
                            error_messages={'required': u'郵箱不能爲空','invalid': u'郵箱格式錯誤'},
                            widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'郵箱'}))

渲染標籤

'''
forms組件只會自動幫我們渲染獲取用戶輸入的標籤(input、select、radio、checkbox)
無法渲染提交按鈕
'''
def register(request):
    # 產生一個空對象
    form_obj = MyForms()
    # 直接將空對象傳遞給html頁面
    return render(request,'register.html',locals())

# 渲染標籤的三種方式
'''
方式一:
    優點:代碼簡潔
    缺點:封裝程度太高,不利於擴展
    {{ form_obj.as_p }}
    {{ form_obj.as_ul }}
    {{ form_obj.as_table }}
'''
'''
方式二:
    優點:可擴展性強
    缺點:代碼過於冗餘
    <p>{{ form_obj.username.label }}:{{ form_obj.username }}</p>
    <p>{{ form_obj.password.label }}:{{ form_obj.password }}</p>
    <p>{{ form_obj.email.label }}:{{ form_obj.email }}</p>
'''
'''
方式三:
    優點:代碼簡潔,可擴展性也高,推薦使用
    {% for form in form_obj %}
        <p>{{ form.label }}:{{ form }}</p>
    {% endfor %}
    
label屬性默認展示的是類中定義的字段首字母大寫的形式
也可以自己修改 直接給字段對象加label屬性即可
     username = forms.CharField(min_length=3,max_length=8,label='用戶名')
'''

展示信息

'''
使用forms組件時瀏覽器會自動幫我們校驗數據,但是前端的校驗太弱了
可以給form標籤設置novalidate參數使瀏覽器不自動校驗
forms組件當數據不合法的情況下,會保存上次的數據 讓你基於之前的結果進行修改
更加的人性化
'''
def register(request):
    # 1、先產生空對象
    form_obj = MyForms()
    if request.method == 'POST':
        # 3、獲取數據信息並校驗
        """
        1、校驗數據需要傳入字典格式
        2、request.POST可以看成是一個字典
        3、保證get請求與post請求傳給html頁面的變量名必須一樣
        """
        form_obj = MyForms(request.POST)
        # 4、判斷數據是否合法 ps只有當類中數據全部合法纔算合法
        if form_obj.is_valid():
            # 合法時的操作
            return HttpResponse('OK')
        
    # 2、將空對象傳遞給html頁面
    return render(request,'register.html',locals())

{% for form in form_obj %}
        <p>{{ form.label }}:{{ form }}
            <span style="color: red">{{ form.errors.0 }}</span></p>
{% endfor %}

# 針對錯誤提示可以使用error_messages來自定義錯誤提示 (第一道防線)
'''
error_messages中需要記憶的幾個參數
    1、'invalid': '郵箱不合法'   用於郵箱不合法時的錯誤提示
    2、'required': 'xxx不能爲空' 用於提交信息爲空時的錯誤提示
'''
# username字符串類型最小3位最大8位
    username = forms.CharField(min_length=3,max_length=8,label='用戶名',
                               error_messages={
                                   'min_length':'用戶名最少3位',
                                   'max_length':'用戶名最大8位',
                                   'required':"用戶名不能爲空"})
    # password字符串類型最小3位最大8位
    password = forms.CharField(min_length=3,max_length=8,label='密碼',
                               error_messages={
                                   'min_length': '密碼最少3位',
                                   'max_length': '密碼最大8位',
                                   'required': "密碼不能爲空"})
    # email字段必須符合郵箱格式  [email protected]
    email = forms.EmailField(label='郵箱',
                             error_messages={
                                 'invalid':'郵箱格式不正確',
                                 'required': "郵箱不能爲空"})

鉤子函數(HOOK)

'''
鉤子函數:在特定的節點自動觸發完成相應操作
鉤子函數在forms組件中就類似於第二道防線,能夠讓我們自定義校驗規則

在forms組件中有兩類鉤子
    1.局部鉤子
        案例:校驗用戶輸入的用戶名必須是字母數字下劃線的組合
        當你需要給單個字段增加校驗規則的時候可以使用
    2.全局鉤子
        案例:校驗用戶兩次密碼輸入是否一致
          當你需要給多個字段增加校驗規則的時候可以使用
'''
# 局部鉤子
def clean_user(self):
    # 獲取到用戶名
    user = self.cleaned_data.get('user')
    obj = models.User.objects.filter(user=user).first()
    if obj:
        # 提示前端展示錯誤信息
        self.add_error('user', '用戶名已存在')
    # 將鉤子函數鉤去出來數據再放回去
    return user
# 全局鉤子
def clean(self):
    pwd = self.cleaned_data.get('pwd')
    pwd2 = self.cleaned_data.get('pwd2')
    if pwd != pwd2:
        self.add_error('pwd2', '兩次密碼不一致')
    # 將鉤子函數鉤出來數據再放回去
    return self.cleaned_data

其他參數及補充知識點

# 必須掌握的
'''
label    自定義字段名
error_messages    自定義報錯信息(第一道防線)
initial        給字段設置默認值
required    控制字段是否必填
render_value=True   展示錯誤信息時讓密碼不重置
PasswordInput(render_value=True) 默認爲False,展示錯誤信息重置密碼
'''

# 修改字段樣式
# EmailInput會將input的type屬性由text修改成email
widget=forms.widgets.EmailInput(attrs={'class': 'form-control', 'placeholder': '郵箱', 'name': 'email'}),
# PasswordInput會將input的type屬性由text修改成password
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control', 'placeholder': '密碼', 'name': 'pwd'}),
# 多個屬性值之間用空格分隔

# 第一道防線————正則校驗
from django.core.validators import RegexValidator
validators=[
    RegexValidator(r'^1[3456789]\d{9}', '手機號格式錯誤')
]

其他類型渲染

 # radio
    gender = forms.ChoiceField(
        choices=((1, ""), (2, ""), (3, "保密")),
        label="性別",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )
    # select
    hobby = forms.ChoiceField(
        choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),),
        label="愛好",
        initial=3,
        widget=forms.widgets.Select()
    )
    # 多選
    hobby1 = forms.MultipleChoiceField(
        choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),),
        label="愛好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )
    # 單選checkbox
    keep = forms.ChoiceField(
        label="是否記住密碼",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )
    # 多選checkbox
    hobby2 = forms.MultipleChoiceField(
        choices=((1, "籃球"), (2, "足球"), (3, "雙色球"),),
        label="愛好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )

forms組件源碼

'''forms組件內部原理'''
'''
通過手動拋出異常來展示提示信息(通過看源碼知道的第二種展示信息的方法)
# 不推薦,應爲過於繁瑣,知道有這個方法即可
from django.core.exceptions import ValidationError
raise ValidationError('用戶名已存在')
'''
# 切入點:forms_obj.is_valid()
    def is_valid(self):
        """
        Returns True if the form has no errors. Otherwise, False. If errors are
        being ignored, returns False.
        """
        return self.is_bound and not self.errors

# self.is_bound:只要你往組件傳值(request.POST)那麼self.is_bound就爲True

# self.errors
    @property #(讓被裝飾的函數直接通過對象點屬性的方式調用)
    def errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None: # 一定會觸發,因爲self._errors默認就是None
            self.full_clean()
        return self._errors
    
# self.full_clean()
    def full_clean(self):
        """
        Cleans all of self.data and populates self._errors and
        self.cleaned_data.
        """
        self._errors = ErrorDict() # 我們的errors就是從這裏開始創建出來的
        if not self.is_bound:  # 只要傳值了就不會觸發
            return
        self.cleaned_data = {} # 我們的cleaned_data就是在這裏定義的
        # If the form is permitted to be empty, and none of the form data has
        # changed from the initial data, short circuit any validation.
        if self.empty_permitted and not self.has_changed():
            return
        
        # forms組件的精髓就在下面三句代碼裏
        self._clean_fields() # 校驗字段、局部鉤子
        self._clean_form() # 全局鉤子
        self._post_clean()
        
# self._clean_fields()
    def _clean_fields(self):
        for name, field in self.fields.items():
            # value_from_datadict() gets the data from the data dictionaries.
            # Each widget type knows how to retrieve its own data, because some
            # widgets split data over several HTML fields.
            ...
            try: # 字段校驗
                if isinstance(field, FileField):
                    initial = self.get_initial_for_field(field, name)
                    value = field.clean(value, initial)
                else:
                    value = field.clean(value)
                self.cleaned_data[name] = value
                if hasattr(self, 'clean_%s' % name):#查找局部鉤子
                    value = getattr(self, 'clean_%s' % name)() #調用局部鉤子並且返回值
                    self.cleaned_data[name] = value
            except ValidationError as e: # 可以大膽的猜測,通過手動拋出異常也能展示提示信息
                self.add_error(name, e)

# self._clean_form() # 全局鉤子
    def _clean_form(self):
        try:
            cleaned_data = self.clean() # 所以全局鉤子需要返回值
        except ValidationError as e:
            self.add_error(None, e)
        else:
            if cleaned_data is not None:
                self.cleaned_data = cleaned_data

froms常用字段插件

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

initial

初始值,input框裏面的初始值。

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

error_messages

重寫錯誤信息。

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用戶名",
        initial="張三",
        error_messages={
            "required": "不能爲空",
            "invalid": "格式錯誤",
            "min_length": "用戶名最短8位"
        }
    )
    pwd = forms.CharField(min_length=6, label="密碼")

password

class LoginForm(forms.Form):
    ...
    pwd = forms.CharField(
        min_length=6,
        label="密碼",
        widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
    )

radioSelect

單radio值爲字符串

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用戶名",
        initial="張三",
        error_messages={
            "required": "不能爲空",
            "invalid": "格式錯誤",
            "min_length": "用戶名最短8位"
        }
    )
    pwd = forms.CharField(min_length=6, label="密碼")
    gender = forms.ChoiceField(
        choices=((1, ""), (2, ""), (3, "保密")),
        label="性別",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )

單選Select

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

多選Select

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

單選checkbox

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

多選checkbox

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

choice字段注意事項

在使用選擇標籤時,需要注意choices的選項可以配置從數據庫中獲取,但是由於是靜態字段 獲取的值無法實時更新,需要重寫構造方法從而實現choice實時更新。

方式一:

from django.forms import Form
from django.forms import widgets
from django.forms import fields

 
class MyForm(Form):
 
    user = fields.ChoiceField(
        # choices=((1, '上海'), (2, '北京'),),
        initial=2,
        widget=widgets.Select
    )
 
    def __init__(self, *args, **kwargs):
        super(MyForm,self).__init__(*args, **kwargs)
        # self.fields['user'].choices = ((1, '上海'), (2, '北京'),)
        #
        self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')

方式二:

from django import forms
from django.forms import fields
from django.forms import models as form_model

 
class FInfo(forms.Form):
    authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())  # 多選
    # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())  # 單選

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類型

 

 

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