在本講中,我們開始詳情頁功能的開發,詳情頁就是對單個視訊進行播放並展示視訊的相關資訊,比如視訊標題、描述、評論資訊、相關推薦等。我們將會學習到通用檢視類DetailView的使用、評論動態載入、以及如何通過ajax實現喜歡和收藏功能,並通過一段段很酷的程式碼來說明這些功能。
效果展示
整體功能
大家可先通過 網站演示地址 瀏覽一下網站效果。點選某個視訊即可瀏覽詳情頁。詳情頁實現了是對單個視訊進行展示,使用者可看到視訊的一些元資訊,包括標題、描述、觀看次數、喜歡數、收藏數等等。另外,網站還實現了評論功能,通過上拉網頁即可分頁載入評論列表,使用者還能新增評論。網頁側欄是推薦視訊列表,這裡使用的推薦邏輯比較簡單,就是推薦觀看次數最多的視訊。
我們把詳情頁分為4個小的業務模組來開發,分別是:視訊詳情顯示、喜歡和收藏功能、評論功能、推薦功能。下面我們分別對這四個功能模組進行開發講解。
視訊詳情顯示
因為在上一講中,我們已經建立了video模型,所以不必再新建模型,我們就在video模型的基礎上進行擴充套件。上一講,我們建立的欄位有title、desc、classification、file、cover、status、create_time。這些欄位目前是不夠用的,我們再加幾個欄位,需要加觀察次數、喜歡的使用者、收藏的使用者。video模型擴充套件後如下
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)
view_count = models.IntegerField(default=0, blank=True)
liked = models.ManyToManyField(settings.AUTH_USER_MODEL,
blank=True, related_name="liked_videos")
collected = models.ManyToManyField(settings.AUTH_USER_MODEL,
blank=True, related_name="collected_videos")
create_time = models.DateTimeField(auto_now_add=True, blank=True, max_length=20)
複製程式碼
新增了3個欄位
- view_count 觀看次數。資料型別是IntegerField,預設是0
- liked 喜歡的使用者。資料型別是ManyToManyField,這是一種多對多的關係,表示一個視訊可以被多個使用者喜歡,一個使用者也可以喜歡多個視訊。記得設定使用者表為settings.AUTH_USER_MODEL
- collected 收藏的使用者。資料型別是ManyToManyField,這是一種多對多的關係,表示一個視訊可以被多個使用者收藏,一個使用者也可以收藏多個視訊。設定使用者表為settings.AUTH_USER_MODEL
更多關於ManyToManyField的使用介紹,可以查詢django官網的介紹。
下面就是詳情展示階段,我們先配置好詳情頁的路由資訊,在video/urls.py中追加detail的路由資訊。
app_name = `video`
urlpatterns = [
path(`index`, views.IndexView.as_view(), name=`index`),
path(`search/`, views.SearchListView.as_view(), name=`search`),
path(`detail/<int:pk>/`, views.VideoDetailView.as_view(), name=`detail`),
]
複製程式碼
path(`detail/<int:pk>/`, views.VideoDetailView.as_view(), name=`detail`)
即表示詳情資訊,注意每條視訊都是有自己的主鍵的,所以設定路徑匹配為detail/<int:pk>/
,其中<int:pk>
表示主鍵,這是django中表示主鍵的一種方法。這樣我們就可以在瀏覽器輸入127.0.0.1:8000/video/detail/xxx來訪問詳情了。
怎麼顯示詳情呢,聰明的django為我們提供了DetailView。urls.py中設定的檢視類是VideoDetailView,我們讓VideoDetailView繼承DetailView即可。
class VideoDetailView(generic.DetailView):
model = Video
template_name = `video/detail.html`
複製程式碼
看起來超級簡單,django就是如此的酷,只需要我們配置幾行程式碼,就能實現很強大的功能。這裡我們配置model為Video模型,模板為video/detail.html,其它的工作都不用管,全都交給django去幹,oh,這棒極了。
模板檔案位於templates/video/detail.html,它的程式碼比較簡單,這裡就不貼了。
從效果圖上我們看到還有個觀看次數的展示,這裡的觀看次數本質上就是資料庫裡的一個自增欄位,每次觀看的時候,view_count
自動加1。對於這個小需求,我們需要做兩件事情,首先這video模型裡面,新增一個次數自增函式,命名為increase_view_count
,這很簡單,如下所示:
def increase_view_count(self):
self.view_count += 1
self.save(update_fields=[`view_count`])
複製程式碼
然後,還需要我們在VideoDetailView檢視類裡面呼叫到這個函式。這個時候get_object()
派上用場了。因為每次呼叫DetailView的時候,django都會回撥get_object()
這個函式。因此我們可以把increase_view_count()
放到get_object()
裡面執行。完美的程式碼如下
class VideoDetailView(generic.DetailView):
model = Video
template_name = `video/detail.html`
def get_object(self, queryset=None):
obj = super().get_object()
obj.increase_view_count() # 呼叫自增函式
return obj
複製程式碼
目前為止,我們就能在詳情頁看到標題、描述、觀看次數、收藏次數、喜歡次數。預覽如下
雖然可以顯示收藏人數、喜歡人數。但是目前還沒實現點選喜歡/收藏的功能。下面我們來實現。
收藏和喜歡功能
收藏和喜歡是一組動作,因此可以用ajax來實現:使用者點選後呼叫後端介面,介面返回json資料,前端顯示結果。
既然需要介面,那我們先新增喜歡/收藏介面的路由,在video/urls.py追加程式碼如下
path(`like/`, views.like, name=`like`),
path(`collect/`, views.collect, name=`collect`),
複製程式碼
由於喜歡和收藏的功能實現非常類似,限於篇幅,我們只實現喜歡功能。
我們先寫like函式:
@ajax_required
@require_http_methods(["POST"])
def like(request):
if not request.user.is_authenticated:
return JsonResponse({"code": 1, "msg": "請先登入"})
video_id = request.POST[`video_id`]
video = Video.objects.get(pk=video_id)
user = request.user
video.switch_like(user)
return JsonResponse({"code": 0, "likes": video.count_likers(), "user_liked": video.user_liked(user)})
複製程式碼
首先判斷使用者是否登入,如果登入了則呼叫switch_like(user)
來實現喜歡或不喜歡功能,最後返回json。注意這裡新增了兩個註解@ajax_required
和@require_http_methods(["POST"])
,分別驗證request必須是ajax和post請求。
switch_like()函式則寫在了video/model.py裡面
def switch_like(self, user):
if user in self.liked.all():
self.liked.remove(user)
else:
self.liked.add(user)
複製程式碼
所有的後端工作都準備好了,我們再把視線轉向前端。前端主要是寫ajax程式碼。
由於ajax程式碼量較大,我們封裝到一個單獨的js檔案中 ==> static/js/detail.js
在detail.js中,我們先實現喜歡的ajax呼叫:
$(function () {
// 寫入csrf
$.getScript("/static/js/csrftoken.js");
// 喜歡
$("#like").click(function(){
var video_id = $("#like").attr("video-id");
$.ajax({
url: `/video/like/`,
data: {
video_id: video_id,
`csrf_token`: csrftoken
},
type: `POST`,
dataType: `json`,
success: function (data) {
var code = data.code
if(code == 0){
var likes = data.likes
var user_liked = data.user_liked
$(`#like-count`).text(likes)
if(user_liked == 0){
$(`#like`).removeClass("grey").addClass("red")
}else{
$(`#like`).removeClass("red").addClass("grey")
}
}else{
var msg = data.msg
alert(msg)
}
},
error: function(data){
alert("點贊失敗")
}
});
});
複製程式碼
上述程式碼中,關鍵程式碼是$.ajax()函式,我們傳入了引數:video_id
和csrftoken
。其中csrftoken可通過/static/js/csrftoken.js
生成。在success回撥中,通過判斷user_liked
的值來確定自己是否喜歡過,然後改變模板中相應的css。
推薦功能
每個網站都有自己的推薦功能,且都有自己的推薦邏輯。我們視點的推薦邏輯是根據訪問次數最高的n個視訊來降序排序,然後推薦給使用者的。
實現起來非常容易,我們知道詳情頁實現用的是VideoDetailView,我們可以在get_context_data()中把推薦內容傳遞給前端模板。只需要我們改寫VideoDetailView的get_context_date()函式。
def get_context_data(self, **kwargs):
context = super(VideoDetailView, self).get_context_data(**kwargs)
form = CommentForm()
recommend_list = Video.objects.get_recommend_list()
context[`form`] = form
context[`recommend_list`] = recommend_list
return context
複製程式碼
改寫後,我們新增了一行
recommend_list = Video.objects.get_recommend_list()
複製程式碼
我們把獲取推薦列表的函式get_recommend_list()
封裝到了Video模型裡面。在Video/models.py裡面
我們追加程式碼:
class VideoQuerySet(models.query.QuerySet):
def get_recommend_list(self):
return self.filter(status=0).order_by(`-view_count`)[:4]
複製程式碼
關鍵是self.filter(status=0).order_by(`-view_count`)[:4]
,通過order_by把view_count降序排序,並選取前4條資料。
注意此處我們用了VideoQuerySet查詢器,需要我們在Video下面新增一行依賴。表示用VideoQuerySet作為Video的查詢管理器。
objects = VideoQuerySet.as_manager()
複製程式碼
當模板拿到資料後,即可渲染顯示。這裡我們將推薦側欄的程式碼封裝到templates/video/recommend.html裡面。
# templates/video/recommend.html
{% load thumbnail %}
<span class="video-side-title">推薦列表</span>
<div class="ui unstackable divided items">
{% for item in recommend_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-title" href="{% url `video:detail` item.pk %}">{{ item.title }}</a>
<div class="meta">
<span class="description">{{ item.view_count }}次觀看</span>
</div>
</div>
</div>
{% empty %}
<h3>暫無推薦</h3>
{% endfor %}
</div>
複製程式碼
並在detail.html中將它包含進來
{% include "video/recommend.html" %}
複製程式碼
評論功能
評論區位於詳情頁下側,顯示效果如下。共分為兩個部分:評論form和評論列表。
評論功能是一個獨立的模組,該功能通用性較高,在其他很多網站中都有評論功能,為了避免以後開發其他網站時重複造輪子,我們建立一個新的應用,命名為comment
python3 manage.py startapp comment
複製程式碼
接下來,我們建立comment模型
# 位於comment/models.py
class Comment(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
nickname = models.CharField(max_length=30,blank=True, null=True)
avatar = models.CharField(max_length=100,blank=True, null=True)
video = models.ForeignKey(Video, on_delete=models.CASCADE)
content = models.CharField(max_length=100)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "v_comment"
複製程式碼
- user 使用者。資料型別是ForeignKey,外來鍵是settings.AUTH_USER_MODEL,並設定為級聯刪除on_delete=models.CASCADE
- nickname 使用者暱稱。資料型別是CharField。
- avatar 頭像。資料型別是CharField。
- video 對應的視訊。資料型別是ForeignKey,對應Video模型,級聯刪除 on_delete=models.CASCADE
- content 評論內容。 資料型別是CharField。
- timestamp 評論時間。 資料型別是DateTimeField。
有了模型之後,我們就可以專心寫業務程式碼了,首先在comment下建立路由檔案urls.py
。並寫入程式碼:
from django.urls import path
from . import views
app_name = `comment`
urlpatterns = [
path(`submit_comment/<int:pk>`,views.submit_comment, name=`submit_comment`),
path(`get_comments/`, views.get_comments, name=`get_comments`),
]
複製程式碼
我們配置了兩條路由資訊:評論提交 和 獲取評論。
提交評論,需要一個form,我們把form放到video/forms.py
from django import forms
from comment.models import Comment
class CommentForm(forms.ModelForm):
content = forms.CharField(error_messages={`required`: `不能為空`,},
widget=forms.Textarea(attrs = {`placeholder`: `請輸入評論內容` })
)
class Meta:
model = Comment
fields = [`content`]
複製程式碼
然後在video/views.py的VideoDetailView下新增form的相關程式碼。
class VideoDetailView(generic.DetailView):
model = Video
template_name = `video/detail.html`
def get_object(self, queryset=None):
obj = super().get_object()
obj.increase_view_count()
return obj
def get_context_data(self, **kwargs):
context = super(VideoDetailView, self).get_context_data(**kwargs)
form = CommentForm()
context[`form`] = form
return context
複製程式碼
在get_context_data()函式裡面,我們把form傳遞給模板。
同樣的,提交評論也是非同步的,我們用ajax實現,我們開啟static/js/detail.js,寫入
// 提交評論
var frm = $(`#comment_form`)
frm.submit(function () {
$.ajax({
type: frm.attr(`method`),
url: frm.attr(`action`),
dataType:`json`,
data: frm.serialize(),
success: function (data) {
var code = data.code
var msg = data.msg
if(code == 0){
$(`#id_content`).val("")
$(`.comment-list`).prepend(data.html);
$(`#comment-result`).text("評論成功")
$(`.info`).show().delay(2000).fadeOut(800)
}else{
$(`#comment-result`).text(msg)
$(`.info`).show().delay(2000).fadeOut(800);
}
},
error: function(data) {
}
});
return false;
});
複製程式碼
評論通過ajax提交後,我們在submit_comment()中就能接收到這個請求。處理如下
def submit_comment(request,pk):
video = get_object_or_404(Video, pk = pk)
form = CommentForm(data=request.POST)
if form.is_valid():
new_comment = form.save(commit=False)
new_comment.user = request.user
new_comment.nickname = request.user.nickname
new_comment.avatar = request.user.avatar
new_comment.video = video
new_comment.save()
data = dict()
data[`nickname`] = request.user.nickname
data[`avatar`] = request.user.avatar
data[`timestamp`] = datetime.fromtimestamp(datetime.now().timestamp())
data[`content`] = new_comment.content
comments = list()
comments.append(data)
html = render_to_string(
"comment/comment_single.html", {"comments": comments})
return JsonResponse({"code":0,"html": html})
return JsonResponse({"code":1,`msg`:`評論失敗!`})
複製程式碼
在接收函式中,通過form自帶的驗證函式來儲存記錄,然後將這條記錄返回到前端模板。
下面我們開始評論列表的開發。
評論列表部分,我們使用了的是上拉動態載入的方案,即當頁面拉到最下側時,js載入程式碼會自動的獲取下一頁的資料並顯示出來。前端部分,我們使用了一種基於js的開源載入外掛。基於這個外掛,可以很容易實現網頁的上拉動態載入效果。它使用超級簡單,僅需要呼叫$(`.comments`).dropload({})即可。我們把呼叫的程式碼封裝在static/js/load_comments.js裡面。
完整的呼叫程式碼如下:
$(function(){
// 頁數
var page = 0;
// 每頁展示15個
var page_size = 15;
// dropload
$(`.comments`).dropload({
scrollArea : window,
loadDownFn : function(me){
page++;
$.ajax({
type: `GET`,
url: comments_url,
data:{
video_id: video_id,
page: page,
page_size: page_size
},
dataType: `json`,
success: function(data){
var code = data.code
var count = data.comment_count
if(code == 0){
$(`#id_comment_label`).text(count + "條評論");
$(`.comment-list`).append(data.html);
me.resetload();
}else{
me.lock();
me.noData();
me.resetload();
}
},
error: function(xhr, type){
me.resetload();
}
});
}
});
});
複製程式碼
不用過多的解釋,這段程式碼已經非常非常清晰了,本質還是ajax的介面請求呼叫,呼叫後返回結果更新前端網頁內容。
我們看到ajax呼叫的介面是get_comments,我們繼續來實現它,它位於comment/views.py中。程式碼如下所示,這段程式碼也很簡單,沒有什麼複雜的技術。當獲取到page和page_size後,使用paginator物件來實現分頁。最後通過render_to_string將html傳遞給模板。
def get_comments(request):
if not request.is_ajax():
return HttpResponseBadRequest()
page = request.GET.get(`page`)
page_size = request.GET.get(`page_size`)
video_id = request.GET.get(`video_id`)
video = get_object_or_404(Video, pk=video_id)
comments = video.comment_set.order_by(`-timestamp`).all()
comment_count = len(comments)
paginator = Paginator(comments, page_size)
try:
rows = paginator.page(page)
except PageNotAnInteger:
rows = paginator.page(1)
except EmptyPage:
rows = []
if len(rows) > 0:
code = 0
html = render_to_string(
"comment/comment_single.html", {"comments": rows})
else:
code = 1
html = ""
return JsonResponse({
"code":code,
"html": html,
"comment_count": comment_count
})
複製程式碼