在本講中,我們開始首頁(yè)功能的開發(fā)蟹但,在開發(fā)過程中癣缅,大家將會(huì)學(xué)習(xí)到Django中的通用視圖類、分頁(yè)對(duì)象paginator以及foreignKey外鍵的使用岂傲。
效果演示
整體功能
大家可先通過 網(wǎng)站演示地址 瀏覽一下首頁(yè)的效果难裆。我們首頁(yè)呢,比較簡(jiǎn)潔大方镊掖,讓人一目了然乃戈。我這樣設(shè)計(jì)的目的呢,是讓大家把精力放到學(xué)習(xí)django上面來亩进,不必過度關(guān)注花哨的頁(yè)面效果症虑。
我們把首頁(yè)拆解為4個(gè)小的業(yè)務(wù)模塊來開發(fā),分別是:列表顯示镐侯、分頁(yè)功能侦讨、搜索功能驶冒、分類功能苟翻。下面我們分別對(duì)這四個(gè)功能模塊進(jìn)行開發(fā)講解。
開發(fā)思路
開發(fā)一個(gè)功能的基本思路是:先新建應(yīng)用骗污,然后分析功能涉及到哪些業(yè)務(wù)崇猫,從而分析出需要的數(shù)據(jù)庫(kù)字段,然后編寫模型需忿,之后就是展示階段诅炉,通過url路由配置視圖函數(shù),來將模型里面的數(shù)據(jù)顯示出來屋厘。
ok涕烧,我們通過命令建立應(yīng)用,命名為video汗洒。執(zhí)行后议纯,django將為我們新建video文件夾。
python3 manage.py startapp video
下面的功能模塊開發(fā)都在該應(yīng)用(video)下進(jìn)行溢谤。
建模型
此處瞻凤,我們需要建立兩個(gè)模型憨攒,分別是分類表(classification)和視頻表(video)。他們是多對(duì)一的關(guān)系(一個(gè)分類對(duì)應(yīng)多個(gè)視頻阀参,一個(gè)視頻對(duì)應(yīng)一個(gè)分類)肝集。
首先編寫Classification表,在model.py下面蛛壳,我們鍵入如下代碼杏瞻。
字段有title(分類名稱)和status(是否啟用)
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"
字段說明
- title 分類名稱。數(shù)據(jù)類型是CharField炕吸,最大長(zhǎng)度為max_length=100伐憾,允許為空null=True
- status 是否啟用。數(shù)據(jù)類型是BooleanField,默認(rèn)為default=True
- db_table 表名
然后編寫Video模型赫模,根據(jù)網(wǎng)站業(yè)務(wù)树肃,我們?cè)O(shè)置了title(標(biāo)題)、 desc(描述)瀑罗、 classification(分類)胸嘴、file(視頻文件)、cover(封面)斩祭、status(發(fā)布狀態(tài))等字段劣像。其中classification是一個(gè)ForeignKey外鍵字段,表示一個(gè)分類對(duì)應(yīng)多個(gè)視頻摧玫,一個(gè)視頻對(duì)應(yīng)一個(gè)分類(多對(duì)一)
class Video(models.Model):
STATUS_CHOICES = (
('0', '發(fā)布中'),
('1', '未發(fā)布'),
)
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)
字段說明
- title 視頻標(biāo)題耳奕。數(shù)據(jù)類型是charField,最大長(zhǎng)度為max_length=100诬像,允許為空null=True
- desc 視頻描述屋群。數(shù)據(jù)類型是charField,最大長(zhǎng)度為max_length=255坏挠,允許為空null=True
- file 視頻文件地址芍躏。數(shù)據(jù)類型是fileField。其中存的是視頻文件的地址降狠,在之后的視頻管理中我們將會(huì)對(duì)視頻的上傳進(jìn)行具體的講解对竣。
- cover 視頻封面。數(shù)據(jù)類型是ImageField榜配。存儲(chǔ)目錄為upload_to='cover/'否纬,允許為空null=True
- status 視頻狀態(tài)。是一個(gè)選擇狀態(tài)蛋褥,用choices設(shè)置多選元祖临燃。
- create_time 創(chuàng)建時(shí)間。數(shù)據(jù)類型是DateTimeField 。設(shè)置自動(dòng)生成時(shí)間auto_now_add=True
ForeignKey 表明一種一對(duì)多的關(guān)聯(lián)關(guān)系谬俄。比如這里我們的視頻和分類的關(guān)系柏靶,一個(gè)視頻只能對(duì)應(yīng)一個(gè)分類,而一個(gè)分類下可以有多個(gè)視頻溃论。
更多關(guān)于ForeinkKey的說明屎蜓,可以參看 ForeignKey官方介紹
列表顯示
要想訪問到首頁(yè),必須先配置好路由钥勋。在video下建立urls.py文件炬转,寫入如下代碼
from django.urls import path
from . import views
app_name = 'video'
urlpatterns = [
path('index', views.IndexView.as_view(), name='index'),
]
一條path語(yǔ)句就代表一條路由信息。這樣我們就可以在瀏覽器輸入127.0.0.1:8000/video/index來訪問首頁(yè)了算灸。
顯示列表數(shù)據(jù)非常簡(jiǎn)單扼劈,我們使用django中內(nèi)置的視圖模版類ListView來顯示,首先在view.py中編寫IndexView類菲驴,用它來顯示列表數(shù)據(jù)荐吵。鍵入如下代碼
class IndexView(generic.ListView):
model = Video
template_name = 'video/index.html'
context_object_name = 'video_list'
此處,我們使用了django提供的通用視圖類ListView, ListView使用很簡(jiǎn)單赊瞬,只需要我們簡(jiǎn)單的配置幾行代碼先煎,即可將數(shù)據(jù)庫(kù)里面的數(shù)據(jù)渲染到前端。比如上述代碼中巧涧,我們配置了
- model = Video, 作用于Video模型
- template_name = 'video/index.html' 薯蝎,告訴ListView要使用我們已經(jīng)創(chuàng)建的模版文件。
- context_object_name = 'video_list' 谤绳,上下文變量名占锯,告訴ListView,在前端模版文件中缩筛,可以使用該變量名來展現(xiàn)數(shù)據(jù)消略。
之后,我們?cè)趖emplates文件夾下歪脏,建立video目錄疑俭,用來存放視頻相關(guān)的模板文件粮呢,首先我們創(chuàng)建首頁(yè)文件index.html婿失。并將剛才獲取到的數(shù)據(jù)顯示出來。
<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">發(fā)布于{{ item.create_time|time_since}}</span>
</div>
<div class="description">
{{ item.view_count}}次觀看
</div>
</div>
</div>
</div>
{% empty %}
<h3>暫無數(shù)據(jù)</h3>
{% endfor %}
</div>
通過for循環(huán)啄寡,將video_list渲染到前端豪硅。這里我們使用到了django中的內(nèi)置標(biāo)簽,比如for語(yǔ)句挺物、empty語(yǔ)句懒浮。這些都是django中非常常用的語(yǔ)句。在之后的教程中我們會(huì)經(jīng)常遇到。
另外砚著,還使用了thumbnail標(biāo)簽來顯示圖片次伶,thumbnail是一個(gè)很常用的python庫(kù),常常被用來做圖片顯示稽穆。
顯示結(jié)果如下
分類功能
在寫分類功能之前冠王,我們先學(xué)習(xí)一個(gè)回調(diào)函數(shù) get_context_data() 這是ListView視圖類中的一個(gè)函數(shù),在 get_context_data() 函數(shù)中舌镶,可以傳一些額外內(nèi)容到模板柱彻。因此我們可以使用該函數(shù)來傳遞分類數(shù)據(jù)。
要使用它餐胀,很簡(jiǎn)單哟楷。
只需要在IndexView類下面,追加get_context_data()的實(shí)現(xiàn)即可否灾。
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
在上述代碼中卖擅,我們將分類數(shù)據(jù)通過Classification.objects.filter(status=True).values()從數(shù)據(jù)庫(kù)里面過濾出來,然后賦給classification_list墨技,最后放到context字典里面磨镶。
在前端模板(templates/video/index.html)中,就可以通過classification_list來取數(shù)據(jù)健提。添加代碼
<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>
顯示效果如下
當(dāng)然現(xiàn)在只是實(shí)現(xiàn)了分類展示效果琳猫,我們還需要繼續(xù)實(shí)現(xiàn)點(diǎn)擊效果,即點(diǎn)擊不同的分類私痹,顯示不同的視頻列表脐嫂。
我們先給每個(gè)分類按鈕加上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,點(diǎn)擊后紊遵,會(huì)傳到視圖類中账千,在視圖類中,我們使用 get_queryset() 函數(shù)暗膜,將get數(shù)據(jù)取出來匀奏。通過self.request.GET.get("c", None) 賦給c,判斷c是否為None学搜,如果為None娃善,就響應(yīng)全部,如果有值瑞佩,就通過get_object_or_404(Classification, pk=self.c)先獲取當(dāng)前類聚磺,然后classification.video_set獲取外鍵數(shù)據(jù)。
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')
更多關(guān)于ForeignKey的使用方法炬丸,可參考 這里
分頁(yè)功能
在Django中瘫寝,有現(xiàn)成的分頁(yè)解決方案,我們開發(fā)者省了不少事情。如果是簡(jiǎn)單的分頁(yè)焕阿,只需要配置一下paginate_by即可實(shí)現(xiàn)咪啡。
class IndexView(generic.ListView):
model = Video
template_name = 'video/index.html'
context_object_name = 'video_list'
paginate_by = 12
c = None
- painate_by = 12每頁(yè)顯示12條
這樣每頁(yè)的分頁(yè)數(shù)據(jù)就能正確的顯示出來來,現(xiàn)在來完善底部的頁(yè)碼條暮屡。
頁(yè)碼列表需要視圖類和模板共同來完成瑟匆,我們先來寫視圖類。在前面我們已經(jīng)寫過get_context_data了栽惶,該函數(shù)的主要功能就是傳遞額外的數(shù)據(jù)給模板愁溜。這里,我們就利用get_context_data來傳遞頁(yè)碼數(shù)據(jù)外厂。
我們先定義一個(gè)工具函數(shù)冕象,叫g(shù)et_page_list。 在項(xiàng)目根目錄下汁蝶,新建一個(gè)文件helpers.py該文件當(dāng)作一個(gè)全局的工具類渐扮,用來存放各種工具函數(shù)。把get_page_list放到helpers.py里面 該函數(shù)用來生產(chǎn)頁(yè)碼列表掖棉,不但這里可以使用墓律,以后在其他地方也可以調(diào)用該函數(shù)。
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
分頁(yè)邏輯:
if 頁(yè)數(shù)>=10:
當(dāng)前頁(yè)<=5時(shí)幔亥,起始頁(yè)為1
當(dāng)前頁(yè)>(總頁(yè)數(shù)-5)時(shí)耻讽,起始頁(yè)為(總頁(yè)數(shù)-9)
其他情況 起始頁(yè)為(當(dāng)前頁(yè)-5)
舉例:
假設(shè)一共16頁(yè)
情況1: 當(dāng)前頁(yè)==5 則頁(yè)碼列表為[1,2,3,4,5,6,7,8,9,10]
情況2: 當(dāng)前頁(yè)==8 則頁(yè)碼列表為[3,4,5,6,7,8,9,10,11,12]
情況3: 當(dāng)前頁(yè)==15 則頁(yè)碼列表為[7,8,9,10,11,12,13,14,15,16]
當(dāng)然你看到這個(gè)邏輯會(huì)有點(diǎn)亂,建議大家讀著代碼帕棉,多試驗(yàn)幾遍针肥。
當(dāng)拿到頁(yè)碼列表,我們繼續(xù)改寫get_context_data()函數(shù)香伴。 將獲取到的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
你或許對(duì) paginator = context.get('paginator') page = context.get('page_obj')這兩行代碼感到陌生,我們只需要知道context.get('page_obj')返回的是當(dāng)前頁(yè)碼即纲,context.get('paginator')返回的是分頁(yè)對(duì)象具帮,就夠了。更加詳細(xì)的介紹低斋,可參考官方蜂厅。
當(dāng)數(shù)據(jù)傳遞給模板之后,模板就負(fù)責(zé)顯示出來就行了拔稳。
因?yàn)榉猪?yè)功能比較常用葛峻,所以需要把它單獨(dú)拿出來封裝到一個(gè)單獨(dú)的文件中锹雏,我們新建templates/base/page_nav.html文件巴比。然后在index.html里面我們將該文件include進(jìn)來。
{% 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對(duì)象的幾個(gè)屬性:has_previous采记、previous_page_number、next_page_number政勃。通過這幾個(gè)屬性唧龄,即可實(shí)現(xiàn)復(fù)雜的頁(yè)碼顯示效果。其中我們還這href里面加了
{% if c %}&c={{c}}
代表分類的id奸远。
搜索功能
要實(shí)現(xiàn)搜索既棺,我們需要一個(gè)搜索框
因?yàn)樗阉骺蚴呛芏囗?yè)面都需要的,所以我們把代碼寫到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>
點(diǎn)擊搜索或回車的代碼寫在了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
關(guān)鍵代碼就是Video.objects.filter(title__contains=self.q).filter(status=0)
title__contains是包含的意思胖烛,表示查詢title包含q的記錄。利用filter將數(shù)據(jù)過濾出來诅迷。這里寫了兩層過濾佩番,第一層過濾搜索關(guān)鍵詞,第二層過濾status已發(fā)布的視頻罢杉。
另外趟畏,這里也用到了get_context_data來存放額外的數(shù)據(jù),包括分頁(yè)數(shù)據(jù)滩租、q關(guān)鍵詞拱镐。
配置模板文件是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>暫無數(shù)據(jù)</h3>
{% endfor %}
</div>
{% include "base/page_nav.html" %}
搜索功能效果