在本講中,咱們開始詳情頁功能的開發,詳情頁就是對單個視頻進行播放並展現視頻的相關信息,好比視頻標題、描述、評論信息、相關推薦等。咱們將會學習到通用視圖類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
更多關於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_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"
複製代碼
有了模型以後,咱們就能夠專心寫業務代碼了,首先在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
})
複製代碼