基于django的视频点播网站开发-step9-后台视频管理功能

从本讲开始,我们开始视频管理功能的开发,视频管理包括视频上传、视频列表、视频编辑、视频删除。另外还有视频分类的功能,会一同讲解。这一讲非常重要,因为你将学习到一些之前没有学过的技术,比如大文件上传技术。

个人博客:mypython.me

视频上传

我们先来实现视频的上传,视频的上传采用的是分块上传的策略,并用了分块上传类库:django_chunked_upload,使用该类库,再配合前端上传js库(jquery.fileupload.js),即可完美的实现文件的分块上传功能。

照例先编写添加视频的路由

添加视频,当然需要上传视频的页面,我们的页面是video_add路由来显示,通过urls .py中指定

代码语言:txt
复制
path('video\_add/', views.AddVideoView.as\_view(), name='video\_add'),

AddViewView仅仅用来显示上传页面,它的代码很简单

代码语言:txt
复制
class AddVideoView(SuperUserRequiredMixin, TemplateView):
template\_name = &#39;myadmin/video\_add.html&#39;</code></pre></div></div><p>只是继承了TemplateView来显示myadmin/video_add.html</p><p>myadmin/video_add.html中实现了上传视频的全过程,视频的上传采用的是分块上传的策略,前端使用的是js上传库(jquery.fileupload.js),后端使用的是django_chunked_upload,上传的逻辑是这样的:前端先选择一个文件,通过jquery.fileupload.js中的$.fileupload()方法来上传文件,后端接收到后分批返回已上传块的进度,前端根据进度来更新界面。由于上传前需要做一些校验的操作,代码较复杂,所以我们把上传的代码封装到了一个js中:static/js/myadmin/video_upload.js,主要的代码如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">$(&#34;#chunked\_upload&#34;).fileupload({

url: api_chunked_uplad,

dataType: "json",

maxChunkSize: 100000, // Chunks of 100 kB

formData: form_data,

add: function(e, data) { // Called before starting upload

var fileSize = data.originalFiles[0][&#39;size&#39;];

var type = data.originalFiles[0][&#39;type&#39;]; 



if(fileSize &gt; 100000000){

    alert(&#39;文件太大了,请上传100M以内的文件&#39;);

    return;

}



if(!type.startsWith(&#34;video/&#34;)){

    alert(&#39;视频格式不正确&#39;);

    return;

}



form\_data.splice(1);

calculate\_md5(data.files[0], 100000);  // Again, chunks of 100 kB

data.submit();



$(&#39;#progress\_label&#39;).on(&#39;click&#39;, false);

$(&#39;#progress\_layout&#39;).show()

},

chunkdone: function (e, data) { // Called after uploading each chunk

if (form\_data.length &lt; 2) {

  form\_data.push(

    {&#34;name&#34;: &#34;upload\_id&#34;, &#34;value&#34;: data.result.upload\_id}

  );

}

var progress = parseInt(data.loaded / data.total \* 100.0, 10);

console.log(progress);

if(progress &gt; lastprogress){

    lastprogress = progress

    $(&#39;#upload\_progress&#39;).progress({

        percent: progress

    });

}

},

done: function (e, data) { // Called when the file has completely uploaded

$.ajax({

  type: &#34;POST&#34;,

  url: api\_chunked\_upload\_complete,

  data: {

    csrfmiddlewaretoken: csrf,

    upload\_id: data.result.upload\_id,

    md5: md5

  },

  dataType: &#34;json&#34;,

  success: function(data) {

    console.log(data)

    $(&#39;#upload\_label&#39;).text(&#39;上传成功&#39;);

    $(&#39;#upload\_progress&#39;).progress({

        percent: 100

    });

    $(&#39;#next\_layout&#39;).show();

    $(&#39;#next&#39;).click(function(){

        window.location = &#39;/myadmin/video\_publish/&#39; + data.video\_id

    });

  }

});

},

});

在$.fileupload()方法中,有一个回调方法chunkdone(),该方法是用来更新进度的,告诉前端已经上传了多少字节。另外还有一个回调方法done(),该方法表示上传完毕,前端可在里面做一些额外的事情。

上传完毕后,调用了一个接口api_chunked_upload_complete,来给后端发送一个回执:我已上传完毕。

api_chunked_upload和api_chunked_upload_complete的路由是

代码语言:txt
复制
path('chunked_upload/',  views.MyChunkedUploadView.as_view(), name='api_chunked_upload'),

path('chunked_upload_complete/', views.MyChunkedUploadCompleteView.as_view(),name='api_chunked_upload_complete'),

在MyChunkedUploadCompleteView中,我们在利用Video模型创建了这条视频

代码语言:txt
复制
class MyChunkedUploadCompleteView(ChunkedUploadCompleteView):

model = MyChunkedUpload



def get\_response\_data(self, chunked\_upload, request):

    video = Video.objects.create(file=chunked\_upload.file)

    return {&#39;code&#39;: 0, &#39;video\_id&#39;: video.id, &#39;msg&#39;: &#39;success&#39;}</code></pre></div></div><p>**上传完毕效果如下**</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722996258203949780.png" /></div></div></div></figure><p>然后用户点击下一步,进入video_publish页面,开始发布前的资料填写</p><p>video_publish的路由是</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">path(&#39;video\_publish/&lt;int:pk&gt;/&#39;, views.VideoPublishView.as\_view(), name=&#39;video\_publish&#39;),</code></pre></div></div><p>video_publish的视图类是VideoPublishView,它的代码如下</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">class VideoPublishView(SuperUserRequiredMixin, generic.UpdateView):

model = Video

form\_class = VideoPublishForm

template\_name = &#39;myadmin/video\_publish.html&#39;



def get\_context\_data(self, \*\*kwargs):

    context = super(VideoPublishView, self).get\_context\_data(\*\*kwargs)

    clf\_list = Classification.objects.all().values()

    clf\_data = {&#39;clf\_list&#39;:clf\_list}

    context.update(clf\_data)

    return context



def get\_success\_url(self):

    return reverse(&#39;myadmin:video\_publish\_success&#39;)</code></pre></div></div><p>对应的页面是myadmin/video_publish.html</p><p>就是下面这个页面</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722996258630292661.png" /></div></div></div></figure><p>要填写的视频资料有视频标题、描述、分类、封面,</p><p>其中分类是通过get_context_data()带过来的,</p><p>填写后,点击**发布**,django将通过UpdateView自动为你更新视频信息。并通过get_success_url跳转到成功页面myadmin:video_publish_success,它的路由是</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">path(&#39;video\_publish\_success/&#39;, views.VideoPublishSuccessView.as\_view(), name=&#39;video\_publish\_success&#39;),</code></pre></div></div><p>对应VideoPublishSuccessView是</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">class VideoPublishSuccessView(generic.TemplateView):

template\_name = &#39;myadmin/video\_publish\_success.html&#39;</code></pre></div></div><p>如下</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722996259025627960.png" /></div></div></div></figure><p>我们点击**视频列表**即可查看视频</p><h4 id="8gv4m" name="%E8%A7%86%E9%A2%91%E5%88%97%E8%A1%A8">视频列表</h4><p>视频列表的路由是</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">path(&#39;video\_list/&#39;, views.VideoListView.as\_view(), name=&#39;video\_list&#39;),</code></pre></div></div><p>对应的视图类是VideoListView</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">class VideoListView(AdminUserRequiredMixin, generic.ListView):

model = Video

template\_name = &#39;myadmin/video\_list.html&#39;

context\_object\_name = &#39;video\_list&#39;

paginate\_by = 10

q = &#39;&#39;



def get\_context\_data(self, \*, object\_list=None, \*\*kwargs):

    context = super(VideoListView, self).get\_context\_data(\*\*kwargs)

    paginator = context.get(&#39;paginator&#39;)

    page = context.get(&#39;page\_obj&#39;)

    page\_list = get\_page\_list(paginator, page)

    context[&#39;page\_list&#39;] = page\_list

    context[&#39;q&#39;] = self.q

    return context



def get\_queryset(self):

    self.q = self.request.GET.get(&#34;q&#34;, &#34;&#34;)

    return Video.objects.get\_search\_list(self.q)</code></pre></div></div><p>这里继承了ListView来显示视频列表,并通过get_queryset实现了搜索功能,通过get_context_data()实现了分页功能。</p><p>最后展示效果如下</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722996259592325155.png" /></div></div></div></figure><p>你可能会发现,页面中还有编辑和删除的功能。编辑呢,是对单个视频对资料进行更新,删除即删除本条视频和视频文件。</p><h4 id="7jhf7" name="%E8%A7%86%E9%A2%91%E7%BC%96%E8%BE%91">视频编辑</h4><p>我们先实现编辑功能,路由是</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">path(&#39;video\_edit/&lt;int:pk&gt;/&#39;, views.VideoEditView.as\_view(), name=&#39;video\_edit&#39;),</code></pre></div></div><p>对应对视图类是VideoEditView,这个视图类是需要传递主键的。</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">class VideoEditView(SuperUserRequiredMixin, generic.UpdateView):

model = Video

form\_class = VideoEditForm

template\_name = &#39;myadmin/video\_edit.html&#39;



def get\_context\_data(self, \*\*kwargs):

    context = super(VideoEditView, self).get\_context\_data(\*\*kwargs)

    clf\_list = Classification.objects.all().values()

    clf\_data = {&#39;clf\_list&#39;:clf\_list}

    context.update(clf\_data)

    return context



def get\_success\_url(self):

    messages.success(self.request, &#34;保存成功&#34;)

    return reverse(&#39;myadmin:video\_edit&#39;, kwargs={&#39;pk&#39;: self.kwargs[&#39;pk&#39;]})</code></pre></div></div><p>其实编辑页面和发布页面很相似,都是继承UpdateView视图类,并在get_context_data()里面传递分类信息。最终成功后通过<code>messages.success(self.request, &#34;保存成功&#34;)</code>消息告之前端。</p><h4 id="6l53j" name="%E8%A7%86%E9%A2%91%E5%88%A0%E9%99%A4">视频删除</h4><p>删除功能就更加简单了。路由是</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">path(&#39;video\_delete/&#39;, views.video\_delete, name=&#39;video\_delete&#39;),</code></pre></div></div><p>这里通过video_delete函数来实现,前端通过ajax(ajax代码位于static/js/myadmin/video_list.js)调用这个函数。</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">@ajax\_required

@require_http_methods(["POST"])

def video_delete(request):

video\_id = request.POST[&#39;video\_id&#39;]

instance = Video.objects.get(id=video\_id)

instance.delete()

return JsonResponse({&#34;code&#34;: 0, &#34;msg&#34;: &#34;success&#34;})</code></pre></div></div><p>获取该视频,然后instance.delete()删除之。</p><h4 id="403rd" name="%E8%A7%86%E9%A2%91%E5%88%86%E7%B1%BB">视频分类</h4><p>分类管理功能包括分类的增删改查。</p><p>增删改查的路由是</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">path(&#39;classification\_add/&#39;, views.ClassificationAddView.as\_view(), name=&#39;classification\_add&#39;),

path('classification_list/', views.ClassificationListView.as_view(), name='classification_list'),

path('classification_edit/<int:pk>/', views.ClassificationEditView.as_view(), name='classification_edit'),

path('classification_delete/', views.classification_delete, name='classification_delete'),

先来看分类添加的功能

分类添加是通过ClassificationAddView视图类来实现的,代码如下

代码语言:txt
复制
class ClassificationAddView(SuperUserRequiredMixin, generic.View):

def get(self, request):

    form = ClassificationAddForm()

    return render(self.request, &#39;myadmin/classification\_add.html&#39;, {&#39;form&#39;: form})



def post(self, request):

    form = ClassificationAddForm(data=request.POST)

    if form.is\_valid():

        form.save(commit=True)

        return render(self.request, &#39;myadmin/classification\_add\_success.html&#39;)

    return render(self.request, &#39;myadmin/classification\_add.html&#39;, {&#39;form&#39;: form})</code></pre></div></div><p>此处是通过get和post一同来实现的,get()负责展示界面,post()负责逻辑判断。在post()中,直接调用form.save来保存记录,然后跳转到成功页myadmin/classification_add_success.html。</p><p>**分类添加**</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722996260128273932.png" /></div></div></div></figure><p>**添加成功**</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722996260490308369.png" /></div></div></div></figure><p>然后点击视频列表,即可查看列表,视频列表的视图类是ClassificationListView,即</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">class ClassificationListView(AdminUserRequiredMixin, generic.ListView):

model = Classification

template\_name = &#39;myadmin/classification\_list.html&#39;

context\_object\_name = &#39;classification\_list&#39;

paginate\_by = 10

q = &#39;&#39;



def get\_context\_data(self, \*, object\_list=None, \*\*kwargs):

    context = super(ClassificationListView, self).get\_context\_data(\*\*kwargs)

    paginator = context.get(&#39;paginator&#39;)

    page = context.get(&#39;page\_obj&#39;)

    page\_list = get\_page\_list(paginator, page)

    context[&#39;page\_list&#39;] = page\_list

    context[&#39;q&#39;] = self.q

    return context



def get\_queryset(self):

    self.q = self.request.GET.get(&#34;q&#34;, &#34;&#34;)

    return Classification.objects.filter(title\_\_contains=self.q)</code></pre></div></div><p>继承ListView来显示列表,通过get_queryset()来实现搜索功能,通过get_context_data()来实现分页功能,通过template_name来指定模板</p><p>效果如下</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722996260973920618.png" /></div></div></div></figure><p>接着来实现编辑和删除功能。</p><p>编辑对应的视图类是ClassificationEditView,它的实现超级简单,继承UpdateView即可。</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">class ClassificationEditView(SuperUserRequiredMixin, generic.UpdateView):

model = Classification

form\_class = ClassificationEditForm

template\_name = &#39;myadmin/classification\_edit.html&#39;



def get\_success\_url(self):

    messages.success(self.request, &#34;保存成功&#34;)

    return reverse(&#39;myadmin:classification\_edit&#39;, kwargs={&#39;pk&#39;: self.kwargs[&#39;pk&#39;]})</code></pre></div></div><p>编辑页面和添加页面很相似,这里就不贴图了。</p><p>最后是删除功能,是通过ajax来实现的,ajax代码位于static/js/myadmin/classification_list.js,在ajax中,通过调用删除接口classification_delete来实现删除功能,</p><p>接口classification_delete的代码:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">@ajax\_required

@require_http_methods(["POST"])

def classification_delete(request):

classification\_id = request.POST[&#39;classification\_id&#39;]

instance = Classification.objects.get(id=classification\_id)

instance.delete()

return JsonResponse({&#34;code&#34;: 0, &#34;msg&#34;: &#34;success&#34;})</code></pre></div></div><p>功能略多,同学们可根据自身情况,根据后台demo地址的演示来一步步学习。</p>