[Python+Django]初心者筆記12(form表單介紹 part2:model form基本作法)

[Python+Django]初心者筆記12(form表單介紹 part2:model form基本作法)

modelform表單上顯示的欄位的建立的基本原則就是利用models.py定義的資料表欄位來產生
相較於之前的文章建立的book_renew_librarian.html,其實程式碼差異不大,但是當網頁上的欄位非常多
且無須自行實做每個欄位的驗證的時候
,利用modelform的方式來建立表單,就不用逐一在forms.py定義所需要的欄位
以下就來示範modelform來實做book_renew_librarian的方式!每個步驟都跟前篇文章
[Python+Django]初心者筆記11(form表單介紹 part1)
https://dotblogs.com.tw/kevinya/2018/07/19/143507
做出來的book_renew_librarian非常相似!其中有差異的部分非常少,註解都有把差異的部分標示出來

首先我們在catalog/templates/catalog/bookinstance_list_borrowed_all.html加上另一個超連結RenewByModelForm,
用來跟原本的超連結Renew做一個區別:

- <a href="{% url 'renew-book-librarian-modelform' bookinst.id %}">RenewByModelForm</a>


再來新增一個html檔:locallibrary\catalog\templates\catalog\book_renew_librarian_modelform.html:

{% extends "base_generic.html" %}
{% block content %}

    <!-- book_inst的由來是views.py -->
    <h1>Renew: {{bookinst.book.title}}</h1>
    <p>Borrower: {{bookinst.borrower}}</p>
    <p{% if bookinst.is_overdue %} class="text-danger"{% endif %}>Due date: {{bookinst.due_back}}</p>
    
    <form action="" method="post">
        {% csrf_token %}
        <table>
            <!-- forms.py定義的欄位將會自動在下面的form變數裡面產生 -->
        {{ form }}
        </table>
        <input type="submit" value="Submit" />
    </form>

    <!-- 讓畫面上的date欄位有可愛的UI可以快速選取日期 -->
    <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
    <link rel="stylesheet" href="/resources/demos/style.css">
    <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
    <script>
        $( function() {
            //把好用的datepicker元件,套用在新的欄位名稱id_due_back
            $( "#id_due_back" ).datepicker( { dateFormat: 'yy-mm-dd' } );
        } );
    </script>

{% endblock %}


然後於locallibrary\catalog\urls.py額外定義另一個url mapping:

#圖書館管理人員限定的 更新讀者書本到期日的功能(改用modelform的方式實做)
#網址格式:/catalog/book/<bookinstance id>/renew_bymodelform/ 
#renew_book_librarian是底線分隔,表示這是一個function-based view
urlpatterns += [   
    path('book/<uuid:pk>/renew_bymodelform/', views.renew_book_librarian_modelform, name='renew-book-librarian-modelform'),
]


再來於locallibrary\catalog\forms.py最下面加上這段以modelform方式實做的欄位驗證:
 

#利用modelform快速建立create,delete,update功能
#類似asp.net MVC的skeleton
from django.forms import ModelForm
from .models import BookInstance

class RenewBookModelForm(ModelForm):
    #clean_OOXX就是用來驗證這個欄位的
    #ps.OOXX必須為models.py定義的欄位名稱
    def clean_due_back(self):
       data = self.cleaned_data['due_back']
       
       #Check date is not in past.
       if data < datetime.date.today():
           raise ValidationError(_('Invalid date - renewal in past(model form)'))

       #Check date is in range librarian allowed to change (+4 weeks)
       if data > datetime.date.today() + datetime.timedelta(weeks=4):
           raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead(model form)'))

       # Remember to always return the cleaned data.
       return data

    class Meta:
        model = BookInstance
        #下面這是modelform指定單一欄位的方式
        #另有1. fields = '__all__' 或 exclude 的寫法,因此欄位數量很多時,用modelform實做較為方便
        fields = ['due_back']
        #複寫due_back的label名稱
        labels = { 'due_back': _('Renewal date'), }
        help_texts = { 'due_back': _('Enter a date between now and 4 weeks (default 3).'), } 

最後在locallibrary\catalog\views.py定義server side的行為:

#renew_book_librarian用於讀書館員幫讀者手動更新書的到期日
#****************改用modelform的方式實做!***********
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
from django.urls import reverse
import datetime

#用modelform方式實做的話,與之前的差異點:需改 import RenewBookModelForm
#from .forms import RenewBookForm
from .forms import RenewBookModelForm

def renew_book_librarian_modelform(request, pk):
    
    book_inst=get_object_or_404(BookInstance, pk = pk)
  
    if request.method == 'POST':
      
        #用modelform方式實做的話,與之前的差異點:需改用RenewBookModelForm去做欄位驗證
        #form = RenewBookForm(request.POST)     
        form = RenewBookModelForm(request.POST)

        # Check if the form is valid:
        if form.is_valid():            
            
            #用modelform方式實做的話,與之前的差異點:欄位名稱需改成due_back
            #book_inst.due_back = form.cleaned_data['renewal_date']
            book_inst.due_back = form.cleaned_data['due_back']
            book_inst.save()
            
            return HttpResponseRedirect(reverse('all-borrowed') )
    
    else:
        proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)

        #用modelform方式實做的話,與之前的差異點:欄位名稱需改成due_back
        # form = RenewBookForm(initial={'renewal_date': proposed_renewal_date,})
        form = RenewBookModelForm(initial={'due_back': proposed_renewal_date,})

    return render(request, 'catalog/book_renew_librarian_modelform.html', {'form': form, 'bookinst':book_inst})    

這樣子就可以了,畫面上看起來除了下面的超連結跟之前不一樣之外,其他的功能都完全相同,只是實做方式改用modelform的方式而已:


參考資料:
[Python+Django]初心者筆記11(form表單介紹 part1)
https://dotblogs.com.tw/kevinya/2018/07/19/143507
Django Tutorial Part 9: Working with forms
https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Forms