Django框架—form组件和ModelForm
我知道前端页面可以通过form表单向后端提交数据,需要用form表单将获取用户输入的标签包裹起来。
与此同时,很多场景,我们需要对用户的输入进行校验,如长度/格式合不合法,若不合法我们希望在相应的位置提示用户对应的错误信息。
如果我们通过js来实现上述效果,必然是可以的,但是过程很复杂,而Django中的form组件帮我们封装好了所有上述的功能,只需要调用一些接口就可以实现。
1.form组件的功能
form组件的功能
-
通过后端的form组件生成前端页面对应的HTML标签
-
对前端form页面提交的数据进行合法性校验,并作出相应提示
-
校验的之后可以保留前端页面输入内容,增加用户体验
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
执行同步指令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,'edit_book.html',{'form':form,'n':n})