• form组件


    form组件

    环境

    from django import forms  # 组件
    from django.core.exceptions import ValidationError  # 校验错误
    from django.forms import widgets  # 部件,定义样式
    

    定义表单

    class UserForm(forms.Form):
        username = forms.CharField(min_length=5, label="用户名", widget=forms.widgets.TextInput,
                                   error_messages={"required": "用户名不能为空", "min_length": "用户名不能少于5个字符"})
        password = forms.CharField(min_length=5, label="密码", widget=forms.widgets.PasswordInput,
                                   error_messages={"required": "密码不能为空", "min_length": "密码不能少于5个字符"})
        r_passowrd = forms.CharField(min_length=5, label="确认密码", widget=forms.widgets.PasswordInput,
                                     error_messages={"required": "确认密码不能为空", "min_length": "确认密码不能少于5个字符"})
        email = forms.EmailField(min_length=5, label="邮箱", widget=forms.EmailInput,
                                 error_messages={"required": "邮箱不能为空", "min_length": "邮箱不能少于5个字符"})
    	
        # 定义全局样式
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for field in self.fields.values():
                field.widget.attrs.update({"class": "form-control"})
    
        # 定义验证钩子, 此为自定义校验,第一次验证已经通过,这是第二次验证
        # 局部钩子
        # 判断用户名是否已经存在
        def clean_username(self):
            val = self.cleaned_data.get('username')
            user = UserInfo.objects.get(username=val)
            if user:
                raise ValidationError('用户名已经存在')
            else:
                return val  # 必须返回val
            
         #全局钩子
        def clean(self):
            pwd = self.cleaned_data.get('password')
            r_pwd = self.cleaned_data.get('r_password')
            if pwd and r_pwd and pwd != r_pwd:  # 同时有值并且不相等则抛异常
                raise ValidationError('两次密码不一致')
            else:
                return self.cleaned_data
    

    is_valid()源码解析

    form组件核心方法

    • is_valid

      • self.is_bound--> True, self.errors 为 True 则校验通过
          def is_valid(self):
              """Return True if the form has no errors, or False otherwise."""
              return self.is_bound and not self.errors
      
    • self.errors

          @property
          def errors(self):
              """Return an ErrorDict for the data provided for the form."""
              if self._errors is None:
                  self.full_clean()
              return self._errors
      
    • self.full_clean()

      ​ 校验接收的表单数据

      • 定义self._errors 为字典(校验错误的字典)
      • 定义self.cleaned_data 为字典(校验通过的字典)
      • 执行self._clean_fields()进行校验form字段
          def full_clean(self):
              """
              Clean all of self.data and populate self._errors and self.cleaned_data.
              """
              self._errors = ErrorDict()
              if not self.is_bound:  # Stop further processing.
                  return
              self.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
      
              self._clean_fields()  # 基础规则 与 局部钩子
              self._clean_form()	  # 全局钩子 不同字段间的比较
              self._post_clean()
      
    • self._clean_fields()

      表单格式基础验证 与 局部钩子(自定义字段验证)

      class UserForm(forms.Form):	
          username = forms.CharField(min_length=6)
      user_form = UserForm({"username": "admin"})
      
      • self.fields:

        form表单字典:{name:field}: {"username": username}

      • name: form表单字段名: "username"

      • field:form表单字段对象: usernameforms.CharField()类实例化的username对象(规则对象)

      • value: 表单实例化对象后,传入的需要校验的字段的值: "admin"

          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.
                  if field.disabled:
                      value = self.get_initial_for_field(field, name)
                  else:
                      value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
                  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)
      
      • 循环表单字典{name:field}

        表单校验:

        • 校验通过

          1. value = field.clean(value): 校验value。self.cleaned_data[name] = value, 存入cleaned_data{"username": "admin"}

          2. 通过反射检查是否有自定义校验'clean_%s' % name,即 clean_字段名 方法: clean_username,通过则self.cleaned_data[name] = value

            前提为通过了第一步的校验

              def clean_username(self):
                  val = self.cleaned_data.get('username')
                  user = UserInfo.objects.get(username=val)
                  if user:
                      raise ValidationError('用户名已经存在')
                  else:
                      return val  # 必须返回val
          
        • 校验不通过

          1. self.add_error(name, e),如没有通过第一步的校验,则直接将 {"username": 错误信息} 存入self._errors,不执行第二步的反射方法校验

            self._errors 字典(django.forms.utils.ErrorDict)

            错误信息: 列表 (django.forms.utils.ErrorList)

            表单对象.errors.get("username")[0], 取得错误描述

          2. 如第一步校验通过,第二步反射方法校验不通过,依然会将没有通过校验的值存入cleaned_data,最后add_error()中将没有通过第二步校验的字段名、错误信息 键值对存入self._errors,并将键值从cleaned_data中删除

    • self._clean_form()

    全局钩子, 不同字段间校验

    add_error(None,e)增加键为"__all__"的异常, 如: {"__all__":[" 两次的密码不一致", ], }

    指定form字段异常,需重写clean()方法

        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
    

    self.clean()

    需重写

    self.add_error(表单字段名,异常), 指定form表单字段异常

        def clean(self):
            """
            Hook for doing any extra form-wide cleaning after Field.clean() has been
            called on every field. Any ValidationError raised by this method will
            not be associated with a particular field; it will have a special-case
            association with the field named '__all__'.
            """
            return self.cleaned_data
    
        # 重写父类clean方法
        def clean(self):
            pwd = self.cleaned_data.get('password')
            r_pwd = self.cleaned_data.get('r_password')
            if pwd and r_pwd and pwd != r_pwd:  # 同时有值并且不相等则抛异常
                self.add_error("r_password", "两次密码不一致")
                raise ValidationError('两次密码不一致')
            else:
                return self.cleaned_data
    
    
    • 为r_password增加异常

    注册登录实例

    settings.py

    # 用户认证组件扩展字段添加配置,
    AUTH_USER_MODEL = "app01.UserInfo"
    

    models.py

    # 扩展用户组件后,原auth_user表移除,变为uaerinfo表
    from django.db import models
    from django.contrib.auth.models import AbstractUser
    
    
    # Create your models here.
    
    class UserInfo(AbstractUser):
        tel = models.CharField(max_length=32)
    

    urls.py

    from django.contrib import admin
    from django.urls import path
    from apps.app01 import views
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('login/', views.login),
        path('get_valid_code', views.get_valid_code, name='get_valid_code'),
        path('reg/', views.reg)
    ]	
    

    views.py

    import random
    import re
    import io
    from PIL import Image, ImageDraw, ImageFont
    from django.shortcuts import render
    from django.http import HttpResponse, JsonResponse
    from django.contrib import auth
    from django import forms
    from django.core.exceptions import ValidationError
    from django.forms import widgets
    from apps.app01.models import UserInfo
    
    
    # Create your views here.
    # form 表单验证
    class UserForm(forms.Form):
        username = forms.CharField(min_length=5, label="用户名", widget=forms.widgets.TextInput,
                                   error_messages={"required": "用户名不能为空", "min_length": "用户名不能少于5个字符"})
        password = forms.CharField(min_length=5, label="密码", widget=forms.widgets.PasswordInput,
                                   error_messages={"required": "密码不能为空", "min_length": "密码不能少于5个字符"})
        r_password = forms.CharField(min_length=5, label="确认密码", widget=forms.widgets.PasswordInput,
                                     error_messages={"required": "确认密码不能为空", "min_length": "确认密码不能少于5个字符"})
        email = forms.EmailField(min_length=5, label="邮箱", widget=forms.EmailInput,
                                 error_messages={"required": "邮箱不能为空", "min_length": "邮箱不能少于5个字符", "invalid": "邮箱格式错误"})
    
        # 自定义全局样式
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for field in self.fields.values():
                field.widget.attrs.update({"class": "form-control"})
    
        # 定义验证钩子, 此为自定义校验,第一次验证已经通过,这是第二次验证
        # 局部钩子
        # 判断用户名是否已经存在
        def clean_username(self):
            val = self.cleaned_data.get('username')
            try:
                UserInfo.objects.get(username=val)
            except Exception as e:
                return val
            else:
                raise ValidationError('用户名已经存在')
    
        # 密码不能有纯数字
        def clean_password(self):
            val = self.cleaned_data.get('password')
            if val.isdigit():
                raise ValidationError('密码不能是纯数字')
            else:
                return val
    
        # 邮箱必须是163邮箱
        def clean_email(self):
            val = self.cleaned_data.get('email')
            ret = re.match(r'w+@163.com$', val)
            if not ret:
                raise ValidationError('必须为163邮箱')
            else:
                return val
    
        # 定义全局钩子
        def clean(self):
            pwd = self.cleaned_data.get('password')
            r_pwd = self.cleaned_data.get('r_password')
            if pwd and r_pwd and pwd != r_pwd:  # 同时有值并且不相等则抛异常
                self.add_error("r_password", "两次密码不一致")
                raise ValidationError('两次密码不一致')
            else:
                return self.cleaned_data
    
    # 登录
    def login(request):
        if request.method == "POST":
            res_code = {"user": None, "state": None}
            # 取得session中网页请求中的验证码
            username = request.POST.get('username')
            password = request.POST.get('password')
            valid_code = request.POST.get('valid_code')
            session_valid_code = request.session.get('keep_str')
            if valid_code.upper() == session_valid_code.upper():
                user_obj = auth.authenticate(username=username, password=password)
                if user_obj:
                    res_code['user'] = username
                else:
                    res_code["state"] = "用户名或者密码错误"
            else:
                res_code["state"] = "验证码错误"
            return JsonResponse(res_code)
        else:
            return render(request, 'login.html')
    
    
    def get_random_color():
        return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
    
    
    def get_random_chr():
        rand_digi = str(random.randint(0, 9))
        rand_upper_alpha = chr(random.randint(65, 90))
        rand_lower_alpha = chr(random.randint(97, 122))
        rand_chr = random.choice([rand_digi, rand_upper_alpha, rand_lower_alpha])
        return rand_chr
    
    
    # 验证码图片生成,生成session
    def get_valid_code(request):
        img = Image.new('RGB', (160, 35), get_random_color())
        draw = ImageDraw.Draw(img)
        font = ImageFont.truetype('static/fonts/msMonaco.ttf', 32)
    
        # 验证码图片设置文字
        keep_str = ""
        for i in range(4):
            rand_chr = get_random_chr()
            draw.text((i * 30 + 20, 0), rand_chr, get_random_color(), font=font)
            keep_str += rand_chr
    
        # 图片加噪点
        width = 160
        height = 35
        # 画线
        for i in range(10):
            x1 = random.randint(0, width)
            x2 = random.randint(0, width)
            y1 = random.randint(0, height)
            y2 = random.randint(0, height)
            draw.line((x1, y1, x2, y2), fill=get_random_color())
        # 画点和弧
        for i in range(10):
            draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color())
            x = random.randint(0, width)
            y = random.randint(0, height)
            draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_random_color())
    
        # 图片验证码保存在session中,login调用比对
        request.session['keep_str'] = keep_str
    
        # 内存读写
        f = io.BytesIO()
        img.save(f, "png")
        data = f.getvalue()
    
        return HttpResponse(data)
    
    # 注册
    def reg(request):
        if request.method == 'POST':
            print(request.POST)
            res = {"user": None, "err_msg": None}
            form = UserForm(request.POST)
            if form.is_valid():
                res["usr"] = request.POST.get("username")
            else:
                res["err_msg"] = form.errors
            return JsonResponse(res)
        else:
            user_form = UserForm()
            return render(request, 'reg.html', {"user_form": user_form})
    
    

    login.html

    {% load static %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
    <script src="{% static 'js/jquery.js' %}"></script>
    <body>
    <h1>Login Page</h1>
    <div class="container">
        <div class="row">
            <div class="col-md-4">
                <form action="" method="post">
                    {% csrf_token %}
                    <div class="form-group">
                        <label for="username">用户名</label>
                        <input type="text" class="form-control" id="username" name="username">
                    </div>
                    <div class="form-group">
                        <label for="password">密码</label>
                        <input type="password" class="form-control" id="password" name="password">
                    </div>
                    <div class="form-group">
                        <label for="valid_code">验证码</label>
                        <div class="row">
                            <div class="col-md-6">
                                <input type="text" class="form-control" id="valid_code" name="valid_code">
                            </div>
                            <div class="col-md-6">
                                <img src="{% url 'get_valid_code' %}" width="160" height="35" alt="" id="img">
                            </div>
                        </div>
                    </div>
                    <div class="form-group">
                        <input type="button" class="btn btn-primary pull-right" id="valid_btn" value="登陆">
                        <span class="err_msg"></span>
                    </div>
    
                </form>
            </div>
        </div>
    </div>
    
    <script>
        $("#valid_btn").click(function () {
            const username = $("#username").val();
            const password = $("#password").val();
            const valid_code = $("#valid_code").val();
            $.ajax({
                url: "",
                type: "post",
                data: {
                    csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val(),
                    username: username,
                    password: password,
                    valid_code: valid_code,
                },
                success: function (response) {
                    console.log(response.user);
                    if (response.user) {
                        console.log('ok');
                        location.href = 'http://www.baidu.com'
                    } else {
                        $(".err_msg").html(response.state).css("color", "red")
                    }
    
                }
    
            })
        });
        // 验证码点击刷新
        $("#img").click(function () {
            this.src += "?"
    
        })
    </script>
    
    </body>
    </html>
    

    reg.html

    {% load static %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
    <script src="{% static 'js/jquery.js' %}"></script>
    <body>
    <h1>register Page</h1>
    <div class="container">
        <div class="row">
            <div class="col-md-4">
                <form action="">
                    {% csrf_token %}
                    {% for field in user_form %}
                        <div class="form-group">
                            <label for="{{ field.label }}">{{ field.label }}</label>
                            {{ field }} <span class="errs pull-right"></span>
                        </div>
                    {% endfor %}
    
                    <div class="form-group">
                        <input type="button" class="btn btn-primary pull-left" id="reg_btn" value="注册">
                        <span class="err_msg"></span>
                    </div>
                </form>
            </div>
        </div>
    </div>
    
    <script>
    
        $("#reg_btn").click(function () {
    
            $.ajax({
                url: "",
                type: "post",
                data: {
                    username: $("#id_username").val(),
                    password: $("#id_password").val(),
                    r_password: $("#id_r_password").val(),
                    email: $("#id_email").val(),
                    csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val(),
                },
                success: function (response) {
                    // 点击先清除错误信息
                    $(".errs").html("");
                    // 移除class属性为form-group的标签的 has-error属性,此处为input标签的父标签div标签
                    $(".form-group").removeClass("has-error");
    
                    // 循环标签, 为显示错误的span添加错误信息
                    $.each(response.err_msg, function (i, j) {
                        console.log(i, j);
                        $("#id_" + i).next().html(j[0]).css("color", "red").parent().addClass("has-error")
                        // 链式操作
                        // i 字段名, j 错误信息
                        // "#id_"+i 对应字段名为i的input标签
                        // next() input标签的下一个标签: span标签
                        // html(j[0], span标签文本赋值, j[0], js数组的第一个值
                        // css("color", "red") 添加css样式
                        // parent() 父标签,span的父标签 即<div calss="form-group">标签
                        // addClass("has-error") 添加div标签class属性
    
                    })
                }
    
            })
    
        })
    
    </script>
    
    </body>
    </html>
    
  • 相关阅读:
    JDBC笔记
    MySQL索引
    MySQL事务
    联想笔记本更新BIOS后无法开机,显示自动修复?
    Mysql(笔记)
    [ERROR] Some problems were encountered while processing the POMs: 'modules.module[1]' specifies duplicate child module servlet-01 @ line 13, column 17
    JavaWeb(笔记)
    I/O方式(本章最重要)
    I/O接口
    I/O-外部设备
  • 原文地址:https://www.cnblogs.com/relaxlee/p/12952080.html
Copyright © 2020-2023  润新知