Django網路應用開發的5項基礎核心技術包括模型(Model)的設計URL的設計與配置,View(視圖)的編寫,Template(模板)的設計和Form(表單)的使用。今天小編我就來拼個老命給你來介紹下第三項Django核心基礎知識之View視圖的編寫吧, 並詳細介紹下如何自定義使用Django自帶的通用視圖。一旦你開始使用Django自帶通用視圖,你就會愛上她,根本停不下來。想持續瞭解後續Django Web開發技術請訂閱我的公眾號【Python與Django大咖之路】。

什麼是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_name和last_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)。上述案例中的index,article_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, {"object_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或model_name_list

ListView的自定義

你或許已經注意到了2個問題:需要顯示的文章對象列表並沒有按發布時間逆序排列,內容對象名稱object_list也不友好。或許你也不喜歡默認的模板名字,還希望通過這個視圖給模板傳遞額外的內容(比如現在的時間)。你可以輕易地通過重寫queryset, template_name和context_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 }}獲取文章標題。

你同樣可以通過重寫queryset, template_name和context_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

在實際應用中,上述代碼可能還不能滿足你的要求。比如你希望一個用戶只能看到自己發表的文章詳情。當用戶查看別人的文章詳情時,返回http 404錯誤。這時候你可以通過更具體的get_object()方法來返回一個更具體的對象。代碼如下:

# Create your views here.
from django.views.generic import DetailView
from django.http import Http404
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_object(self, queryset=None):
obj = super().get_object(queryset=queryset)
if obj.author != self.request.user:
raise Http404()
return obj

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_name和form_class來完成CreateView的自定義。雖然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().form_valid(form)

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

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

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_name和form_class來完成UpdateView的自定義。

  • 本例中默認的form是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().form_valid(form)

Django通用視圖之FormView

FormView一般用來展示某個表單,而不是某個模型對象。當用戶輸入信息未通過表單驗證,顯示錯誤信息。當用戶輸入信息通過表單驗證提交成功後,轉到其它頁面。使用FormView一般需要定義template_name, form_class和跳轉的success_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 ArticleDeleteView(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>

但這段代碼還有個問題,你注意到沒? 用戶可以刪除任意文章,包括別人發表的文章。如果我們想用戶只能刪除自己的文章,上述代碼怎麼改? 我們通過get_queryset方法篩選出作者自己的文章即可。views.py可改成下文:

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

class ArticleDeleteView(DeleteView):
model = Article
success_url = reverse_lazy(index)

def get_queryset(self):
return self.model.objects.filter(author=self.request.user)

推薦閱讀:

相關文章