基於django的視頻點播網站開發-step5-詳情頁功能

在本講中,咱們開始詳情頁功能的開發,詳情頁就是對單個視頻進行播放並展現視頻的相關信息,好比視頻標題、描述、評論信息、相關推薦等。咱們將會學習到通用視圖類DetailView的使用、評論動態加載、以及如何經過ajax實現喜歡和收藏功能,並經過一段段很酷的代碼來講明這些功能。css

效果展現html

總體功能

你們可先經過 網站演示地址 瀏覽一下網站效果。點擊某個視頻便可瀏覽詳情頁。詳情頁實現了是對單個視頻進行展現,用戶可看到視頻的一些元信息,包括標題、描述、觀看次數、喜歡數、收藏數等等。另外,網站還實現了評論功能,經過上拉網頁便可分頁加載評論列表,用戶還能添加評論。網頁側欄是推薦視頻列表,這裏使用的推薦邏輯比較簡單,就是推薦觀看次數最多的視頻。前端

咱們把詳情頁分爲4個小的業務模塊來開發,分別是:視頻詳情顯示、喜歡和收藏功能、評論功能、推薦功能。下面咱們分別對這四個功能模塊進行開發講解。python

視頻詳情顯示

由於在上一講中,咱們已經創建了video模型,因此沒必要再新建模型,咱們就在video模型的基礎上進行擴展。上一講,咱們建立的字段有title、desc、classification、file、cover、status、create_time。這些字段目前是不夠用的,咱們再加幾個字段,須要加觀察次數喜歡的用戶收藏的用戶。video模型擴展後以下git

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個字段github

  • view_count 觀看次數。數據類型是IntegerField,默認是0
  • liked 喜歡的用戶。數據類型是ManyToManyField,這是一種多對多的關係,表示一個視頻能夠被多個用戶喜歡,一個用戶也能夠喜歡多個視頻。記得設置用戶表爲settings.AUTH_USER_MODEL
  • collected 收藏的用戶。數據類型是ManyToManyField,這是一種多對多的關係,表示一個視頻能夠被多個用戶收藏,一個用戶也能夠收藏多個視頻。設置用戶表爲settings.AUTH_USER_MODEL

更多關於ManyToManyField的使用介紹,能夠查詢django官網的介紹。ajax

下面就是詳情展現階段,咱們先配置好詳情頁的路由信息,在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

怎麼顯示詳情呢,聰明的django爲咱們提供了DetailView。urls.py中設置的視圖類是VideoDetailView,咱們讓VideoDetailView繼承DetailView便可。json

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_idcsrftoken。其中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
    })
複製代碼
相關文章
相關標籤/搜索