Django 中 的 視圖View及 通用視圖(Generic View)

什麼是View視圖? Django的View是如何工作的


Django的Web開發也遵循經典軟件設計開發的MVC模式。View (視圖) 主要根據用戶的請求返回數據,用來展示用戶可以看到的內容(比如網頁,圖片),也可以用來處理用戶提交的數據,比如保存到數據庫中。Django的視圖(View)通常和URL路由一起工作的。服務器在收到用戶通過瀏覽器發來的請求後,會根據urls.py裏的關係條目,去視圖View裏查找到與請求對應的處理方法,從而返回給客戶端http頁面數據。


我們先看一個最簡單的視圖View。當用戶發來一個請求request時,我們通過HttpResponse打印出Hello, World!

# views.py
from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, World!")

這個例子過於簡單。在實際Web開發過程中,我們的View不僅要負責從數據庫提取數據,還需要指定顯示內容的模板,並提供模板渲染頁面所需的內容對象(context object)。


我們再來看看下面一個新聞博客的例子。/blog/展示所有博客文章列表。/blog/article/<int:id>/展示一篇文章的詳細內容。

# blog/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('blog/', views.index, name='index'),
   path('blog/article/<int:id>/', views.article_detail, name='article_detail'),
]


# blog/views.py
from django.shortcuts import render, get_object_or_404
from .models import Article


# 展示所有文章
def index(request):
    latest_articles = Article.objects.all().order_by('-pub_date')
    return render(request, 'blog/article_list.html', {"latest_articles": latest_articles})


# 展示所有文章
def article_detail(request, id):
    article = get_object_or_404(Article, pk=id)
    return render(request, 'blog/article_detail.html', {"article": article})

那麼這段代碼是如何工作的?
  • 當用戶在瀏覽器輸入/blog/時,URL收到請求後會調用視圖views.py裏的index方法,展示所有文章。

  • 當用戶在瀏覽器輸入/blog/article/<int:id>/時,URL不僅調用了views.py裏的article方法,而且還把參數文章id通過<int:id>括號的形式傳遞給了視圖裏的article_detail方法。

  • views.py裏的index方法先提取要展示的數據對象列表latest_articles, 然後通過render方法傳遞給模板blog/article_list.html.。

  • views.py裏的article_detail方法先通過get_object_or_404方法和id調取某篇具體的文章對象article,然後通過render方法傳遞給模板blog/article_detail.html顯示。

在本例中,我們使用了views裏常用的2個便捷方法render()和get_object_or_404()。
  • render方法有4個參數。第一個是request, 第二個是模板的名稱和位置,第三個是需要傳遞給模板的內容, 也被稱爲context object。第四個參數是可選參數content_type(內容類型), 我們什麼也沒寫。

  • get_object_or_404方法第一個參數是模型Models或數據集queryset的名字,第二個參數是需要滿足的條件(比如pk = id, title = 'python')。當需要獲取的對象不存在時,給方法會自動返回Http 404錯誤。


下圖是模板的代碼。模板可以直接調用通過視圖傳遞過來的內容。

# blog/article_list.html
{% block content %}
{% for article in latest_articles %}
     {{ article.title }}
     {{ article.pub_date }}
{% endfor %}
{% endblock %}

# blog/article_detail.html
{% block content %}
{{ article.title }}
{{ article.pub_date }}
{{ article.body }}
{% endblock %}


一個更復雜點案例: View視圖處理用戶提交的數據

視圖View不僅用於確定給客戶展示什麼內容,以什麼形式展示,而且也用來處理用戶通過表單提交的數據


我們再來看個用戶修改個人資料的常見視圖views.py。

from django.shortcuts import render, get_object_or_404
from django.contrib.auth.models import User
from .forms import ProfileForm
from django.http import HttpResponseRedirect
from django.urls import reverse

def profile_update(request, pk):
    user = get_object_or_404(User, pk=pk)
  
    if request.method == "POST":
        form = ProfileForm(request.POST)

        if form.is_valid():
            user.first_name = form.cleaned_data['first_name']
            user.last_name = form.cleaned_data['last_name']
            user.save()

            return HttpResponseRedirect(reverse('users:profile', args=[user.id]))
    else:
        default_data = {'first_name': user.first_name, 'last_name': user.last_name,
                    }
        form = ProfileForm(default_data)

    return render(request, 'users/profile_update.html', {'form': form, 'user': user})


views.profile_update是如何工作的?


  • 我們先從url獲取user的主鍵pk(id), 利用get_object_or_404方法獲取需要修改個人資料的用戶對象user

  • 當用戶通過POST方法提交個人資料修改表單,我們利用is_valid()方法先驗證表單ProfileForm的數據是否有效。如果有效,我們將更新過的first_namelast_name數據存入user對象。更新成功返回個人信息頁。

  • 如果用戶沒有提交表單或不是通過POST方法提交表單,我們先獲取現有數據生成default_data,利用ProfileForm顯示


ProfileForm實際上是非常簡單的,包含了我們允許用戶修改的字段。在這個案例裏,我們沒允許用戶修改用戶名和電子郵件,所以沒有加進去。

# users/forms.py
from django import forms

class ProfileForm(forms.Form):

    first_name = forms.CharField(label='First Name', max_length=50, required=False)
    last_name = forms.CharField(label='Last Name', max_length=50, required=False)


基於函數的視圖(Function Based View)和基於類的視圖(Class Based View)


Django的視圖有兩種: 基於函數的視圖(Function Base View)和基於類的視圖(Class Based View)。上述案例中的indexarticle_detail和profile_update的方法都是基於函數的視圖。優點是直接,容易讀者理解。缺點是不便於繼承和重用。在實際Web開發過程中,我們對不同的對象總是反覆進行以下同樣的操作,應該需要簡化的。

  • 展示對象列表(比如所有用戶,所有文章)

  • 查看某個對象的詳細信息(比如用戶資料,比如文章詳情)

  • 通過表單創建某個對象(比如創建用戶,新建文章)

  • 通過表單更新某個對象信息(比如修改密碼,修改文字內容)

  • 用戶填寫表單提交後轉到某個完成頁面

  • 刪除某個對象


Django提供了很多通用的基於類的視圖(Class Based View),來幫我們簡化視圖的編寫。這些View與上述操作的對應關係如下:

  • 展示對象列表(比如所有用戶,所有文章)- ListView

  • 展示某個對象的詳細信息(比如用戶資料,比如文章詳情) - DetailView

  • 通過表單創建某個對象(比如創建用戶,新建文章)- CreateView

  • 通過表單更新某個對象信息(比如修改密碼,修改文字內容)- UpdateView

  • 用戶填寫表單後轉到某個完成頁面 - FormView

  • 刪除某個對象 - DeleteView


上述常用通用視圖一共有6個,前2個屬於展示類視圖(Display view), 後面4個屬於編輯類視圖(Edit view)。下面我們就來看下這些通用視圖是如何工作的,如何簡化我們代碼的。


重要:如果你要使用Edit view,請務必在模型models裏定義get_absolute_url()方法,否則會出現錯誤。這是因爲通用視圖在對一個對象完成編輯後,需要一個返回鏈接。


Django通用視圖之ListView


ListView用來展示一個對象的列表。它只需要一個參數模型名稱即可。比如我們希望展示所有文章列表,我們的views.py可以簡化爲:

# Create your views here.
from django.views.generic import ListView
from .models import Article

class IndexView(ListView):

    model = Article

上述代碼等同於:

# 展示所有文章
def index(request):
    queryset = Article.objects.all()
    return render(request, 'blog/article_list.html', {"article_list": queryset})

儘管我們只寫了一行model = Article, ListView實際上在背後做了很多事情:

  • 提取了需要顯示的對象列表或數據集queryset: Article.objects.all()

  • 指定了用來顯示對象列表的模板名稱(template name): 默認app_name/model_name_list.html, 即blog/article_list.html.

  • 指定了內容對象名稱(context object name):默認值object_list


ListView的自定義


你或許已經注意到了2個問題:需要顯示的文章對象列表並沒有按發佈時間逆序排列,內容對象名稱object_list也不友好。或許你也不喜歡默認的模板名字,還希望通過這個視圖給模板傳遞額外的內容(比如現在的時間)。你可以輕易地通過重寫querysettemplate_namecontext_object_name來完成ListView的自定義。如果你還需要傳遞模型以外的內容,比如現在的時間,你還可以通過重寫get_context_data方法傳遞額外的參數或內容。

# Create your views here.
from django.views.generic import ListView
from .models import Article
from django.utils import timezone

class IndexView(ListView):

    queryset = Article.objects.all().order_by("-pub_date")
    template_name = 'blog/article_list.html'
    context_object_name = 'latest_articles'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['now'] = timezone.now()
        return context

如果上述的queryset還不能滿足你的要求,比如你希望一個用戶只看到自己發表的文章清單,你可以通過更具體的get_queryset()方法來返回一個需要顯示的對象列表。

# Create your views here.
from django.views.generic import ListView
from .models import Article
from django.utils import timezone

class IndexView(ListView):

    template_name = 'blog/article_list.html'
    context_object_name = 'latest_articles'

    def get_queryset(self):
        return Article.objects.filter(author=self.request.user).order_by('-pub_date')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['now'] = timezone.now()
        return context


URL如何指向基於類的視圖(View)


目前urls.py裏path和re_path都只能指向視圖view裏的一個函數或方法,而不能指向一個基於類的視圖(Class Based View)。Django提供了一個額外as_view()方法,可以將一個類僞裝成方法。這點在當你使用Django在帶的view類或自定義的類時候非常重要。更多內容見Django基礎技術知識(2)URL的設計與配置


具體使用方式如下:

# blog/urls.py
from django.urls import path, re_path

from . import views

urlpatterns = [
      path('blog/', views.IndexView.as_view(), name='index'),
]


Django通用視圖之DetailView

DetailView用來展示一個具體對象的詳細信息。它需要URL提供訪問某個對象的具體參數(如pk, slug值)。本例中用來展示某篇文章詳細內容的view可以簡寫爲:

# Create your views here.
from django.views.generic import DetailView
from .models import Article

class ArticleDetailView(DetailView):

    model = Article

DetailView默認的模板是app/model_name_detail.html,默認的內容對象名字context_object_name是model_name。本例中默認模板是blog/article_detail.html, 默認對象名字是article, 在模板裏可通過 {{ article.title }}獲取文章標題。


你同樣可以通過重寫querysettemplate_namecontext_object_name來完成DetailView的自定義。你還可以通過重寫get_context_data方法傳遞額外的參數或內容。如果你指定了queryset, 那麼返回的object是queryset.get(pk = id), 而不是model.objects.get(pk = id)。

# Create your views here.
from django.views.generic import ListView,DetailView
from .models import Article
from django.utils import timezone

class ArticleDetailView(DetailView):

    queryset = Article.objects.all().order_by("-pub_date") # 一般不寫
    template_name = 'blog/article_detail.html'
    context_object_name = 'article'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['now'] = timezone.now()
        return context


Django通用視圖之CreateView

CreateView一般通過某個表單創建某個對象,通常完成後會轉到對象列表。比如一個最簡單的文章創建CreateView可以寫成:

from django.views.generic.edit import CreateView
from .models import Article

class ArticleCreateView(CreateView):
    model = Article
    fields = ['title', 'body', 'pub_date']

CreateView默認的模板是model_name_form.html, 即article_form.html。默認的context_object_name是form。模板代碼如下圖所示:

# blog/article_form.html
<form method="post">{% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Save" />
</form>

如果你不想使用默認的模板和默認的表單,你可以通過重寫template_nameform_class來完成CreateView的自定義

  • 本例中默認的模板是article_form.html, 你可以改爲article_create_form.html

  • 雖然form_valid方法不是必需,但很有用。當用戶提交的數據是有效的時候,你可以通過定義此方法做些別的事情,比如發送郵件,存取額外的數據。

from django.views.generic.edit import CreateView
from .models import Article
from .forms import ArticleCreateForm

class ArticleCreateView(CreateView):
    model = Article
    template_name = 'blog/article_create_form.html'
    form_class = ArticleCreateForm

    def form_valid(self, form):
       form.do_sth()
       return super(ArticleCreateView, self).form_valid(form)

form_valid方法一個常見用途就是就是將創建對象的用戶與model裏的user結合。見下面例子。

class ArticleCreateView(CreateView):
    model = Article
    template_name = 'blog/article_create_form.html'
    form_class = ArticelCreateForm

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

Django通用視圖之UpdateView

UpdateView一般通過某個表單更新現有對象的信息,更新完成後會轉到對象詳細信息頁面。它需要URL提供訪問某個對象的具體參數(如pk, slug值)。比如一個最簡單的文章更新的UpdateView如下所示。

from django.views.generic.edit import UpdateView
from .models import Article

class ArticleUpdateView(UpdateView):
    model = Article
    fields = ['title', 'body', 'pub_date']

UpdateView和CreateView很類似,比如默認模板都是model_name_form.html。但是區別有兩點: 

  • CreateView顯示的表單是空表單,UpdateView中的表單會顯示現有對象的數據。

  • 用戶提交表單後,CreateView轉向對象列表,UpdateView轉向對象詳細信息頁面。


你可以通過重寫template_nameform_class來完成UpdateView的自定義。

  • 本例中默認的模板是article_form.html, 你可以改爲article_update_form.html

  • 雖然form_valid方法不是必需,但很有用。當用戶提交的數據是有效的時候,你可以通過定義此方法做些別的事情,比如發送郵件,存取額外的數據。

from django.views.generic.edit import UpdateView
from .models import Article
from .forms import ArticleUpdateForm

class ArticleUpdateView(UpdateView):
    model = Article
    template_name = 'blog/article_update_form.html'
    form_class = ArticleUpdateForm

    def form_valid(self, form):
       form.do_sth()
       return super(ArticleUpdateView, self).form_valid(form)


Django通用視圖之FormView

FormView一般用來展示某個表單,而不是某個模型對象。當用戶輸入信息未通過表單驗證,顯示錯誤信息。當用戶輸入信息通過表單驗證提交後,轉到其它頁面。使用FormView一般需要定義template_nameform_classsuccess_url.


見下面代碼。

# views.py - Use FormView
from myapp.forms import ContactForm
from django.views.generic.edit import FormView

class ContactView(FormView):
    template_name = 'contact.html'
    form_class = ContactForm
    success_url = '/thanks/'

    def form_valid(self, form):
        # This method is called when valid form data has been POSTed.
        # It should return an HttpResponse.
        form.send_email()
        return super().form_valid(form)


Django通用視圖之DeleteView

DeleteView一般用來刪除某個具體對象。它要求用戶點擊確認後再刪除一個對象。使用這個通用視圖,你需要定義模型的名稱model和成功刪除對象後的返回的URL。默認模板是myapp/model_confirm_delete.html。默認內容對象名字是model_name。本例中爲article。


本例使用了默認的模板blog/article_confirm_delete.html,刪除文章後通過reverse_lazy方法返回到index頁面。

from django.urls import reverse_lazy
from django.views.generic.edit import DeleteView
from .models import Article

class ArticleDelete(DeleteView):
    model = Article
    success_url = reverse_lazy('index')

模板內容如下:

# blog/article_confirm_delete.html
<form method="post">{% csrf_token %}
    <p>Are you sure you want to delete "{{ article }}"?</p>
    <input type="submit" value="Confirm" />
</form>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章