zoukankan      html  css  js  c++  java
  • 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': '16'}
                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('...')
  • 相关阅读:
    Linux安装和配置java
    动态代理:JDK动态代理和CGLIB代理的区别
    常用算法复习
    Linux重新学习
    oracle复杂查询是sql
    oracle触发器
    oracle存储过程
    oracle数据库操作
    java知识回顾
    Spring AOP简述
  • 原文地址:https://www.cnblogs.com/shenjianping/p/11562148.html
Copyright © 2011-2022 走看看