1. 程式人生 > >Django 中類檢視詳解

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

方法訪問的我們定義的檢視函式,那麼我們就返回一個html頁面讓他輸入值,如果是使用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()),

然後在瀏覽器中輸入網址,寫入資訊,就能在控制檯打印出獲取的資訊了。

除了getpost方法,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.Viewdispatch(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作為引數了。

上面演示的是常用屬性的用法,而我們還有兩個比較常用的方法。
接下來我們就將演示這兩種方法。

  1. 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類下面將會進行講解)

  1. 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。

上面類中的屬性和方法說明:

  1. model:重寫model類屬性,指定這個列表是給哪個模型的。
  2. template_name:指定這個列表的模板。
  3. paginate_by:指定這個列表一頁中展示多少條資料。
  4. context_object_name:指定這個列表模型在模板中的引數名稱。
  5. ordering:指定這個列表的排序方式。
  6. page_kwarg:獲取第幾頁的資料的引數名稱。預設是page。
  7. get_context_data:獲取上下文的資料。
  8. get_queryset:如果你提取資料的時候,並不是要把所有資料都返回,那麼你可以重寫這個方法。將一些不需要展示的資料給過濾掉。

Paginator和Page類:

PaginatorPage類都是用來做分頁的。他們在Django中的路徑為django.core.paginator.Paginatordjango.core.paginator.Page

Paginator常用屬性和方法:

  1. count:總共有多少條資料。
  2. num_pages:總共有多少頁。
  3. 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類的常用屬性和方法:

  1. has_next:是否還有下一頁。
  2. has_previous:是否還有上一頁。
  3. next_page_number:下一頁的頁碼。
  4. previous_page_number:上一頁的頁碼。
  5. number:當前頁。 只有這一個是屬性,其他的都是方法
  6. start_index:當前這一頁的第一條資料的索引值。
  7. 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方法。

想深入學習django的可以看一下這個視訊:超詳細講解Django打造大型企業官網