17 Django - ModelForm组件

Django - ModelForm组件

一、ModelForm组件

  这是一个神奇的组件,通过名字我们可以看出来,这个组件的功能就是把model和form组合起来,先来一个简单的例子来看一下这个东西怎么用:比如我们的数据库中有这样一张学生表,字段有姓名,年龄,爱好,邮箱,电话,住址,注册时间等等一大堆信息,现在让你写一个创建学生的页面,你的后台应该怎么写呢?首先我们会在前端一个一个罗列出这些字段,让用户去填写,然后我们从后台一个一个接收用户的输入,创建一个新的学生对象,保存其值,重点不是这些,而是合法性验证,我们需要在前端判断用户输入是否合法,比如姓名必须在多少字符以内,电话号码必须是多少位的数字,邮箱必须是邮箱的格式这些当然可以一点一点手动写限制,各种判断,这毫无问题,除了麻烦我们现在有个更优雅(以后在Python相关的内容里,要多用“优雅”这个词,并且养成习惯)的方法:ModelForm

  ModelForm本质上也是forms组件,所谓modelform,就是由model衍生而来的form。

  Form类和ModelForm类都继承了BaseForm类。

  先来简单的,生硬的把它用上,再来加验证条件。

1、创建ModelForm

 
    from django import forms    # 首先导入ModelForm
    # 在视图函数中,定义一个类,比如就叫StudentList,这个类要继承ModelForm,
    # 在这个类中再写一个原类Meta(规定写法,并注意首字母是大写的),在这个原类中,有以下属性(部分):
    class StudentListModelForm(forms.ModelForm):
            class Meta:
                model = Student    # 对应的Models.py中的Student类(表)
                fields = "__all__" # 字段,如果是__all__,就是表示渲染出所有的字段
                exclude = None # 排除的字段
                # error_messages用法:
                error_messages = {
                    'name':{'required': "用户名不能为空",},
                    'age':{'required': "年龄不能为空",},
                }
          # labels,自定义在前端显示的名字
          labels= {
                 "name":"用户名",
                "age":"年龄"
               }
    # widgets用法,比如把输入用户名的input框改为Textarea
    # 首先得导入模块
    from django.forms import widgets as wid   # 因为重名,所以起个别名
                widgets = {
                    "name":wid.Textarea(attrs={"class":"c1"}) # 还可以自定义属性        
              }                  

  然后在url对应的视图函数中实例化这个类,把这个对象传给前端:

    def student(request):
            if request.method == 'GET':
                student_list = StudentListModelForm()
                return render(request, 'student.html', {'student_list': student_list})

  在页面中for循环这个student_list,拿到student对象,直接在前端打印这个student,是个input框,student.label ,拿到数据库中每个字段的名字 ,也可以在类中进行自定义,还可以通过student.errors.0 拿到错误信息,有了这些,我们就可以通过bootstrap,自己拼出来想要的样式了,比如:

  <body>
    <div class="container">
            <h1>student</h1>
            <form method="POST" novalidate>
                {% csrf_token %}
                {% for student in student_list %}
                    <div class="form-group col-md-6">
                        <label class="col-md-3 control-label">{{ student.label }}</label>
                        <div class="col-md-9" style="position: relative;">{{ student }}</div>
                    </div>
                {% endfor %}
                <div class="col-md-2 col-md-offset-10">
                    <input type="submit" value="提交" class="btn-primary">
                </div>
            </form>
    </div>
  </body>
 

  现在还缺一个input框的form-control样式,可以考虑在后台的widget里面添加,比如下面这样:

 
  from django.forms import widgets as wid    # 因为重名,所以起个别名
    widgets = {
      "name":wid.TextInput(attrs={'class':'form-control'}),
      "age":wid.NumberInput(attrs={'class':'form-control'}),
      "email":wid.EmailInput(attrs={'class':'form-control'})
    }
 

  当然也可以在js中,找到所有的input框,加上这个样式,也行。

2、添加记录

  保存数据的时候,不用挨个取数据了,只需要save一下,如下:

 
    def student(request):
          if request.method == 'GET':
             student_list = StudentListModelForm()  # 实例化对象传入页面可以根据字段渲染标签   
         return render(request,'student_add.html',{'student_list':student_list})    
      else:
              student_list = StudentListModelForm(request.POST) # 实例化对象传入页面可校验字段        
         if student_list.is_valid():
                  student_list.save()    # 将记录加入数据库,并且如果有多对多关系也可以自动绑定     
             return redirect('/studentlist/')
         else:     # 若输入错误则将错误信息渲染在页面上 
             return render(request,'student_add.html',{'student_list':student_list})

3、编辑数据

  如果不用ModelForm,编辑的时候得显示之前的数据吧,还得挨个取一遍值,使用ModelForm,只需要加一个instance=obj(obj是要修改的数据库中的一条数据对象),就可以得到同样的效果。

  保存的时候要注意,一定要有这个对象(instance=obj),否则不知道更新哪一个数据。

  代码示例:

 
  from django.shortcuts import render, HttpResponse, redirect
  from django import forms
  # Create your views here.
  from app01 import models
  def test(request):
            # model_form = models.Student
            model_form = models.Student.objects.all()
            return render(request,'test.html',{'model_form':model_form})
  class StudentListModelForm(forms.ModelForm):
            class Meta:
                model = models.Student #对应的Model中的类
                fields = "__all__" #字段,如果是__all__,就是表示列出所有的字段
                exclude = None #排除的字段
                labels = None #提示信息
                help_texts = None #帮助提示信息
                widgets = None #自定义插件
                error_messages = None #自定义错误信息
                #error_messages用法:
                error_messages = {
                    'name':{'required':"用户名不能为空",},
                    'age':{'required':"年龄不能为空",},
                }
  #widgets用法,比如把输入用户名的input框给为Textarea,首先得导入模块
  from django.forms import widgets as wid #因为重名,所以起个别名
               widgets = {
                    "name":wid.Textarea
                }
                #labels,自定义在前端显示的名字
                labels= {
                    "name":"用户名"
                }

  def student(request):
            if request.method == 'GET':
                student_list = StudentListModelForm()
                return render(request,'student_add.html',{'student_list':student_list})
            else:
                student_list = StudentListModelForm(request.POST)
                if student_list.is_valid():
                    student_list.save()
                    return redirect('/studentlist/')
           else:    
                return render(request,'student_add.html',{'student_list':student_list})

  def student_edit(request,pk):
            obj = models.Student.objects.filter(pk=pk).first()
            if not obj:
                return redirect('test')
            if request.method == "GET":
                student_list = StudentListModelForm(instance=obj)  # 编辑传入obj可渲染默认值
                return render(request,'student_edit.html',{'student_list':student_list})
            else:
                student_list = StudentListModelForm(request.POST,instance=obj)  # 传入instance
                if student_list.is_valid():
                    student_list.save()   # 示例化对象时若传入instance,则对原数据进行更新而非添加
                    return redirect('/studentlist/')
          else:     # 若输入错误则将错误信息渲染在页面上 
                return render(request,'student_edit.html',{'student_list':student_list})
 

  总结:从上边可以看到ModelForm用起来是非常方便的,比如添加、编辑之类的操作。但是也带来额外不好的地方,model和form之间耦合了。如果不耦合的话,mf.save()方法也无法直接提交保存。 但是耦合的话使用场景通常局限用于小程序,写大程序就最好不用了。modelform中钩子使用同forms组件。

二、forms组件补充

1、forms组件中的ChoiceField()和参数choices的使用

  在使用forms组件进行校验时使用ChoiceField和参数choices可以渲染出来一个单选的select标签,代码如下:

 
  class UserForm(forms.Form):
    username=forms.CharField(min_length=5,label="用户名")
    gender=forms.ChoiceField(choices=((1,"男"),(2,"女")))
    password=forms.CharField(min_length=5,
          widget=widgets.PasswordInput(),
          label="密码")
    r_pwd=forms.CharField(min_length=5,
          widget=widgets.PasswordInput(),
          label="确认密码")
    email=forms.EmailField(min_length=5,label="邮箱")
 

  分析:gender字段渲染出来单选的select标签,元组的第一个值为value,第二个值为标签文本值

  <select name="gender" id="id_gender">
    <option value="1">男</option>
    <option value="2">女</option>
  </select>

2、以上choices中数据是写死的,但是如果数据是需要从数据库中获取该怎么办呢?使用ModelChoiceField,它继承了ChoiceField,并且有一个queryset参数,用法如下:

 
  class BookForm(forms.Form):    # 图书管理系统中的校验添加书籍表单的form组件
      title=forms.CharField(max_length=32)
      price=forms.IntegerField()
      pub_date=forms.DateField(widget=widgets.TextInput(attrs={"type":"date"}))
      publish=forms.ModelChoiceField(queryset=Publish.objects.all())
      authors=forms.ModelMultipleChoiceField(queryset=Author.objects.all())
 

  分析:该form组件中的publish字段渲染出来单选的select标签,option中的内容是Publish表中的数据(Publish类中__str__方法返回的字符串),authors字段渲染出来一个多选的select标签,option中的内容是Author表中的数据(Author类中__str__方法返回的字符串),ModelMultipleChoiceField类继承了ModelChoiceField类。

  ModelChoiceField通常对应model中的ForeignKey;

  ModelMultipleChoiceField通常对应model中的ManyToManyField;

  注意:forms组件在django启动时加载的,所以修改了forms组件相关代码,要自己手动重启django。

3、更多关于form组件的Django内置字段参见老师博客 - form组件补充

  博客地址:https://www.cnblogs.com/yuanchenqi/articles/7614921.html

三、补充

1、model中的choices(跟forms组件中的choices没直接关系)

models.py中代码如下:

  from django.contrib.auth.models import AbstractUser

  class UserInfo(AbstractUser):
      tel=models.CharField(max_length=32)
      gender=models.IntegerField(choices=((1,"男"),(2,"女")),default=1)

  分析:我们知道迁移后数据库中有了userinfo表,表中除了有原生auth_user表的那些字段外,还有了tel和gender字段,注意gender字段我们在IntegerField中定义了choices=((1,"男"),(2,"女")),这时表中gender字段对应的值还是1或者2,但是我们可以通过user_obj.get_gender_display()方法得到1对应的值(男)或者2对应的值(女)。也可以是gender=CharField(choices=(("male","男"),("female","女")),default="male")。

2、include语法

  页面框架一样,局部不一样时用继承,但是当两个页面只有局部代码一样时,可以用include语法,即把两个页面相同部分的代码放到一个页面(如form.html)中,其他两个页面中只需要在那部分代码的位置写{% include "form.html" %}即可,避免了代码的重复。

3、通过学习modelform,我们知道使用modelform时不需要我们再单独定义一遍校验字段,它会帮我们自动翻译成我们之前学习的forms组件中定义的字段:

 
  模型类                        modelform组件
  models.CharField()   --- >   forms.CharField()
  models.DecimalField()   --- >   forms.DecimalField()
  models.ForeignKey()   --- >   forms.ModelChoiceField()
  models.ManyToManyField()   --- >   forms.ModelMultipleChoiceField()
  models.EmailField()   --- >   forms.EmailField()  
  # 所以在使用modelform时定义models.EmailField()才有意义。
 

一、ModelForm组件

  这是一个神奇的组件,通过名字我们可以看出来,这个组件的功能就是把model和form组合起来,先来一个简单的例子来看一下这个东西怎么用:比如我们的数据库中有这样一张学生表,字段有姓名,年龄,爱好,邮箱,电话,住址,注册时间等等一大堆信息,现在让你写一个创建学生的页面,你的后台应该怎么写呢?首先我们会在前端一个一个罗列出这些字段,让用户去填写,然后我们从后台一个一个接收用户的输入,创建一个新的学生对象,保存其值,重点不是这些,而是合法性验证,我们需要在前端判断用户输入是否合法,比如姓名必须在多少字符以内,电话号码必须是多少位的数字,邮箱必须是邮箱的格式这些当然可以一点一点手动写限制,各种判断,这毫无问题,除了麻烦我们现在有个更优雅(以后在Python相关的内容里,要多用“优雅”这个词,并且养成习惯)的方法:ModelForm

  ModelForm本质上也是forms组件,所谓modelform,就是由model衍生而来的form。

  Form类和ModelForm类都继承了BaseForm类。

  先来简单的,生硬的把它用上,再来加验证条件。

1、创建ModelForm

 
    from django import forms    # 首先导入ModelForm
    # 在视图函数中,定义一个类,比如就叫StudentList,这个类要继承ModelForm,
    # 在这个类中再写一个原类Meta(规定写法,并注意首字母是大写的),在这个原类中,有以下属性(部分):
    class StudentListModelForm(forms.ModelForm):
            class Meta:
                model = Student    # 对应的Models.py中的Student类(表)
                fields = "__all__" # 字段,如果是__all__,就是表示渲染出所有的字段
                exclude = None # 排除的字段
                # error_messages用法:
                error_messages = {
                    'name':{'required': "用户名不能为空",},
                    'age':{'required': "年龄不能为空",},
                }
          # labels,自定义在前端显示的名字
          labels= {
                 "name":"用户名",
                "age":"年龄"
               }
    # widgets用法,比如把输入用户名的input框改为Textarea
    # 首先得导入模块
    from django.forms import widgets as wid   # 因为重名,所以起个别名
                widgets = {
                    "name":wid.Textarea(attrs={"class":"c1"}) # 还可以自定义属性        
              }                  

  然后在url对应的视图函数中实例化这个类,把这个对象传给前端:

    def student(request):
            if request.method == 'GET':
                student_list = StudentListModelForm()
                return render(request, 'student.html', {'student_list': student_list})

  在页面中for循环这个student_list,拿到student对象,直接在前端打印这个student,是个input框,student.label ,拿到数据库中每个字段的名字 ,也可以在类中进行自定义,还可以通过student.errors.0 拿到错误信息,有了这些,我们就可以通过bootstrap,自己拼出来想要的样式了,比如:

  <body>
    <div class="container">
            <h1>student</h1>
            <form method="POST" novalidate>
                {% csrf_token %}
                {% for student in student_list %}
                    <div class="form-group col-md-6">
                        <label class="col-md-3 control-label">{{ student.label }}</label>
                        <div class="col-md-9" style="position: relative;">{{ student }}</div>
                    </div>
                {% endfor %}
                <div class="col-md-2 col-md-offset-10">
                    <input type="submit" value="提交" class="btn-primary">
                </div>
            </form>
    </div>
  </body>
 

  现在还缺一个input框的form-control样式,可以考虑在后台的widget里面添加,比如下面这样:

 
  from django.forms import widgets as wid    # 因为重名,所以起个别名
    widgets = {
      "name":wid.TextInput(attrs={'class':'form-control'}),
      "age":wid.NumberInput(attrs={'class':'form-control'}),
      "email":wid.EmailInput(attrs={'class':'form-control'})
    }
 

  当然也可以在js中,找到所有的input框,加上这个样式,也行。

2、添加记录

  保存数据的时候,不用挨个取数据了,只需要save一下,如下:

 
    def student(request):
          if request.method == 'GET':
             student_list = StudentListModelForm()  # 实例化对象传入页面可以根据字段渲染标签   
         return render(request,'student_add.html',{'student_list':student_list})    
      else:
              student_list = StudentListModelForm(request.POST) # 实例化对象传入页面可校验字段        
         if student_list.is_valid():
                  student_list.save()    # 将记录加入数据库,并且如果有多对多关系也可以自动绑定     
             return redirect('/studentlist/')
         else:     # 若输入错误则将错误信息渲染在页面上 
             return render(request,'student_add.html',{'student_list':student_list})

3、编辑数据

  如果不用ModelForm,编辑的时候得显示之前的数据吧,还得挨个取一遍值,使用ModelForm,只需要加一个instance=obj(obj是要修改的数据库中的一条数据对象),就可以得到同样的效果。

  保存的时候要注意,一定要有这个对象(instance=obj),否则不知道更新哪一个数据。

  代码示例:

 
  from django.shortcuts import render, HttpResponse, redirect
  from django import forms
  # Create your views here.
  from app01 import models
  def test(request):
            # model_form = models.Student
            model_form = models.Student.objects.all()
            return render(request,'test.html',{'model_form':model_form})
  class StudentListModelForm(forms.ModelForm):
            class Meta:
                model = models.Student #对应的Model中的类
                fields = "__all__" #字段,如果是__all__,就是表示列出所有的字段
                exclude = None #排除的字段
                labels = None #提示信息
                help_texts = None #帮助提示信息
                widgets = None #自定义插件
                error_messages = None #自定义错误信息
                #error_messages用法:
                error_messages = {
                    'name':{'required':"用户名不能为空",},
                    'age':{'required':"年龄不能为空",},
                }
  #widgets用法,比如把输入用户名的input框给为Textarea,首先得导入模块
  from django.forms import widgets as wid #因为重名,所以起个别名
               widgets = {
                    "name":wid.Textarea
                }
                #labels,自定义在前端显示的名字
                labels= {
                    "name":"用户名"
                }

  def student(request):
            if request.method == 'GET':
                student_list = StudentListModelForm()
                return render(request,'student_add.html',{'student_list':student_list})
            else:
                student_list = StudentListModelForm(request.POST)
                if student_list.is_valid():
                    student_list.save()
                    return redirect('/studentlist/')
           else:    
                return render(request,'student_add.html',{'student_list':student_list})

  def student_edit(request,pk):
            obj = models.Student.objects.filter(pk=pk).first()
            if not obj:
                return redirect('test')
            if request.method == "GET":
                student_list = StudentListModelForm(instance=obj)  # 编辑传入obj可渲染默认值
                return render(request,'student_edit.html',{'student_list':student_list})
            else:
                student_list = StudentListModelForm(request.POST,instance=obj)  # 传入instance
                if student_list.is_valid():
                    student_list.save()   # 示例化对象时若传入instance,则对原数据进行更新而非添加
                    return redirect('/studentlist/')
          else:     # 若输入错误则将错误信息渲染在页面上 
                return render(request,'student_edit.html',{'student_list':student_list})
 

  总结:从上边可以看到ModelForm用起来是非常方便的,比如添加、编辑之类的操作。但是也带来额外不好的地方,model和form之间耦合了。如果不耦合的话,mf.save()方法也无法直接提交保存。 但是耦合的话使用场景通常局限用于小程序,写大程序就最好不用了。modelform中钩子使用同forms组件。

二、forms组件补充

1、forms组件中的ChoiceField()和参数choices的使用

  在使用forms组件进行校验时使用ChoiceField和参数choices可以渲染出来一个单选的select标签,代码如下:

 
  class UserForm(forms.Form):
    username=forms.CharField(min_length=5,label="用户名")
    gender=forms.ChoiceField(choices=((1,"男"),(2,"女")))
    password=forms.CharField(min_length=5,
          widget=widgets.PasswordInput(),
          label="密码")
    r_pwd=forms.CharField(min_length=5,
          widget=widgets.PasswordInput(),
          label="确认密码")
    email=forms.EmailField(min_length=5,label="邮箱")
 

  分析:gender字段渲染出来单选的select标签,元组的第一个值为value,第二个值为标签文本值

  <select name="gender" id="id_gender">
    <option value="1">男</option>
    <option value="2">女</option>
  </select>

2、以上choices中数据是写死的,但是如果数据是需要从数据库中获取该怎么办呢?使用ModelChoiceField,它继承了ChoiceField,并且有一个queryset参数,用法如下:

 
  class BookForm(forms.Form):    # 图书管理系统中的校验添加书籍表单的form组件
      title=forms.CharField(max_length=32)
      price=forms.IntegerField()
      pub_date=forms.DateField(widget=widgets.TextInput(attrs={"type":"date"}))
      publish=forms.ModelChoiceField(queryset=Publish.objects.all())
      authors=forms.ModelMultipleChoiceField(queryset=Author.objects.all())
 

  分析:该form组件中的publish字段渲染出来单选的select标签,option中的内容是Publish表中的数据(Publish类中__str__方法返回的字符串),authors字段渲染出来一个多选的select标签,option中的内容是Author表中的数据(Author类中__str__方法返回的字符串),ModelMultipleChoiceField类继承了ModelChoiceField类。

  ModelChoiceField通常对应model中的ForeignKey;

  ModelMultipleChoiceField通常对应model中的ManyToManyField;

  注意:forms组件在django启动时加载的,所以修改了forms组件相关代码,要自己手动重启django。

3、更多关于form组件的Django内置字段参见老师博客 - form组件补充

  博客地址:https://www.cnblogs.com/yuanchenqi/articles/7614921.html

三、补充

1、model中的choices(跟forms组件中的choices没直接关系)

models.py中代码如下:

  from django.contrib.auth.models import AbstractUser

  class UserInfo(AbstractUser):
      tel=models.CharField(max_length=32)
      gender=models.IntegerField(choices=((1,"男"),(2,"女")),default=1)

  分析:我们知道迁移后数据库中有了userinfo表,表中除了有原生auth_user表的那些字段外,还有了tel和gender字段,注意gender字段我们在IntegerField中定义了choices=((1,"男"),(2,"女")),这时表中gender字段对应的值还是1或者2,但是我们可以通过user_obj.get_gender_display()方法得到1对应的值(男)或者2对应的值(女)。也可以是gender=CharField(choices=(("male","男"),("female","女")),default="male")。

2、include语法

  页面框架一样,局部不一样时用继承,但是当两个页面只有局部代码一样时,可以用include语法,即把两个页面相同部分的代码放到一个页面(如form.html)中,其他两个页面中只需要在那部分代码的位置写{% include "form.html" %}即可,避免了代码的重复。

3、通过学习modelform,我们知道使用modelform时不需要我们再单独定义一遍校验字段,它会帮我们自动翻译成我们之前学习的forms组件中定义的字段:

 
  模型类                        modelform组件
  models.CharField()   --- >   forms.CharField()
  models.DecimalField()   --- >   forms.DecimalField()
  models.ForeignKey()   --- >   forms.ModelChoiceField()
  models.ManyToManyField()   --- >   forms.ModelMultipleChoiceField()
  models.EmailField()   --- >   forms.EmailField()  
  # 所以在使用modelform时定义models.EmailField()才有意义。
 

猜你喜欢

转载自www.cnblogs.com/xintiao-/p/10078970.html