Part VIII: forms assembly, cookie and session

forms component

Registration page data is often used to verify the user's input

  • Rendering page
  • Check data
  • Display information

When using forms component, py need to create a file in the application, such as:
myforms.py

Write in a class file:

from django import forms
 
 
class MyRegForm(forms.Form):
    # 用户名最少3位最多8位
    username = forms.CharField(max_length=8,min_length=3)
    password = forms.CharField(max_length=8,min_length=3)
    # email字段必须填写符合邮箱格式的数据
    email = forms.EmailField()

forms parity data

Data validation syntax:

  • .is_valid () verify compliance with rules
  • obtaining verification data .cleaned_data
  • .errors validation failure and the reasons for obtaining verification fails, returns a set of li ul front end upset layout exhibition
# 1.传入待校验的数据  用自己写的类 传入字典格式的待校验的数据
form_obj = views.MyRegForm({'username':'jason','password':'12','email':'123456'})
# 2.判断数据是否符合校验规则
form_obj.is_valid()  # 该方法只有在所有的数据全部符合校验规则才会返回True
False
# 3.如何获取校验之后通过的数据
form_obj.cleaned_data
{'username': 'jason'}
# 4.如何获取校验失败及失败的原因
form_obj.errors
{
'password': ['Ensure this value has at least 3 characters (it has 2).'],
'email': ['Enter a valid email address.']
}
# 5.注意 forms组件默认所有的字段都必须传值 也就意味着传少了是肯定不行的 而传多了则没有任何关系 只校验类里面写的字段 多传的直接忽略了
form_obj = views.MyRegForm({'username':'jason','password':'123456'})
form_obj.is_valid()
Out[12]: False
form_obj.errors
Out[18]: {'email': ['This field is required.']}
 
# 多传的hobby字段,在MyRegForm类中并没有限制,因此,这里的hobby会被忽略,只校验类中的字段
form_obj = views.MyRegForm({'username':'jason','password':'123456',"email":'[email protected]',"hobby":'hahahaha'})
form_obj.is_valid()
Out[14]: True
form_obj.cleaned_data
Out[15]: {'username': 'jason', 'password': '123456', 'email': '[email protected]'}
form_obj.errors
Out[16]: {}

forms rendering label

rendering component forms only help get user input, select, pull-down, tag files, buttons, and do not render the form tag form

Rendered each input field in the class are the message, the first letter capitalized (unless using the label within the field parentheses class defines message)

1, the first to write a class

from django import forms
 
 
class MyRegForm(forms.Form):
    # 用户名最少3位最多8位
    username = forms.CharField(max_length=8,min_length=3)
    password = forms.CharField(max_length=8,min_length=3)
    # email字段必须填写符合邮箱格式的数据
    email = forms.EmailField()

2、urls.py

url(r'^reg/', views.reg)

3、views.py

def teg(request):
    # 1、先生成一个空的对象
    form_obj = MyRegForm()
    # 2、直接将该对象传给前端页面
    return render(request, 'reg.html',  locals())

4, tested in reg.html page

The first rendering: a plurality of p convenience local tag test package is not too high for easy expansion

# reg.html
 
{{ form_obj.as_p }}

# reg.html
{{ form_obj.as_ul }}

# reg.html
{{ form_obj.as_table }}

The second rendering: high scalability, writing more complicated

{{ form_obj.username.id_for_label }}  # 获取字段的id
{{ form_obj.username.label }}  # 获取的是字段的提示信息User
{{ form_obj.username }}  # 为类中的username产生一个input框
 
# reg.html
<label for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}</label>{{ form_obj.username }}
{{ form_obj.password.label }}{{ form_obj.password }}
{{ form_obj.email.label }}{{ form_obj.email }}

The third rendering mode is recommended:

Regardless of the definition of several fields in class Class, for the next cycle, all of the fields can be rendered

# reg.html
 
{% for form in form_obj %}
<p>{{ form.label }}{{ form }}</p>
{% endfor %}

forms rendering an error message (message)

1, the front end of automatic calibration

Front-end code:

# reg.html
 
<form action="" method="post">
    {% for form in form_obj %}
    <p>{{ form.label }}{{ form }}</p>
    {% endfor %}
    <input type="submit">
</form>

Back-end code:

# views.py
 
def reg(request):
    # 1、先生成一个空的对象
    form_obj = MyRegForm()
    if request.method == 'POST':
        # 3、获取用户输入的所有数据并交给forms组件校验
        form_obj = MyRegForm(request.POST)  # 对上面的空对象重新赋值
        # 4、获取校验结果
        if form_obj.is_valid():
            return HttpResponse('数据没有问题!')
        else:
            # 5.后端打印失败字段和提示信息
            print(form_obj.errors)
 
    # 2、直接将该对象传给前端页面
    return render(request, 'reg.html', locals())

Data validation must have front and rear end, but the front-end verification fragile, dispensable, while the rear end of the check must be very comprehensive

So you can cancel browser automatically help us check function:

# form表单取消前端浏览器自动校验功能
 
<form action="" method="post" novalidate>  # 在form标签内设置novalidate

Front-end code:

<form action="" method="post" novalidate>
    {% for form in form_obj %}
    <p>
        {{ form.label }}{{ form }}
    <sanp>{{ form.errors.0 }}</sanp>
    </p>
    {% endfor %}
    <input type="submit">
</form>

If the front-end code is not form.errors index 0, is then removed in the form of a sleeve ul li, it will disrupt the page layout:

def reg(request):
    # 1、先生成一个空的对象
    form_obj = MyRegForm()  # 第一次get请求传入空字典,只是为了渲染页面
    if request.method == 'POST':
        # 3、获取用户输入的所有数据并交给forms组件校验
        form_obj = MyRegForm(request.POST)  # 对上面的空对象重新赋值,此时post请求,是为了校验
        # 4、获取校验结果
        if form_obj.is_valid():
            return HttpResponse('数据没有问题!')
        else:
            # 5.后端打印失败字段和提示信息
            print(form_obj.errors)
 
    # 2、直接将该对象传给前端页面
    return render(request, 'reg.html', locals())

Results are as follows:

However, we find that the above effect is not very good, a message in English, the error message is no way to customize, input box, enter the password is in plain text, so we need one step closer to perfection, for which we need forms assembly common parameters.

Common forms component parameters

  • Message label input box
  • error_messages custom message being given
  • Set whether to allow the required field is empty
  • initial set default values
  • Types control widget type property (adjustment pattern input frame)

label sets the input box message:

# 在定义的类中的字段括号内添加参数
username = forms.CharField(max_length=8, min_length=3, label='用户名:')

Results are as follows:

error_messages自定义报错信息:

class MyRegForm(forms.Form):
    # 用户名最少3位最多8位
    username = forms.CharField(
        max_length=8, min_length=3, label='用户名:',
        error_messages={
            'max_length': '用户名最长8个字符!',
            'min_length': '用户名最短3个字符!',
            'required': '用户名不能为空!',
        }
                               )
 
    # 设置邮箱校验
    # email字段必须填写符合邮箱格式的数据
    email = forms.EmailField(label='邮箱',error_messages={
        'required':'邮箱必填',
        'invalid':'邮箱格式不正确'
    })

字段默认必填,可以通过required设置字段为空:

class MyRegForm(forms.Form):
    # 用户名最少3位最多8位
    username = forms.CharField(
        max_length=8, min_length=3, label='用户名:',
        error_messages={
            'max_length': '用户名最长8个字符!',
            'min_length': '用户名最短3个字符!'
        },
        required=False
                               )

initial设置为默认值:

class MyRegForm(forms.Form):
    # 用户名最少3位最多8位
    username = forms.CharField(
        max_length=8, min_length=3, label='用户名:',
        error_messages={
            'max_length': '用户名最长8个字符!',
            'min_length': '用户名最短3个字符!'
        },
        required=False, initial='我是默认值'
                               )

widget控制type类型及属性(调整input框的样式)

# 设置widget=forms.widgets.passwordInput(),使密码变为密文显示
# attr设置input框的样式
 
password = forms.CharField(max_length=8,min_length=3,label='密码',
                           widget=forms.widgets.PasswordInput(attrs={'class':'form-control'})
                           )
widget=forms.widgets.TextInput(attrs={'class':'form-control c1 c2'})

全局或局部钩子

使用全局或者局部钩子,其实相当于第二层的校验,多了一层校验

全局钩子:针对多个字段,一般用于第二次确认密码是否一致

局部钩子:针对单个字段,校验用户名中不能包含666

# 定义类
 
class MyRegForm(forms.Form):
    # 用户名最少3位最多8位
    username = forms.CharField(max_length=8, min_length=3, label='用户名:')
    password = forms.CharField(max_length=8, min_length=3, label='密码:')
    confirm_password = forms.CharField(max_length=8, min_length=3, label='密码:')
# 全局钩子
 
def clean(self):
    # 校验密码和确认密码是否一致
    password = self.cleaned_data.get('password')
    confirm_password = self.cleaned_data.get('confirm_password')
    if not password == confirm_password:
        # 展示提示信息
        self.add_error('confirm_password','两次密码不一致')
    return self.cleaned_data
 
# 局部钩子
def clean_username(self):
    username = self.cleaned_data.get('username')
    if '666' in username:
        self.add_error('username','光喊666是不行的')
    return username
 
# 如果你想同时操作多个字段的数据你就用全局钩子
# 如果你想操作单个字段的数据 你就用局部钩子

forms补充知识点

正则校验

  • RegexValidator
phone = forms.CharField(
    validators=[
        RegexValidator(r'^[0-9]+$', '请输入数字'),
        RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
    ]
)

利用forms组件校验注册功能

首先在应用下新建一个myforms.py文件

# myforms.py

from django import forms
from app01 import models

class MyRegForm(forms.Form):
    username = forms.CharField(
        min_length=3, max_length=8, label='用户名:',
  error_messages={
            'min_length': '用户名最短3个字符!',
  'max_length': '用户名最长8个字符!',
  'required': '用户名不能为空!'
  },
  widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
    )
    password = forms.CharField(
        min_length=3, max_length=8, label='密码:',
  error_messages={
            'min_length': '密码最短3个字符!',
  'max_length': '密码最长8个字符!',
  'required': '密码不能为空!'
  },
  widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
    )
    confirm_password = forms.CharField(
        min_length=3, max_length=8, label='确认密码:',
  error_messages={
            'min_length': '确认密码最短3个字符!',
  'max_length': '确认密码最长8个字符!',
  'required': '确认密码不能为空!'
  },
  widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
    )
    email = forms.EmailField(
        label='邮箱',
  error_messages={
            'required': '邮箱不能为空',
  'invalid': '邮箱格式不正确'
  },
  widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
    )

    # 钩子函数
  # 局部钩子校验用户名是否已存在
  def clean_username(self):
        username = self.cleaned_data.get('username')
        is_alive = models.UserInfo.objects.filter(username=username)
        if is_alive:
            self.add_error('username', '用户名已存在!')
        return username

    # 全局钩子校验密码与确认密码是否一致
  def clean(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if password != confirm_password:
            self.add_error('confirm_password', '两次密码不一致!')
        return self.cleaned_data

在views.py中书写逻辑代码:

# views.py

from app01.myforms import MyRegForm

def register(request):
    # 1、先生成form_obj对象
  form_obj = MyRegForm()
    # if request.is_ajax():  # 判断当前请求是否是ajax
  if request.method == 'POST':
        # 定义一个与ajax回调函数交互的字典
  back_dic = {'code': 1000, 'msg': ''}

        # 校验数据 用户名 密码和确认密码
  form_obj = MyRegForm(request.POST)
        if form_obj.is_valid():
            clean_data = form_obj.cleaned_data  # 用变量接收正确的结果,clean_data = {'username', 'password', 'confirm_password', 'email'}
 # 将确认密码的键值对删除
  clean_data.pop('confirm_password')
            # 获取用户头像文件
  avatar_obj = request.FILES.get('avatar')
            # 判断用户头像是否为空
  if avatar_obj:
                # 添加到clean_data中
  clean_data['avatar'] = avatar_obj  # clean_data = {'username', 'password', 'avatar_obj', 'email'}
  models.UserInfo.objects.create_user(**clean_data)
                back_dic['msg'] = '注册成功!'
  back_dic['url'] = '/login/'
  else:
            back_dic['code'] = 2000
  back_dic['msg'] = form_obj.errors
        return JsonResponse(back_dic)

    # 2、将form_obj对象返回给html页面
  return render(request, 'register.html', locals())

html页面

# register.py

<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>注册页面</title>
 <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
 <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<div class="container">
 <div class="row">
 <h2 class="text-center">注册页面</h2>
 <div class="col-md-8 col-md-offset-2">
 <form id="myform">
  {% csrf_token %}

                {% for form in form_obj %}
                    <div class="form-group">
 <label for="{{ form.id_for_label }}">{{ form.label }}</label>
  {{ form }}
                    <span style="color: red" class="pull-right"></span>
 </div>  {% endfor %}

                <div class="form-group">
 <label for="id_avatar">用户头像:<img src="/static/image/default.jpg" alt="" width="80" style="margin-left: 10px" id="id_img"></label>
 <input type="file" name="myfile" id="id_avatar" style="display: none">
 </div>
 <input type="button" value="取消" class="btn btn-default btn-sm">
 <input type="button" value="注册" class="btn btn-primary btn-sm pull-right" id="id_submit">
             </form>

 </div> </div></div>
<script>
  $('#id_avatar').change(function () {
        // 1、先获取用户上传的头像文件
  var avatarFile = $(this)[0].files[0];
  // 2、利用文件阅读器对象
  var myFileReader = new FileReader();
  // 3、将文件交由阅读器对象读取
  myFileReader.readAsDataURL(avatarFile);
  // 4、修改img标签的src属性,等待文件阅读器对象读取文件之后再操作img标签
  myFileReader.onload = function(){
            $('#id_img').attr('src', myFileReader.result)
        }
    });

  // 点击按钮触发ajax提交动作
  $('#id_submit').on('click', function () {
        // 1、先生成一个内置对象 FormData  var myFormData = new FormData();
  // 2、添加普通键值对
  {#console.log($('#myform').serializeArray())#}
  $.each($('#myform').serializeArray(), function (index, obj) {
            myFormData.append(obj.name, obj.value)
        });
  // 3、添加文件数据
  myFormData.append('avatar', $('#id_avatar')[0].files[0])
        // 4、发送数据
  $.ajax({
            url: '',
  type: 'post',
  data: myFormData,
  // 两个关键性参数
  contentType: false,
  processData: false,

  success: function (data) {
                if (data.code===1000){
                    // 注册成功之后 应该跳转到后端返回过来的url
  location.href = data.url
  }else{
                    $.each(data.msg, function (index, obj) {
                        // 1、先手动拼接字段名所对应的input框的id值
  var targetId = '#id_' + index; // id_username
 // 2、利用id选择器查找标签
  $(targetId).next().text(obj[0]).parent().addClass('has-error')
                    })
                }
            }
        })
    });
  $('input').focus(function () {
        // 移除span标签内部的文本 还需要移除div标签的class中的has-error属性
  $(this).next().text('').parent().removeClass('has-error')
    })
</script>
</body>
</html>

django的cookie和session

cookie:保存在服务端的键值对

session:保存在服务端上的键值对

cookie与session的作用:

  • 解决http协议的无状态特征
  • 保存信息

cookie产生

当用户第一次登陆成功之后,服务端会返回一个随机字符串,保存在客户端浏览器上,之后再次超服务端发送请求,只需要携带该随机字符串,服务端就能够识别当前用户身份。

cookie可以设置超时时间,超过一定的时间,cookie就会失效。

cookie虽然是保存在客户端浏览器上的,但是是服务端设置的,浏览器也是可以拒绝服务端的要求,不保存cookies。

操作cookie其实就是在操作HttpResponse(render,redirect,,,一次请求一次响应):

obj = HttpResponse('...')
return obj
 
obj1 = render(...)
return obj1
 
obj2 = redirect(...)
 
"""
通过对象的方式进行键值对的赋值
"""
 
# 设置cookie
obj.set_cookie()
# 获取cookie
request.COOKIES.get()
# 删除cookie
obj.delete_cookie()

Django中操作Cookie

获取Cookie

request.COOKIES['key']
request.COOKIES.get(key)
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)

参数:

  • default: 默认值
  • salt: 加密盐
  • max_age: 后台控制过期时间

设置Cookie

rep = HttpResponse(...)
rep = render(request, ...)
 
rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密盐', max_age=None, ...)

参数:

  • key, 键
  • value='', 值
  • max_age=None, 超时时间
  • expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
  • path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
  • domain=None, Cookie生效的域名
  • secure=False, https传输
  • httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

删除Cookie

def logout(request):
    rep = redirect("/login/")
    rep.delete_cookie("user")  # 删除用户浏览器上之前设置的usercookie值
    return rep

利用cookie写登录功能

1、先定义路由和视图的关系

# urls.py
 
url(r'^login/', views.login)

2、编写登录页面

# login.html
 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<form action="" method="post">
    <p>username:<input type="text" name="username"></p>
    <p>password:<input type="text" name="password"></p>
    <input type="submit">
</form>
</body>
</html>

3、编写视图函数

# views.py
 
# 登录函数
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'jason' and password == '123':
            # 保存用户登录状态
            obj = redirect('/home')
            # 设置cookie
            obj.set_cookie('username', 'jason')
            return obj
    return render(request, 'login.html')
 
 
# 功能函数
def home(request):
    # 检验浏览器是否有对应的cookie
    if request.COOKIES.get('username'):
        return HttpResponse('我是home页面,只要登录成功就可以看到我。')
    else:
        return redirect('/login')

上面简单的写了利用cookie的登录功能,但是如果有很多功能都需要验证是否携带cookie,我们不可能在每个功能中去验证,因此需要我们写一个登录认证装饰器。

登录认证装饰器版

views.py中添加登录认证装饰器

# 登录认证装饰器
from functools import wraps
 
 
def login_auth(func):
    @wraps(func)
    def inner(request, *args, **kwargs):
        if request.COOKIES.get('username'):
            res = func(request, *args, **kwargs)
            return res
        else:
            return redirect('/login')
    return inner
 
 
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'jason' and password == '123':
            # 保存用户登录状态
            obj = redirect('/home')
            # 设置cookie
            obj.set_cookie('username', 'jason')
            return obj
    return render(request, 'login.html')
 
 
@login_auth
def home(request):
    return HttpResponse('我是home页面,只要登录成功就可以看到我。')

高级版登录功能

需求一:

当用户访问A网站,但是发现没有登录,将用户跳转到登录页面,用户登录成功后,自动跳转到原来想访问的A页面。

需求二:

当用户没有访问其他页面,直接访问登录页面,登录成功后,将用户跳转到home页面。

request的方法:

通过request拿到用户想要访问的路径。

print('request.path_info:',request.path_info)  # 只拿路径部分 不拿参数
print('request.get_full_path():',request.get_full_path())  # 路径加参数
 
# 打印结果
request.path_info: /home/
request.get_full_path(): /home/?username=jason&password=123

views.py文件改动代码如下:

# 登录认证装饰器
from functools import wraps
 
 
def login_auth(func):
    @wraps(func)
    def inner(request, *args, **kwargs):
        target_url = request.path_info  # 获取路径
        if request.COOKIES.get('username'):
            res = func(request, *args, **kwargs)
            return res
        else:
            return redirect(f'/login/?next={target_url}')  # 将路径拼接到url后面
    return inner
 
 
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'jason' and password == '123':
            # 第一种方式,若next后面没有路径,直接跳转到默认值
            # target_url = request.GET.get('next', '/home/')
            # 第二种方式,对next后面有没有路径进行判断
            target_url = request.GET.get('next')
            if target_url:
                # 保存用户登录状态
                obj = redirect(target_url)
            else:
                obj = redirect('/home/')
            # 设置cookie
            obj.set_cookie('username', 'jason')
            return obj
    return render(request, 'login.html')

退出登录功能,清除cookie

urls.py新退出登录的路由与视图的关系

# urls.py
 
url(r'^logout/', views.logout)

views.py中新增退出的相关视图函数

# views.py
 
@login_auth
def logout(request):
    obj = HttpResponse('注销成功!')
    obj.delete_cookie('username')
    return obj

设置cookie的超时时间

obj.set_cookie('username', 'jason', max_age=3)

session语法

# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']
 
 
# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()
 
# 会话session的key
request.session.session_key
 
# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()
 
# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")
 
# 删除当前会话的所有Session数据
request.session.delete()
 
# 删除当前的会话数据并删除会话的Cookie。
request.session.flush()
    这用于确保前面的会话数据不可以再次被用户的浏览器访问
    例如,django.contrib.auth.logout() 函数中就会调用它。
 
# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
    * 如果value是个整数,session会在些秒数后失效。
    * 如果value是个datatime或timedelta,session就会在这个时间后失效。
    * 如果value是0,用户关闭浏览器session就会失效。
    * 如果value是None,session会依赖全局session失效策略。

保存在服务端上的键值对

设置:request.session['key'] = value

  • django内部会自动生成一个随机字符串
  • 去django_session表中存储数据,键就是随机字符串,值就是要保存的数据(中间件干的)
  • 将生成的随机字符串返回给客户端浏览器,浏览器保存键值对(sessionid:随机字符串)
# urls.py
 
url(r'^set_session/', views.set_session)
 
 
# views.py
 
def set_session(request):
    request.session['username'] = 'sean'
    return HttpResponse('set_session')

获取:request.session.get('key')

  • django会在自动取出浏览器的cookie查找sessionid键值对,获取随机字符串
  • 拿着该随机字符串去django_session表中对比数据
  • 如果对比上了,就讲随机字符串对应的数据获取出来并封装到request.session供用户调用
# urls.py
 
url(r'^get_session/', views.get_session)
 
 
 
# views.py
 
def get_session(request):
    print(request.session.get('username'))
    return HttpResponse('get_session')

django中默认的超时时间为14天

Guess you like

Origin www.cnblogs.com/cnhyk/p/12274275.html