11.Django高级之forms组件


forms组件之校验字段

# 第一步:定义一个类,继承forms.Form
# 第二步:在类中写字段,要校验的字段,字段属性就是校验规则
# 第三步:实例化得到一个Form对象,把要校验的数据传入
# 第四步:调用register_form.is_valid()校验,校验通过就是True
# 第五步:校验通过有register_form.cleaned_data
# 第六步:校验不通过 register_form.errors

"en">

    "UTF-8">
    注册用户
    
    "https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    


class="col-md-6 col-md-offset-3">
class="panel panel-primary">
class="panel-heading">

class="panel-title">注册功能

class="panel-body">

class="text-center">注册

"" method="post">

用户名:"text" name="name" class="form-control">

密码:"password" name="password" class="form-control">

确认密码:"password" name="re_password" class="form-control">

邮箱:"text" name="email" class="form-control">

"id_btn" type="submit" class="btn btn-primary btn-block">
校验手动渲染示例html
校验手动渲染示例html
class User(models.Model):
    name = models.CharField(max_length=32,verbose_name='用户名')
    password = models.CharField(max_length=32,verbose_name='密码')
    email = models.EmailField(verbose_name='邮箱')
创建用户表models.py
# views.py

from django.shortcuts import render
from django import forms

#定义类
class RegisterForm(forms.Form):
    # name字符串类型最大8位,最小3位
    name = forms.CharField(max_length=8, min_length=3, label='用户名')
    # password字符串类型最大8位,最小3位
    password = forms.CharField(max_length=8, min_length=3, label='密码')
    # re_password字符串类型最大8位,最小3位
    re_password = forms.CharField(max_length=8, min_length=3, label='确认密码')
    # email必须符合邮箱格式,xxx@xx.com
    email = forms.EmailField(label='邮箱')
   

#在视图中使用
register_form = RegisterForm(request.POST)
if register_form.is_valid():
      # 校验通过,存
      # 取出校验通过的数据
      print('校验通过')
      print(register_form.cleaned_data)
    
        # 存储前先删除多余的字段
        register_form.cleaned_data.pop('re_password')
       # 将数据存入数据库的user表中
       models.User.objects.create(**register_form.cleaned_data)
        
else:
     # 校验不通过
     print('校验不通过')
     print(register_form.errors)

forms组件之渲染标签

def register(request):
    if request.method == 'GET':
        # GET请求没有数据,需要生成一个空form对象
        # 这个form跟下面没有关系,是get请求过来的得到一个空form
        register_form = RegisterFrom()
        # 传到前端页面后,通过form进行渲染
        return render(request, 'register.html', {'form': register_form})
    else:
        register_form = RegisterFrom(request.POST)

        if register_form.is_valid():
            print('效验通过')
            print(register_form.cleaned_data)
            register_form.cleaned_data.pop('re_password')
            models.User.objects.create(**register_form.cleaned_data)
        else:
            print('效验不通过')
            print(register_form.errors)

        return render(request,'register.html')

视图层:views.py
视图层:views.py

渲染方式一

可扩展性强,但是需要书写的代码太多,一般情况下不用

 

通过form自动渲染一

 

用户名 {{ form.name }}

   

密码 {{ form.password }}

   

确认密码 {{ form.re_password }}

   

邮箱 {{ form.email }}

    ?

渲染方式二

推荐使用,代码书写简单,并且可扩展性强

通过form自动渲染二(基本用这种)

{% for item in form %}

{{ item.label }}{{ item }}

{% endfor %} {{ error }}

渲染方式三

代码书写极少,封装程度太高,不便于后续的扩展,一般情况下只在本地测试使用

通过form自动渲染三

{{ form.as_p }} {# {{ form.as_table }}#} {# {{ form.as_ul }}#}

forms组件之渲染错误信息

前端渲染代码:(全部采用方式二)

from django import forms
from django.forms import widgets

class RegisterForm(forms.Form):
    name = forms.CharField(max_length=8, min_length=3, label='用户名',
                           error_messages={
                               'max_length': '用户名最长为8位',
                               'min_length': '用户名最短为3位',
                               'required': '用户名不能为空位'
                           },
                           widget=widgets.TextInput(attrs={'class':'form-control'}))

    password = forms.CharField(max_length=8, min_length=3, label='密码',
                               error_messages={
                                   'max_length': '密码最长为8位',
                                   'min_length': '密码最短为3位',
                                   'required': '密码不能为空'
                               },
                               widget=widgets.PasswordInput(attrs={'class':'form-control'}))

    re_password = forms.CharField(max_length=8, min_length=3, label='确认密码',
                                  error_messages={
                                      'max_length' : '密码最长为8位',
                                      'min_length' : '密码最短为3位',
                                      'required' : '密码不能为空'
                                  },
                                  widget=widgets.PasswordInput(attrs={'class':'form-control'}))

    email = forms.EmailField(label='邮箱',
                             error_messages={'required': '邮箱不能为空', 'invalid': '邮箱格式不正确'},
                             widget=widgets.TextInput(attrs={'class':'form-control'}))


# views视图函数处理部分:  
def register(request):
    if request.method == 'GET':
        register_form = RegisterForm()
        return render(request, 'register.html', {'form': register_form})
    else:
        register_form = RegisterForm(request.POST)
        if register_form.is_valid():
            print('校验通过')
            print(register_form.cleaned_data)
            register_form.cleaned_data.pop('re_password')
            models.User.objects.create(**register_form.cleaned_data)
            return HttpResponse('ok')
        else:
            # 校验不通过
            print('校验不通过')
            print(register_form.errors)

            return render(request, 'register.html', {'form': register_form})
html页面

forms组件参数配置

class Ret(Form):
    name = forms.CharField(max_length=10, min_length=2, label='用户名',
                           error_messages={
                               'required': '该字段不能为空',
                               'invalid': '格式错误', 
                               'max_length': '太长',
                               'min_length': '太短'},
                           widget=widgets.TextInput(attrs={'class':'form-control'}))

forms组件局部钩子,全局钩子

注意点:局部钩子拿什么,校验通过就返回什么。全局钩子拿什么,校验通过返回所有

   -局部钩子
       -def clean_字段名(self):
            -校验规则
            -如果通过,return 值
            -如果不通过,抛异常
   -全局钩子(多个字段校验)
        -def clean(self):
            -如果通过,return clean_data
            -如果不通过,抛异常

局部钩子

 def clean_name(self):  # name字段的局部钩子
        # 获取用户输入的用户名
        name = self.cleaned_data.get('name')
        # 校验名字不能以sb开头
        if name.startswith('sb'):
            # 校验不通过,必须抛异常,
            raise ValidationError('不能以sb开头')
        else:
            # 校验通过,再返回name对应的值
            return name

全局钩子

  def clean(self):   # 全局钩子
        password = self.cleaned_data.get('password')
        re_password = self.cleaned_data.get('re_password')
        if re_password != password:
            # 校验不通过
            self.add_error('re_password','两次密码不一致')
        else:
            # 局部钩子拿什么返回什么,全局钩子所有都返回
            return self.changed_data

forms组件总代码

-使用步骤:
        -写一个类,继承Form类
        -写字段,字段参数(限制该字段的长短)
        -错误信息中文:字段参数
        -widget:控制生成标签的属性
        -视图函数中:
            -实例化得到form对象时,把要校验的数据传入
            -is_valid():clean_data和errors就有值了
            -如果校验通过就存,不通过就给页面提示
       -渲染页面
            -for循环的方式渲染页面(在标签前后可以再加标签)
from django.db import models

# 创建用户表
class User(models.Model):
    name = models.CharField(max_length=32,verbose_name='用户名')
    password = models.CharField(max_length=32,verbose_name='密码')
    email = models.EmailField(verbose_name='邮箱')

模型层models.py
模型层models.py
from django.conf.urls import url
from app01 import views

urlpatterns = [
    url(r'^register/$',views.register)
]
路由层urls.py
from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError

class RegisterForm(forms.Form):
    name = forms.CharField(max_length=8, min_length=3, label='用户名',
                           error_messages={
                               'max_length': '用户名最长为8位',
                               'min_length': '用户名最短为3位',
                               'required': '用户名不能为空位'
                           },
                           widget=widgets.TextInput(attrs={
                               'class':'form-control'
                           }))

    password = forms.CharField(max_length=8, min_length=3, label='密码',
                               error_messages={
                                   'max_length': '密码最长为8位',
                                   'min_length': '密码最短为3位',
                                   'required': '密码不能为空'
                               },
                               widget=widgets.PasswordInput(attrs={
                                   'class':'form-control'
                               }))

    re_password = forms.CharField(max_length=8, min_length=3, label='确认密码',
                                  error_messages={
                                      'max_length' : '密码最长为8位',
                                      'min_length' : '密码最短为3位',
                                      'required' : '密码不能为空'
                                  },
                                  widget=widgets.PasswordInput(attrs={
                                      'class':'form-control'
                                  }))

    email = forms.EmailField(label='邮箱',
                             error_messages={
                                 'required': '邮箱不能为空',
                                 'invalid': '邮箱格式不正确'
                             },
                             widget=widgets.TextInput(attrs={
                                 'class':'form-control'
                                 
                                 
    def clean_name(self):  # name字段的局部钩子
        # 获取用户输入的用户名
        name = self.cleaned_data.get('name')
        # 校验名字不能以sb开头
        if name.startswith('sb'):
            # 校验不通过,必须抛异常,
            raise ValidationError('不能以sb开头')
        else:
            # 校验通过,再返回name对应的值
            return name

    def clean(self):   # 全局钩子
        password = self.cleaned_data.get('password')
        re_password = self.cleaned_data.get('re_password')
        if re_password != password:
            # 校验不通过
            self.add_error('re_password','两次密码不一致')
        else:
            # 局部钩子拿什么返回什么,全局钩子所有都返回
            return self.changed_data



from app01 import models


def register(request):
    if request.method == 'GET':
        # GET请求没有数据,需要生成一个空form对象
        # 这个form跟下面没有关系,是get请求过来的得到一个空form
        register_form = RegisterFrom()
        # 传到前端页面后,通过form进行渲染
        return render(request, 'register.html', {'form': register_form})

    else:
        # 实例化得到对象,传入要校验的数据
        register_form = RegisterForm(request.POST)
        if register_form.is_valid():
            # 校验通过,存
            # 取出校验通过的数据
            print('校验通过')
            print(register_form.cleaned_data)
            register_form.cleaned_data.pop('re_password')
            models.User.objects.create(**register_form.cleaned_data)
            return HttpResponse('ok')

        else:
            # 校验不通过
            print('校验不通过')
            print(register_form.errors)
          
            return render(request, 'register.html', {'form': register_form})
模板层register.html

视图层views中定义form类:

from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError

class RegisterForm(forms.Form):
    name = forms.CharField(max_length=8, min_length=3, label='用户名',
                           error_messages={
                               'max_length': '用户名最长为8位',
                               'min_length': '用户名最短为3位',
                               'required': '用户名不能为空位'
                           },
                           widget=widgets.TextInput(attrs={
                               'class':'form-control'
                           }))

    password = forms.CharField(max_length=8, min_length=3, label='密码',
                               error_messages={
                                   'max_length': '密码最长为8位',
                                   'min_length': '密码最短为3位',
                                   'required': '密码不能为空'
                               },
                               widget=widgets.PasswordInput(attrs={
                                   'class':'form-control'
                               }))

    re_password = forms.CharField(max_length=8, min_length=3, label='确认密码',
                                  error_messages={
                                      'max_length' : '密码最长为8位',
                                      'min_length' : '密码最短为3位',
                                      'required' : '密码不能为空'
                                  },
                                  widget=widgets.PasswordInput(attrs={
                                      'class':'form-control'
                                  }))

    email = forms.EmailField(label='邮箱',
                             error_messages={
                                 'required': '邮箱不能为空',
                                 'invalid': '邮箱格式不正确'
                             },
                             widget=widgets.TextInput(attrs={
                                 'class':'form-control'
                                 
                                 
    def clean_name(self):  # name字段的局部钩子
        # 获取用户输入的用户名
        name = self.cleaned_data.get('name')
        # 校验名字不能以sb开头
        if name.startswith('sb'):
            # 校验不通过,必须抛异常,
            raise ValidationError('不能以sb开头')
        else:
            # 校验通过,再返回name对应的值
            return name

    def clean(self):   # 全局钩子
        password = self.cleaned_data.get('password')
        re_password = self.cleaned_data.get('re_password')
        if re_password != password:
            # 校验不通过
            self.add_error('re_password','两次密码不一致')
        else:
            # 局部钩子拿什么返回什么,全局钩子所有都返回
            return self.changed_data



from app01 import models


def register(request):
    if request.method == 'GET':
        # GET请求没有数据,需要生成一个空form对象
        # 这个form跟下面没有关系,是get请求过来的得到一个空form
        register_form = RegisterFrom()
        # 传到前端页面后,通过form进行渲染
        return render(request, 'register.html', {'form': register_form})

    else:
        # 实例化得到对象,传入要校验的数据
        register_form = RegisterForm(request.POST)
        if register_form.is_valid():
            # 校验通过,存
            # 取出校验通过的数据
            print('校验通过')
            print(register_form.cleaned_data)
            register_form.cleaned_data.pop('re_password')
            models.User.objects.create(**register_form.cleaned_data)
            return HttpResponse('ok')

        else:
            # 校验不通过
            print('校验不通过')
            print(register_form.errors)
          
            return render(request, 'register.html', {'form': register_form})

forms组件源码分析

1 为什么局部钩子要写成 clean_字段名,为什么要抛异常
2 入口在 is_valid()
3 校验流程
    -先校验字段自己的规则(最大,最小,是否必填,是不是合法)
    -校验局部钩子函数
    -全局钩子校验
    
    
4 流程
    is_valid()---》return self.is_bound and not self.errors
    self.errors:方法包装成了数据数据
        一旦self._errors有值,就不进行校验了(之前调用过了)
    self.full_clean():核心
        self._errors = ErrorDict()
        if not self.is_bound:  
            return
        self.cleaned_data = {}
        self._clean_fields()
        self._clean_form()
        self._post_clean()
        
        
    self._clean_fields():核心代码,局部钩子执行位置
    
     value = field.clean(value)# 字段自己的校验规则
     self.cleaned_data[name] = value #把校验后数据放到cleaned_data
     if hasattr(self, 'clean_%s' % name): # 判断有没有局部钩子
        value = getattr(self, 'clean_%s' % name)() #执行局部钩子
        self.cleaned_data[name] = value #校验通过,把数据替换一下
       # 如果 校验不通过,会抛异常,会被捕获,捕获后执行
    self.add_error(name, e)
    
    def _clean_form(self):#全局钩子执行位置
    def _clean_form(self):
        try:
            #如果自己定义的form类中写了clean,他就会执行
            cleaned_data = self.clean()
        except ValidationError as e:
            self.add_error(None, e)

如何看源码

快速定位到当前py文件

查看当前py文件中有哪些类,哪些方法

forms组件之ModelForm

这是一个神奇的组件,通过名字我们可以看出来,这个组件的功能就是把model和form组合起来,先来一个简单的例子来看一下这个东西怎么用:比如我们的数据库中有这样一张用户表,字段有姓名,密码,年龄,账户余额,入职时间,性别,部门,等等一大堆信息,现在让你写一个添加用户的页面,你的后台应该怎么写呢?首先我们会在前端一个一个罗列出这些字段,让用户去填写,然后我们从后天一个一个接收用户的输入,创建一个新的用户对象,保存,重点不是这些,而是合法性验证,我们需要在前端判断用户输入是否合法,比如姓名必须在多少字符以内,电话号码必须是多少位的数字,邮箱必须是邮箱的格式这些当然可以一点一点手动写限制,各种判断,这毫无问题,除了麻烦我们现在有个更优雅(以后在Python相关的内容里,要多用“优雅”这个词,并且养成习惯)的方法:ModelForm先来简单的,生硬的把它用上,再来加验证条件。

from django.urls import path
from app01 import views
urlpatterns = [
    path('user/add/', views.user_add),
]
urls.py
from django.shortcuts import render, redirect, HttpResponse
from app01.models import Department, UserInfo
from django import forms

# 自定义一个类,继承ModelForm类
class UserModelForm(forms.ModelForm):
    class Meta:
        model = UserInfo  # 对应model中的类
        fields = ["name", "password", "age", "account", "create_time", "gender", "depart"]
        # fields = ['字段1','字段2',...]  或者这种用法,把需要展示的字段添加到这个列表中
        # fields = '__all__'  # 获取所有字段
     
    # 根据源码重写父类方法
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
       # 循环找到所有字段的插件,添加class = "form-control"
        for name, field in self.fields.items():
            # 不想让某个字段用样式可以做判断
            # if name == "password":
            #     continue
            field.widget.attrs = {"class": "form-control"}  # 可以自定义属性


def user_add(request):
    user_data = UserModelForm()  # 然后在url对应的视图函数中实例化这个类,把这个对象传给前端
    return render(request, 'user_add.html', {"user_data": user_data})

views.py
views.py
{% load static %}

"en">

    "UTF-8">
    Title
    "stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.css' %}">
    
    



    
class="container">
class="row">
class="col-md-8 col-md-offset-2">
class="panel panel-default">
class="panel-heading">

class="panel-title text-center"> class="glyphicon glyphicon-plus"> 添加用户

class="panel-body">
"post"> {% csrf_token %} {% for obj in user_data %}
class="form-group"> {{ obj }}
{% endfor %}
user_add.html
user_add.html
from django.db import models


class Department(models.Model):
    title = models.CharField(max_length=64, verbose_name="部门名称")

    def __str__(self):
        return self.title

class UserInfo(models.Model):
    name = models.CharField(max_length=32, verbose_name="姓名")
    password = models.CharField(max_length=64, verbose_name="密码")
    age = models.IntegerField(verbose_name="年龄")
    account = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name="账户余额")
    create_time = models.DateTimeField(verbose_name="入职时间")
    # 级联删除
    depart = models.ForeignKey(to="Department", to_field="id", on_delete=models.CASCADE, verbose_name="部门")
    gender_choices = (
        (1, ""),
        (2, "")
    )
    gender = models.SmallIntegerField(choices=gender_choices, verbose_name="性别")

models.py
models.py