Django高级之forms组件

forms组件之校验字段

# 第一步:定义一个类,继承forms.Form
# 第二步:在类中写字段,要校验的字段,字段属性就是校验规则
# 第三步:实例化得到一个Form对象,把要校验的数据传入
# 第四步:调用register_form.is_valid()校验,校验通过就是True
# 第五步:校验通过有register_form.cleaned_data
# 第六步:校验不通过 register_form.errors
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册用户</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="col-md-6 col-md-offset-3">
    <div class="panel panel-primary">
        <div class="panel-heading">
            <h3 class="panel-title">注册功能</h3>
        </div>
        <div class="panel-body">
            <h1 class="text-center">注册</h1>
            <form action="" method="post">
                <p>用户名:<input type="text" name="name" class="form-control"></p>
                <p>密码:<input type="password" name="password" class="form-control"></p>
                <p>确认密码:<input type="password" name="re_password" class="form-control"></p>
                <p>邮箱:<input type="text" name="email" class="form-control"></p>
                <input id="id_btn" type="submit" class="btn btn-primary btn-block">
        </div>
    </div>
</div>
</body>
</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

渲染方式一

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

 <h2>通过form自动渲染一</h2>
<form action="" method="post">
     <p>用户名 {{ form.name }}</p>   
     <p>密码 {{ form.password }}</p>    
    <p>确认密码 {{ form.re_password }}</p>    
    <p>邮箱 {{ form.email }}</p>    
<input type="submit" value="提交"></form>

渲染方式二

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

<h2>通过form自动渲染二(基本用这种)</h2>

<form action="" method="post">
    {% for item in form %}
        <p>{{ item.label }}{{ item }}</p>                    
    {% endfor %}
<input type="submit" value="提交"><span style="color: red">{{ error }}</span>

</form>

渲染方式三

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

<h2>通过form自动渲染三</h2>
<form action="" method="post">
    {{ form.as_p }}
    {#    {{ form.as_table }}#}
    {#    {{ form.as_ul }}#}

</form>

forms组件之渲染错误信息

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页面</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="col-md-6 col-md-offset-3">
    <div class="panel panel-primary">
        <div class="panel-heading">
            <h3 class="panel-title text-center"></h3>
        </div>
        <div class="panel-body">
            <h1 class="text-center">注册</h1>
            <form action="" method="post" novalidate>
                {% for item in form %}
                    <div class="form-group">
                        <p>{{ item.label }}{{ item }} <span style="color: red">{{ item.errors.0 }}</span></p>
                    </div>

                {% endfor %}
                <input type="submit" value="提交" class="form-control"><span style="color: red">{{ error }}</span>

            </form>
        </div>
    </div>
</div>

</body>
</html>
html页面

后端views中定义form类:

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

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
from django.conf.urls import url
from app01 import views

urlpatterns = [
    url(r'^register/$',views.register)
]
路由层urls.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>模板层注册页面</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title text-center"></h3>
                </div>
                <div class="panel-body">
                    <h1 class="text-center">注册</h1>
                    <form action="" method="post" novalidate>
                        {% for item in form %}
                            <div class="form-group">
                                <p>{{ item.label }}{{ item }} <span style="color: red">{{ item.errors.0 }}</span></p>
                            </div>

                        {% endfor %}
                        <input type="submit" value="提交" class="btn btn-primary btn-block"><span style="color: red">{{ error }}</span>

                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>
模板层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
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.css' %}">
    <script src="{% static 'js/jQuery3.6.js' %}"></script>
    <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.js' %}"></script>
</head>
<body>

    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h2 class="panel-title text-center">
                            <span class="glyphicon glyphicon-plus"></span>
                            添加用户
                        </h2>
                    </div>

                    <div class="panel-body">
                        <form method="post">
                            {% csrf_token %}
                            {% for obj in user_data %}
                                <div class="form-group">
                                <label>{{ obj.label }}</label>
                                {{ obj }}
                                 </div>
                            {% endfor %}
                            <button type="submit" class="btn btn-primary btn-block">添 加</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
</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

 

posted @ 2021-03-28 23:24  山风有耳  阅读(92)  评论(0编辑  收藏  举报