跳到主要內容

[Django教學14]解析5個常用的Django Class-based Views使用方式

Photo by Austin Distel on Unsplash
在一般的Django專案開發,可以看到Django應用程式(APP)下的views.py檔案中,依照功能的需求撰寫了許多的檢視函式(View Function),這種寫法就稱為函式導向的檢視(Function-based Views),雖然較為直覺,但是隨著Django專案的規模越來越大,這樣的寫法就像是在Python專案中寫了許多的函式(Function)一樣,對於未來的維護及擴充性,較不理想。

所以,這時候就會使用Python物件導向(OOP)的概念,將許多的函式(Function)模組化為類別(Class)及方法(Method),來提高程式碼的重用性及維護性。

Django也是相同的道理,它提供了許多內建的檢視類別,讓開發人員能夠藉由覆寫(Overriding) 的方式,將函式導向的檢視(Function-based Views),封裝成類別導向的檢視(Class-based Views),不但具有物件導向(OOP)的優點外,Django在背後也幫我們做了許多的事情,讓程式碼的寫法能夠更加簡潔。

因此,本文將利用5個常用的Django檢視類別,來實作待辦清單(To Do List)網站,藉此與函式導向的檢視(Function-based Views)寫法作比較,重點包含:
  • 前置作業
  • Django ListView(清單檢視類別)
  • Django CreateView(新增檢視類別)
  • Django UpdateView(修改檢視類別)
  • Django DeleteView(刪除檢視類別)
  • Django DetailView(內容檢視類別)

一、前置作業

在進行今天的實作前,先來說明一下本文所使用的待辦清單(To Do List)資料模型,如下範例:
from django.db import models
from django.utils import timezone


class Todo(models.Model):
    title = models.CharField(max_length=100)  #標題
    finish = models.BooleanField(default=False)  #是否完成
    created = models.DateField(default=timezone.now)  #建立日期
所以有了資料模型後,就可以在forms.py檔案中,建立待辦清單(To Do List)所需的資料模型表單(ModelForm),如下範例:
from django import forms
from .models import Todo


class TodoModelForm(forms.ModelForm):
    class Meta:
        model = Todo
        fields = ('title', 'finish')
        widgets = {
            'title': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '待辦事項'})
        }

二、Django ListView(清單檢視類別)

通常使用在查詢的功能。其中包含了許多相關的屬性(Attribute)及方法(Method),讓開發人員可以透過覆寫(Overriding)的方式,自訂所需的資料。

首先,開啟Django應用程式(APP)下的views.py檔案,引用及繼承Django ListView類別,接著,就可以利用覆寫(Overriding) model屬性,來指定查詢的資料模型,如下範例:
from django.shortcuts import render
from .models import Todo
from django.views.generic import (
    ListView
)


class TodoListView(ListView):
    model = Todo  # Todo.objects.all()
這時候Django就會自動查詢Todo資料模型中,所有的資料,也就是第9行註解的效果。而如果有特定的資料篩選條件,則可以覆寫(Overriding) queryset屬性,如下範例:
from django.shortcuts import render
from .models import Todo
from django.views.generic import (
    ListView
)


class TodoListView(ListView):
    model = Todo
    queryset = Todo.objects.filter(finish=False)  # 指定查詢條件
範例中篩選尚未完成的待辦事項來顯示。接下來,要將查詢結果傳送到Django Template(樣板),可以使用template_name屬性來指定,如下範例:
from django.shortcuts import render
from .models import Todo
from django.views.generic import (
    ListView
)


class TodoListView(ListView):
    model = Todo
    queryset = Todo.objects.filter(finish=False)  # 指定查詢條件
    template_name = 'todo/todo_list.html'  #樣板路徑
這樣的寫法,就稱為類別導向的檢視(Class-based Views)寫法。完成後,開啟Django應用程式(APP)下的urls.py檔案,來進行網址的設定,如下範例:
from django.urls import path
from .views import (
    TodoListView
)

app_name = 'todo'
urlpatterns = [
    path('', TodoListView.as_view(), name='list'),
]
這裡和函式導向的檢視(Function-based Views)不一樣的地方是,Django類別導向的檢視(Class-based Views)使用as_view()來進行轉換。

開啟剛剛所指定的清單樣板(todo_list.html),透過迴圈將資料顯示出來,如下範例:
{% extends 'base.html' %}

{% block content %}

<table class="table">
    <thead>
        <tr>
            <th>待辦事項</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        {% for todo in object_list %}
        <tr>
            <td>{{ todo.title }}</td>
            <td style="text-align:right;">
                <a href="{% url 'todo:update' todo.id %}" class="btn btn-success">修改</a>
                <a href="{% url 'todo:delete' todo.id %}" class="btn btn-danger">刪除</a>
                <a href="{% url 'todo:detail' todo.id %}" class="btn btn-info">明細</a>
            </td>
        </tr>
        {% endfor %}
    </tbody>
</table>

{% endblock  %}
範例中可以看到Django類別導向的檢視(Class-based Views)預設將查詢結果的物件object_list傳入樣板中。

三、Django CreateView(新增檢視類別)

在新增資料時,所使用的檢視類別,包含了傳遞表單、表單驗證及儲存資料等方法(Method),同樣可以透過覆寫(Overriding)的方式來進行客製化。

由於本文的新增資料表單放置在清單樣板(todo_list.html)中,所以在Django ListView(清單檢視類別)中,需傳遞資料模型表單(ModelForm)至清單樣板(todo_list.html),而傳遞的方式可以透過覆寫(Overriding) get_context_data()方法來達成,如下範例:
from django.shortcuts import render
from .models import Todo
from django.views.generic import (
    ListView
)


class TodoListView(ListView):
    model = Todo
    queryset = Todo.objects.filter(finish=False)  # 指定查詢條件
    template_name = 'todo/todo_list.html'  #樣板路徑

    # 要傳遞的資料
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["form"] = TodoModelForm()  #資料模型表單
        return context
接著,建立Django CreateView(新增檢視類別),指定儲存時,所要新增的資料模型及儲存成功後所要導向的網址,如下範例:
class TodoCreateView(CreateView):
    model = Todo
    form_class = TodoModelForm  # 使用的表單類別
    success_url = '/todo'  # 儲存成功後要導向的網址
只要寫這幾行程式碼,Django就會在儲存時,驗證及取得表單資料,並且寫入資料庫中,非常的強大。

開啟Django應用程式(APP)下的urls.py檔案,加入Django CreateView(新增檢視類別)網址,如下範例:
from django.urls import path
from .views import (
    TodoListView,
    TodoCreateView
)

app_name = 'todo'
urlpatterns = [
    path('', TodoListView.as_view(), name='list'),
    path('create', TodoCreateView.as_view(), name='create'),
]
在清單樣板(todo_list.html)中,建立新增功能的表單,並且在action的地方指定儲存後所要導向的網址,如下範例:
{% extends 'base.html' %}

{% block content %}

<form action="/todo/create" method="POST">
    {% csrf_token %}
    <div class="row">
        <div class="col">
            {{ form.title }}
        </div>
        <div class="col">
            <input type="submit" class="btn btn-primary" value="儲存">
        </div>
    </div>
</form>
<br />
<table class="table">
    <thead>
        <tr>
            <th>待辦事項</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        {% for todo in object_list %}
        <tr>
            <td>{{ todo.title }}</td>
            <td style="text-align:right;">
                <a href="{% url 'todo:update' todo.id %}" class="btn btn-success">修改</a>
                <a href="{% url 'todo:delete' todo.id %}" class="btn btn-danger">刪除</a>
                <a href="{% url 'todo:detail' todo.id %}" class="btn btn-info">明細</a>
            </td>
        </tr>
        {% endfor %}
    </tbody>
</table>

{% endblock  %}

四、Django UpdateView(修改檢視類別)

主要用於修改資料的功能,用法和Django CreateView(新增檢視類別)大同小異,如下範例:
class TodoUpdateView(UpdateView):
    model = Todo
    form_class = TodoModelForm  # 使用的表單類別
    template_name = 'todo/todo_update.html'  # 修改樣板
    success_url = '/todo'  # 儲存成功後要導向的網址
Django UpdateView(修改檢視類別),會利用網址所傳遞的pk(主鍵),找到該筆資料,並且顯示在修改樣板(todo_update.html)的表單中,除此之外,在修改完資料儲存時,同樣會驗證及取得表單資料,更新至資料庫中。

接著,開啟Django應用程式(APP)下的urls.py檔案,加入Django UpdateView(修改檢視類別)網址,如下範例:
from django.urls import path
from .views import (
    TodoListView,
    TodoCreateView,
    TodoUpdateView
)

app_name = 'todo'
urlpatterns = [
    path('', TodoListView.as_view(), name='list'),
    path('create', TodoCreateView.as_view(), name='create'),
    path('update/<int:pk>', TodoUpdateView.as_view(), name='update'),
]
其中要特別注意的是,在修改功能的網址,主鍵參數一定要命名為pk,這也是在撰寫Django類別導向的檢視(Class-based Views)規範,否則會出現錯誤。

在修改樣板(todo_update.html)中,利用表單來顯示要修改的資料,如下範例:
{% extends 'base.html' %}

{% block content %}

    <form action="" method="POST">
        {% csrf_token %}
        <div class="form-group">
            <label>待辦事項</label>
            {{ form.title }}
        </div>
        <div class="form-group">
            <label class="form-check-label">是否完成</label>
            {{ form.finish }}
        </div>
        <input class="btn btn-primary" type="submit" value="" />
    </form>

{% endblock  %}

五、Django DeleteView(刪除檢視類別)

用在刪除資料的功能,使用方式和Django UpdateView(修改檢視類別)相似,指定刪除的資料模型、樣板及刪除成功後所要導向的網址,同樣會在使用者點擊確定刪除按鈕時,利用網址所傳入的主鍵參數(pk),來刪除資料,如下範例:
class TodoDeleteView(DeleteView):
    model = Todo
    template_name = 'todo/todo_delete.html'  # 刪除樣板
    success_url = '/todo'  # 刪除成功後要導向的網址
urls.py檔案的部分,加入Django DeleteView(刪除檢視類別)網址,如下範例:
from django.urls import path
from .views import (
    TodoListView,
    TodoCreateView,
    TodoUpdateView,
    TodoDeleteView
)

app_name = 'todo'
urlpatterns = [
    path('', TodoListView.as_view(), name='list'),
    path('create', TodoCreateView.as_view(), name='create'),
    path('update/<int:pk>', TodoUpdateView.as_view(), name='update'),
    path('delete/<int:pk>', TodoDeleteView.as_view(), name='delete'),
]
接著開啟刪除樣板(todo_delete.html),顯示要刪除的資料,讓使用者進行確認,如下範例:
{% extends 'base.html' %}

{% block content %}

    是否確定刪除{{ object.title }}?
    <br />
    <br />
    <form action="" method="POST">
        {% csrf_token %}
        <input class="btn btn-primary" type="submit" value="" />
        <a class="btn btn-warning" href="{% url &#39;todo:list&#39; %}">取消</a>
    </form>

{% endblock  %}

六、Django DetailView(內容檢視類別)

用來顯示特定資料的詳細內容,只需要覆寫(Overriding)資料模型(model)及樣板名稱(template_name)屬性即可,如下範例:
class TodoDetailView(DetailView):
    model = Todo
    template_name = 'todo/todo_detail.html'
開啟Django應用程式(APP)下的urls.py檔案,加入Django DetailView(內容檢視類別)網址,如下範例:
from django.urls import path
from .views import (
    TodoListView,
    TodoCreateView,
    TodoUpdateView,
    TodoDeleteView,
    TodoDetailView
)

app_name = 'todo'
urlpatterns = [
    path('', TodoListView.as_view(), name='list'),
    path('create', TodoCreateView.as_view(), name='create'),
    path('update/<int:pk>', TodoUpdateView.as_view(), name='update'),
    path('delete/<int:pk>', TodoDeleteView.as_view(), name='delete'),
    path('detail/<int:pk>', TodoDetailView.as_view(), name='detail'),
]
最後,建立內容樣板(todo_detail.html),來顯示資料的詳細內容,如下範例:
{% extends 'base.html' %}

{% block content %}

    <ul class="list-group list-group-flush">
        <li class="list-group-item">標題:{{ object.title }}</li>
        <li class="list-group-item">是否完成:{{ object.finish }}</li>
        <li class="list-group-item">建立日期:{{ object.created|date:'Y/m/d' }}</li>
    </ul>

{% endblock  %}

七、小結

以上就是5個常用的Django Class-based Views(類別導向的檢視)使用方式,大家可以和[Django教學7]善用Django ModelForm快速開發CRUD應用程式教學文章中所使用的Function-based Views(函式導向的檢視)比較一下,可以看到程式碼簡潔了許多,不過相對的就有更多的規範需要遵守,但是透過遵循Django所訂定的原則及覆寫(Overriding)的方式,讓專案的寫法與架構更趨近於一致,能夠大幅提升未來的維護性」

如果您喜歡我的文章,請幫我按五下Like(使用GoogleFacebook帳號免費註冊),支持我創作教學文章,回饋由LikeCoin基金會出資,完全不會花到錢,感謝大家。

GitHub網址:https://github.com/mikeku1116/django-class-based-todo

有想要看的教學內容嗎?歡迎利用以下的Google表單讓我知道,將有機會成為教學文章,分享給大家😊

你可能有興趣的文章









留言

  1. 您好,謝謝撰寫優質的文章。

    關於這一行 (create),我發現必須要加 tailing slash (/todo"/"),不然會被多 redirect 一次

    success_url = '/todo/' # 儲存成功後要導向的網址

    不知道這樣是不是正常的,
    謝謝。

    回覆刪除
  2. https://i.imgur.com/6PwkcFj.png

    回覆刪除
  3. 都照著做還是遇到一堆bug.... sucks

    回覆刪除
  4. 直接下載版主的github,可以省去打字的錯誤
    https://github.com/mikeku1116/django-class-based-todo

    回覆刪除

張貼留言