欢迎来到赛兔子家园

Django中form组件

form组件简介

主要作用

  • 数据校验 (对用户提交的数据格式进行校验)
  • 生成html标签 + 携带数据(保留原来提交的数据,不再担心form表单提交时页面刷新;编辑页面显示默认值)
  • 前端页面显示错误信息

特征:

form组件可以脱离数据库,当我们有对数据校验的需求时,就可以用form组件进行数据校验。

前端不同提交方式作用:

1、form表单提交

  • 生成HTML标签 + 携带数据

  • 数据校验,对用户提交的数据格式校验

2、ajax提交

  • 数据校验,对用户提交的数据格式校验
Form组件使用

默认是英文错误提示信息,如何修改为中文?

方法1:

修改settings.py

# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-hans'

方法2:

自定义错误信息error_messages字典

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2022/9/26 11:16'
# software: PyCharm

from django import forms

class UserForm(forms.Form):
    use = forms.CharField(min_length=5,max_length=13,error_messages={"required":"用户名不能为空"})
    pwd = forms.CharField(error_messages={"required":"密码不能为空"})
    email = forms.EmailField(error_messages={"required":"邮箱不能为空","invalid":"邮箱格式错误"})

error_messages字典中可以写多种错误信息。

常用三个方法:

is_valid()            form组件类中定义的所有字段,都通过其规则校验的话,返回True,有一项或者若干项校验失败,返回False。
cleaned_data     只有先经过is_valid校验后,才能使用该方法进行获取干净的数据。
errors                 校验失败的错误信息,是个字典,存放一个或多个错误信息。

示例:

models.py

class UserProfile(models.Model):
    """
    用户表
    """
    user = models.CharField(verbose_name="用户名",max_length=32)
    pwd = models.CharField(verbose_name="密码",max_length=64)
    email = models.EmailField(verbose_name="邮箱",max_length=32)

    def __str__(self):
        return self.user
models
from django.contrib import admin
from django.urls import path
from web.views import formviews

urlpatterns = [
    path('admin/', admin.site.urls),
    path('register/', formviews.register, name="register"),
]
urls
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2022/9/26 11:09'
# software: PyCharm

from django.shortcuts import redirect,render,HttpResponse,reverse

from web.forms.bookform import UserForm
from web import models

def register(request):
    if request.method == "POST":
        forms_obj = UserForm(request.POST)
        if forms_obj.is_valid(): # 必须先进行is_valid,才能获取
            models.UserProfile.objects.create(**forms_obj.cleaned_data)
            return HttpResponse("注册用户成功")
        else:
            # 错误信息包含在forms_obj对象中,前端通过该模板对象进行提取
            return render(request,"register.html",{"forms_obj":forms_obj})
    return render(request,"register.html")
views
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>form组件</title>
    <style>
        .errorMsg {
            color: red;
            margin-left: 10px;
        }
    </style>
</head>
<body>
<form action="" method="post">
    {% csrf_token %}
    <p>用户名 <input type="text" name="user"> <span class="errorMsg">{{ forms_obj.errors.user.0 }}</span></p>
    <p>密 码 <input type="password" name="pwd"> <span class="errorMsg">{{ forms_obj.errors.pwd.0 }}</span></p>
    <p>邮 箱 <input type="text" name="email"> <span class="errorMsg">{{ forms_obj.errors.email.0 }}</span></p>
    <p><input type="submit"></p>
</form>
</body>
</html>
register

以上最基础的示例,存在很多问题:

  • 返回错误信息时,没有问题的字段内容,由于页面刷新也被清空了
  • 自定义更多更复杂的校验规则,如两次密码输入不一致的问题等等
form组件提供几种前端标签渲染方式

基础版

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2022/9/26 11:16'
# software: PyCharm

from django import forms

class UserForm(forms.Form):
    use = forms.CharField(min_length=5,max_length=13,error_messages={"required":"用户名不能为空"})
    pwd = forms.CharField(error_messages={"required":"密码不能为空"})
    email = forms.EmailField(error_messages={"required":"邮箱不能为空","invalid":"邮箱格式错误"})
views
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2022/9/26 11:16'
# software: PyCharm

from django import forms

class UserForm(forms.Form):
    user = forms.CharField(min_length=5,max_length=13,error_messages={"required":"用户名不能为空"})
    pwd = forms.CharField(error_messages={"required":"密码不能为空"})
    email = forms.EmailField(error_messages={"required":"邮箱不能为空","invalid":"邮箱格式错误"})
forms
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>form组件</title>
    <style>
        .errorMsg {
            color: red;
            margin-left: 10px;
        }
    </style>
</head>
<body>
<form action="" method="post">
    {% csrf_token %}
    <p>用户名 <input type="text" name="user"> <span class="errorMsg">{{ forms_obj.errors.user.0 }}</span></p>
    <p>密 码 <input type="password" name="pwd"> <span class="errorMsg">{{ forms_obj.errors.pwd.0 }}</span></p>
    <p>邮 箱 <input type="text" name="email"> <span class="errorMsg">{{ forms_obj.errors.email.0 }}</span></p>
    <p><input type="submit"></p>
</form>
</body>
</html>
register.html

这种方式最大的问题,就是数据校验不通过,前端页面因为刷新,导致没问题的input的值也没了;

中级版

这个版本倒是省心省力,前端input标签不需要自己写,form组件帮我们做了,但也因此少了灵活性,用的也少,后端代码不需要做特殊处理,记得form标签需要加novalidate(目的是避免浏览器帮我们做数据校验),csrf_token和提交按钮不会自动生成,还需要我们自己处理。

特点就是错误信息需要我们手动处理,但解决前端页面因为刷新,导致没问题的input的值没了的问题。

# 进阶版
def register(request):
    if request.method == "POST":
        forms_obj = UserForm(request.POST)
        if forms_obj.is_valid():  # 必须先进行is_valid,才能获取
            models.UserProfile.objects.create(**forms_obj.cleaned_data)
            return HttpResponse("注册用户成功")
        else:
            # 前端input标签中没有错误的输入值会保留,这里返回给前端
            return render(request, "register.html", {"forms_obj": forms_obj})
    # 需要在给页面的的时候,就传递一个forms_obj对象,方便生成相应的标签
    forms_obj = UserForm()
    return render(request, "register.html",{"forms_obj":forms_obj})
views
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>form组件</title>
    <style>
        .errorMsg {
            color: red;
            margin-left: 10px;
        }
    </style>
</head>
<body>
<form action="" method="post" novalidate>
    {% csrf_token %}
    <!-- 直接as_p 啥都有了 -->
    {{ forms_obj.as_p }}
    <p><input type="submit"></p>
</form>

</body>
</html>
register

高级版

前端代码部分手写,使用比较多。当有错误提示时,没有错误的字段输入值会保留。

views.py

def register(request):
    if request.method == "POST":
        forms_obj = UserForm(request.POST)
        if forms_obj.is_valid():  # 必须先进行is_valid,才能获取
            models.UserProfile.objects.create(**forms_obj.cleaned_data)
            return HttpResponse("注册用户成功")
        else:
            return render(request, "register.html", {"forms_obj": forms_obj})
    # 需要在给页面的的时候,就传递一个forms_obj对象,方便生成相应的标签
    forms_obj = UserForm()
    return render(request, "register.html",{"forms_obj":forms_obj})
views
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>form组件</title>
    <style>
        .errorMsg {
            color: red;
            margin-left: 10px;
        }
    </style>
</head>
<body>
<form action="" method="post" novalidate>
    {% csrf_token %}
    <!--想渲染什么字段,就forms对象点啥字段 -->
    <p>用户名 {{ forms_obj.user }}<span class="errorMsg">{{ forms_obj.errors.user.0 }}</span></p>
    <p>密 码 {{ forms_obj.pwd }}<span class="errorMsg">{{ forms_obj.errors.pwd.0 }}</span></p>
    <p>邮 箱 {{ forms_obj.email }}<span class="errorMsg">{{ forms_obj.errors.email.0 }}</span></p>
    <p><input type="submit"></p>
</form>
</body>
</html>
register

最终版(常用版)

这个版本中,后端加上了更多的内容,例如:渲染标签时,添加一些属性值,让其具有相应的样式。使用到booststrap。

views.py

def register(request):
    if request.method == "GET":
        form_obj = UserForm()
        return render(request, "register.html", {"forms_obj": form_obj})
    form_obj = UserForm(request.POST)
    if not form_obj.is_valid():
        return render(request, "register.html", {"forms_obj": form_obj})

    User.objects.create(**form_obj.cleaned_data)
    return HttpResponse("添加成功")
views
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2022/9/26 11:16'
# software: PyCharm

from django import forms
from django.forms import widgets


class UserForm(forms.Form):
    user = forms.CharField(
        min_length=5, max_length=13,
        error_messages={"required": "用户名不能为空"},
        widget=widgets.TextInput(attrs={"class": "form-control"}),
        label='用户名'
    )
    pwd = forms.CharField(
        error_messages={"required": "密码不能为空"},
        widget=widgets.PasswordInput(attrs={"class": "form-control"}),
        label="密码"
    )
    email = forms.EmailField(
        error_messages={
            "required": "邮箱不能为空",
            "invalid": "邮箱格式错误"
        },
        widget=widgets.EmailInput(attrs={"class": "form-control"}),
        label="邮箱"
    )
forms
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>form组件</title>
    <style>
        .errorMsg {
            color: red;
            margin-left: 10px;
        }
    </style>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-lg-offset-2">
            <form action="" method="post" novalidate>
                {% csrf_token %}
                {% for field in forms_obj %}
                    <div class="form-group">
                        <label for={{ field.id_for_label }}>{{ field.label }}</label>
                        <!-- 渲染input标签,并在有错误提示时渲染该错误信息 -->
                        {{ field }} <span class="errorMsg">{{ field.errors.0 }}</span>
                    </div>
                {% endfor %}
                <p><input type="submit" class="btn btn-success pull-left"></p>
            </form>
        </div>
    </div>
</div>
</body>
</html>
register
自定义规则(钩子方法)

作用:在现有的校验基础上,如最大长度、最短长度限制、不能输入为空这些基础上,根据需求实现一些具体的校验规则,我们通常将这些自定义的校验规则,称之为钩子方法。

钩子方法分为:

  • 局部钩子方法
  • 全局钩子方法

加上基础校验,就有了三种校验规则,执行流程如下:

  • 基础校验,每个字段内部(required + validators + min_length=6,max_length=10)
  • 自定义局部钩子方法,当该字段的基础校验通过后,才执行的自定义的钩子校验方法。
  • 全局钩子校验,无论基础校验和和自定义钩子方法是否通过,最后都会走全局钩子方法。
局部钩子

局部钩子针对某个字段设定的自定义校验规则,钩子方法在form组件类中以方法形式展现,想要对哪个字段进行自定义钩子,就以clean_开头,拼接字段名就可以了,如clean_user就是对user字段添加自定义钩子方法。

实例:使用局部钩子对密码一致性进行校验

在注册时,通常有密码和确认密码选项,但是数据库一般只有一个密码字段,所以,在开发中,要对用户输入的两个密码进行一致性校验。在校验时,注意点:

  • form组件类中要重新生成一个确认密码的输入框字段,且该字段必须在密码字段下面,这是因为form组件中,校验是for循环每个字段校验的,在自定义钩子方法中,一般只能从cleaned_data中获取到当前字段和之前字段校验成功的数据,后面的字段数据由于还没循环到,导致没有添加到cleaned_data中,获取不到。
  • 如果校验都通过了,写入到数据库之前,要把确认密码字段的数据删除掉,因为数据库中没有确认密码这个字段,不删除,可能会导致报错,因为多了个键值对,对应不上了!当然,这还要看你具体的orm语句是如何处理的。

views.py

def register(request):
    if request.method == "GET":
        form_obj = UserForm()
        return render(request, "register.html", {"forms_obj": form_obj})
    form_obj = UserForm(request.POST)
    if not form_obj.is_valid():
        return render(request, "register.html", {"forms_obj": form_obj})

    User.objects.create(**form_obj.cleaned_data)
    return HttpResponse("添加成功")
views
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2022/9/26 11:16'
# software: PyCharm

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

from web.models import UserProfile


class UserForm(forms.Form):
    user = forms.CharField(
        min_length=5, max_length=13,
        error_messages={"required": "用户名不能为空"},
        widget=widgets.TextInput(attrs={"class": "form-control"}),
        label='用户名'
    )
    pwd = forms.CharField(
        min_length=6,
        error_messages={"required": "密码不能为空"},
        widget=widgets.PasswordInput(attrs={"class": "form-control"}),
        label="密码"
    )

    re_pwd = forms.CharField(
        min_length=6,
        error_messages={"required": "密码不能为空"},
        widget=widgets.PasswordInput(attrs={"class":"form-control"}),
        label="确认密码"
    )

    email = forms.EmailField(
        error_messages={
            "required": "邮箱不能为空",
            "invalid": "邮箱格式错误"
        },
        widget=widgets.EmailInput(attrs={"class": "form-control"}),
        label="邮箱"
    )

    def clean_user(self):
        """自定义关于user字段的钩子方法,作用:用户名不能重复"""
        # 前端传递过来的user字段值
        value = self.cleaned_data.get("user")
        user_obj = UserProfile.objects.filter(user=value).first()
        if user_obj:  # 用户名已存在
            # 固定写法
            raise ValidationError("用户名已存在")
        else:
            # 如果用户名不存在,表示该字段值没问题,正常返回该值就行了
            return value

    def clean_pwd(self):
        """自定义关于pwd字段的钩子方法,作用:密码不能是纯数字"""
        value = self.cleaned_data.get("pwd")
        if value.isdecimal():
            raise ValidationError("密码不能是纯数字")
        else:
            return value

    def clean_re_pwd(self):
        re_pwd = self.cleaned_data.get('re_pwd')
        pwd = self.cleaned_data.get('pwd')
        if pwd is None:  # 表示密码字段没有输入值,这里直接放行,反正密码钩子校验也通不过,这里钩子不校验也无所谓了
            return re_pwd
            # 如果密码字段校验都没问题,这里直接判断两个值是否相等就行了
        if re_pwd and pwd and re_pwd == pwd:
            return re_pwd
        else:
            raise ValidationError("两次密码输入不一致")
forms
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>form组件</title>
    <style>
        .errorMsg {
            color: red;
            margin-left: 10px;
        }
    </style>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-lg-offset-2">
            <form action="" method="post" novalidate>
                {% csrf_token %}
                {% for field in forms_obj %}
                    <div class="form-group">
                        <label for={{ field.id_for_label }}>{{ field.label }}</label>
                        <!-- 渲染input标签,并在有错误提示时渲染该错误信息 -->
                        {{ field }} <span class="errorMsg">{{ field.errors.0 }}</span>
                    </div>
                {% endfor %}
                <p><input type="submit" class="btn btn-success pull-left"></p>
            </form>
        </div>
    </div>
</div>
</body>
</html>
register
全局钩子

根据form组件的源码执行流程,当基础钩子和局部钩子都通过后,这时的cleaned_data数据也基本上是合法且完整的了,可以在全局钩子做最后的校验规则的补充。

全局钩子的基本使用和注意事项

在form组件类中重写clean方法,即是在定义全局钩子。

全局钩子的校验逻辑是,通过校验,返回cleaned_data,否则raise ValidationError错误,错误信息将会保存在forms对象non_field_errors()方法中,前端通过form对象调用:{{ forms_obj.non_field_errors.0 }}

def register(request):
    if request.method == "GET":
        form_obj = UserForm()
        return render(request, "register.html", {"forms_obj": form_obj})
    form_obj = UserForm(request.POST)
    # print(form_obj.errors)
    # print("全局钩子中的错误",form_obj.errors.get("__all__"))
    if not form_obj.is_valid():
        form_obj.non_field_errors() # 全局钩子错误信息
        return render(request, "register.html", {"forms_obj": form_obj})

    User.objects.create(**form_obj.cleaned_data)
    return HttpResponse("添加成功")
views
class UserForm(forms.Form):
    user = forms.CharField(
        label="用户名",
        min_length=4,
        max_length=13,
        error_messages={"required": "该字段不能为空"},
        widget=forms.TextInput(attrs={"class":"form-control"})
    )
    pwd = forms.CharField(
        label="密码",
        error_messages={"required": "该字段不能为空"},
        widget=forms.PasswordInput(attrs={"class":"form-control"}),

    )
    email = forms.EmailField(
        label="邮箱",
        widget=forms.EmailInput(attrs={"class":"form-control"}),
        error_messages={
            "required": "该字段不能为空",
            "invalid": "邮箱格式错误"
        }
    )

    def clean(self):
        if self.cleaned_data.get("user") == "tian":
            print("全局校验进入了")
            raise ValidationError("全局错误了")
forms
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>form组件</title>
    <style>
        .errorMsg {
            color: red;
            margin-left: 10px;
        }
    </style>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-lg-offset-2">
            <form action="" method="post" novalidate>
                {% csrf_token %}
                {% for field in forms_obj %}
                    <div class="form-group">
                    <label for="">{{ field.label }}</label>
                    <!-- 渲染input标签,并在有错误提示时渲染该错误信息 -->
                    {{ field }} <span class="errorMsg">{{ field.errors.0 }}</span>
                </div>
                {% endfor %}
                <h1>{{ forms_obj.non_field_errors.0 }}</h1> <!--全局钩子错误信息-->
                <p><input type="submit" class="btn btn-success pull-left"></p>
            </form>
        </div>
    </div>
</div>
</body>
</html>
register
重写__init__方法

如果form组件类中,每个字段都要写required,添加属性值之类的,在每个字段中写是非常麻烦,可以将相同的值在__init__中写:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'tian'
__data__ = '2022/9/26 11:16'
# software: PyCharm

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

from web.models import UserProfile


class UserForm(forms.Form):
    user = forms.CharField(
        min_length=5, max_length=13,
        error_messages={"required": "用户名不能为空"},
        widget=widgets.TextInput(),
        label='用户名'
    )
    pwd = forms.CharField(
        min_length=6,
        error_messages={"required": "密码不能为空"},
        widget=widgets.PasswordInput(),
        label="密码"
    )

    re_pwd = forms.CharField(
        min_length=6,
        error_messages={"required": "密码不能为空"},
        widget=widgets.PasswordInput(),
        label="确认密码"
    )

    email = forms.EmailField(
        error_messages={
            "required": "邮箱不能为空",
            "invalid": "邮箱格式错误"
        },
        widget=widgets.EmailInput(),
        label="邮箱"
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 为所有的字段添加相同的属性
        for field in self.fields.values():
            field.widget.attrs.update({"class": "form-control"})
            field.error_messages.update({"required": "该字段不能为空"})
forms
forms处理下拉菜单

下拉菜单处理分为几种情况:

单选下拉框

  • 值是固定的,用户表的性别字段,值是固定的,多选一,这种情况前端搞个单选下拉框即可。
  • 值是动态的,下拉框的值是外键字段关联的表中的数据,例如书籍表的出版社字段,它关联出版社表,前端展示书籍对应的出版社字段时,数据是从出版社表中动态获取的,所以这个单选下拉框跟上面的下拉框还不一样。

多选下拉框

  • 多选下拉框的值通常也来自外键字段,例如书籍的作者字段对应作者表,这种情况的多选下拉框跟上面的下拉框还不一样。

下面示例,涵盖了上面三种下拉框

 mdels.py

class Book(models.Model):
    """"""
    title = models.CharField(max_length=32, verbose_name="书名")
    price = models.DecimalField(max_digits=5, decimal_places=2)
    pub_date = models.DateTimeField(auto_now_add=True)
    publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)
    authors = models.ManyToManyField(to="Author")


class Publish(models.Model):
    """出版社"""
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=64)
    email = models.EmailField()

    def __str__(self):
        return self.name


class Author(models.Model):
    """
    作者
    """
    name = models.CharField(max_length=32, verbose_name="用户名")
    gender = models.IntegerField(choices=((1, ""), (2, "")), default=1, verbose_name="性别")
    age = models.SmallIntegerField()
    au_detail = models.OneToOneField("AuthorDetail", on_delete=models.CASCADE)

    def __str__(self):
        return self.name


class AuthorDetail(models.Model):
    """
    作者详细信息
    """
    gender_choices = (
        (0, ""),
        (1, ""),
        (2, "保密"),
    )
    gender = models.SmallIntegerField(choices=gender_choices, default=1, verbose_name="性别")
    tel = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)
    birthday = models.DateField(auto_now_add=True)
models
from django.contrib import admin
from django.urls import path
from web.views import formviews

urlpatterns = [
    path('add_book/', formviews.add_book, name="add_book"),
    path('add_author/', formviews.add_author, name="add_author"),

]
urls
from django.shortcuts import redirect, render, HttpResponse, reverse

from web.forms.bookform import UserForm,BookForm,AuthorForm
from web import models

def add_book(request):
    """添加书籍"""
    if request.method == "POST":
        forms_obj = BookForm(request.POST)
        if forms_obj.is_valid():
            data = forms_obj.cleaned_data
            # print(data)
            # queryset对象
            # print(list(data['authors']))
            # print(data['publish'])
            obj = models.Book.objects.create(
                title=data['title'],
                price=data['price'],
                pub_date=data['pub_date'],
                publish=data['publish']
            )
            obj.authors.add(*data['authors'])
            return HttpResponse("OK")
        else:
            return render(request, 'register.html', {"forms_obj": forms_obj})
    forms_obj = BookForm()
    return render(request, 'add_author.html', {"forms_obj": forms_obj})


def add_author(request):
    """ 添加作者 """
    if request.method == "POST":
        forms_obj = AuthorForm(request.POST)
        if forms_obj.is_valid():
            # print(forms_obj.cleaned_data)
            models.Author.objects.create(**forms_obj.cleaned_data)
            return HttpResponse("OK")
        else:
            return render(request, 'register.html', {"forms_obj": forms_obj})
    forms_obj = AuthorForm()
    return render(request, 'add_author.html', {"forms_obj": forms_obj})
views
from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError

from web.models import UserProfile,Publish,Author,Book

class AuthorForm(forms.Form):
    user = forms.CharField(label="作者名")
    # 值是固定的单选下拉框,initial=2初始值为女
    gender = forms.ChoiceField(choices=((1,""),(2,"")),label="性别",initial=2)

    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        for field in self.fields.values():
            field.widget.attrs.update({"class":'form-control'})
            field.error_messages.update({"required":"该字段不能为空"})



class BookForm(forms.Form):
    title = forms.ChoiceField(label="书籍名")
    price = forms.DecimalField(max_digits=4,decimal_places=2,label="价格")
    pub_date = forms.DateField(label="出版时间",widget=widgets.TextInput(attrs={"type":"date"}))

    # 值是动态的单选下拉框
    publish = forms.ModelChoiceField(queryset=Publish.objects.all().order_by("-pk"),label="出版社",empty_label="请选择")
    # 值是动态的多选下拉框
    authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all(),label="作者")

    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        for field in self.fields.values():
            field.widget.attrs.update({"class":"form-control"})
            field.error_messages.update({"required": "该字段不能为空"})
forms

add_book.html 和 add_author.html(两个代码一样)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>form组件</title>
    <style>
        .errorMsg {
            color: red;
            margin-left: 10px;
        }
    </style>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-lg-offset-2">
            <form action="" method="post" novalidate>
                {% csrf_token %}
                {% for field in forms_obj %}
                <div class="form-group">
                    <label for="">{{ field.label }}</label>
                    {{ field }} <span class="errorMsg">{{ field.errors.0 }}</span>
                </div>
                {% endfor %}
                <p><input type="submit" class="btn btn-success pull-left"></p>
            </form>
        </div>
    </div>
</div>
</body>
</html>
add_book
form组件补充

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类型
常用内置字段

django内置插件:

extInput(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
常用内置插件

常用选择插件

# 单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
# )
选择插件

 

posted on 2022-09-26 11:20  赛兔子  阅读(56)  评论(0编辑  收藏  举报

导航