什麼是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', {"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也不友好。或許你也不喜歡默認的模板名字,還希望通過這個視圖給模板傳遞額外的內容(比如現在的時間)。你可以輕易地通過重寫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
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的自定義。
本例中默認的模板是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_name和form_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_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 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>