在本講中,咱們開始首頁功能的開發,在開發過程當中,你們將會學習到Django中的通用視圖類、分頁對象paginator以及foreignKey外鍵的使用。html
效果演示 前端
你們可先經過 網站演示地址 瀏覽一下首頁的效果。咱們首頁呢,比較簡潔大方,讓人一目瞭然。我這樣設計的目的呢,是讓你們把精力放到學習django上面來,沒必要過分關注花哨的頁面效果。python
咱們把首頁拆解爲4個小的業務模塊來開發,分別是:列表顯示、分頁功能、搜索功能、分類功能。下面咱們分別對這四個功能模塊進行開發講解。數據庫
開發一個功能的基本思路是:先新建應用,而後分析功能涉及到哪些業務,從而分析出須要的數據庫字段,而後編寫模型,以後就是展現階段,經過url路由配置視圖函數,來將模型裏面的數據顯示出來。django
ok,咱們經過命令創建應用,命名爲video。執行後,django將爲咱們新建video文件夾。瀏覽器
python3 manage.py startapp video
複製代碼
下面的功能模塊開發都在該應用(video)下進行。bash
此處,咱們須要創建兩個模型,分別是分類表(classification)和視頻表(video)。他們是多對一的關係(一個分類對應多個視頻,一個視頻對應一個分類)。app
首先編寫Classification表,在model.py下面,咱們鍵入以下代碼。 字段有title(分類名稱)和status(是否啓用)ide
class Classification(models.Model):
list_display = ("title",)
title = models.CharField(max_length=100,blank=True, null=True)
status = models.BooleanField(default=True)
class Meta:
db_table = "v_classification"
複製代碼
字段說明函數
而後編寫Video模型,根據網站業務,咱們設置了title(標題)、 desc(描述)、 classification(分類)、file(視頻文件)、cover(封面)、status(發佈狀態)等字段。其中classification是一個ForeignKey外鍵字段,表示一個分類對應多個視頻,一個視頻對應一個分類(多對一)
class Video(models.Model):
STATUS_CHOICES = (
('0', '發佈中'),
('1', '未發佈'),
)
title = models.CharField(max_length=100,blank=True, null=True)
desc = models.CharField(max_length=255,blank=True, null=True)
classification = models.ForeignKey(Classification, on_delete=models.CASCADE, null=True)
file = models.FileField(max_length=255)
cover = models.ImageField(upload_to='cover/',blank=True, null=True)
status = models.CharField(max_length=1 ,choices=STATUS_CHOICES, blank=True, null=True)
create_time = models.DateTimeField(auto_now_add=True, blank=True, max_length=20)
複製代碼
字段說明
ForeignKey 代表一種一對多的關聯關係。好比這裏咱們的視頻和分類的關係,一個視頻只能對應一個分類,而一個分類下能夠有多個視頻。 更多關於ForeinkKey的說明,能夠參看 ForeignKey官方介紹
要想訪問到首頁,必須先配置好路由。在video下創建urls.py文件,寫入以下代碼
from django.urls import path
from . import views
app_name = 'video'
urlpatterns = [
path('index', views.IndexView.as_view(), name='index'),
]
複製代碼
一條path語句就表明一條路由信息。這樣咱們就能夠在瀏覽器輸入127.0.0.1:8000/video/index來訪問首頁了。
顯示列表數據很是簡單,咱們使用django中內置的視圖模版類ListView來顯示,首先在view.py中編寫IndexView類,用它來顯示列表數據。鍵入以下代碼
class IndexView(generic.ListView):
model = Video
template_name = 'video/index.html'
context_object_name = 'video_list'
複製代碼
此處,咱們使用了django提供的通用視圖類ListView, ListView使用很簡單,只須要咱們簡單的配置幾行代碼,便可將數據庫裏面的數據渲染到前端。好比上述代碼中,咱們配置了
以後,咱們在templates文件夾下,創建video目錄,用來存放視頻相關的模板文件,首先咱們建立首頁文件index.html。並將剛纔獲取到的數據顯示出來。
<div class="ui grid">
{% for item in video_list %}
<div class="four wide column">
<div class="ui card">
<a class="image">
{% thumbnail item.cover "300x200" crop="center" as im %}
<img class="ui image" src="{{ im.url }}">
{% empty %}
{% endthumbnail %}
<i class="large play icon v-play-icon"></i>
</a>
<div class="content">
<a class="header">{{ item.title }}</a>
<div class="meta">
<span class="date">發佈於{{ item.create_time|time_since}}</span>
</div>
<div class="description">
{{ item.view_count}}次觀看
</div>
</div>
</div>
</div>
{% empty %}
<h3>暫無數據</h3>
{% endfor %}
</div>
複製代碼
經過for循環,將video_list渲染到前端。這裏咱們使用到了django中的內置標籤,好比for語句、empty語句。這些都是django中很是經常使用的語句。在以後的教程中咱們會常常遇到。
另外,還使用了thumbnail標籤來顯示圖片,thumbnail是一個很經常使用的python庫,經常被用來作圖片顯示。
顯示結果以下
在寫分類功能以前,咱們先學習一個回調函數 get_context_data() 這是ListView視圖類中的一個函數,在 get_context_data() 函數中,能夠傳一些額外內容到模板。所以咱們可使用該函數來傳遞分類數據。
要使用它,很簡單。
只須要在IndexView類下面,追加get_context_data()的實現便可。
class IndexView(generic.ListView):
model = Video
template_name = 'video/index.html'
context_object_name = 'video_list'
def get_context_data(self, *, object_list=None, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
classification_list = Classification.objects.filter(status=True).values()
context['classification_list'] = classification_list
return context
複製代碼
在上述代碼中,咱們將分類數據經過Classification.objects.filter(status=True).values()從數據庫裏面過濾出來,而後賦給classification_list,最後放到context字典裏面。
在前端模板(templates/video/index.html)中,就能夠經過classification_list來取數據。添加代碼
<div class="classification">
<a class="ui red label" href="">所有</a>
{% for item in classification_list %}
<a class="ui label" href="">{{ item.title }}</a>
{% endfor %}
</div>
複製代碼
顯示效果以下
固然如今只是實現了分類展現效果,咱們還須要繼續實現點擊效果,即點擊不一樣的分類,顯示不一樣的視頻列表。
咱們先給每一個分類按鈕加上href連接
<div class="classification">
<a class="ui red label" href="{% url 'home' %}">所有</a>
{% for item in classification_list %}
<a class="ui label" href="?c={{ item.id }}">{{ item.title }}</a>
{% endfor %}
</div>
複製代碼
經過添加?c={{ item.id }} 這裏用c表明分類的id,點擊後,會傳到視圖類中,在視圖類中,咱們使用 get_queryset() 函數,將get數據取出來。經過self.request.GET.get("c", None) 賦給c,判斷c是否爲None,若是爲None,就響應所有,若是有值,就經過get_object_or_404(Classification, pk=self.c)先獲取當前類,而後classification.video_set獲取外鍵數據。
def get_queryset(self):
self.c = self.request.GET.get("c", None)
if self.c:
classification = get_object_or_404(Classification, pk=self.c)
return classification.video_set.all().order_by('-create_time')
else:
return Video.objects.filter(status=0).order_by('-create_time')
複製代碼
更多關於ForeignKey的使用方法,可參考 這裏
在Django中,有現成的分頁解決方案,咱們開發者省了很多事情。若是是簡單的分頁,只須要配置一下paginate_by便可實現。
class IndexView(generic.ListView):
model = Video
template_name = 'video/index.html'
context_object_name = 'video_list'
paginate_by = 12
c = None
複製代碼
這樣每頁的分頁數據就能正確的顯示出來來,如今來完善底部的頁碼條。
頁碼列表須要視圖類和模板共同來完成,咱們先來寫視圖類。在前面咱們已經寫過get_context_data了,該函數的主要功能就是傳遞額外的數據給模板。這裏,咱們就利用get_context_data來傳遞頁碼數據。
咱們先定義一個工具函數,叫get_page_list。 在項目根目錄下,新建一個文件helpers.py該文件看成一個全局的工具類,用來存放各類工具函數。把get_page_list放到helpers.py裏面 該函數用來生產頁碼列表,不但這裏可使用,之後在其餘地方也能夠調用該函數。
def get_page_list(paginator, page):
page_list = []
if paginator.num_pages > 10:
if page.number <= 5:
start_page = 1
elif page.number > paginator.num_pages - 5:
start_page = paginator.num_pages - 9
else:
start_page = page.number - 5
for i in range(start_page, start_page + 10):
page_list.append(i)
else:
for i in range(1, paginator.num_pages + 1):
page_list.append(i)
return page_list
複製代碼
分頁邏輯:
if 頁數>=10:
當前頁<=5時,起始頁爲1
當前頁>(總頁數-5)時,起始頁爲(總頁數-9)
其餘狀況 起始頁爲(當前頁-5)
複製代碼
舉例:
假設一共16頁
狀況1: 當前頁==5 則頁碼列表爲[1,2,3,4,5,6,7,8,9,10]
狀況2: 當前頁==8 則頁碼列表爲[3,4,5,6,7,8,9,10,11,12]
狀況3: 當前頁==15 則頁碼列表爲[7,8,9,10,11,12,13,14,15,16]
複製代碼
固然你看到這個邏輯會有點亂,建議你們讀着代碼,多試驗幾遍。
當拿到頁碼列表,咱們繼續改寫get_context_data()函數。 將獲取到的classification_list追加到context字典中。
def get_context_data(self, *, object_list=None, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
paginator = context.get('paginator')
page = context.get('page_obj')
page_list = get_page_list(paginator, page)
classification_list = Classification.objects.filter(status=True).values()
context['c'] = self.c
context['classification_list'] = classification_list
context['page_list'] = page_list
return context
複製代碼
你或許對 paginator = context.get('paginator') page = context.get('page_obj')這兩行代碼感到陌生,咱們只須要知道context.get('page_obj')返回的是當前頁碼,context.get('paginator')返回的是分頁對象,就夠了。更加詳細的介紹,可參考官方。
當數據傳遞給模板以後,模板就負責顯示出來就好了。
由於分頁功能比較經常使用,因此須要把它單獨拿出來封裝到一個單獨的文件中,咱們新建templates/base/page_nav.html文件。而後在index.html裏面咱們將該文件include進來。
{% include "base/page_nav.html" %}
複製代碼
打開page_nav.html,寫入代碼
{% if is_paginated %}
<div class="video-page">
<div class="ui circular labels">
{% if page_obj.has_previous %}
<a class="ui circular label" href="?page={{ page_obj.previous_page_number }}{% if c %}&c={{c}}{% endif %}{% if q %}&q={{q}}{% endif %}"><</a>
{% endif %}
{% for i in page_list %}
{% if page_obj.number == i %}
<a class="ui red circular label">{{ i }}</a>
{% else %}
<a class="ui circular label" href="?page={{ i }}{% if c %}&c={{c}}{% endif %}{% if q %}&q={{q}}{% endif %}">{{ i }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a class="ui circular label" href="?page={{ page_obj.next_page_number }}{% if c %}&c={{c}}{% endif %}{% if q %}&q={{q}}{% endif %}">></a>
{% endif %}
</div>
</div>
{% endif %}
複製代碼
上面代碼中,咱們用到了page_obj對象的幾個屬性:has_previous、previous_page_number、next_page_number。經過這幾個屬性,便可實現複雜的頁碼顯示效果。其中咱們還這href裏面加了
{% if c %}&c={{c}}
複製代碼
表明分類的id。
要實現搜索,咱們須要一個搜索框
由於搜索框是不少頁面都須要的,因此咱們把代碼寫到templates/base/header.html文件裏面。
<div class="ui small icon input v-video-search">
<input class="prompt" value="{{ q }}" type="text" placeholder="搜索視頻" id="v-search">
<i id="search" class="search icon" style="cursor:pointer;"></i>
</div>
複製代碼
點擊搜索或回車的代碼寫在了static/js/header.js裏面。
咱們還須要配置一下路由,添加一行搜索的路由。
app_name = 'video'
urlpatterns = [
path('index', views.IndexView.as_view(), name='index'),
path('search/', views.SearchListView.as_view(), name='search'),
]
複製代碼
搜索路由指向的視圖類爲SearchListView
下面咱們來寫SearchListView的代碼
class SearchListView(generic.ListView):
model = Video
template_name = 'video/search.html'
context_object_name = 'video_list'
paginate_by = 8
q = ''
def get_queryset(self):
self.q = self.request.GET.get("q","")
return Video.objects.filter(title__contains=self.q).filter(status=0)
def get_context_data(self, *, object_list=None, **kwargs):
context = super(SearchListView, self).get_context_data(**kwargs)
paginator = context.get('paginator')
page = context.get('page_obj')
page_list = get_page_list(paginator, page)
context['page_list'] = page_list
context['q'] = self.q
return context
複製代碼
關鍵代碼就是Video.objects.filter(title__contains=self.q).filter(status=0) title__contains是包含的意思,表示查詢title包含q的記錄。利用filter將數據過濾出來。這裏寫了兩層過濾,第一層過濾搜索關鍵詞,第二層過濾status已發佈的視頻。
另外,這裏也用到了get_context_data來存放額外的數據,包括分頁數據、q關鍵詞。
配置模板文件是templates/video/search.html
所以模板代碼寫在search.html裏面
<div class="ui unstackable items">
{% for item in video_list %}
<div class="item">
<div class="ui tiny image">
{% thumbnail item.cover "300x200" crop="center" as im %}
<img class="ui image" src="{{ im.url }}">
{% empty %}
{% endthumbnail %}
</div>
<div class="middle aligned content">
<a class="header" href="{% url 'video:detail' item.pk %}">{{ item.title }}</a>
</div>
</div>
{% empty %}
<h3>暫無數據</h3>
{% endfor %}
</div>
{% include "base/page_nav.html" %}
複製代碼
搜索功能效果