1. 程式人生 > >17 Django - ModelForm元件

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()才有意義。