文章目录
- 一、form简介
- 二、Form中常用字段约束与插件
- 三、form中的所有内置字段
- 1.内置字段
- Field字段字段
- CharField(Field)
- IntegerField(Field)
- FloatField(IntegerField)和DecimalField(IntegerField)
- BaseTemporalField(Field)
- RegexField(CharField)
- FileField(Field)
- ChoiceField(Field)
- ModelChoiceField(ChoiceField)
- ModelMultipleChoiceField(ModelChoiceField)
- TypedChoiceField(ChoiceField)
- TypedMultipleChoiceField(MultipleChoiceField)
- ComboField(Field)
- MultiValueField(Field)
- SplitDateTimeField(MultiValueField)
- GenericIPAddressField
- 四、字段校验
- 五、Hook钩子用法
- 六、ModelForm使用
一、form简介
我知道前端页面可以通过form表单向后端提交数据,需要用form表单将获取用户输入的标签包裹起来。
与此同时,很多场景,我们需要对用户的输入进行校验,如长度/格式合不合法,若不合法我们希望在相应的位置提示用户对应的错误信息。
如果我们通过js来实现上述效果,必然是可以的,但是过程很复杂,而Django中的form组件帮我们封装好了所有上述的功能,只需要调用一些接口就可以实现。
1.form组件的功能
form组件的功能
- 通过后端的form组件生成前端页面对应的HTML标签
- 对前端form页面提交的数据进行合法性校验,并作出相应提示
- 校验的之后可以保留前端页面输入内容,增加用户体验
2.form组件的简单使用
数据库模型准备
新建项目form_lesson下app01文件中的models.py
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>
通过前端页面效果,验证了form组件的功能:
- 前端页面是form类的对象生成的 -->生成HTML标签功能
- 当用户名和密码输入为空或输错之后 页面都会提示 -->用户提交校验功能
- 当用户输错之后 再次输入 上次的内容还保留在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})
几个注意点
- 如果提交的数据需要写进数据库里面,那么form获取数据的字段(属性)必须与写入表中的字段名对应(也就是相同)。
- 提取的数据通过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)
注意点:
- 全局钩子中,直接抛出异常报的错误提示,默认是在全局错误变量中也就是self.fields.errors中。
- 如果需要指定在某个标签后提示,需要将这个错误添加到对应的标签错误属性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
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>
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函数
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函数
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,'e