目錄
什么是視圖(View)及其工作原理
接近現實的函數視圖
更復雜的案例: 視圖處理用戶提交的數據
基于函數的視圖和基于類的視圖
Django通用類視圖
a. ListView
b. DetailView
c. CreateView
d. UpdateView
e. FormView
f. DeleteView
小結
Django的視圖(view)是處理業務邏輯的核心,它負責處理用戶的請求并返回響應數據。Django提供了兩種編寫視圖的方式:基于函數的視圖和基于類的視圖。本章會詳細介紹如何編寫視圖以及如何使用Django提供的通用類視圖。
什么是視圖(View)及其工作原理
Django的Web開發也遵循經典軟件設計MVC模式開發的。View (視圖) 主要根據用戶的請求返回數據,用來展示用戶可以看到的內容(比如網頁,圖片),也可以用來處理用戶提交的數據,比如保存到數據庫中。Django的視圖(views.py
)通常和URL路由(URLconf)一起工作的。服務器在收到用戶通過瀏覽器發來的請求后,會根據用戶請求的url地址和urls.py
里配置的url-視圖映射關系,去執行相應視圖函數或視圖類,從而返回給客戶端響應數據。
我們先看一個最簡單的函數視圖。當用戶發來一個請求request
時,我們通過HttpResponse
打印出Hello, World!
。
# views.py
from django.http import HttpResponsedef index(request):return HttpResponse("Hello, World!")
提示:每個視圖函數的第一個默認參數都必需是request
, 它是一個全局變量。Django把每個用戶請求封裝成了request
對象,它包含里當前請求的所有信息,比如請求路徑request.path
, 當前用戶request.user
以及用戶通過POST提交的數據request.POST
。
上面例子過于簡單。在實際Web開發過程中,我們的View不僅要負責從數據庫讀寫數據,還需要指定顯示內容的模板,并提供模板渲染頁面所需的內容對象(context object
)。接下來我們要看一個更接近現實的案例。
接近現實的函數視圖
我們依然以blog
為例,需要編寫兩個視圖函數,一個用于展示文章列表,一個用于展示文章詳情,你的urls.py
和views.py
正常情況下應如下所示:
# blog/urls.py
from django.urls import path
from . import viewsurlpatterns = [path('blog/', views.index, name='index'),path('blog/articles/<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/5/
時,URL不僅調用了views.py
里的article_detail
函數,而且還把參數文章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
顯示。
在本例中,我們使用了視圖函數里常用的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不僅用于確定給客戶展示什么內容,以什么形式展示,而且也用來處理用戶通過表單提交的數據。我們再來看兩個創建和修改文章的視圖函數article_create
和article_update
,看看它們是如何處理用戶通過表單POST提交過來的數據。
from django.shortcuts import render, redirect, get_object_or_404from django.urls import reversefrom .models import Articlefrom .forms import ArticleForm# 創建文章def article_create(request):# 如果用戶通過POST提交,通過request.POST獲取提交數據if request.method == "POST":# 將用戶提交數據與ArticleForm表單綁定form = ArticleForm(request.POST)# 表單驗證,如果表單有效,將數據存入數據庫if form.is_valid():form.save()# 創建成功,跳轉到文章列表return redirect(reverse("blog:article_list"))else:# 否則空表單form = ArticleForm()return render(request, "blog/article_form.html", { "form": form, })# 更新文章def article_update(request, pk):# 從url里獲取單篇文章的id值,然后查詢數據庫獲得單個對象實例article = get_object_or_404(Article, pk=id)# 如果用戶通過POST提交,通過request.POST獲取提交數據if request.method == 'POST':# 將用戶提交數據與ArticleForm表單綁定,進行驗證form = ArticleForm(instance=article, data=request.POST)if form.is_valid():form.save()# 更新成功,跳轉到文章詳情return redirect(reverse("blog:article_detail", args=[pk,]))else:# 否則用實例生成表單form = ArticleForm(instance=article)return render(request, "blog/article_form.html", { "form": form, "object": article})
我們給每一行代碼添加了說明。值得一提的是在創建和更新文章時我們向模板傳遞了form
這個變量,模板會根據我們自定義的Form類自動生成表單。我們還使用了自定義的Form類對用戶提交的數據(request.POST
)進行驗證,并將通過驗證的數據存入數據庫。
這里所使用ArticleForm
實際上是非常簡單的,僅包含了title
和body
兩個字段。
#blog/forms.py
from .models import Article
from django import formsclass ArticleForm(forms.ModelForm):class Meta:model = Articlefields = ['title', 'body']
基于函數的視圖和基于類的視圖
Django提供了兩種編寫視圖的方式: 基于函數的視圖(Function Base View, FBV)和基于類的視圖(Class Based View, CBV)。前面案例中的index
,article_detail
和article_update
的方法都是基于函數的視圖。函數視圖的優點是比較直接,容易讀者理解, 缺點是不便于繼承和重用。
基于類的視圖以class
定義,而不是函數視圖的def
定義。使用類視圖后可以將視圖對應的不同請求方式以類中的不同方法來區別定義(get方法處理GET請求,post方法處理POST請求),相對于函數視圖邏輯更清晰,代碼也有更高的復用性。
from django.views.generic import Viewclass MyClassView(View):"""類視圖"""def get(self, request):"""處理GET請求"""return render(request, 'register.html')def post(self, request):"""處理POST請求"""return ...
注意:在URL配置文件中使用類視圖時,需要使用as_view()
將其偽裝成方法:
# blog/urls.py
from django.urls import path, re_path
from . import viewsurlpatterns = [path('', views.MyClassView.as_view()),
]
Django通用類視圖
在實際Web開發過程中,我們對不同的數據或模型總是反復進行以下同樣的操作,使用通用的類視圖可以大大簡化我們的代碼量。
- 展示對象列表(比如所有用戶,所有文章)
- 查看某個對象的詳細信息(比如用戶資料,比如文章詳情)
- 通過表單創建某個對象(比如創建用戶,新建文章)
- 通過表單更新某個對象信息(比如修改密碼,修改文字內容)
- 用戶填寫表單提交后轉到某個完成頁面
- 刪除某個對象
Django提供了很多通用的基于類的視圖,來幫我們簡化視圖的編寫。這些View與上述操作的對應關系如下:
-
展示對象列表(比如所有用戶,所有文章)-?
ListView
-
展示某個對象的詳細信息(比如用戶資料,比如文章詳情) -?
DetailView
-
通過表單創建某個對象(比如創建用戶,新建文章)-?
CreateView
-
通過表單更新某個對象信息(比如修改密碼,修改文字內容)-?
UpdateView
-
用戶填寫表單后轉到某個完成頁面 -?
FormView
-
刪除某個對象 -?
DeleteView
上述常用通用視圖一共有6個,前2個屬于展示類視圖(Display view), 后面4個屬于編輯類視圖(Edit view)。下面我們就來看下這些通用視圖是如何工作的,如何簡化我們代碼的。
注意:如果你要使用Edit view,請務必在模型里里定義get_absolute_url()
方法,否則會出現錯誤。這是因為通用視圖在對一個對象完成編輯后,需要一個返回鏈接。get_absolute_url()
可以為某個對象生成獨一無二的url。
a. ListView
ListView
用來展示一個對象的列表。它只需要一個參數模型名稱即可。比如我們希望展示所有文章列表,我們的views.py
可以簡化為:
# Create your views here.
from django.views.generic import ListView
from .models import Articleclass 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()
- 指定了用來顯示對象列表的模板名稱: 默認
app_name/model_name_list.html
, 即blog/article_list.html
. - 指定了內容對象名稱(context object name):默認值
object_list
你或許已經注意到了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 timezoneclass 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 timezoneclass 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
b. DetailView
DetailView
用來展示一個具體對象的詳細信息。它需要URL傳遞某個對象的具體參數(如id, pk, slug值)。本例中用來展示某篇文章詳細內容的view可以簡寫為:
# Create your views here.
from django.views.generic import DetailView
from .models import Articleclass ArticleDetailView(DetailView):model = Article
DetailView
默認的模板是app/model_name_detail.html
,默認的內容對象名字context_object_name
是model_name。本例中默認模板是blog/article_detail.html
, 默認對象名字是article
, 在模板里可通過 ``獲取文章標題。
你同樣可以通過重寫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 timezoneclass 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
c. CreateView
CreateView
一般通過某個表單創建某個對象,通常完成后會轉到對象列表。比如一個最簡單的文章創建CreateView可以寫成:
from django.views.generic.edit import CreateView
from .models import Articleclass ArticleCreateView(CreateView):model = Articlefields = ['title', 'body',]
CreateView默認的模板是model_name_form.html,
?即article_form.html
。這里CreateView還會根據fields
自動生成表單字段。默認的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的自定義。
對于CreateView
, 重寫它的form_valid
方法不是必需,但很有用。當用戶提交的數據是有效的時候,執行該方法。你可以通過定義此方法做些別的事情,比如發送郵件,存取額外的數據。
from django.views.generic.edit import CreateView
from .models import Article
from .forms import ArticleCreateFormclass ArticleCreateView(CreateView):model = Articletemplate_name = 'blog/article_create_form.html'form_class = ArticleCreateFormdef form_valid(self, form):form.do_sth()return super(ArticleCreateView, self).form_valid(form)
form_valid方法一個常見用途就是就是將創建對象的用戶與model里的user結合(需要用戶先登錄再提交)。見下面例子。
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from .models import Articleclass ArticleCreate(LoginRequiredMixin, CreateView):model = Articlefields = ['title', 'body']def form_valid(self, form):form.instance.author = self.request.userreturn super().form_valid(form)
d. UpdateView
UpdateView一般通過某個表單更新現有對象的信息,更新完成后會轉到對象詳細信息頁面。它需要URL提供訪問某個對象的具體參數(如pk, slug值)。比如一個最簡單的文章更新的UpdateView如下所示。
from django.views.generic.edit import UpdateView
from .models import Articleclass ArticleUpdateView(UpdateView):model = Articlefields = ['title', 'body',]
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 ArticleUpdateFormclass ArticleUpdateView(UpdateView):model = Articletemplate_name = 'blog/article_update_form.html'form_class = ArticleUpdateFormdef form_valid(self, form):form.do_sth()return super(ArticleUpdateView, self).form_valid(form)
另一個進行UpdateView的常用自定義方法是get_object
方法。比如你希望一個用戶只能編輯自己發表的文章對象。當一個用戶嘗試編輯別人的文章時,返回http 404錯誤。這時候你可以通過更具體的get_object()
方法來返回一個更具體的對象。代碼如下:
from django.views.generic.edit import UpdateView
from .models import Article
from .forms import ArticleUpdateFormclass ArticleUpdateView(UpdateView):model = Articletemplate_name = 'blog/article_update_form.html'form_class = ArticleUpdateFormdef get_object(self, queryset=None):obj = super().get_object(queryset=queryset)if obj.author != self.request.user:raise Http404()return obj
e. FormView
FormView一般用來展示某個表單,而不是用于創建或更新某個模型對象。當用戶輸入信息未通過表單驗證,顯示錯誤信息。當用戶輸入信息通過表單驗證提交后,跳到其它頁面。使用FormView一般需要定義template_name
,?form_class
和success_url
.
見下面代碼。
# views.py - Use FormView
from myapp.forms import ContactForm
from django.views.generic.edit import FormViewclass ContactView(FormView):template_name = 'contact.html'form_class = ContactFormsuccess_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)
f. 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 Articleclass ArticleDelete(DeleteView):model = Articlesuccess_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>
小結
本章我們詳細介紹了Django的視圖(View)是如何工作的,并展示了如何使用基于函數的視圖和通用類視圖(ListView
,?DetailView
,?CreateView
,?UpdateView
,?DeleteView
)編寫基本的增刪改查視圖。在Django進階部分我們將介紹更多視圖編寫技巧。下章我們將講解模板語言, 常見的模板標簽和過濾器以及如何正確配置模板文件。