文章目錄
- 0.思路引導
- 1.ListView
- 2.將 index 視圖函數改寫為類視圖
- 3.將 category 視圖函數改寫為類視圖
- 4.將 archive 視圖函數改寫成類視圖
- 5.將 tag 視圖函數改寫成類視圖
- 6.DetailView
- 7.將DetailView視圖函數改寫成類視圖
0.思路引導
1)在開發網站的過程中,有一些視圖函數雖然處理的對象不同,但是其大致的代碼邏輯是一樣的。比如一個博客和一個論壇,通常其首頁都是展示一系列的文章列表或者帖子列表;
2)對處理首頁的視圖函數來說,其處理的邏輯即:首先從數據庫取出文章或者帖子列表,然后將這些數據傳遞給模板并渲染模板;
3)于是,django 把這些相同的邏輯代碼抽取了出來,寫成了一系列的通用視圖函數,即基于類的通用視圖(Generic Class Based View);
4)使用類視圖是 django 推薦的做法,熟悉了類視圖的使用方法后,能夠減少視圖函數的重復代碼,節省開發時間。
接下來就讓我們把博客應用中的視圖函數改成基于類的通用視圖。
1.ListView
在我們的博客應用中,有幾個視圖函數是從數據庫中獲取文章(Post)列表數據的:
文件位置:blog/views.py
def index(request):# ...def archive(request, year, month):# ...def category(request, pk):# ...def tag(request, pk):# ...
這些視圖函數都是從數據庫中獲取文章(Post)列表,唯一的區別就是獲取的文章列表可能不同。比如 index 獲取全部文章列表,category 獲取某個分類下的文章列表。
針對這種從數據庫中獲取某個模型列表數據(比如這里的 Post 列表)的視圖,Django 專門提供了一個 ListView 類視圖。
2.將 index 視圖函數改寫為類視圖
文件位置:blog/views.py
原視圖函數如下:
def index(request):post_list = Post.objects.all()return render(request, 'blog/index.html', context={'post_list': post_list})
修改成如下所示:
from django.views.generic import ListViewclass IndexView(ListView):model = Posttemplate_name = 'blog/index.html'context_object_name = 'post_list'
注意:
1)要寫一個類視圖,首先需要繼承 django 提供的某個類視圖。至于繼承哪個類視圖,需要根據你的視圖功能而定。比如這里 IndexView 的功能是從數據庫中獲取文章(Post)列表,ListView 就是從數據庫中獲取某個模型列表數據的,所以 IndexView 繼承 ListView。
2)model:將 model 指定為 Post,告訴 django 我要獲取的模型是 Post。
3)template_name:指定這個視圖即將去渲染的模板。
4)context_object_name:指定獲取的模型列表數據保存的變量名,這個變量會被傳遞給模板。
接下來,需要更改blog 的 URL 配置。
文件位置:blog/urls.py
原文件如下:
app_name = 'blog'
urlpatterns = [path('', views.index, name='index'),...
]
修改為如下所示:
app_name = 'blog'
urlpatterns = [path('', views.IndexView.as_view(), name='index'),...
]
注意:
1)URL中每一個path對應著一個視圖函數,這樣當用戶訪問這個 URL 時,Django 就知道調用哪個視圖函數去處理這個請求了。
2)在 Django 中 URL 模式的配置方式就是通過 url 函數將 URL 和視圖函數綁定。比如 path(’’, views.index, name=‘index’),它的第一個參數是 URL 模式,第二個參數是視圖函數 index。
3)對 url 函數來說,第二個參數傳入的值必須是一個函數。而 IndexView 是一個類,不能直接替代 index 函數。好在將類視圖轉換成函數視圖非常簡單,只需調用類視圖的 as_view() 方法即可。
3.將 category 視圖函數改寫為類視圖
文件位置:blog/views.py
原視圖函數如下:
def category(request, pk):"""定義 分類 視圖"""# 記得在開始部分導入 Category 類cate = get_object_or_404(Category, pk=pk)post_list = Post.objects.filter(category=cate)#.order_by('-created_time')return render(request, 'blog/index.html', context={'post_list': post_list})
初步改寫如下:
class CategoryView(ListView):model = Posttemplate_name = 'blog/index.html'context_object_name = 'post_list'def get_queryset(self):cate = get_object_or_404(Category, pk=self.kwargs.get('pk'))return super(CategoryView, self).get_queryset().filter(category=cate)
注意:
1)和 IndexView 不同的地方是,我們覆寫了父類的 get_queryset 方法。該方法默認獲取指定模型的全部列表數據,為了獲取指定分類下的文章列表數據,我們覆寫該方法,通過filter改變它的默認行為。
2)在類視圖中,從 URL 捕獲的路徑參數值保存在實例的 kwargs 屬性(是一個字典)里,非路徑參數值保存在實例的 args 屬性(是一個列表)里。
3)所以我們使了 self.kwargs.get(‘pk’) 來獲取從 URL 捕獲的分類 id 值。然后調用父類的 get_queryset 方法獲得全部文章列表,緊接著就對返回的結果調用了 filter 方法來篩選該分類下的全部文章并返回。
4)此外我們可以看到 CategoryView 類中指定的屬性值和 IndexView 中是一模一樣的,所以如果為了進一步節省代碼,甚至可以直接繼承 IndexView。
優化后如下:
class CategoryView(IndexView):def get_queryset(self):cate = get_object_or_404(Category, pk=self.kwargs.get('pk'))return super(CategoryView, self).get_queryset().filter(category=cate)
接下倆,在 URL 配置中把 category 視圖替換成類視圖 CategoryView:
文件位置:blog/urls.py
app_name = 'blog'
urlpatterns = [...path('categories/<int:pk>/', views.CategoryView.as_view(), name='category'),
]
4.將 archive 視圖函數改寫成類視圖
文件位置:blog/views.py
原視圖函數如下:
def archive(request, year, month):"""定義 歸檔 視圖"""post_list = Post.objects.filter(created_time__year=year,created_time__month=month)#.order_by('-created_time')return render(request, 'blog/index.html', context={'post_list': post_list})
改寫如下:
class ArchiveView(IndexView):def get_queryset(self):year = self.kwargs.get('year')month = self.kwargs.get('month')return super().get_queryset().filter(created_time__year=year, created_time__month=month)
接下倆,在 URL 配置中把 archive 視圖替換成類視圖 ArchiveView:
文件位置:blog/urls.py
app_name = 'blog'
urlpatterns = [...path('archives/<int:year>/<int:month>/', views.ArchiveView.as_view(), name='archive'),
]
5.將 tag 視圖函數改寫成類視圖
文件位置:blog/views.py
原視圖函數如下:
def tag(request, pk):# 記得在開始部分導入 Tag 類t = get_object_or_404(Tag, pk=pk)post_list = Post.objects.filter(tags=t)#.order_by('-created_time')return render(request, 'blog/index.html', context={'post_list': post_list})
改寫如下:
class TagView(IndexView):def get_queryset(self):t = get_object_or_404(Tag, pk=self.kwargs.get('pk'))return super().get_queryset().filter(tags=t)
接下倆,在 URL 配置中把 tag 視圖替換成類視圖 TagView:
文件位置:blog/urls.py
app_name = 'blog'
urlpatterns = [...path('tags/<int:pk>/', views.TagView.as_view(), name='tag'),
]
6.DetailView
除了從數據庫中獲取模型列表的數據外,從數據庫獲取模型的一條記錄數據也是常見的需求。
比如查看某篇文章的詳情,就是從數據庫中獲取這篇文章的記錄然后渲染模板。
對于這種類型的需求,django 提供了一個 DetailView 類視圖。
7.將DetailView視圖函數改寫成類視圖
接下倆,將 detail 視圖函數轉換為等價的類視圖 PostDetailView視圖
文件位置:blog/views.py
原視圖函數如下:
def detail(request, pk):"""定義文章詳情頁視圖"""post = get_object_or_404(Post, pk=pk)#閱讀量+1post.increase_views()md = markdown.Markdown(extensions=['markdown.extensions.extra','markdown.extensions.codehilite',# 'markdown.extensions.toc',# 記得在頂部引入 TocExtension 和 slugifyTocExtension(slugify=slugify),])post.body = md.convert(post.body)m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S)post.toc = m.group(1) if m is not None else ''return render(request, 'blog/detail.html', context={'post': post})
改寫如下:
class PostDetailView(DetailView):model = Posttemplate_name = 'blog/detail.html'context_object_name = 'post'def get(self, request, *args, **kwargs):# 覆寫 get 方法的目的是因為每當文章被訪問一次,就得將文章閱讀量 +1# get 方法返回的是一個 HttpResponse 實例# 之所以需要先調用父類的 get 方法,是因為只有當 get 方法被調用后,# 才有 self.object 屬性,其值為 Post 模型實例,即被訪問的文章 postresponse = super().get(request, *args, **kwargs)# 將文章閱讀量 +1# 注意 self.object 的值就是被訪問的文章 postself.object.increase_views()# 視圖必須返回一個 HttpResponse 對象return responsedef get_object(self, queryset=None):# 覆寫 get_object 方法的目的是因為需要對 post 的 body 值進行渲染post = super().get_object(queryset=None)md = markdown.Markdown(extensions=['markdown.extensions.extra','markdown.extensions.codehilite',# 記得在頂部引入 TocExtension 和 slugifyTocExtension(slugify=slugify),])post.body = md.convert(post.body)m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S)post.toc = m.group(1) if m is not None else ''return post
注意:
1)首先我們為 PostDetailView 類指定了一些屬性的值,這些屬性的含義和 ListView 中一樣。
2)緊接著我們覆寫了 get 方法。這對應著 detail 視圖函數中將 post 的閱讀量 +1 的那部分代碼。事實上,你可以簡單地把 get 方法的調用看成是 detail 視圖函數的調用。
3)接著我們又復寫了 get_object 方法。對應著 detail 視圖函數中根據文章的 id(也就是 pk)獲取文章,然后對文章的 post.body 進行 Markdown 解析。
4)get 方法看成是 detail 視圖函數,至于其它的像 get_object、get_context_data 都是輔助方法,這些方法最終在 get 方法中被調用,這里你沒有看到被調用的原因是它們隱含在了 super(PostDetailView, self).get(request, *args, **kwargs) 即父類 get 方法的調用中。最終傳遞給瀏覽器的 HTTP 響應就是 get 方法返回的 HttpResponse 對象。
接下倆,在 URL 配置中把 detail 視圖替換成類視圖 PostDetailView:
文件位置:blog/urls.py
app_name = 'blog'
urlpatterns = [...path('posts/<int:pk>', views.PostDetailView.as_view(), name = 'detail'),
]