首页 > 其他分享 >深入理解Django的ModelForm操作

深入理解Django的ModelForm操作

时间:2024-03-22 09:46:53浏览次数:28  
标签:__ form fields self Django field 深入 ModelForm

深入理解Django的ModelForm操作

原文链接 :https://www.cnblogs.com/shenjianping/p/11562148.html

一、ModelForm的使用

顾名思义,ModelForm就是将Model与Form进行绑定,Form有自动生成表单的作用,但是每一个forms字段需要自己手动填写,而Model就是数据库表包含了所有的数据字段。所以ModelForm有着以下功能:
Form所有的功能
将Model字段自动转换成forms字段

1.1、实例演示

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.2、实例解析

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()

二、源码一览

假设这里以修改视图流程来了解一下源码:

2.1、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。
2.2、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都有,无论是字段的验证还是其它的功能。

2.3、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)

如果commit=False,就不会保存实例,当调用save方法后不会保存ManyToMany字段,需要自行去调用save_m2m方法,例如:
参考文章:https://docs.djangoproject.com/en/2.2/topics/forms/modelforms/#modelform

# 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()

三、扩展

3.1、自定义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': '16'}
            field.widget.attrs.update(attrs_dict)

3.1、动态生成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'})
             if field_name is not 'Checker':
                field_obj.widget.attrs.update({'readonly': "readonly"})   # 将其他字段 在 html 表单 中 显示为只读


        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('...')

标签:__,form,fields,self,Django,field,深入,ModelForm
From: https://www.cnblogs.com/HeroZhang/p/18088701

相关文章

  • 深入理解指针
    1、内存和地址1.1内存在生活中相当于一栋楼中房间号,在计算机中cpu,在处理数据时,需要的数据就是在内存中读取的,处理以后的数据也会放在内存中,内存会被划分成一个一个的内存单元,每个内存单元的大小取一个字节一个比特位可以存储一个2进制的为1或0;bit——比特位;Byte——字节;1B......
  • django中分页器的使用方法(初、高级版本)
    效果图:方法如下:1.简单版(较繁琐但是直观):1.1定义数据库模型(models.py)中添加表classProductSample(models.Model):#示例商品表id=models.AutoField(db_column='ID',primary_key=True)#Fieldnamemadelowercase.item_id=models.CharField(verbose......
  • 深入理解并行编程-读书笔记 rcu
     在当前的Linux内核中发挥着重要的作用。RCU主要针对的数据对象是链表,目的是提高遍历读取数据的效率,为了达到目的使用RCU机制读取数据的时候不对链表进行耗时的加锁操作。这样在同一时间可以有多个线程同时读取该链表,并且允许一个线程对链表进行修改(修改的时候,需要加锁)。RCU适用......
  • OLAP数据库选型指南:Doris与ClickHouse的深入对比与分析
    码到三十五:个人主页心中有诗画,指尖舞代码,目光览世界,步履越千山,人间尽值得!在当今数据驱动的时代,数据的存储、处理和分析变得尤为重要。为了满足这一需求,市场上涌现出了许多优秀的数据处理和分析工具。其中,Doris和ClickHouse是两个备受关注的开源项目。本......
  • 深入理解并行编程-学习笔记
    一简介1并行编程的目标并行编程(在单线程编程的目标之上)有如下三个主要目标。1.性能。2.生产率。3.通用性。线程优化:性能与扩展通用:需要牢记的是,并行编程只是提高性能的方案之一。其他熟知的方案按实现难度递增的顺序罗列如下。1.运行多个串行应用实例。2.利用现有的......
  • 使用元类实现Django的ORM
    一、ORM基本介绍ORM是python编程语言后端web框架Django的核心思想,“ObjectRelationalMapping”,即对象-关系映射,简称ORM。二、实现目标创建一个实例对象,用创建它的类名当做数据表名,用创建它的类属性对应数据表的字段,当对这个实例对象操作时,能够对应MySQL语句如图:三、......
  • 深入理解 CSS:基础概念、注释、选择器及优先级
    在构建网页的过程中,我们不仅需要HTML来搭建骨架,还需要CSS来装扮我们的网页。那么,什么是CSS呢?本文将带大家了解css的基础概念,注释、选择器及优先级。一、CSS简介1.1什么是CSSCSS,全称为CascadingStyleSheets(层叠样式表),是一种用于描述网页上的信息格式化和显示方式的语言。它的......
  • 深入理解 SpringAOP(一):AOP 组件概述
    概述spring-aop模块是Spring框架中最重要的组件之一,它为我们提供了强大的AOP功能,并为其他扩展功能(如声明式事务、声明式异步处理等)提供了支持。在本文中,我们将深入探讨SpringAOP的源码,从代理对象的创建开始,揭示SpringAOP的运行机制。首先,在阅读这篇文章前,请先确保对Sp......
  • 深入理解 SpringAOP(二):AOP的执行流程
    概述在之前的文章中,我们已经对SpringAOP的关键组件进行了描述,并且了解了其基本操作和流程。在本文中,我们将进一步深入源码,揭示SpringAOP的内部实现细节,理解其运行机制的每个环节,包括切面的织入方式、代理对象的创建过程、连接点的定位与匹配等。通过对完整运行流程的深入研究......
  • 深入理解 SpringMVC
    前言SpringMVC可以说是我们日常开发中最依赖的Spring组件了,它基于Servlet容器实现,允许我们通过注解的方式开发Web程序。在本篇文章,将深入SpringMVC源码,梳理SpringMVC对Web请求处理流程,弄懂相关核心组件的原理,最终做到在使用的时候知其然也知其所以然。一、接受并分......