web框架-(六)Django補充---form表單驗證

1、form表單驗證

1. 常規html頁面的form表單驗證

常規頁面中,若是想實現對錶單中用戶輸入信息的數據驗證,須要配合Ajax來實現.css

使用前咱們先來熟悉下函數參數:request,其中包含的意義:html

request.path               除去了域名和端口的訪問路徑,
request.get_host       域名+端口信息
request.get_full_path()   全部路徑,包含傳遞的參數
requets.is_secure()     是否使用https進行連接

Django中的form庫:每個邦定Form實體都有一個errors屬性,它爲你提供了一個字段與錯誤消息相映射的字典表。能夠這麼定義:python

>>> f = ContactForm({'subject': 'Hello', 'message': ''})
>>> f.errors
{'message': [u'This field is required.']}

實現驗證的必要條件:jquery

  1. 在views.py中定義一個繼承了父類forms.Form的子類;
  2. 在頁面中須要設置name屬性;
  3. 後臺驗證時使用LoginForm(request.POST)獲取表單確認結果;
  4. 獲取結果後,使用LoginForm(request.POST).is_valid()判斷是否有誤(True爲無錯誤,False爲有錯誤);
  5. 用戶頁面提交時,須要將輸入內容拼接爲字典形式,而且屬性name的值要與views.py 中定義的一致;

1)建立forms類:git

class LoginForm(forms.Form):
    user = forms.CharField(required=True, error_messages={'required': '用戶名不能爲空.'})
    pwd = forms.CharField(required=True,
                          min_length=6,
                          max_length=10,
                          error_messages={'required': '密碼不能爲空.', 'min_length': "至少6位"})

    num = forms.IntegerField(error_messages={'required': '數字不能空.', 'invalid': '必須輸入數字'})

    phone = forms.CharField(validators=[mobile_validate, ], )

2)views函數處理:web

def login(request):
    if request.method == 'POST':
        result = {'status': False, 'message': None}
        obj = LoginForm(request.POST)
        ret = obj.is_valid()
        if ret:
            print(obj.clean())
            result['status'] = True
        else:
            from django.forms.utils import ErrorDict
            print(type(obj.errors), obj.errors.as_json())
            # print(obj.errors)
            error_str = obj.errors.as_json()
            result['message'] = json.loads(error_str)
        return HttpResponse(json.dumps(result))
    return render(request, 'login.html')    

3)login.htm文件:ajax

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <style>
        .error_msg{
            color: red;
        }
    </style>
</head>
<body>
    <div>
        <div>
            <input type="text" name="user" />
        </div>
        <div>
            <input type="password" name="pwd" />
        </div>
        <input type="button" value="提交" onclick="DoSubmit();" />
    </div>
    <script src="/static/jquery-1.12.4.js"></script>
    <script>
        function DoSubmit(){
            $('.error_msg').remove();
            var input_dict = {};
            $('input').each(function(){
                var v = $(this).val();
                var n = $(this).attr('name');
                input_dict[n] = v;
            });
            console.log(input_dict);
            $.ajax({
                url: '/login/',
                type: 'POST',
                data: input_dict,
                dataType:'json',
                success: function (result) {
                    if(result.status){
                        location.href = '/index/'
                    }else {
                        $.each(result.message,function (k,v) {
                            console.log(k,v[0].message);
                            var tag = document.createElement('span');
                            tag.className = 'error_msg';
                            tag.innerText = v[0].message;
                            $('input[name="'+k +'"]').after(tag);
                            
                        })

                    }

                }
            })
        }
    </script>
</body>
</html>
login.html  

2. django自帶form驗證正則表達式

Django的Form主要具備一下幾大功能:數據庫

  • 生成HTML標籤
  • 驗證用戶數據(顯示錯誤信息)
  • HTML Form提交保留上次提交數據
  • 初始化頁面顯示內容

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

2.1 django內置字段

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.2 Django內置插件:

TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget

2.3 經常使用插件:

# 單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,'北京'),),
#     initial=[1,],
#     widget=widgets.SelectMultiple
# )
 
 
# 單checkbox
# user = fields.CharField(
#     widget=widgets.CheckboxInput()
# )
 
 
# 多選checkbox,值爲列表
# user = fields.MultipleChoiceField(
#     initial=[2, ],
#     choices=((1, '上海'), (2, '北京'),),
#     widget=widgets.CheckboxSelectMultiple
# )

在使用選擇標籤時,須要注意choices的選項能夠從數據庫中獲取,可是因爲是靜態字段 ***獲取的值沒法實時更新***,那麼須要自定義構造方法從而達到此目的。

方式一:

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

方式二:

使用django提供的ModelChoiceField和ModelMultipleChoiceField字段來實現

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

3. 自定義驗證規則

方法一:

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'郵箱'}))

方法三:自定義方法

class register_fm(forms.Form):
    def email_form(value):
        email_re = re.compile(r"^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$")
        if not email_re.match(value):
            raise ValidationError('郵箱格式錯誤,請從新輸入!')
    username = fields.CharField(
        required=True,
        min_length=2,
        max_length=12,
        error_messages={
            'required': '用戶名不能爲空',
            'min_length': '用戶名最少2個字符',
            'max_length': '用戶名最多爲12個字符',
    },)
    email = fields.CharField(
        required=True,
        validators=[email_form,],
        error_messages={
            'required': '郵箱不能爲空',
    },)
    pwd = fields.CharField(
        required=True,
        min_length=6,
        max_length=20,
        error_messages={
            'required': '密碼不能爲空',
            'min_length': '密碼最少6個字符',
            'max_length': '密碼最多爲20個字符',
    },)
    repwd = fields.CharField(
        required=True,
        max_length=20,
        error_messages={
            'required':'確認密碼不能爲空',
            'max_length': '密碼最多爲20個字符',
    },)
    check_code = fields.CharField(
        required=True,
        max_length=4,
        error_messages={
            'required':'驗證碼不能爲空',
            'max_length': '驗證碼最多爲4個字符',
    },)
    def clean_username(self):
        cleaned_data = self.cleaned_data
        c = models.UserInfo.objects.filter(username=cleaned_data['username'])
        if c:
           raise ValidationError('用戶名已存在,請從新輸入!')
        else:
            return cleaned_data
    def clean_repwd(self):
        password1 = self.cleaned_data.get('pwd')
        password2 = self.cleaned_data.get('repwd')
        if password1 and password2 and password1!=password2:
            raise ValidationError('兩次密碼不一致,請從新輸入!')
        return self.cleaned_data

執行自定義方法時,可能你們會發現一個問題;若是字段爲空(如用戶名、密碼等),點擊提交時後臺會報錯;由於celaned_data沒有想要取得數據;

這裏咱們有兩種處理方式:

1. 增長判斷:

1)若是隻判斷單條數據,咱們能夠以下:

def clean_username(self):
	"""
	Form中字段中定義的格式匹配完以後,執行此方法進行驗證
	:return:
	"""
	value = self.cleaned_data['username']
	if value:
		if "666" in value:
			raise ValidationError('666已經被玩爛了...', 'invalid')
			return value    

2)若是判斷多個數據,咱們能夠考慮判斷下長度(如登陸狀況,判斷用戶名和密碼):

def clean(self):
	"""
	Form中字段中定義的格式匹配完以後,執行此方法進行驗證
	:return:
	"""
	value = self.cleaned_data
	if len(value)>1:
		c=models.UserInfo.objects.filter(username=value['username'],pwd=value['password']).count()
		if c:
			return self.cleaned_data
		else:
			raise ValidationError("用戶名或密碼錯誤!")

2. 更改獲取方式

def clean_username(self):
	"""
	Form中字段中定義的格式匹配完以後,執行此方法進行驗證
	:return:
	"""
	value = self.cleaned_data.get("username")
	if value:
		if "666" in value:
			raise ValidationError('666已經被玩爛了...', 'invalid')
			return value    

 

這裏咱們寫一個登陸的實例:

1)建立數據庫;

class UserInfo(models.Model):
    """
    用戶表
    """
    nid = models.BigAutoField(primary_key=True)
    username = models.CharField(verbose_name='用戶名', max_length=32, unique=True)
    password = models.CharField(verbose_name='密碼', max_length=64)
models.py

2)url定義;

urlpatterns = [
    url(r'^login.html$',login),
]
urls.py

3)自定義驗證規則;

class login_fm(forms.Form):
    username = fields.CharField(
        max_length=12,
        required=True,
        error_messages={
            'required': '用戶名不能爲空',
            'max_length': '用戶名最多爲12個字符',
        },)
    pwd = fields.CharField(
        max_length=20,
        required=True,
        error_messages={
            'required': '密碼不能爲空',
            'max_length': '密碼最多爲20個字符',
        },)
    def clean(self):
        cleaned_data = self.cleaned_data
        if len(cleaned_data)>1:
            c = models.UserInfo.objects.filter(username=cleaned_data['username'],password=cleaned_data['pwd']).count()
            if c:
                return cleaned_data
            else:
                raise ValidationError('用戶名或密碼錯誤,請從新輸入!')
forms.py

4)views;

from io import BytesIO
from django.shortcuts import HttpResponse
from django.shortcuts import render,redirect
from utils.check_code import create_validate_code
from repository import models
import json
from web.views import forms
from django.core.exceptions import ValidationError
class JsonCustomEncoder(json.JSONEncoder):
    def default(self, field):
        if isinstance(field,ValidationError):
            return {'code': field.code, 'messages': field.messages}
        else:
            return json.JSONEncoder.default(self, field)
def login(request):
    """
    登陸
    :param request:
    :return:
    """
    if request.method == 'GET':
           return render(request,'login.html')
    elif request.method == 'POST':
        ret = {'status': True, 'error': None, 'data': None}
        u = request.POST.get('username')
        obj = forms.login_fm(request.POST)
        if obj.is_valid():
                ret['status'] = True
                request.session['username'] = u
                request.session['is_login'] = True
        else:
            ret['status'] = False
            ret['error'] = obj.errors.as_data()
        result = json.dumps(ret, cls=JsonCustomEncoder)
        return HttpResponse(result)
account.py

5)html模板;

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <link rel="stylesheet" href="../static/plugins/bootstrap/css/bootstrap.css"/>
    <link rel="stylesheet" href="../static/plugins/font-awesome/css/font-awesome.css"/>
    <link rel="stylesheet" href="../static/css/edmure.css"/>
    <link rel="stylesheet" href="../static/css/commons.css"/>
    <link rel="stylesheet" href="../static/css/account.css"/>
</head>
<body>
<div class="bg">
    <div class="login">
        <div style="font-size: 25px; font-weight: bold;text-align: center;">
            用戶登陸
        </div>
        <form role="form" id="login_form">
            {% csrf_token %}
            <div class="form-group">
                <label for="username">用戶名</label>
                <input type="email" class="form-control" name="username" id="username" placeholder="請輸入用戶名">
            </div>
            <div class="form-group">
                <label for="password">密碼</label>
                <input type="password" class="form-control" name="pwd" id="password" placeholder="請輸入密碼">
            </div>
            <div class="form-group">
                <span class="error_mg" id="error_msg">{{ error_msg}}</span>
            </div>
            <a id="login_yes" class="btn btn-default">登陸</a>
        </form>
    </div>
</div>
<script src="../static/js/jquery-1.12.4.js"></script>
<script src="../static/js/jquery.cookie.js"></script>
<script>
    $(function(){
        $.ajaxSetup({
            beforeSend:function (xhr,settings){
                xhr.setRequestHeader('X-CSRFtoken',$.cookie('csrftoken'));
            }
        });
        $('#login_yes').click(function () {
            $.ajax({
                url:"/login.html",
                type:"POST",
                data:$('#login_form').serialize(),
                success:function (data) {
                    var obj=JSON.parse(data);
                    if (obj.status){
                        location.href='/backend/base-info.html';
                    }else{
                        if (obj.error.__all__){                           
                            $('#error_msg').text(obj.error.__all__[0].messages);
                        } else{
                            if (obj.error.username){
                                $('#error_msg').text(obj.error.username[0].messages)
                            }else{
                                if (obj.error.pwd){
                                    $('#error_msg').text(obj.error.pwd[0].messages)
                                }else{
                                    $('#error_msg').text(obj.error)
                                }
                            }
                        }
                    }
                }
            })
        })
    })
</script>
</body>
</html>

login.html
login.html

4. 初始化數據

在Web應用程序中開發編寫功能時,時經常使用到獲取數據庫中的數據並將值初始化在HTML中的標籤上。

1)forms:

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()
 
    city = fields.ChoiceField(
        choices=((1, '上海'), (2, '北京'),),
        widget=widgets.Select
    )

2)views:

from django.shortcuts import render, redirect
from .forms import MyForm
 
 
def index(request):
    if request.method == "GET":
        values = {'user': 'root', 'city': 2}
        obj = MyForm(values)
 
        return render(request, 'index.html', {'form': obj})
    elif request.method == "POST":
        return redirect('http://www.google.com')
    else:
        return redirect('http://www.google.com')

3)html文件:

<form method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    <p>{{ form.user }} {{ form.user.errors }}</p>
    <p>{{ form.city }} {{ form.city.errors }}</p>
 
    <input type="submit"/>
</form>

2、跨站請求

django爲用戶實現防止跨站請求僞造的功能,經過中間件django.middleware.csrf.CsrfViewMiddleware來完成。而對於django中設置防跨站請求僞造功能有分爲全局和局部。 

全局:文件位置setting.py,文件中間件位置:django.middleware.csrf.CsrfViewMiddleware

局部:

  • @csrf_protect,爲當前函數強制設置防跨站請求僞造功能,即使settings中沒有設置全局中間件。
  • @csrf_exempt,取消當前函數防跨站請求僞造功能,即使settings中設置了全局中間件。
  • 注意:以上須要導入模塊:from django.views.decorators.csrf import csrf_exempt,csrf_protect

2.1 應用

使用時,views.py中無需設置任何東西,常規return render(xxx)便可,主要是在HTML頁面中.

2.2 普通表單

普通form中添加在form中設置{% csrf_token %}便可.以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>csrf</title>
</head>
<body>
    <form action="/csrf/" method="POST">
        {% csrf_token %}
        <input type="text" name="v"/>
        <input type="submit" value="提交"/>
    </form>

</body>
</html>
相關文章
相關標籤/搜索