Django之ModelForm操作
一、ModelForm的使用
顾名思义,ModelForm就是将Model与Form进行绑定,Form有自动生成表单的作用,但是每一个forms字段需要自己手动填写,而Model就是数据库表包含了所有的数据字段。所以ModelForm有着以下功能:
- Form所有的功能
- 将Model字段自动转换成forms字段
(一)实例演示
1、创建ModelForm
from app01 import models
from django.forms import ModelForm
from django.forms.widgets import Textarea class BookModelForm(ModelForm): class Meta:
model = models.Book #对应的Model类
fields = '__all__' #对应的Model类中字段
exclude = None #排除的字段
labels = {
"title":"书籍名", #用于html页面中显示的名字
"price":"价格"
}
help_texts = {
"title":"我是书籍的帮助信息" #自定义帮助信息
}
error_messages = {
"title":{"required":"书籍名不能为空"} #自定义错误信息
}
widgets = {
"title":Textarea(attrs={"class":"form-control"}) #自定义属性
}
2、添加数据
from django.shortcuts import render,redirect,HttpResponse def BookAdd(request):
book_list = models.Book.objects.all()
#获取添加数据的表单
if request.method == "GET":
form = BookModelForm()
return render(request,'booklist.html',locals())
#POST请求添加数据
form = BookModelForm(data=request.POST)
if form.is_valid():
#保存数据
form.save()
return HttpResponse('...')
3、修改数据
def BookEdit(request,id):
book = models.Book.objects.filter(id=id).first()
#获取修改数据的表单
if request.method == "GET":
form = BookModelForm(instance=book)
return render(request, 'booklist.html', locals())
#POST请求添加修改过后的数据
form = BookModelForm(data=request.POST,instance=book)
#对数据验证并且保存
if form.is_valid():
form.save()
return HttpResponse('...')
4、路由配置
urlpatterns = [
re_path('books/$', tests.BookAdd),
re_path('books/(?P<id>\d+)/$', tests.BookEdit),
]
5、前端html渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body> <form method="POST" novalidate>
{% csrf_token %}
{% for book in form %}
<div>
{# 拿到数据字段的labels,没有就默认显示字段名 #}
<label >{{ book.label }}</label>
<div>{{ book }}{{ book.help_text }}</div>
</div>
{% endfor %}
<div class="col-md-2 col-md-offset-10">
<input type="submit" value="提交" class="btn-primary">
</div>
</form> </body>
</html>
(二)实例解析
1、model字段转成forms字段
在创建ModelForm类时会将model字段转成forms字段,这里着重说明三种情况:
model字段是ForeignKey
如果model中是外键,那么在forms字段中对应的就是ModelChoiceField,如果使用的是Form,那么外键就应该这样定义:
publish=forms.ModelChoiceField(queryset=Publish.objects.all())
当然,在ModelForm中已经帮你自动实现了,将会产生这样的标签:
<select id="id_publish" name="publish">
<option value="obj1.pk">Object1</option>
<option value="obj2.pk">Object2</option>
...
</select>
model字段是ManyToMany
如果model中是ManyToMany,那么在forms字段中对应的就是ModelMultipleChoiceField,如果使用的是Form,那么ManyToMany就应该这样定义:
authors=forms.ModelMultipleChoiceField(queryset=Author.objects.all())
当然,在ModelForm中已经帮你自动实现了,将会产生这样的标签:
<select name="authors" id="id_authors" multiple="multiple" required="">
<option value="obj1.pk">obj1</option>
<option value="obj2.pk">obj2</option>
...
</select>
model字段中有choice参数
在model中可能会遇到这样的情况:
status_choices=(
(1,'已签合同'),
(2,'未签合同')
)
status=models.IntegerField(choices=status_choices,verbose_name='状态',default=2)
这样的情况在forms中对应的字段是ChoiceField字段,如果使用Form自定义字段,可以这样写:
status=forms.ChoiceField(choices=((1,"已签合同"),(2,"未签合同")))
当然,在ModelForm中也已经帮你自动实现了。
总结
在查看ChoiceField、ModelChoiceField、ModelMultipleChoiceField源码可知它们三者关系:
ChoiceField(Field)
ModelChoiceField(ChoiceField)
ModelMultipleChoiceField(ModelChoiceField)
它们分别依次继承,所以最后一个有它们所有的属性和方法。
2、保存数据时使用save方法
- 添加数据
这比Form更为简单和直接,在forms中要么通过cleaned_data将数据依次取出分别保存,要么以字典的形式一次存入:
... obj = BookForm(request.POST)
if obj.is_valid():
models.Book.objects.create(**obj.cleaned_data) ...
但是在ModelForm中,可以这样使用:
... obj = BookModelForm(request.POST)
if obj.is_valid():
obj.save()
...
- 修改数据
在修改数据时Form和ModelForm也是略有不同的在Form中:
... obj = BookForm(request.POST)
if obj.is_valid():
models.Book.objects.filter(id=nid).update(**obj.cleaned_data) ...
而在ModelForm中需要传入实例:
... obj = BookModelForm(data=request.POST,instance=book) if form.is_valid():
form.save() ...
三、源码一览
假设这里以修改视图流程来了解一下源码:
(一)ModelForm实例化
form = BookModelForm(instance=book)
ModelForm实例化并且传入instance参数,它会先使用元类生成自己,如果有__new__先执行__new__方法并且返回生成的对象,然后执行__init__方法初始化参数。在元类当中可以看到最后返回了当前BookModelForm,并且将收集了所有的已经声明的字段赋值给该类:
class ModelFormMetaclass(DeclarativeFieldsMetaclass):
def __new__(mcs, name, bases, attrs):
...
... new_class.base_fields = fields
...
... return new_class
然后再执行BaseModelForm中的__init__方法进行初始化:
class BaseModelForm(BaseForm):
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False, instance=None, use_required_attribute=None):
...
...
object_data = {}
...
self.instance = instance
...
...
#获取self.fields = copy.deepcopy(self.base_fields)
super().__init__(
data, files, auto_id, prefix, object_data, error_class,
label_suffix, empty_permitted, use_required_attribute=use_required_attribute,
)
...
初始化过程中将instance接收进来,并且将self.base_fields进行深拷贝给self.fields。
(二)is_valid
这个对ModelForm进行校验就是和Form的一样:
def is_valid(self):
"""Return True if the form has no errors, or False otherwise."""
return self.is_bound and not self.errors
在self.errors方法中执行的self.full_clean方法:
def full_clean(self):
"""
Clean all of self.data and populate self._errors and self.cleaned_data.
"""
self._errors = ErrorDict()
if not self.is_bound: # Stop further processing.
return
self.cleaned_data = {}
# If the form is permitted to be empty, and none of the form data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return self._clean_fields()#对每一个字段进行执行clean_fieldname方法
self._clean_form() #返回cleaned_data
self._post_clean() #预留钩子
这也就注定了Form中存在的功能ModelForm都有,无论是字段的验证还是其它的功能。
(三)save
save方法应该说比Form操作更方便快捷,可以简单的看看内部源码:
def save(self, commit=True):
"""
Save this form's self.instance object if commit=True. Otherwise, add
a save_m2m() method to the form which can be called after the instance
is saved manually at a later time. Return the model instance.
"""
if self.errors:
raise ValueError(
"The %s could not be %s because the data didn't validate." % (
self.instance._meta.object_name,
'created' if self.instance._state.adding else 'changed',
)
)
if commit:
# If committing, save the instance and the m2m data immediately.
self.instance.save()
self._save_m2m()
else:
# If not committing, add a method to the form to allow deferred
# saving of m2m data.
self.save_m2m = self._save_m2m
return self.instance
save方法有一个默认参数commit=True,表示保存实例以及ManyToMany数据,self.instance.save(),调用的是model实例的save方法(位于django.db.models.Model):
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
"""
Save the current instance. Override this in a subclass if you want to
control the saving process. The 'force_insert' and 'force_update' parameters can be used to insist
that the "save" must be an SQL insert or update (or equivalent for
non-SQL backends), respectively. Normally, they should not be set.
"""
# Ensure that a model instance without a PK hasn't been assigned to
# a ForeignKey or OneToOneField on this model. If the field is
# nullable, allowing the save() would result in silent data loss.
for field in self._meta.concrete_fields:
# If the related field isn't cached, then an instance hasn't
# been assigned and there's no need to worry about this check.
if field.is_relation and field.is_cached(self):
obj = getattr(self, field.name, None)
# A pk may have been assigned manually to a model instance not
# saved to the database (or auto-generated in a case like
# UUIDField), but we allow the save to proceed and rely on the
# database to raise an IntegrityError if applicable. If
# constraints aren't supported by the database, there's the
# unavoidable risk of data corruption.
if obj and obj.pk is None:
# Remove the object from a related instance cache.
if not field.remote_field.multiple:
field.remote_field.delete_cached_value(obj)
raise ValueError(
"save() prohibited to prevent data loss due to "
"unsaved related object '%s'." % field.name
) using = using or router.db_for_write(self.__class__, instance=self)
if force_insert and (force_update or update_fields):
raise ValueError("Cannot force both insert and updating in model saving.") deferred_fields = self.get_deferred_fields()
if update_fields is not None:
# If update_fields is empty, skip the save. We do also check for
# no-op saves later on for inheritance cases. This bailout is
# still needed for skipping signal sending.
if len(update_fields) == 0:
return update_fields = frozenset(update_fields)
field_names = set() for field in self._meta.fields:
if not field.primary_key:
field_names.add(field.name) if field.name != field.attname:
field_names.add(field.attname) non_model_fields = update_fields.difference(field_names) if non_model_fields:
raise ValueError("The following fields do not exist in this "
"model or are m2m fields: %s"
% ', '.join(non_model_fields)) # If saving to the same database, and this model is deferred, then
# automatically do a "update_fields" save on the loaded fields.
elif not force_insert and deferred_fields and using == self._state.db:
field_names = set()
for field in self._meta.concrete_fields:
if not field.primary_key and not hasattr(field, 'through'):
field_names.add(field.attname)
loaded_fields = field_names.difference(deferred_fields)
if loaded_fields:
update_fields = frozenset(loaded_fields) self.save_base(using=using, force_insert=force_insert,
force_update=force_update, update_fields=update_fields)
save
如果commit=False,就不会保存实例,当调用save方法后不会保存ManyToMany字段,需要自行去调用save_m2m方法,例如:
# Create a form instance with POST data.
>>> f = BookForm(request.POST) # Create, but don't save the new book instance.
>>> new_book = f.save(commit=False) # Modify the book in some way.
>>> new_book.some_field = 'some_value' # Save the new instance.
>>> new_book.save() # Now, save the many-to-many data for the form.
>>> f.save_m2m()
参考文章:https://docs.djangoproject.com/en/2.2/topics/forms/modelforms/#modelform
四、扩展
(一)自定义BaseModelForm
class BaseRequestModelForm(object):
def __init__(self, request, *args, **kwargs):
self.request = request
super(BaseRequestModelForm, self).__init__(*args, **kwargs)
这样,ModelForm中可以传入request参数,当然还可以添加其它参数,然后再继承自己的ModelForm,这样自定义的ModelForm不仅仅有自己的功能,还可以传参定制其它功能,在使用时继承下面这个ModelForm即可:
class BaseModelForm(BaseRequestModelForm,forms.ModelForm): def __init__(self,request,*args,**kwargs):
super().__init__(request,*args,**kwargs)
#####给modelform字段加样式
for name,field in self.fields.items():
attrs_dict={'class':'form-control'}
if 'DateTimeField' in field.__repr__():
attrs_dict = {'class': 'form-control', 'date_time': 'datetimepicker', 'size': ''}
field.widget.attrs.update(attrs_dict)
(二)动态生成ModelForm
每一个model都可以对应一个ModelForm类可用于自动生成表单等功能,但是如果能够动态生成ModelForm岂不是更加省事,其实就是动态的生成一个类,并且设置类的一些属性,首先先看一个普通的ModelForm类,依照此类动态生成:
class BookModelForm(ModelForm): class Meta:
model = models.Book #对应的Model类
fields = '__all__' #对应的Model类中字段 def __new__(cls,*args,**kwargs):
"""
:param cls:
:param args:
:param kwargs:
:return:
"""
#base_fields = [{'field_name':field_obj},] forms字段对象
for field_name in cls.base_fields:
field_obj = cls.base_fields[field_name]
field_obj.widget.attrs.update({'class':'form-control'}) return ModelForm.__new__(cls)
然后就可以进行动态生成这样的ModelForm类了。
from django.forms import ModelForm def CreateDynamicModelForm(model,fields=None,form_create=False,*args,**kwargs):
#默认为修改表单 attrs = {} #创建类使用的属性字典
#如果没有传入fields默认就是全部
if not fields:
fields = "__all__"
#传入request参数
if kwargs.get('request'):
attrs["request"] = kwargs.get('request') class Meta:
pass setattr(Meta,'model',model)
setattr(Meta,'fields',fields)
attrs["Meta"] = Meta
#如果给每一个字段加入样式,重写__new__方法
def __new__(cls,*args,**kwargs):
"""
:param cls:
:param args:
:param kwargs:
:return:
"""
#base_fields = [{'field_name':field_obj},] forms字段对象
for field_name in cls.base_fields:
field_obj = cls.base_fields[field_name]
field_obj.widget.attrs.update({'class':'form-control'}) return ModelForm.__new__(cls) attrs["__new__"] = __new__
##创建类
name = 'DynamicModelForm' #创建类的名称
bases = (ModelForm,) #创建类的基类
dynamic_model_form = type(name,bases,attrs) return dynamic_model_form
以后就可以这样使用了,也不用那么麻烦的手动写ModelForm类了。
from django.shortcuts import render,redirect,HttpResponse
from app01 import models #添加数据
def BookAdd(request):
book_list = models.Book.objects.all()
BookModelForm = CreateDynamicModelForm(models.Book, form_create=True, request=request)
#获取添加数据的表单
if request.method == "GET":
form = BookModelForm()
return render(request,'booklist.html',locals())
#POST请求添加数据
form = BookModelForm(data=request.POST)
if form.is_valid():
#保存数据
form.save()
return HttpResponse('...') #修改数据
def BookEdit(request,id):
#动态生成ModelForm类
BookModelForm = CreateDynamicModelForm(models.Book,request=request)
book = models.Book.objects.filter(id=id).first()
#获取修改数据的表单
if request.method == "GET":
form = BookModelForm(instance=book)
return render(request, 'booklist.html', locals())
#POST请求添加修改过后的数据
form = BookModelForm(data=request.POST,instance=book)
#对数据验证并且保存
if form.is_valid():
form.save()
return HttpResponse('...')
Django之ModelForm操作的更多相关文章
- Django(九)上:ModelForm操作
一.内容回顾 Model - 数据库操作 - 验证 class A(MOdel): user = email = pwd = Form - class LoginForm(Form): email = ...
- django(7)modelform操作及验证、ajax操作普通表单数据提交、文件上传、富文本框基本使用
一.modelForm操作及验证 1.获取数据库数据,界面展示数据并且获取前端提交的数据,并动态显示select框中的数据 views.py from django.shortcuts import ...
- Django使用普通表单、Form、以及modelForm操作数据库方式总结
Django使用普通表单.Form.以及modelForm操作数据库主要应用于增删该查的情景下,流程通用如下,只是实现方式不一样: 进入填写表单页面: 在表单页面填写信息,并提交: 表单数据验证 验证 ...
- Django ModelForm操作及验证
一.内容回顾 Model - 数据库操作 - 验证 class A(MOdel): user = email = pwd = Form - class LoginForm(Form): email = ...
- web框架-(七)Django补充---models进阶操作及modelform操作
通过之前的课程我们可以对于Django的models进行简单的操作,今天了解下进阶操作和modelform: 1. Models进阶操作 1.1 字段操作 AutoField(Field) - int ...
- Django之Model操作
Django之Model操作 本节内容 字段 字段参数 元信息 多表关系及参数 ORM操作 1. 字段 字段列表 AutoField(Field) - int自增列,必须填入参数 primary_ke ...
- Python之路【第二十二篇】:Django之Model操作
Django之Model操作 一.字段 AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bi ...
- Django中Model-Form验证
Django中Model-Form验证 class UserType(models.Model): caption=models.CharField(max_length=32) class User ...
- Django之ORM操作
Django之ORM操作 前言 Django框架功能齐全自带数据库操作功能,本文主要介绍Django的ORM框架 到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计 ...
随机推荐
- 几种RAID级别的比较
等级 概要 冗余 盘数 读快 写快 RAID 0 便宜.快速.危险 No N Yes Yes RAID 1 高速度.简单.安全 Yes 2(通常) Yes No RAID 5 安全(速度)成本折中 Y ...
- Vue-cli的安装步骤详细版本
https://github.com/vuejs/vue-cli 官网 使用官方推荐的webpack 条件:node在4.以上,npm在3以上 安装步骤:1.cmd打开命令行窗口2.输入cnpm in ...
- 总结web开发的四大域
一.HttpSession域: 1.作用域范围: 一次会话. 数据产生后,使用后,如果还需要继续使用的情况下,HttpSession域 2.HttpSession作用: 在第一次调用request.g ...
- SQO2008配置管理工具服务显示远程过程调用失败0x800706be
需要进行删除或更改程序里面,去卸载Microsoft SQL Server 2012 Express LocalDB就可以用了 如果还不可以看,看看是不是还有其它的占用了这个实例名 如: Micros ...
- 使用Ueditor点击上传图片时显示延迟的问题
最近在做一个项目,需要用到Ueditor,但是在点击上传图片的时候,总是隔了4-5秒才显示文件框 查了一些资料,最后发现,只需在 修改:(1) dialog/images/image.js 把imag ...
- php floor()函数 语法
php floor()函数 语法 floor函数是什么意思? php floor()函数用来向下舍入为最接近的整数.语法是floor(number),表示返回不大于参数number的下一个整数,有小数 ...
- Python基础教程(012)--排查手误错误
前言 排查由于手误书写错误的方法 内容 手动输入错误的时候,颜色是红色 函数名称写错 1,颜色不一样 2,nameError错误 掌握知识点 学会在写代码的时候排查错误
- 饿了么 <el-input></el-input>输入框获取与失去焦点事件
//1.定义focus事,绑定属性 <el-input v-model="headerInput" @focus="onInputFocus" @blur ...
- Monkey学习
Monkey是一个工程,生成伟随机事件流(在一段时间内完全不重复的事件流),由种子生成.可以模拟用户,点击,触屏等.最好用来做压力测试.无法做功能测试. adb shell monkey -p -v ...
- BZOJ5484: [Usaco2018 Dec]Sort It Out
5484: [Usaco2018 Dec]Sort It Out https://www.lydsy.com/JudgeOnline/problem.php?id=5484 Sol. 考虑没有在被喊叫 ...