Django之forms組件
- 2020 年 1 月 21 日
- 筆記
一、校驗數據功能
我們在寫註冊頁面時,之前只是提交了數據,然後就保存了數據,後端根本就沒有對數據進行校驗,比如價格寫的不是純數字也讓保存,這肯定是不行的,在前端是可以校驗的,但我們不能只依靠前端驗證,萬一前端不校驗,那整個過程就沒校驗了,所以,不管前端有沒有校驗,我們後端都應該進行驗證。
現在註冊頁面要向我提交三個數據,用戶名、密碼、郵箱,當它提交過來後,首先我要對數據進行校驗,根據數據的校驗結果然後再決定保存還是給前端返回錯誤資訊。
views.py
from django import forms class UserInfo_form(forms.Form): name=forms.CharField(max_length=10,label='姓名') email = forms.CharField(max_length=20, label='郵箱') pwd=forms.CharField(max_length=20,label='密碼')def register(request): if request.method=='GET': form=UserInfo_form() return render(request,'register.html',{'form':form}) form=UserInfo_form(request.POST) #這是實例化一個forms類的一個實例化對象 res={'state':True,'error':{}} name=request.POST.get('name') email=request.POST.get('email') pwd=request.POST.get('pwd') r_pwd=request.POST.get('r_pwd') if not form.is_valid(): #is_valid,這就是對實例化對象進行校驗。然後form.errors裡面放的是錯誤資訊,form.clean_data放的是沒問題的資訊 res['error'] = form.errors res['state'] = False if pwd != r_pwd : res['error']['r_pwd']=['密碼不統一'] if pwd==r_pwd and form.is_valid(): User.objects.create_user(username=name,password=pwd,email=email) return JsonResponse(res)
二、欄位類型
1,欄位
Field required=True, 是否允許為空 widget=None, HTML插件 label=None, 用於生成Label標籤或顯示內容 initial=None, 初始值 help_text='', 幫助資訊(在標籤旁邊顯示) error_messages=None, 錯誤資訊 {'required': '不能為空', 'invalid': '格式錯誤'} show_hidden_initial=False, 是否在當前插件後面再加一個隱藏的且具有默認值的插件(可用於檢驗兩次輸入是否一直) 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類型
2,插件
# 單radio,值為字元串 # user = fields.CharField( # initial=2, # widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),)) # ) # 單radio,值為字元串 # user = fields.ChoiceField( # choices=((1, '上海'), (2, '北京'),), # initial=2, # widget=widgets.RadioSelect # ) # 單select,值為字元串 # user = fields.CharField( # initial=2, # widget=widgets.Select(choices=((1,'上海'),(2,'北京'),)) # ) # 單select,值為字元串 # user = fields.ChoiceField( # choices=((1, '上海'), (2, '北京'),), # initial=2, # widget=widgets.Select # ) # 多選select,值為列表 # user = fields.MultipleChoiceField( # choices=((1,'上海'),(2,'北京'),), #這是寫死的情況 choices=Author.objects.all().value_list('pk','name') # initial=[1,], # widget=widgets.SelectMultiple # ) # 單checkbox # user = fields.CharField( # widget=widgets.CheckboxInput() # ) # 多選checkbox,值為列表 # user = fields.MultipleChoiceField( # initial=[2, ], # choices=((1, '上海'), (2, '北京'),), # widget=widgets.CheckboxSelectMultiple
對於多選下拉框的驗證方法
1,方法一
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.ChoiceField( # choices=((1, '上海'), (2, '北京'),), initial=2, widget=widgets.Select ) def __init__(self, *args, **kwargs): super(MyForm,self).__init__(*args, **kwargs) # self.fields['user'].widget.choices = ((1, '上海'), (2, '北京'),) # 或 self.fields['user'].widget.choices = models.Classes.objects.all().value_list('id','caption')
2,方法二
from django import forms from django.forms import fields from django.forms import widgets from django.forms import models as form_model from django.core.exceptions import ValidationError from django.core.validators import RegexValidator class FInfo(forms.Form): authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())
三、渲染標籤功能
1,方式一
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <h3>註冊頁面</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form action="" method="post"> {% csrf_token %} <div> <label for="">用戶名</label> {{ form.name }} </div> <div> <label for="">密碼</label> {{ form.pwd }} </div> <div> <label for="">確認密碼</label> {{ form.r_pwd }} </div> <div> <label for=""> 郵箱</label> {{ form.email }} </div> <input type="submit" class="btn btn-default pull-right"> </form> </div> </div> </div> </body> </html>
2,方式二
<form action="" method="post"> {% csrf_token %} {% for field in form %} <div> <label for="">{{ field.label }}</label> {{ field }} </div> {% endfor %} <input type="submit" class="btn btn-default pull-right"> </form>
3,方式三
<form action="" method="post"> {% csrf_token %} {{ form.as_p }} <input type="submit" class="btn btn-default pull-right"> </form>
四、在前端頁面局部刷新錯誤資訊
views.py
def register(request): if request.method=='GET': form=UserInfo_form() return render(request,'register.html',{'form':form}) form=UserInfo_form(request.POST) res={'state':True,'error':{}} name=request.POST.get('name') email=request.POST.get('email') pwd=request.POST.get('pwd') r_pwd=request.POST.get('r_pwd') if not form.is_valid(): print(form) print(form.cleaned_data) print(form.errors) res['error'] = form.errors res['state'] = False if pwd != r_pwd : res['error']['r_pwd']=['密碼不統一'] if pwd==r_pwd and form.is_valid(): User.objects.create_user(username=name,password=pwd,email=email) return JsonResponse(res)
register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/css/bootstrap.css"> <script src="/static/jquery-3.3.1.js"></script> <script src="/static/js/bootstrap.js"></script> <style> .outer{ margin-top: 200px; } .title{ margin-bottom: 50px; } </style> </head> <body> {% csrf_token %} <div class="container outer"> {% csrf_token %} <div class="row"> <div class="col-md-6 col-md-offset-3"> <div style="color: blue;font-size: 50px;text-align: center;font-family: 華文隸書" class="title">註冊頁面</div> {% for field in form %} <div class="form-group "> <label for="id_{{ field.name }}">{{ field.label }}</label> {{ field }}<span class="right_span" style="color: red"></span> </div> {% endfor %} <button class="register btn btn-info">註冊</button> </div> </div> </div> <script> $('input').addClass('form-control'); $('#id_pwd').attr('type','password').attr('placeholder','只能有數字、字母、下劃線組成的,長度5到20'); $('#id_r_pwd').attr('type','password'); $('#id_name').attr('placeholder','長度在4到10,不能全為數字'); $('#id_email').attr('placeholder','例[email protected]'); {#$('#id_pwd').attr('placeholder','只能有數字、字母、下劃線組成的,長度5到20');#} $('.register').click(function () { $('span').html(''); $.ajax({ url:'', type:'post', data:{csrfmiddlewaretoken:$('[name="csrfmiddlewaretoken"]').val(), name:$('[name="name"]').val(),pwd:$('[name="pwd"]').val(),r_pwd:$('[name="r_pwd"]').val(),email:$('[name="email"]').val()}, success:function (res) { state=res['state']; if (state){ location.href='/login/' }else { $.each(res['error'],function(key,val) { console.log(key,val) $('#id_'+key).next().html(val[0]) #每個欄位錯誤的資訊都會放在一個列表裡 }) } } }) }) </script> </body>
五、鉤子
1,局部鉤子
views.py
class UserInfo_form(forms.Form): name=forms.CharField(max_length=10,label='姓名') email = forms.CharField(max_length=20, label='郵箱') pwd=forms.CharField(max_length=20,label='密碼') r_pwd=forms.CharField(max_length=20,label='確認密碼') 下面的局部鉤子,必須在上面的對應的過濾條件通過之後,才會走到對應的鉤子,局部鉤子只能對對應的欄位進行操作 def clean_name(self): #clean_欄位名,固定寫法 val=self.cleaned_data.get('name') if val.isdigit(): #如果不符合要求,要拋錯誤 raise ValidationError('不能全為數字') elif len(val)<4 or len(val)>10: raise ValidationError('長度只能在4到10之間') elif User.objects.filter(username=val): raise ValidationError('用戶名已存在') else: #如果正確的話,必須返回取進來的值,因為在進行鉤子驗證時,會把值給清空的。 return val def clean_email(self): val=self.cleaned_data.get('email') if re.search(r'^w+@w+.com$',val): return val else: raise ValidationError('郵箱格式不對') def clean_pwd(self): val=self.cleaned_data.get('pwd') if re.search('^[0-9a-zA-Z_]{5,20}$',val): return val elif len(val)<5 or len(val)>20: raise ValidationError('長度只能在5到20之間') else: raise ValidationError('密碼只能有數字、下劃線、字母組成') def clean_r_pwd(self): val = self.cleaned_data.get('r_pwd') if re.search('^[0-9a-zA-Z_]{5,20}$', val): return val elif len(val) < 5 or len(val) > 20: raise ValidationError('長度只能在5到20之間') else: raise ValidationError('密碼只能有數字、下劃線、字母組成')
2,全局鉤子
def clean(self): #在這裡能拿到所有欄位的值。 pwd=self.cleaned_data.get("pwd") r_pwd=self.cleaned_data.get("r_pwd") if pwd==r_pwd: return self.cleaned_data else: raise ValidationError('兩次密碼不一致!')
六、自定義規則
方式一:
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'郵箱'}))
方式三:
from django import forms from django.forms import fields from django.forms import widgets from django.core.exceptions import ValidationError from django.core.validators import RegexValidator class FInfo(forms.Form): username = fields.CharField(max_length=5, validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.', 'invalid')], ) email = fields.EmailField() def clean_username(self): """ Form中欄位中定義的格式匹配完之後,執行此方法進行驗證 :return: """ value = self.cleaned_data['username'] if "666" in value: raise ValidationError('666已經被玩爛了...', 'invalid') return value
方式四:同時生成多個標籤進行驗證
from django.forms import Form from django.forms import widgets from django.forms import fields from django.core.validators import RegexValidator ############## 自定義欄位 ############## class PhoneField(fields.MultiValueField): def __init__(self, *args, **kwargs): # Define one message for all fields. error_messages = { 'incomplete': 'Enter a country calling code and a phone number.', } # Or define a different message for each field. f = ( fields.CharField( error_messages={'incomplete': 'Enter a country calling code.'}, validators=[ RegexValidator(r'^[0-9]+$', 'Enter a valid country calling code.'), ], ), fields.CharField( error_messages={'incomplete': 'Enter a phone number.'}, validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid phone number.')], ), fields.CharField( validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.')], required=False, ), ) super(PhoneField, self).__init__(error_messages=error_messages, fields=f, require_all_fields=False, *args, **kwargs) def compress(self, data_list): """ 當用戶驗證都通過後,該值返回給用戶 :param data_list: :return: """ return data_list ############## 自定義插件 ############## class SplitPhoneWidget(widgets.MultiWidget): def __init__(self): ws = ( widgets.TextInput(), widgets.TextInput(), widgets.TextInput(), ) super(SplitPhoneWidget, self).__init__(ws) def decompress(self, value): """ 處理初始值,當初始值initial不是列表時,調用該方法 :param value: :return: """ if value: return value.split(',') return [None, None, None]