基於django的視訊點播網站開發-step5-詳情頁功能
在本講中,我們開始詳情頁功能的開發,詳情頁就是對單個視訊進行播放並展示視訊的相關資訊,比如視訊標題、描述、評論資訊、相關推薦等。我們將會學習到通用檢視類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 }) 複製程式碼