web框架-(六)Django补充---form表单验证

一、form表单验证

1. 常规html页面的form表单验证

常规页面中,如果想实现对表单中用户输入信息的数据验证,需要配合Ajax来实现.

使用前我们先来熟悉下函数参数:request,其中包含的意义:

request.path               除去了域名和端口的访问路径,
request.get_host       域名+端口信息
request.get_full_path()   所有路径,包含传递的参数
requets.is_secure()     是否使用https进行链接

Django中的form库:每一个邦定Form实体都有一个errors属性,它为你提供了一个字段与错误消息相映射的字典表。可以这么定义:

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

实现验证的必要条件:

  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类:

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函数处理:

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文件:

<!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;

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>

二、跨站请求

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>
posted @ 2017-01-21 10:05  Co丶cc  阅读(454)  评论(0编辑  收藏  举报