Django框架—form组件和ModelForm

一、form简介

我知道前端页面可以通过form表单向后端提交数据,需要用form表单将获取用户输入的标签包裹起来。

与此同时,很多场景,我们需要对用户的输入进行校验,如长度/格式合不合法,若不合法我们希望在相应的位置提示用户对应的错误信息。

如果我们通过js来实现上述效果,必然是可以的,但是过程很复杂,而Django中的form组件帮我们封装好了所有上述的功能,只需要调用一些接口就可以实现。

1.form组件的功能

form组件的功能

  1. 通过后端的form组件生成前端页面对应的HTML标签

  2. 对前端form页面提交的数据进行合法性校验,并作出相应提示

  3. 校验的之后可以保留前端页面输入内容,增加用户体验

2.form组件的简单使用

数据库模型准备

新建项目form_lesson下app01文件中的models.py

class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    age=models.IntegerField()
    authorDetail=models.OneToOneField(to="AuthorDetail",to_field="nid")
    def __str__(self):
        return self.name
class AuthorDetail(models.Model):
​
    nid = models.AutoField(primary_key=True)
    birthday=models.DateField()
    telephone=models.BigIntegerField()
    addr=models.CharField( max_length=64)
​
class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    city=models.CharField( max_length=32)
    email=models.EmailField()
    def __str__(self):
        return self.name
​
class Book(models.Model):
​
    nid = models.AutoField(primary_key=True)
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    price=models.DecimalField(max_digits=5,decimal_places=2)
    publish=models.ForeignKey(to="Publish",to_field="nid")
    authors=models.ManyToManyField(to='Author',)
    def __str__(self):
        return self.title
models.py

执行同步指令makemigrations和migrate同步到数据库,添加一些数据后表结构如下

form简单使用

使用form之前需要定义一个类,来继承forms.Form。

这里为了项目的整洁性,我们另起一个文件夹,命名utils,在里面写我们要定义的类

from django import forms
​
class LoginForm(forms.Form):
    # 按照form组件定义自己的类,需要继承forms.Form
    username = forms.CharField(
        label="用户名",
        min_length=8,
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        },
        widget=forms.widgets.TextInput(attrs={"class": "form-control"})
    )  # form字段名称是前端input标签的name属性。
    password = forms.CharField(
        label="密码",
        min_length=12,
        widget=forms.widgets.PasswordInput(attrs={"class": "form-control"})
    )

在视图函数中写注册函数

from django.shortcuts import render,HttpResponse,redirect
from utils.formlesson import LoginForm  # 导入自定义的RegFrom类
def login(request):
    form_obj = LoginForm()  # 实例化一个form对象
    if request.method=="POST":
        form_obj = LoginForm(data=request.POST)  # 将input标签的name属性值和form字段相同的数据进行form校验
        if form_obj.is_valid() and request.POST.get("username") == "alex" and request.POST.get("password") == "alex":  # 用is_valid方法判断提交的数据是否合法
            return HttpResponse("登录成功")
    return render(request,"login.html",{"form_obj":form_obj})  # 如果是get请求,返回页面,并且通过form对象在前端渲染获取html标签。

模板文件login.html文件

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}" rel="stylesheet">
</head>
<body>
<div class="container">
    <div class="col-sm-6 col-sm-offset-3">
        <form action="{% url 'register' %}" method="post" novalidate>  <!--novalidate告诉浏览器,不需要对输入内容做校验,以便我们使用自己的校验提示-->
            {% csrf_token %}
            <!--第一种写法:直接生成,但是不利于个性化定制-->
            {{ form_obj.as_p }}
​
​
            <!--第二种写法:更利于个性化的定制-->
            <div class="form-group">
                <label for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}</label>
                <!--获取input标签的id给for属性,获取label提示的名字-->
                {{ form_obj.username }}  <!--获取form对象的字段name,生成name="name"的input标签-->
                {{ form_obj.username.errors.0 }}  <!--获取name字段校验后的错误提示-->
                {{ form_obj.errors }}  <!--获取form标签全局的错误提示,里面统计了所有input标签的错误信息-->
            </div><div class="form-group">
                <label for="{{ form_obj.password.id_for_label }}">{{ form_obj.password.label }}</label>
                {{ form_obj.password }}
                {{ form_obj.password.errors.0 }}
            </div><!--第三种写法:通过for循环对象中的字段,写法更加简便-->
            {% for field in form_obj %}  <!--循环form对象中的所有字段,对每个字段生成相应的标签-->
                <div class="form-group">
                    <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                    {{ field }}
                    {{ field.errors.0 }}
                </div>
            {% endfor %}
            <input type="submit">
        </form>
    </div>
    <div class="row"><script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
        <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
</body>
</html>
login.html

通过前端页面效果,验证了form组件的功能:

  1. 前端页面是form类的对象生成的 -->生成HTML标签功能

  2. 当用户名和密码输入为空或输错之后 页面都会提示 -->用户提交校验功能

  3. 当用户输错之后 再次输入 上次的内容还保留在input框 -->保留上次输入内容

二、Form中常用字段约束与插件

创建Form类时,主要涉及到【字段】和【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;

1.常用字段约束与对应插件

我们通过在formlesson中再定义一个注册form类来演示不同字段约束的标签效果。

通过forms.widgets.插件名(attrs={key,val})来为标签指定插件以及添加属性,添加的属性可以应用bootstrap中的样式类。

initial

initial是input标签输入框中的初始值

from django import forms  # 导入froms模块
class RegForm(forms.Form):
    username = forms.CharField(
        min_length=8,  # 用户名最小长度
        label="用户名",
        initial="张三",
        widget=forms.widgets.TextInput(attrs={"class":"form-control"})
    )

error_messages

自定义错误信息提示内容

class RegForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={  # 自定义错误信息提示内容
            "required": "内容不能为空",  # 原本为
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        },
        widget=forms.widgets.TextInput(attrs={"class":"form-control"})  # 给input标签的添加插件,并添加属性bootstrap样式属性
    )

password

用于生成获取用户输入密码的input标签,

class RegForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={  # 自定义错误信息提示内容
            "required": "内容不能为空",  # 原本为
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        },
        widget=forms.widgets.TextInput(attrs={"class":"form-control"})  # 给input标签的添加插件,并添加属性bootstrap样式属性
    )

radioSelect

生成获取用户选择的单选框input标签

class RegForm(forms.Form):
    gender = forms.ChoiceField(
        choices=((1,""),(2,""),(3,"保密")),  # 单选框中的name值和对应显示文本
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()  # 单选下拉选择框插件,添加样式
    )

单选Select

生成获取用户输入的单选下拉选择框select标签

class RegForm(forms.Form):
    hobby = forms.ChoiceField(  # 注意,单选框用的是ChoiceField,并且插件是Select,不然验证的时候会报错, Select a valid choice的错误。
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select(attrs={'class': 'form-control'})
    )

多选Select

生成获取用户输入的多选下拉选择框select标签

class RegForm(forms.Form):
    hobby = forms.MultipleChoiceField(  # 注意,MultipleChoiceField,并且插件是SelectMultiple,不然验证的时候会报错
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=3,
        widget=forms.widgets.SelectMultiple(attrs={'class': 'form-control'})
    )

单选checkbox

生成获取用户多选的选择框(单选)

class RegForm(forms.Form):
    keep = forms.ChoiceField(
        choice=(
            ("True",1),
            ("False",0)
        ),
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()  # 单选的checkbox插件
    )

注意,在单选的checkbox中,form校验提交的数据勾选了是{"keep","True"},不勾选是{"keep","False"},所以,需要在choices中给出合格的选项。choice=(("True",1),("False",1))。而不是initial设置的val属性{"keep","chekced"}。

多选checkbox

生成获取用户多选的选择框(多选)

class RegForm(forms.Form):
    hobby = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()  # 多选的checkbox插件
    )

date类型

生成获取用户选择时间的input标签

class RegForm(forms.Form):
    reg_date = forms.DateField(widget=forms.widgets.TextInput(attrs={'type':'date','class': 'form-control'}))  #必须指定type,不然不能渲染成选择时间的input框

以上例子对应的视图函数register

from django.shortcuts import render,HttpResponse,redirect
from utils.formlesson import LoginForm
def register(request):
    form_obj = RegForm()
    if request.method == "POST":
        form_obj = RegForm(data=request.POST)
        if form_obj.is_valid():
            print(form_obj.fields)  # 包含form对象中的所有字段,一个orderedDict。OrderedDict([('username', <django.forms.fields.CharField object at 0x000002138B52C9E8>), ('password', <django.forms.fields.CharField object at 0x000002138B52CA20>)])
            print(form_obj.cleaned_data)  # 包含了通过校验的数据,python字典类型,{'username': 'raoyixiong', 'password': 'ryxiong'}
            return HttpResponse("注册成功")
        else:
            return render(request, "register.html", {"form_obj":form_obj})
    return render(request, "register.html", {"form_obj":form_obj})

2.Choice字段获取数据库数据

在使用选择标签时,choices的选项不应该是固定写死的,应该能够从数据库中获取实时更新,但是由于是静态字段 获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。

方式一:init方法实现

from django import forms
from app01 import models
class BookForm(forms.Form):
    publish_id = forms.fields.ChoiceField(
        # choices=((1, '古诗词出版社'), (2, '外国名著出版社'),),
        initial=2,
        widget=forms.widgets.Select(attrs={'class': 'form-control'})
    )
​
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)  # 注意重写父类的init方法,父类init方法里面实现了很多功能
        self.fields["publish_id"].choices=models.Publish.objects.all().values_list("pk","name")

对应的视图函数

from django.shortcuts import render,HttpResponse,redirect
from utils.formlesson import BookForm
def book(request):
    form_obj = BookForm()
    if request.method=="POST":
        print(request.POST)
        form_obj = BookForm(request.POST)
        return render(request,"book.html",{"form_obj":form_obj})
    return render(request,"book.html",{"form_obj":form_obj})

几个注意点

  1. 如果提交的数据需要写进数据库里面,那么form获取数据的字段(属性)必须与写入表中的字段名对应(也就是相同)。

  2. 提取的数据通过value_list获取选项的元组数据,里面选择input标签的value值和显示文本对应的数据,如pk字段对应输入框提交的value值,name字段对应input框展示给用户看的文本。

方式二:通过字段中的queryset属性设置

通过form类中字段属性queryset指定model模型查询获取的数据对象,来生成选择框。

注意:

需要在model表中定义__str__方法,这样在前端才能显示表中的某个字段名,不然会直接显示为一个对象,不利于阅读。

from django import forms
from app01 import models
class BookForm(forms.Form):
    # 单选出版社写法
    publish_id = forms.ModelChoiceField(
        queryset=models.Publish.objects.all(),
        widget=forms.widgets.Select(attrs={'class': 'form-control'}),
    )
​
    # 多选作者写法
    authors = forms.ModelMultipleChoiceField(
        queryset=models.Author.objects.all(),
        widget=forms.widgets.SelectMultiple(attrs={'class': 'form-control'})
    )

三、form中的所有内置字段

1.内置字段

Field字段字段

  • required=True, 是否允许为空

  • widget=None, HTML插件

  • label=None, 用于生成Label标签或显示内容

  • initial=None, 初始值

  • help_text='', 帮助信息(在标签旁边显示)

  • error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}

  • 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': '...'}

FileField(Field)

  • allow_empty_file=False 是否允许空文件

注:需要PIL模块,pip3 install Pillow 以上两个字典使用时,需要注意两点:

  • form表单中 enctype="multipart/form-data"

  • view函数中 obj = MyForm(request.POST, request.FILES)

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= '' 空值的默认值

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) 数字,字母,下划线,减号(连字符)

四、字段校验

1.RegexValidator验证器

自定义类中字段有一个属性validators可以指定验证规则

语法:

validators = [RegexValidator('正则表达式', '格式错误提示内容')],

使用实例

from django import forms
from django.core.validators import RegexValidator
class RegForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        widget=forms.widgets.TextInput(attrs={"class": "form-control"}),
    validators = [RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
    ) 

2.自定义验证函数

在formlesson.py中定义自己的验证函数,注意是函数,不是类的方法。

import re
from django import forms
from app01 import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
​
def mobile_validate(value):  # value为验证成功后的返回的cleaned_data数据
  mobile_re = re.compile(r"^13[0-9]|15[012356789][0-9]{8}$")
  if not mobile_re.match(value):  # 没有匹配上,返回None,抛出异常
      raise ValidationError("手机号码格式不正确")  # 验证不合格自己抛出异常

register类中试用自定义验证函数

class RegForm(forms.Form):
    telephone = forms.CharField(
        max_length=11,
        min_length=11,
        widget=forms.widgets.TextInput(attrs={'class': "form-control",'placeholder': '11位数字的电话号码'}),
        validators=[mobile_validate, ]  # 使用自定义函数校验电话号码。
    )

五、Hook钩子用法

除了通过上面两种方式,还可以在From中定义钩子函数,来实现自定义的验证功能。

1.定义局部钩子

在Fom类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验。

使用方法

class LoginForm(forms.Form):
    # 按照form组件定义自己的类,需要继承forms.Form
    username = forms.CharField(
        label="用户名",
        min_length=8,
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        },
        widget=forms.widgets.TextInput(attrs={"class": "form-control"})
    )  # form字段名称是前端input标签的name属性。
    
    # 自定义局部钩子,用来校验username字段,在之前校验完后再校验
    def clean_username(self):
        value = self.cleaned_data.get("username")  # 因为自定义钩子是在form验证以及自定义验证函数都执行完后,才会验证,所以cleaned_data里面必然是有数据的。
        if "666" in value:
            raise ValidationError("光喊666也不能登录")
        else:
            return value  # 即时符合也需要将cleaned_data返回出去

2.定义全局钩子

我们在Fom类中定义 clean() 方法,就能够实现对字段进行全局校验。

当字段全部验证完,局部钩子也全部执行完之后,才执行这个全局钩子校验。

使用方法

class LoginForm(forms.Form):
    # 按照form组件定义自己的类,需要继承forms.Form
    username = forms.CharField(
        label="用户名",
        min_length=8,
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        },
        widget=forms.widgets.TextInput(attrs={"class": "form-control"})
    )  # form字段名称是前端input标签的name属性。
    
    # 第一次输入密码
    password = forms.CharField(
        label="密码",
        min_length=12,
        widget=forms.widgets.PasswordInput(attrs={"class": "form-control"})
    )
    
    # 确认密码
    re_password = forms.CharField(
        label="确认密码",
        min_length=12,
        widget=forms.widgets.PasswordInput(attrs={"class": "form-control"})
    )
    
    # 定义全局校验钩子
    def clean(self):
        pwd1 = self.cleaned_data.get("password")
        pwd2 = self.cleaned_data.get("re_password")
        if pwd1 == pwd2:
            return self.cleaned_data  # 返回所有的cleaned_data(合格数据)
        else:
            # raise ValidationError('两次输入的密码不一致!')
            # 注意全局钩子中,直接抛出异常报的错误提示,默认是在全局错误变量中也就是self.errors中

            self.add_error('re_password',"两次输入的密码不一致!")
            # 如果需要指定在某个标签后提示,需要将这个错误添加到对应的标签错误属性中。
            print(self.re_password.errors)  
全局钩子使用

注意点:

  1. 全局钩子中,直接抛出异常报的错误提示,默认是在全局错误变量中也就是self.fields.errors中。
  2. 如果需要指定在某个标签后提示,需要将这个错误添加到对应的标签错误属性self.errors中。

六、ModelForm使用

1.什么是ModelFrom

通常在Django项目中,我们编写的大部分都是与Django 的模型紧密映射的表单。

如果我们想通过form组件来添加和编辑书籍信息到这个模型,这种情况下,在form组件中重新定义模型中所有的字段,显得重复冗余。

基于这个原因,Django中提供了一个辅助类让我们可以通过Django的model模型来创建Form组件,这个类就是ModelForm。

ModelForm就是form和model的组合,会根据你model模型中有的字段自动转换成form字段,同样也可以根据form来生成标签。

ModelForm的简单使用

比如我们在models文件定义一个数据模型类Book

from django.db import models
class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    price=models.DecimalField(max_digits=5,decimal_places=2)
    publish=models.ForeignKey(to="Publish",to_field="nid")
    authors=models.ManyToManyField(to='Author',)
    def __str__(self):
        return self.title

使用ModelFrom同样也需要定义一个文件,在里面写对应的类。

这里我们和form组件一样,在项目路径下定义个utils文件,在里面创建一个modelFromLesson文件,文件里面定义我们的类。

class BookModelForm(forms.ModelForm):
    class Meta:
        model = models.Book  # 指定验证某张表
        fields = "__all__"  # __all__表示所有字段,列表指定某些字段
        # exclude = ["title"]  # 指定排除某些字段
        labels = {  # 指定label标签的显示文本
            "title":"书名",
            "price":"价格"
        }
        error_messages = {  # 指定字段验证错误时的错误信息,可以写任何字段
            "title": {"required":"内容不能为空",}
        }
        widgets = {  # 指定字段的插件类型
            "title": forms.widgets.TextInput(attrs={"class": "form-control"}),
            "publishDate": forms.widgets.DateInput(attrs={"class": "form-control"}),
        }

    # 使用init方法给所有字段的标签批量添加bootstrap样式
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        for field in self.fields:
            self.fields[field].widget.attrs.update({
                "class":"form-control"
            })

    # 定义局部钩子
    def clean_title(self):
        val = self.cleaned_data.get("title")
        # 这里写你自定义的校验规则
        return val

    # 定义全局钩子
    def clean(self):
        val = self.cleaned_data
        # 写你自定义的检验规则
        return val
ModelForm的定义

2.class Meta参数

在BookForm中使用class Meta来指定表中字段的详细设置。

  • model属性:指定Model中的哪一个类,也就是表

  • fields:"__all__"表示form认证所有字典,用列表可以指定某些字段。

  • exclude:用列表指定需要排除的某些字段。

  • labels:用于指定label标签的显示文本

  • help_texts:用于指定帮助文本提示内容

  • widgets:指定字段的插件类型

  • error_messages:通过键值对的方式指定不同字段的错误提示

# 定义错误信息
error_messages = {
    'title':{'required':'不能为空',...} #每个字段的所有的错误都可以写
}

定义局部钩子和全局钩子

和form组件类使用方式是一样的。

  • 在类中定义clean_字段名的方法就是局部钩子

  • 在类中定义clean的方法就是全局钩子

from django import forms
class BookModelForm(forms.ModelForm):  # 创建一个modelform类
    # 定义局部钩子
    def clean_title(self):
        pass
    # 定义全局钩子
    def clean(self):
        pass

批量添加样式

批量添加样式和form一样,在类中重写init方法

from django import forms
class BookModelForm(forms.ModelForm):  # 创建一个modelform类
    def __init__(self, *args, **kwargs):  # 批量操作
        super().__init__(*args, **kwargs)
        for field in self.fields:
            # field.error_messages = {'required':'不能为空'} #批量添加错误信息,这是都一样的错误,不一样的还是要单独写。
            self.fields[field].widget.attrs.update({'class': 'form-control'})

3.ModelForm的验证

ModelForm的验证与Form验证类似,通过调用is_valid()或者访问errors属性时隐式调用。也可以自定义局部钩子和全局钩子来实现自定义的校验规则。

如果我们不重写具体字段并设置validators属性的话,ModelForm是按照模型中字段的validators来校验的。

from django.shortcuts import render, redirect, HttpResponse
from utils.modelFormLesson import BookModelForm
from app01 import models
def create_book(request):
    if request.method == "GET":
        book_obj = BookModelForm()  # 根据ModelForm生成一个book对象
        return render(request, "create_book.html", {"book_obj": book_obj})  # book对象传到前端进行渲染
    else:
        data = request.POST
        book_obj = BookModelForm(data)  # 根据用户数据,进行验证
        if book_obj.is_valid():  # 验证用户提交的数据是否和法
            book_obj.save()  # 保存数据到对应的表中,因为我们在Meta中指定了表,所以能够自动识别。这句话本质完成了下面三件事。
            """
            author_obj = book_obj.cleaned_data.pop("authors")  # 拿出book表中不存在的作者字段数据,列表里面存的对象
            new_book_obj = models.Book.objects.create(**book_obj.cleaned_data)  # 在Book表中创建一条表记录
            new_book_obj.authors.add(*author_obj)  # 找到第三张表,存入书籍和作者的关系
            """
            return HttpResponse("创建书籍成功")
        else:  # 不合法,将数据原封存回原地。
            return render(request, "create_book.html", {"book_obj": book_obj})

前端页面create_book.html写法

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
</head>
<body>
​
​
<div class="container-fluid">
    <div class="row">
        <div class="col-sm-6 col-sm-offset-3">
            <form action="" method="post" novalidate>  <!--novalidate取消浏览器自带的验证功能-->
            {% csrf_token %}
                {% for field in book_obj %}
                    {{ field.label }}
                    {{ field }}
                    {{ field.errors }}
                {% endfor %}
                <input type="submit">
            </form>
        </div>
    </div>
</div>
​
​
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
</body>
</html>
create_book.html

ModelFrom的save()方法

每个ModelForm对象还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。

ModelForm的子类对象可以接受现有的模型实例作为关键字参数instance;

  • 如果ModelFrom对象参数中指定了instance为哪一个book对象,那么就会是更新这个book对象的数据;

  • 如果ModelForm对象参数没有指定book实例对象,那么会将拿到的数据添加一条新的Book表记录。

save创建记录

def create_book(request):
    if request.method == "POST":
        form_obj = BookModelFrom(request.POST)
        if form_obj.is_valid():  # 数据验证合格返回True,否则返回False
            form_obj.save()  # form_obj对象没有接受实例,这里是创建记录
            return HttpResponse("创建成功")
        else:
            return HttpResponse("数据不合法")

save更新记录

def create_book(request,n):  # n是修改那一条书籍记录的id值,通过前端传递
    book_obj = models.Book.objects.filter(id=n)  # 找到要修改的书籍记录
    if request.method == "POST":
        form_obj = BookModelForm(request.POST,instance=book_obj)  # 将book_obj传参给BookModelForm对象
        if form_obj.is_valid():
            form_obj.save()  # form_obj对象接受实例参数,这里save是把数据更新到book_obj的记录中
            return HttpResponse("更新成功")
        else:
            return HttpResponse("数据不合法"

4.ModelForm使用实例(创建/更新)

创建数据到数据库

create_book.html

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
</head>
<body>
​
​
<div class="container-fluid">
    <div class="row">
        <div class="col-sm-6 col-sm-offset-3">
            <form action="" method="post" novalidate>  <!--novalidate取消浏览器自带的验证功能-->
            {% csrf_token %}
                {% for field in book_obj %}
                    {{ field.label }}
                    {{ field }}
                    {{ field.errors }}
                {% endfor %}
                <input type="submit">
            </form>
        </div>
    </div>
</div>
​
​
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
</body>
</html>
create_book.html

视图create_book函数

def create_book(request):
    if request.method == "GET":
        book_obj = BookModelForm()  # 根据ModelForm生成一个book对象
        return render(request, "create_book.html", {"book_obj": book_obj})  # book对象传到前端进行渲染
    else:
        data = request.POST
        book_obj = BookModelForm(data)  # 根据用户数据,进行验证
        if book_obj.is_valid():  # 验证用户提交的数据是否和法
            book_obj.save()  # 保存数据到对应的表中,因为我们在Meta中指定了表,所以能够自动识别。这句话本质完成了下面三件事。
            """
            author_obj = book_obj.cleaned_data.pop("authors")  # 拿出不存在的字段数据,列表里面存的对象
            new_book_obj = models.Book.objects.create(**book_obj.cleaned_data)  # 在Book表中创建一条表记录
            new_book_obj.authors.add(*author_obj)  # 找到第三张表,存入书籍和作者的关系
            """
            return HttpResponse("创建书籍成功")
        else:  # 不合法,将数据原封存回原地。
            return render(request, "create_book.html", {"book_obj": book_obj})

修改数据库数据

前端页面edit_book.html

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}">
</head>
<body><h1>编辑页面</h1>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="{% url 'edit_book' n %}" novalidate method="post">
                {% csrf_token %}
                {% for field in form %}
                    <div class="form-group">
                        <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                        {{ field }}
                        <span class="text-danger">{{ field.errors.0 }}</span>
                    </div>
                {% endfor %}
​
                <div class="form-group">
                    <input type="submit" class="btn btn-primary pull-right">
                </div></form></div>
    </div>
</div>
​
​
</body>
<script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script>
<script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script>
</html>
edit_book.html

视图edit_book函数

def edit_book(request,n):  # n是前端编辑书籍在数据库对应的id值
​
    book_obj = models.Book.objects.filter(pk=n).first()  # 找到编辑的book对象
    if request.method == 'GET':
        # all_authors = models.Author.objects.all()
        # all_publish = models.Publish.objects.all()  # 使用ModelForm后不需要在使用这种方式获取全部作者和出版社信息了,ModelForm对象在前端生成标签是都会自动识别并生成。
        form = BookModelForm(instance=book_obj)
        return render(request,'edit_book.html',{'form':form,'n':n})  # 传递的这个n参数是给form表单提交数据的是的action的url用的,用来识别更新的是哪条记录
else:
        form = BookModelForm(request.POST,instance=book_obj)  # 必须指定instance,不然我们调用save方法的是又变成了添加操作
        if form.is_valid():
            form.save()
            return redirect('show')
        else:
            return render(request,'edit_book.html',{'form':form,'n':n})

 

posted @ 2019-06-04 20:49  ryxiong728  阅读(359)  评论(0编辑  收藏  举报