Django 中類檢視詳解
在寫檢視的時候,Django除了使用函式作為檢視,也可以使用類作為檢視。使用類檢視可以使用類的一些特性,比如繼承等。
View檢視:
django.views.generic.base.View
是主要的類檢視,所有的類檢視都是繼承自他。如果我們寫自己的類檢視,也可以繼承自他。然後再根據當前請求的method,來實現不同的方法。
例如:這個檢視只能使用get的方式來請求,那麼就可以在這個類中定義get(self,request,*args,**kwargs)
方法。
在views.py中新建一個類檢視
from django.http import HttpResponse
from django.views.generic import View
class BookListView(View):
def get(self,request,*args,**kwargs):
return HttpResponse('book list view')
這樣就只能使用get
方法來訪問這個類檢視了。
要想訪問我們的類檢視,我們還需要配置url,新增對映,而類檢視不能像我們訪問檢視函式那樣進行對映,還需要使用as_view()方法才能進行對映。
path('get',views.BookListView.as_view()),
將上面程式碼新增至urls中就能成功的進行映射了。
以此類推,如果只需要實現post方法,那麼就只需要在類中實現post(self,request,*args,**kwargs)。
from django.http import HttpResponse
from django.views.generic import View
class BookListView(View):
def post(self,request,*args,**kwargs):
return HttpResponse('book list view')
接下來我們實現一個需求:我們需要獲取到前端傳入的值,如果使用者是使用GET
POST
方法,我們就對獲取的值進行處理。
首先在templates下面新建一個add_book.html
的檔案,寫入程式碼:
<form action="" method="post">
<table>
<tbody>
<tr>
<td>圖書名字:</td>
<td><input type="text" name="name"></td>
</tr>
<tr>
<td>作者:</td>
<td><input type="text" name="author"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="提交"></td>
</tr>
</tbody>
</table>
</form>
然後在views中新建一個類檢視接收資料:
class AddBookView(View):
def get(self,request,*args,**kwargs):
return render(request,'add_book.html')
def post(self,request,*args,**kwargs):
book_name = request.POST.get('name')
book_author = request.POST.get('author')
print('name:{},author:{}'.format(book_name,book_author))
return HttpResponse('success')
在urls中新增對映
path('add',views.AddBookView.as_view()),
然後在瀏覽器中輸入網址,寫入資訊,就能在控制檯打印出獲取的資訊了。
除了get
,post
方法,View還支援以下方法['get','post','put','patch','delete','head','options','trace']。
如果使用者訪問了View中沒有定義的方法。比如你的類檢視只支援get方法,而出現了post方法,那麼就會把這個請求轉發給http_method_not_allowed(request,*args,**kwargs)
。
示例:
新建一個BookDetaial的類:
class BookDetaialView(View):
def post(self,request,book_id):
return HttpResponse('圖書id是:{}'.format(book_id))
def http_method_not_allowed(self, request, *args, **kwargs):
return HttpResponse('你使用的是%s請求,但是不支援POST以外的其他請求!'%request.method)
然後在urls中新增對映:
path('detaial/<int:book_id>',views.BookDetaialView.as_view()),
當我們輸入網址進行訪問的時候,就會返回一個字元竄:你使用的是GET請求,但是不支援POST以外的其他請求!
其實不管是get請求還是post請求,都會走django.views.generic.base.View
中dispatch(request,*args,**kwargs)
方法,所以如果實現這個方法,將能夠對所有請求都處理到。
TemplateView檢視:
django.views.generic.base.TemplateView
,這個類檢視是專門用來返回模版的。在這個類中,有兩個屬性是經常需要用到的,一個是template_name
,這個屬性是用來儲存模版的路徑,TemplateView
會自動的渲染這個變數指向的模版。另外一個是get_context_data
,這個方法是用來返回上下文資料的,也就是在給模版傳的引數的。
需求:在一個網站中,有一些頁面不需要我們從資料庫中提取資料到前端頁面中,例如網址中的“關於我們”
這個頁面一般都是在html中寫死的資料,不需要進行改動,這個時候我們就可以直接在urls中直接渲染html檔案,而不用檢視函式或者檢視類來進行渲染。
新建一個about.html
的檔案,
<body>
這是關於我們的頁面
</body>
然後直接在urls中渲染這個模板:
from django.views.generic import TemplateView
# 如果渲染的模板不需要傳遞任何引數,那麼建議在urls中使用TemplateView直接進行渲染
path('about/',TemplateView.as_view(template_name='about.html')),
而我們又想使用這個TemplateView進行渲染模板,又想傳遞少許引數,這個時候我們就可以定義一個類,繼承至TemplateView
示例:
在views中新建一個類繼承至TemplateView
from django.views.generic import View,TemplateView
class AboutView(TemplateView):
# 指定模板的路徑
template_name = 'about.html'
# 傳入的引數,資料
def get_context_data(self,**kwargs):
context ={'phone':'xxx-xxxxxxx'}
return context
我們也需要在about.html
檔案中接收傳入的資料
<body>
這是關於我們的頁面{{ phone }}
</body>
然後將上面的對映註釋掉,新增新的對映
path('about/',views.AboutView.as_view()),
這裡我們就傳遞了一個引數phone
過去。
ListView:
在網站開發中,經常會出現需要列出某個表中的一些資料作為列表展示出來。比如文章列表,圖書列表等等。在Django中可以使用ListView來幫我們快速實現這種需求。
因為要對錶中的資料進行操作,那麼我們首先的建立一個模型,然後對映到資料庫中去。
models.py中寫入程式碼:
from django.db import models
# Create your models here.
class Article(models.Model):
title = models.CharField(max_length=100);
content = models.TextField()
create_time = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'listView_article'
如果你的models在某個app下面,那麼先將app新增至settings.py
中,然後配置資料庫連線MySQL資料庫,當然不配置也可以,使用預設的SQLite資料庫也可以。
如果models.py
沒有在某個app下面,那麼就不用進行新增app這一步驟了,直接配置資料庫就行了。
然後執行makenigrations和migrate。將模型對映到資料庫中。為了方便我們進行測試,首先我們先來進行批量新增資料:
在views中新家一個函式檢視:用來批量新增資料。
# 新增測試資料
from . import models
def addInfo(request):
articles = []
for x in range(0,101):
article = models.Article(title='標題:%s'%x,content='內容:%s'%x)
articles.append(article)
models.Article.objects.bulk_create(articles) #批量新增資料
return HttpResponse('success')
新增對映
path('addInfo/',views.addInfo),
輸入網址,執行檢視函式,我們就添加了101個數據到資料庫中了。
然後我們新建一個article_list.html
的檔案,寫入程式碼:
<ul>
{% for article in articles %}
<li>{{ article.title }}</li>
{% endfor %}
</ul>
接下來我們新建一個類檢視:
class ArticleListView(ListView):
model = models.Article # 重寫model類屬性,指定模型的列表
template_name = 'article_list.html' # 指定這個列表的模板。
context_object_name = 'articles' # 指定這個列表模型在模板中的引數名稱。
paginate_by = 10 # 每一頁需要展示多少條資料
ordering = 'create_time' # 以時間進行排序展示
然後新增對映
path('list/',views.ArticleListView.as_view()),
輸入網址,就能檢視效果了,並且當前頁面只有10條資料。
而如果我們向訪問後面的頁面,我們只需要在輸入的網址後面以GET的方式新增一個page=x
的引數就行了
例如:我的網址是
http://127.0.0.1:8000/class/list/
那麼我想訪問第二頁,我就應該這樣輸入網址
http://127.0.0.1:8000/class/list/?page=2
以此類推,後面的也是一樣的了,因為我們只添加了101條資料,所以只有11頁,而我們如果讓page=12的話,就會找不到頁面。如果我們在網址後面沒有傳遞page引數,預設返回的就是第一頁。
而如果我們不想使用page
作為引數,而是換一個p
作為引數,那麼就可以使用page_kwarg
這個屬性了
示例:將預設引數page
改為p
class ArticleListView(ListView):
model = models.Article # 重寫model類屬性,指定模型的列表
template_name = 'article_list.html' # 指定這個列表的模板。
context_object_name = 'articles' # 指定這個列表模型在模板中的引數名稱。
paginate_by = 10 # 每一頁需要展示多少條資料
ordering = 'create_time' # 以時間進行排序展示
page_kwarg = 'p' # 指定換頁面的引數 預設為page 以GET的方式傳遞引數
這樣,我們就不能使用page作為引數了,而是隻能使用p
作為引數了。
上面演示的是常用屬性的用法,而我們還有兩個比較常用的方法。
接下來我們就將演示這兩種方法。
- get_context_data:獲取上下文的資料。並且可以新增自己的引數,例如下面的
username
在上面定義的類中新增這個方法
class ArticleListView(ListView):
model = models.Article # 重寫model類屬性,指定模型的列表
template_name = 'article_list.html' # 指定這個列表的模板。
context_object_name = 'articles' # 指定這個列表模型在模板中的引數名稱。
paginate_by = 10 # 每一頁需要展示多少條資料
ordering = 'create_time' # 以時間進行排序展示
page_kwarg = 'p' # 指定換頁面的引數 預設為page 以GET的方式傳遞引數
# 重寫父類的get_context_data方法,新增自己的引數
def get_context_data(self,**kwargs):
# 我們需要先繼承至父模板的get_context_data方法,否則我們有很多方法將不能使用
context = super(ArticleListView,self).get_context_data(**kwargs)
# 然後新增自定義的引數
context['username'] = 'zhiliao'
print(context)
我們就能在控制檯看到打印出來的東西了。
裡面包含了很多東西,有我們常用的paginator類、page_obj類和我們自定義的一些引數等。
(paginator類、page_obj類下面將會進行講解)
- get_queryset:如果你提取資料的時候,並不是要把所有資料都返回,那麼你可以重寫這個方法。將一些不需要展示的資料給過濾掉。
def get_context_data(self,**kwargs):
# 我們需要先繼承至父模板的get_context_data方法,否則我們有很多方法將不能使用
context = super(ArticleListView,self).get_context_data(**kwargs)
# 然後新增自定義的引數
context['username'] = 'zhiliao'
print(context)
return context
# 設定需要返回的資料
def get_queryset(self):
# 沒有重寫方法預設返回所有的資料
# return models.Article.objects.all()
return models.Article.objects.filter(id__lte=93)
注意: 在這裡我們將id>93
的資料過濾掉了,所以現在的頁面只有10頁了。所以傳入的引數page不能大於10。
上面類中的屬性和方法說明:
- model:重寫model類屬性,指定這個列表是給哪個模型的。
- template_name:指定這個列表的模板。
- paginate_by:指定這個列表一頁中展示多少條資料。
- context_object_name:指定這個列表模型在模板中的引數名稱。
- ordering:指定這個列表的排序方式。
- page_kwarg:獲取第幾頁的資料的引數名稱。預設是page。
- get_context_data:獲取上下文的資料。
- get_queryset:如果你提取資料的時候,並不是要把所有資料都返回,那麼你可以重寫這個方法。將一些不需要展示的資料給過濾掉。
Paginator和Page類:
Paginator
和Page
類都是用來做分頁的。他們在Django中的路徑為django.core.paginator.Paginator
和django.core.paginator.Page
。
Paginator常用屬性和方法:
- count:總共有多少條資料。
- num_pages:總共有多少頁。
- page_range:頁面的區間。比如有三頁,那麼就range(1,4)。
示例程式碼:修改上面定義的類的程式碼:
class ArticleListView(ListView):
model = models.Article # 重寫model類屬性,指定模型的列表
template_name = 'article_list.html' # 指定這個列表的模板。
context_object_name = 'articles' # 指定這個列表模型在模板中的引數名稱。
paginate_by = 10 # 每一頁需要展示多少條資料
ordering = 'create_time' # 以時間進行排序展示
page_kwarg = 'p' # 指定換頁面的引數 預設為page 以GET的方式傳遞引數
# 重寫父類的get_context_data方法,新增自己的引數
def get_context_data(self,**kwargs):
# 我們需要先繼承至父模板的get_context_data方法,否則我們有很多方法將不能使用
context = super(ArticleListView,self).get_context_data(**kwargs)
# 然後新增自定義的引數
context['username'] = 'zhiliao'
# print(context)
paginator = context.get('paginator') # paginator類
print(paginator.count) # 獲取資料的個數
# print(paginator.num_pages) # 獲取頁數
# print(paginator.page_range) #或缺頁數的範圍,要前不要後
return context
# 設定需要返回的資料
# def get_queryset(self):
# # 沒有重寫方法預設返回所有的資料
# # return models.Article.objects.all()
# return models.Article.objects.filter(id__lte=89)
然後可自行修改程式碼檢視對應效果。
Page類:
page類的常用屬性和方法:
- has_next:是否還有下一頁。
- has_previous:是否還有上一頁。
- next_page_number:下一頁的頁碼。
- previous_page_number:上一頁的頁碼。
- number:當前頁。
只有這一個是屬性,其他的都是方法
- start_index:當前這一頁的第一條資料的索引值。
- end_index:當前這一頁的最後一條資料的索引值。
示例程式碼:
class ArticleListView(ListView):
model = models.Article # 重寫model類屬性,指定模型的列表
template_name = 'article_list.html' # 指定這個列表的模板。
context_object_name = 'articles' # 指定這個列表模型在模板中的引數名稱。
paginate_by = 10 # 每一頁需要展示多少條資料
ordering = 'create_time' # 以時間進行排序展示
page_kwarg = 'p' # 指定換頁面的引數 預設為page 以GET的方式傳遞引數
# 重寫父類的get_context_data方法,新增自己的引數
def get_context_data(self,**kwargs):
# 我們需要先繼承至父模板的get_context_data方法,否則我們有很多方法將不能使用
context = super(ArticleListView,self).get_context_data(**kwargs)
# 然後新增自定義的引數
context['username'] = 'zhiliao'
# print(context)
paginator = context.get('paginator') # paginator類
page_obj = context.get('page_obj') # page_obj類
# print(paginator.count) # 獲取資料的個數
# print(paginator.num_pages) # 獲取頁數
# print(paginator.page_range) #或缺頁數的範圍,要前不要後
print(page_obj.number)
print(page_obj.start_index())
print(page_obj.end_index())
print(page_obj.has_next())
print(page_obj.has_previous())
print(page_obj.next_page_number())
print(page_obj.previous_page_number())
return context
# 設定需要返回的資料
# def get_queryset(self):
# # 沒有重寫方法預設返回所有的資料
# # return models.Article.objects.all()
# return models.Article.objects.filter(id__lte=89)
給類檢視新增裝飾器:
在開發中,有時候需要給一些檢視新增裝飾器。如果用函式檢視那麼非常簡單,只要在函式的上面寫上裝飾器就可以了。但是如果想要給類新增裝飾器,那麼可以通過以下兩種方式來實現:
需求:在訪問個人中心頁面的時候,如果沒有登入,我們就讓它跳轉到登入頁面,登入之後才能訪問個人中心。
這裡我們就是用get請求來模擬是否登入了,即如果我們在訪問網址的時候使用get方法傳遞了一個username
的引數,我們就認為已經登入, 否則的話就沒有登入。
1. 裝飾類檢視中的dispatch方法:
首先在view中定義個人中心的類檢視和登入頁面的函式檢視:
# 個人中心的類檢視
class Profileview(View):
# 只能通過GET請求來訪問
def get(self,request):
return HttpResponse('個人中心頁面')
# 因為所有的請求方法在類檢視中最終都會通過diapatch這個方法,
# 所以我們需要重寫dispatch方法,並且繼承至父類的dispatch方法。
def dispatch(self, request, *args, **kwargs):
return super(Profileview,self).dispatch(request,*args,**kwargs)
# 登入的函式檢視
def login(request):
return HttpResponse('login')
在上面我們已經說到了所有的請求方法在類檢視中最終都會通過diapatch這個方法,所以我們需要重寫dispatch方法,並且繼承至父類的dispatch方法。然後再使用裝飾器將dispatch裝飾,就達到了對類檢視進行裝飾的目地了,所以我們需要先寫一個裝飾器:
def login_required(func):
def wrapper(request,*args,**kwargs):
username = request.GET.get('username')
# 判斷是否得到username這個值,如果得到了,就認為已經登入成功了,否則就沒有登入
if username:
return func(request,*args,**kwargs)
else:
# 沒有登入 重定向到登入頁面
return redirect(reverse('class:login'))
return wrapper
這裡的reverse中的class
是我在這個view的app下面的urls中的app_name,login是我的給path起的名字。
app_name = 'class'
urlpatterns = [
path('login/',views.login,name='login'),
path('prfile/',views.Profileview.as_view(),name='profile'),
]
然後我們就將我們寫的裝飾器對dispatch進行裝飾,這裡我們需要使用到django中對類檢視裝飾的方法了:
from django.utils.decorators import method_decorator
對類檢視進行裝飾我們一般都使用這個方法。
然後我們使用這個方法對dispatch進行裝飾,在dispatch函式的上面一行寫入:
@method_decorator(login_required)
裝飾完之後Profileview這個類檢視的完整程式碼為:
# 個人中心的類檢視
class Profileview(View):
# 只能通過GET請求來訪問
def get(self,request):
return HttpResponse('個人中心頁面')
# 因為所有的請求方法在類檢視中最終都會通過diapatch這個方法,
# 所以我們需要重寫dispatch方法,並且繼承至父類的dispatch方法。
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(Profileview,self).dispatch(request,*args,**kwargs)
怎樣我們就實現了第一種方法對類檢視進行裝飾了。
就可以輸入url進行測試了,例如我的個人中心網址是:
http://127.0.0.1:8000/class/prfile/
當我進行訪問的時候,他就會給我們跳轉至登入頁面,因為我們沒有傳入一個值username
進去,就認為我們沒有登入,所以我們想要訪問個人中心就需要傳入username
進去。
http://127.0.0.1:8000/class/prfile/?username=xxx
這樣,我們就能訪問到我們的個人中心了,說明我們的裝飾器也起到了相應的效果了。
2. 直接裝飾在整個類上:
但是上面的方法並不太好,因為我們在寫類檢視的時候,絕大多數就不會去重寫dispatch方法,比如說我使用get請求,那麼我就重寫get方法就行了,使用post請求,重寫post方法,而不用去重寫dispatch方法。
那麼怎樣直接裝飾在整個類上面呢,非常簡單,只需要在類檢視上面新增語句程式碼就行了
# 個人中心的類檢視
@method_decorator(login_required,name='dispatch')
class Profileview(View):
# 只能通過GET請求來訪問
def get(self,request):
return HttpResponse('個人中心頁面')
# 因為所有的請求方法在類檢視中最終都會通過diapatch這個方法,
# 所以我們需要重寫dispatch方法,並且繼承至父類的dispatch方法。
# @method_decorator(login_required)
# def dispatch(self, request, *args, **kwargs):
# return super(Profileview,self).dispatch(request,*args,**kwargs)
這樣,我們就不用重寫dispatch方法了,只需要在後面新增一個引數name
指定裝飾的類中的哪一個方法既可以了。
當然,如果我們有多個裝飾器,我們還可以指定一個列表,
@method_decorator([login_required,xx_required],name='dispatch')
但是我們這裡只定義了login_required,而沒有定義xx_required,所以肯定會報錯的,這裡只是想說一下當有多個裝飾器的用法。
這樣,我們就成功的對類檢視進行了裝飾了,推介大家使用第二種方法,不用重寫dispatch方法。