Django blog项目《二十二》:后台admin《视频管理功能实现》

课程视频管理功能,实现课程视频的增、删、改、查和图片上传到服务器功能功能的实现

一、分析

1. 业务流程

1. 课程视频展示
  1. 业务流程

    • 根据提供信息进行课程视频查找(参照文章管理)
  • 从后端查取数据返回到前端,进行页面填充
  1. 请求方式、地址、参数

    • 请求方式:GET

    • 请求地址:/admin/course/

    • 权限:view_course

    • 请求参数:

      参数名 类型 是否必传 备注
      start_time datatime 不是必传 form表单参数
      end_time datatime 不是必传 form表单参数
      title char 不是必传 form表单参数
      teacher char 不是必传 form表单参数
      cate_id int 不是必传 form表单参数
2. 课程视频删除功能
  1. 业务流程

    • 从前端传递一个带有视频id的delete请求
    • 判断数据库中是否有该数据
    • 逻辑删除该课程视频,并返回数据到前端
  2. 请求方式、地址、参数

    • 请求方式:DELETE

    • 请求地址:/admin/course/edit/<int:course_id>/

    • 权限:delete_course

    • 请求参数:

      参数名 类型 是否必传 备注
      course_id int 必传 路径参数
3. 课程视频修改功能
  1. 业务流程

    • 判断课程视频名、课程视频简介、课程视频封面图、课程视频分类、课程视频url、课程大纲是否为空
    • 判断数据库中是否存在id=doc_id的数据
    • 进行表单校验
    • 成功后将数据保存进数据库
      • 将数据返回到前端
  2. 请求方式、地址、参数

    • 请求方式:PUT

    • 请求地址:/admin/course/edit/<int:course_id>/

    • 权限:change_course

    • 请求参数:

      参数名 类型 是否必传 备注
      course_id int 必传 路径参数
      name char 必传 请求体里
      brief char 必传 请求体里
      category int 必传 路径参数
      teacher int 必传 路径参数
      cover_url url 必传 请求体里
      video_url url 必传 请求体里
      outline text 必传 请求体里
  3. 前端功能实

    • 判断课程视频名、课程视频简介、课程视频封面图、课程视频分类、课程视频url、课程大纲是否为空

    • 发起ajax PUT请求

      • 跳转到课程视频管理页面
  4. 后端逻辑处理

    • 接收前端传来的数据并转化为字典
    • 通过form表单校验上面数据
    • 校验成功后将数据保存进数据库
  • 返回数据到前端
4. 课程视频发布功能
  1. 业务流程

    • 判断课程视频名、课程视频简介、课程视频封面图、课程视频分类、课程视频url、课程大纲是否为空
    • 进行表单校验
    • 成功后将数据保存进数据库
    • 将数据返回到前端
  2. 请求方式、地址、参数

    • 请求方式:POST

    • 请求地址:/admin/course/pub/

    • 权限:add_course

    • 请求参数:

      参数名 类型 是否必传 备注
      name char 必传 请求体里
      brief char 必传 请求体里
      category int 必传 路径参数
      teacher int 必传 路径参数
      cover_url url 必传 请求体里
      video_url url 必传 请求体里
      outline text 必传 请求体里
  3. 前端功能实

    • 判断课程视频名、课程视频简介、课程视频封面图、课程视频分类、课程视频url、课程大纲是否为空

    • 发起ajax POST请求

      • 跳转到课程视频管理页面
  4. 后端逻辑处理

    • 接收前端传来的数据并转化为字典
    • 通过form表单校验上面数据
    • 校验成功后将数据保存进数据库
    • 返回数据到前端
5. 上传视频到阿里云
  1. 业务流程

    • 构建一个百度云视频上传接口

    • 判断课程名是否为空

    • 判断课程简介是否为空

    • 调用百度VOD接口上传视频

二、课程视频展示功能实现

1. urls配置
from django.urls import path

from admin import views

app_name = "admin"

urlpatterns = [

    path("course/", views.CourseIndexView.as_view(), name="course"),
    path("course/edit/<int:course_id>/", views.CourseEditView.as_view(), name="course_edit"),
    path("course/pub/", views.CoursePubView.as_view(), name="course_pub"),

]

2. views视图逻辑处理
import json
from datetime import datetime
from urllib.parse import urlencode
from collections import OrderedDict  # 转化为字典

from django.views import View
from django.shortcuts import render
from django.core.paginator import Paginator, EmptyPage
from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin

from news import models
from admin import contains
from settings import FDFS_URL
from course.models import Course, CourseCategory, Teacher
from users.models import Users
from utils.fast.fdfs import client
from admin.forms import ArticleEditForm, DocEditForm, CourseEditForm
from utils.page.per_page import get_page_data
from utils.res_code.res_code import Code, error_map
from utils.res_code.json_function import to_json_data


# 课程视频展示
class CourseIndexView(PermissionRequiredMixin, View):
    """
    course video show page
    route:/admin/course/
    permissions:view_course
    """
    permission_required = ("course.view_course",)
    raise_exception = True

    def get(self, request):
        # 1. 从数据库中获取到文档的数据
        course = Course.objects.select_related("teacher", "category").only("id", "name", "teacher__name",
                                                                           "category__name", "update_time").filter(
            is_delete=False).order_by("-update_time", "-id")

        category = CourseCategory.objects.only("name").filter(is_delete=False)

        # 2. 获取前端传来的数据:判断1个获取一个:start_time、end_time、doc_title、doc_author
        # 判断起始时间start_time
        try:
            start_time = request.GET.get("start_time", "").strip()
            start_time = datetime.strptime(start_time, "Y%m%d%")
        except Exception as e:
            # logger.info("起始时间格式错误:{}".format(e))
            start_time = ""

        # 判断结束时间end_time
        try:
            end_time = request.GET.get("end_time", "").strip()
            end_time = datetime.strptime(end_time, "Y%m%d%")
        except Exception as e:
            # logger.info("起始时间格式错误:{}".format(e))
            end_time = ""

        # 判断起始时间和结束时间输入的三种情况:1.起始时间有、结束无;2.起始时间无、结束时间有;3.起始时间大于结束时间
        # 起始时间有、结束无
        if start_time and not end_time:
            course = course.filter(update_time__lte=start_time)

        # 起始时间无、结束时间有
        if end_time and not start_time:
            course = course.filter(update_time__gte=end_time)

        # 起始时间大于结束时间
        if start_time and end_time:
            course = course.filter(update_time__range=(start_time, end_time))
            if not course:
                return to_json_data(errno=Code.PARAMERR, errmsg=error_map[Code.PARAMERR])

        # 3. 对文章标题进行判断/模糊查询
        name = request.GET.get("name", "").strip()
        if name:
            course = course.filter(is_delete=False, name__icontains=name)

        # 4. 对文章作者进行判断模糊查询
        teacher = request.GET.get("teacher_name", "").strip()
        if teacher:
            course = course.filter(is_delete=False, teacher__name__icontains=teacher)

        cate_id = int(request.GET.get("category", "0"))
        if cate_id:
            course = course.filter(is_delete=False, category=cate_id)

        # 5. 进行分页处理
        try:
            page_num = int(request.GET.get("page", 1))
        except Exception as e:

            logger.info("页码格式错误:{}".format(e))
            page_num = 1

        page_obj = Paginator(course, contains.PER_PAGE_NUMBER)

        try:
            course_info = page_obj.page(page_num)
        except EmptyPage:  # 页码为空
            course_info = page_obj.page(page_obj.num_pages)

        pages_data = get_page_data(page_obj, course_info)

        # 将时间转化为字符串
        start_time = start_time.strftime("%Y%m%d") if start_time else ""
        end_time = end_time.strftime("%Y%m%d") if end_time else ""
        # 6. 将数据传递给前端
        data = {
            'course_info': course_info,
            "categories": category,
            'paginator': page_obj,
            'start_time': start_time,
            'end_time': end_time,
            'name': name,
            'teacher': teacher,
            "cate_id": cate_id,
            'other_param': urlencode({
                'start_time': start_time,
                'end_time': end_time,
                'name': name,
                'teacher': teacher,
                "cate_id": cate_id,
            })
        }
        data.update(pages_data)

        return render(request, 'admin/course/course_index.html', context=data)
3. js前端逻辑处理
$(function () {
    let $startTime = $("input[name=start_time]");
    let $endTime = $("input[name=end_time]");
    const config = {

        autoclose: true,// 自动关闭

        format: 'yyyy/mm/dd',// 日期格式

        language: 'zh-CN',// 选择语言为中文

        showButtonPanel: true,// 优化样式

        todayHighlight: true, // 高亮今天

        calendarWeeks: true,// 是否在周行的左侧显示周数

        clearBtn: true,// 清除

        startDate: new Date(1900, 10, 1),// 0 ~11  网站上线的时候

        endDate: new Date(), // 今天
    };
    $startTime.datepicker(config);
    $endTime.datepicker(config);


    //文档删除
    let $delBtn = $(".btn-del");
    $delBtn.click(function () {
        let _this = this;
        let sCourseId = $(this).data("course-id");
        fAlert.alertConfirm({
            title: "确定删除该课程吗?",
            type: "error",
            confirmButtonText: "确认删除",
            cancelButtonText: "取消",
            confirmCallback: function confirmCallback() {
                $.ajax({
                    url: "/admin/course/edit/" + sCourseId + '/',
                    type: "DELETE",
                    dataType: "json",
                })
                    .done(function (res) {
                        if (res.errno === "200") {
                            message.showSuccess("成功删除课程!");
                            $(_this).parents("tr").remove()
                        } else {
                            swal.showInputError("删除失败:" + res.errmsg);
                        }
                    })
                    .fail(function () {
                        alert("服务器超时,请重试!")
                    })


            }
        })


    });


    // 获取cookie
    function getCookie(name) {
        let cookieValue = null;
        if (document.cookie && document.cookie !== '') {
            let cookies = document.cookie.split(';');
            for (let i = 0; i < cookies.length; i++) {
                let cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }

    function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }

    // 添加token到request中
    $.ajaxSetup({
        beforeSend: function (xhr, settings) {
            if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
            }
        }
    });
});
4. html页面填充
{% extends 'admin/base/base.html' %}
{% load staticfiles %}

{% block title %}
    课程管理页
{% endblock %}

{% block css %}
    <link rel="stylesheet" href="{% static 'css/admin/news/bootstrap-datepicker.min.css' %}">
{% endblock %}

{% block content_header %}
    课程管理
{% endblock %}

{% block content_header_brief %}
    课程的增删改查
{% endblock %}


{% block content %}
    <style>
        .ml20 {
            margin-left: 20px;
        }

        .mt20 {
            margin-top: 20px;
        }
    </style>
    <div class="content">
        <div class="container-fluid">
            <div class="box">
                <div class="box-header" style="margin: 0;">
                    <form action="" class="form-inline">
                        <div class="form-group mt20">
                            <label for="select-time">时间:</label>
                            {% if start_time %}
                                <input type="text" class="form-control" placeholder="请选择起始时间" readonly
                                       id="select-time" name="start_time" value="{{ start_time }}">
                            {% else %}
                                <input type="text" class="form-control" placeholder="请选择起始时间" readonly
                                       id="select-time" name="start_time">
                            {% endif %}
                            -
                            {% if end_time %}
                                <input type="text" class="form-control" placeholder="请选择结束时间" readonly
                                       name="end_time" value="{{ end_time }}">
                            {% else %}
                                <input type="text" class="form-control" placeholder="请选择结束时间" readonly name="end_time">
                            {% endif %}
                        </div>
                        <div class="form-group ml20 mt20">
                            <label for="name">课程名:</label>
                            {% if name %}
                                <input type="text" class="form-control" placeholder="请输入课程名" id="name" name="name"
                                       value="{{ name }}">
                            {% else %}
                                <input type="text" class="form-control" placeholder="请输入课程名" id="name" name="name">
                            {% endif %}

                        </div>
                        <div class="form-group ml20 mt20">
                            <label for="teacher_name">教师姓名:</label>
                            {% if teacher_name %}
                                <input type="text" class="form-control" placeholder="请输入教师姓名" id="teacher_name"
                                       name="teacher_name"
                                       value="{{ teacher_name }}">
                            {% else %}
                                <input type="text" class="form-control" placeholder="请输入教师姓名" id="teacher_name"
                                       name="teacher_name">
                            {% endif %}
                        </div>
                        <div class="form-group ml20 mt20">
                            <label for="category">课程标签:</label>
                            <select class="form-control" id="category" name="category">
                                <option value="0">--请选择标签--</option>
                                {% for category in categories %}

                                    {% if category_id and category.id == category_id %}
                                        <option value="{{ category.id }}" selected>{{ category.name }}</option>
                                    {% else %}
                                        <option value="{{ category.id }}">{{ category.name }}</option>
                                    {% endif %}

                                {% endfor %}
                            </select>
                        </div>
                        <div class="form-group ml20 mt20">
                            <button class="btn btn-primary">查询</button>
                            <a href="{% url 'admin:article' %}" class="btn btn-info ml20">清除查询</a> {# js实现清空 #}
                        </div>
                    </form>
                </div>
                <br>
                <br>
                <div class="box-body">
                    <table class="table table-bordered table-hover">
                        <thead>
                        <tr>
                            <th>课程名</th>
                            <th>任课教师</th>
                            <th>课程类型</th>
                            <th>发布时间</th>
                            <th>操作</th>
                        </tr>
                        </thead>
                        <tbody>
                        {% for course in course_info %}
                            <tr>
                                <td><a href="{% url 'course:course_detail' course.id %}"
                                       target="_blank">{{ course.name }}</a>
                                </td>
                                <td>{{ course.teacher.name }}</td>
                                <td>{{ course.category.name }}</td>
                                <td>{{ course.update_time }}</td>
                                <td>
                                    <a href="{% url 'admin:course_edit' course.id %}" class="btn btn-xs
                                    btn-warning">编辑</a>
                                    <a href="javascript:void (0);" class="btn btn-xs btn-danger btn-del"
                                       data-course-id="{{ course.id }}">删除</a>
                                </td>
                            </tr>
                        {% endfor %}


                        </tbody>
                    </table>
                </div>
                <div class="box-footer">
                    <span class="fa-pull-left">第{{ current_page_num }}页/总共{{ total_page_num }}页</span>
                    <nav class="fa-pull-right" aria-label="Page navigation">
                        <!-- 分页 -->
                        <ul class="pagination">

                            <!-- 上一页 -->
                            {% if article_info.has_previous %}
                                <li><a class="page-link" href="?page={{ article_info.previous_page_number }}
                                &{{ other_param }}">上一页</a>
                                </li>
                            {% else %}
{#                                <li class="disabled"><a href="javascript:void(0);">上一页</a></li>#}
                                <li><a class="page-link"  href="#" >上一页</a></li>
                            {% endif %}

                            <!-- 左标记 -->
                            {% if left_has_more_page %}
                                <li><a class="page-link" href="?page=1&{{ other_param }}">1</a></li>
                                <li><a class="page-link" href="javascript:void(0);">...</a></li>
                            {% endif %}


                            <!-- 左边的页码 -->
                            {% for left_page in left_page_range %}
                                <li><a class="page-link" href="?page={{ left_page }}&{{ other_param }}"
                                >{{ left_page }}</a></li>
                            {% endfor %}

                            <!-- 当前页面 -->
                            {% if current_page_num %}
                                <li class="page-item active"><a class="page-link"
                                        href="?page={{ current_page_num }}&{{ other_param }}">{{ current_page_num }}</a>
                                </li>
                            {% endif %}


                            <!-- 右边的页面 -->
                            {% for right_page in right_page_range %}
                                <li><a class="page-link" href="?page={{ right_page }}&{{ other_param }}"
                                >{{ right_page }}</a></li>
                            {% endfor %}

                            <!-- 右标记 -->
                            {% if right_has_more_page %}
                                <li><a class="page-link" href="javascript:void(0);">...</a></li>
                                <li><a class="page-link" href="?page={{ total_page_num }}&{{ other_param }}"
                                >{{ total_page_num }}</a></li>
                            {% endif %}

                            <!-- 下一页 -->
                            {% if article_info.has_next %}
                                <li><a class="page-link" href="?page={{ article_info.next_page_number }}
                                &{{ other_param }}">下一页</a></li>
                            {% else %}
{#                                <li class="disabled"><a href="javascript:void(0);">下一页</a></li>#}
                                <li><a class="page-link"  href="#" >下一页</a></li>
                            {% endif %}

                        </ul>
                    </nav>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

{% block script %}
    <script src="{% static 'js/admin/news/bootstrap-datepicker.min.js' %}"></script>
    <script src="{% static 'js/admin/news/bootstrap-datepicker.zh-CN.min.js' %}"></script>
    <script src="{% static 'js/admin/course/course_edit.js' %}"></script>
{% endblock %}


三、课程视频删除和修改功能实现

1. views视图逻辑处理
# 课程删除和更新
class CourseEditView(PermissionRequiredMixin,View):
    """
    course video delete and change page
    route:/admin/course/edit/<int:course_id>
    permissions:view_course/change_course/delete_course
    """
    permission_required = ("course.view_course","course.change_course","course.delete_course")
    raise_exception = True

    def get(self, request, course_id):
        teacher = Teacher.objects.only("id", "name").filter(is_delete=False)
        category = CourseCategory.objects.only("id", "name").filter(is_delete=False)
        course = Course.objects.only("name", "brief", "outline", "teacher__name", "category__name", "cover_url",
                                     "video_url").filter(
            is_delete=False, id=course_id).first()
        data = {
            "teachers": teacher,
            "categories": category,
            "course": course,
        }
        return render(request, 'admin/course/course_edit.html', context=data)

    def delete(self, request, course_id):
        course = Course.objects.only("id").filter(is_delete=False, id=course_id).first()
        if course:
            course.is_delete = True
            course.save(update_fields=["is_delete"])
            return to_json_data(errmsg="删除课程成功!")
        else:
            return to_json_data(errno=Code.PARAMERR, errmsg="参数错误")

    def put(self, request, course_id):

        # 1. 从前端获取到数据
        try:
            json_data = request.body
            if not json_data:
                return to_json_data(errno=Code.PARAMERR, errmsg=error_map[Code.PARAMERR])
            dict_data = json.loads(json_data.decode("utf-8"))
        except Exception as e:
            logger.info("课程更新获取失败:{}".format(e))
            return to_json_data(errno=Code.UNKOWNERR, errmsg=error_map[Code.UNKOWNERR])

        # 2.从数据库中获取到该课程信息
        course = Course.objects.filter(is_delete=False, id=course_id).first()
        if not course:
            return to_json_data(errno=Code.PARAMERR, errmsg="课程不存在")

        # 3. 表单校验并保存到数据库
        form = CourseEditForm(dict_data)
        if form.is_valid():
            for key, value in form.cleaned_data.items():
                setattr(course, key, value)
            course.save()
            return to_json_data(errmsg="课程更新成功")

        else:
            err_msg_list = []

            for item in form.errors.values():
                err_msg_list.append(item[0])
            err_str = "/".join(err_msg_list)

            return to_json_data(errno=Code.PARAMERR, errmsg=err_str)

contains.py

PER_PAGE_NUMBER = 10  # 分页每页显示数据条数

IMAGE_MAX_SIZE = 5 * 1024 * 1024  # 5M  图片大小最大为5M

EXT_NAME_LS = ["bmp", "jpg", "png", "tif", "gif", "pcx", "tga", "exif", "fpx", "svg", "psd", "cdr", "pcd", "dxf", "ufo", "eps", "ai", "raw", "WMF", "webp"]

图片上传到服务器

图片上传到服务器需要对阿里云服务器进行部署,这里使用fastdfs进行上传图片到服务器。具体部署见下一章

代码实现:

# 上传图片到服务器
class UploadImageServerView(View):
    def post(self, request):
        # 1. 获取到图片
        image_file = request.FILES.get('image_file')
        if not image_file:
            return to_json_data(errno=Code.PARAMERR, errmsg=error_map[Code.PARAMERR])

        # 2. 判断图片后缀名是否是图片格式

        image_ext_name = image_file.name.split(".")[-1]  # 获取到图片的后缀名

        if image_ext_name not in contains.EXT_NAME_LS:
            return to_json_data(errno=Code.PARAMERR, errmsg="图片格式不正确")

        # 3. 判断图片大小是否满足要求
        image_size = image_file.size
        if image_size > contains.IMAGE_MAX_SIZE:
            return to_json_data(errno=Code.PARAMERR, errmsg="图片太大了")

        # 4. 上传图片:看upload_by_buffer源码里面的参数和返回值
        try:
            upload_image = client.upload_by_buffer(image_file.read(), file_ext_name=image_ext_name)
        except Exception as e:
            logger.info("图片上传失败:{}".format(e))
            return to_json_data(errno=Code.UNKOWNERR, errmsg="图片上传失败")
        else:
            # 5. 判断图片是否上传成功
            if upload_image.get("Status") != 'Upload successed.':
                logger.info('图片上传失败')
                return to_json_data(errno=Code.PARAMERR, errmsg="图片上传失败")
            else:
                # 6. 拼接image_url
                image_id = upload_image.get("Remote file_id")
                image_url = FDFS_URL + image_id
                return to_json_data(data={"image_url": image_url}, errmsg="图片上传成功!")

使用MarkDown进行图片上传:

参数设置可以看plugins里面的image-upload.html实例讲解

返回值必须是json格式的

具体实现:

MarkDown插件请求不带有token必须要自己添加

# 使用markdown上传图片
@method_decorator(csrf_exempt, name='dispatch')
class MarkdownImageView(View):
    """
    markdown image upload
    route:/admin/markdown/image
    method:post
    """

    def post(self, request):
        image_file = request.FILES.get("editormd-image-file")
        if not image_file:
            logger.info("从前端获取图片失败")
            return JsonResponse({'success': 0, 'message': '从前端获取图片失败'})

        image_ext_name = image_file.name.split('.')[-1]

        if image_ext_name not in contains.EXT_NAME_LS:
            return JsonResponse({'success': 0, 'message': '图片格式不正确'})

        image_size = image_file.size

        if image_size > contains.IMAGE_MAX_SIZE:
            return JsonResponse({'success': 0, 'message': '图片太大了'})

        try:
            upload_image = client.upload_by_buffer(image_file.read(), file_ext_name=image_ext_name)
        except Exception as e:
            logger.info("图片上传异常:{}".format(e))
            return JsonResponse({'success': 0, 'message': '图片上传失败'})

        else:
            if upload_image.get("Status") != 'Upload successed.':
                return JsonResponse({'success': 0, 'message': '图片上传失败'})
            else:
                image_url = FDFS_URL + upload_image.get('Remote file_id')
                return JsonResponse({'success': 1, 'message': '图片上传成功', "url": image_url})


表单验证

from django import forms
from news.models import Articles, Tags


class ArticleEditForm(forms.ModelForm):
    """
    course pub fields verify
    fields:article_title、article_digest、article_content、article_tag、image_url
    """
    image_url = forms.URLField(label="图片url", error_messages={"required": "图片URL不能为空"})
    
    tag =forms.ModelChoiceField(queryset=Tags.objects.only("id").filter(is_delete=False), error_messages={
        "required": "课程视频标签id不能为空"})

    class Meta:
        model = Articles
        fields = ["title", "digest", "content", "image_url", "tag"]

        error_messages = {
            'title': {
                'max_length': '课程视频标题长度不能低于150',
                'min_length': '课程视频标题长度不能低于1',
                'required': '课程视频标题不能为空'
            },
            'digest': {
                'max_length': '课程视频摘要长度不能低于200',
                'min_length': '课程视频摘要长度不能低于1',
                'required': '课程视频摘要不能为空'
            },
            'content': {
                'required': '文本内容不能为空'
            },
        }



2. js前端逻辑实现

课程视频编辑和发布js实现

$(function () {

    // 点击按钮实现保存
    let $courseBtn = $('#btn-pub-course');
    $courseBtn.click(function () {
        let sCourseId = $(this).data('course-id');
        // 判断课程名是否为空
        let sName = $("#course-name").val();
        if (!sName) {
            message.showError('请填写课程名!');
            return
        }

        // 判断课程简介是否为空
        let sBrief = $("#course-brief").val();
        if (!sBrief) {
            message.showError('请填写课程简介!');
            return
        }


        // 判断课程分类是否为空
        let sCategory = $("#course-category").val();  // 获取文档描述
        if (!sCategory || sCategory==="0") {
            message.showError('请选择课程分类!');
            return
        }

        // 判断任课教师是否为空
        let sTeacher = $("#course-teacher").val();  // 获取文档描述
        if (!sTeacher || sTeacher==="0") {
            message.showError('请选择任课教师!');
            return
        }


        // 判断课程封面url是否为空
        let sImageUrlInput = $("#image-url");
        let sImageUrl = sImageUrlInput.val();
        if (!sImageUrl) {
            message.showError('请上传课程封面');
            return
        }

        // 判断视频URL是否为空
        let sVideoUrlInput = $("#video-url");
        let sVideoUrl = sVideoUrlInput.val();
        if (!sVideoUrl) {
            message.showError('请上传课程视频');
            return
        }

        // 判断课程大纲是否为空
        let sOutline = $(".markdown-body").html();
        if (!sOutline) {
            message.showError('课程大纲不能为空');
            return
        }


        let data = {
            "name": sName,
            "brief": sBrief,
            "category": sCategory,
            "teacher": sTeacher,
            "cover_url": sImageUrl,
            "video_url": sVideoUrl,
            "outline": sOutline,
        };

        $.ajax({

            url: sCourseId ? '/admin/course/edit/' + sCourseId + '/' : '/admin/course/pub/',
            type: sCourseId ? 'PUT' : 'POST',
            data: JSON.stringify(data),
            contentType: "application/json; charset=utf-8",
            dataType: "json",
        })
            .done(function (res) {
                if (res.errno === "200") {
                    if (sCourseId) {
                        fAlert.alertNewsSuccessCallback("课程更新成功", '跳到课程管理页', function () {
                            window.location.href = '/admin/course/'
                        });

                    } else {
                        fAlert.alertNewsSuccessCallback("课程发布成功", '跳到课程管理页', function () {
                            window.location.href = '/admin/course/'
                        });
                    }
                } else {
                    fAlert.alertErrorToast(res.errmsg);
                }
            })
            .fail(function () {
                message.showError('服务器超时,请重试!');
            });

    });


     //获取cookie
    function getCookie(name) {
        let cookieValue = null;
        if (document.cookie && document.cookie !== '') {
            let cookies = document.cookie.split(';');
            for (let i = 0; i < cookies.length; i++) {
                let cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }

    function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }

    // 添加token到request中
    $.ajaxSetup({
        beforeSend: function (xhr, settings) {
            if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
            }
        }
    });


});


3. html页面填充

课程视频删除页面的html就是课程视频展示的html

课程视频更新和课程视频发布的html也是一样的,只是在html做是否传过来的参数里面有course进行判断是更新还是发布

{% extends 'admin/base/base.html' %}
{% load staticfiles %}

{% block title %}
    {% if course %}
        课程更新页
    {% else %}
         课程发布页
    {% endif %}
{% endblock %}

{% block content_header %}
    {% if course %}
         课程更新
    {% else %}
         课程发布
    {% endif %}
{% endblock %}

{% block content_header_brief %}
    {% if course %}
         课程更新功能实现
    {% else %}
         课程发布功能实现
    {% endif %}
{% endblock %}

{% block css %}
    <link rel="stylesheet" href="{% static 'plugins/markdown_editor/css/editormd.css' %}">
{% endblock %}

{% block content %}
    <div class="content">
        <div class="container-fluid">
            <div class="row">
                <div class="col-md-12 col-xs-12 col-sm-12">
                    <div class="box box-primary">
                        <div class="box-body">

                            <div class="form-group">
                                <label for="course-name">课程名</label>
                                {% if course %}

                                    <input type="text" class="form-control" id="course-name" name="course-name"
                                           placeholder="请输入课程名"
                                           value="{{ course.name }}">
                                {% else %}
                                    <input type="text" class="form-control" id="course-name" name="course-name"
                                           placeholder="请输入课程名" autofocus>
                                {% endif %}
                            </div>

                            <div class="form-group">
                                <label for="course-brief">课程简介</label>
                                {% if course %}
                                    <textarea name="course-brief" id="course-brief" placeholder="请输入课程简介"
                                              class="form-control"
                                              style="height: 8rem; resize: none;">{{ course.brief }}</textarea>
                                {% else %}
                                    <textarea name="course-brief" id="course-brief" placeholder="请输入课程简介"
                                              class="form-control"
                                              style="height: 8rem; resize: none;"></textarea>
                                {% endif %}
                            </div>

                            <div class="form-group">
                                <label for="course-category">课程分类</label>
                                <select name="course-category" id="course-category" class="form-control">
                                    <option value="0">-- 请选择课程分类 --</option>
                                    {% for category in categories %}
                                        <!-- 传tag_id到后台 -->
                                        {% if course and category == course.category %}
                                            <option value="{{ category.id }}" selected>{{ category.name }}</option>
                                        {% else %}
                                            <option value="{{ category.id }}">{{ category.name }}</option>
                                        {% endif %}
                                    {% endfor %}
                                </select>
                            </div>

                            <div class="form-group">
                                <label for="course-teacher">任课教师</label>
                                <select name="course-teacher" id="course-teacher" class="form-control">
                                    <option value="0">-- 请选择任课教师 --</option>
                                    {% for teacher in teachers %}
                                        <!-- 传tag_id到后台 -->
                                        {% if course and teacher == course.teacher %}
                                            <option value="{{ teacher.id }}" selected>{{ teacher.name }}</option>
                                        {% else %}
                                            <option value="{{ teacher.id }}">{{ teacher.name }}</option>
                                        {% endif %}
                                    {% endfor %}
                                </select>
                            </div>

                            <div class="form-group" id="container">
                                <label for="image-url">课程封面图</label>
                                <div class="input-group">
                                    {% if course %}
                                        <input type="text" class="form-control" id="image-url"
                                               name="image-url"
                                               placeholder="请上传课程封面图或输入课程封面图地址" value="{{ course.cover_url }}">
                                    {% else %}
                                        <input type="text" class="form-control" id="image-url"
                                               name="image-url"
                                               placeholder="请上传课程封面图或输入课程封面图地址">
                                    {% endif %}

                                    <div class="input-group-btn">
                                        <label class="btn btn-default btn-file">
                                            上传至服务器 <input type="file" id="upload-image">
                                        </label>
                                    </div>
                                </div>
                            </div>

                            <div class="form-group">
                                <label for="video-url">视频地址</label>
                                <div class="input-group">
                                    {% if course %}
                                        <input type="text" class="form-control" id="video-url"
                                               name="video-url"
                                               placeholder="请上传视频或输入视频地址" value="{{ course.video_url }}">
                                    {% else %}
                                        <input type="text" class="form-control" id="video-url"
                                               name="video-url"
                                               placeholder="请上传视频或输入视频地址">
                                    {% endif %}

                                    <div class="input-group-btn">
                                        <label class="btn btn-default btn-file">
                                            上传至百度云 <input type="file" id="upload-video">
                                        </label>
                                    </div>
                                </div>
                            </div>

                            <div class="form-group">
                                <label for="course-outline">课程大纲</label>
                                {% if course %}
                                    <div id="course-outline">
                                        <textarea name="outline" id="outline">{{ course.outline|safe }}</textarea>
                                    </div>
                                {% else %}
                                    <div id="course-outline">
                                        <textarea name="outline" style="display:none;" id="outline"></textarea>
                                    </div>
                                {% endif %}
                            </div>

                        </div>
                        <div class="box-footer">
                            {% if course %}
                                <a href="javascript:void (0);" class="btn btn-primary pull-right" id="btn-pub-course"
                                   data-course-id="{{ course.id }}">更新课程 </a>
                            {% else %}
                                <a href="javascript:void (0);" class="btn btn-primary pull-right"
                                   id="btn-pub-course" data-course-id="{{ course.id }}">发布课程 </a>
                            {% endif %}
                        </div>

                    </div>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

{% block script %}
    <script src="{% static 'plugins/markdown_editor/editormd.js' %}"></script>
    <script>
        let testEditor;
        $(function () {
            $.get("{% static 'plugins/markdown_editor/examples/test.md' %}", function (md) {
                testEditor = editormd("course-outline", {  //编辑出的id选择器
                    width: "98%",
                    height: 730,
                    path: "{% static 'plugins/markdown_editor/lib/' %}",
                    markdown: md,
                    codeFold: true,
                    saveHTMLToTextarea: true,
                    searchReplace: true,
                    htmlDecode: "style,script,iframe|on*",
                    emoji: true,
                    taskList: true,
                    tocm: true,         			// Using [TOCM]
                    tex: true,                   // 开启科学公式TeX语言支持,默认关闭
                    flowChart: true,             // 开启流程图支持,默认关闭
                    sequenceDiagram: true,       // 开启时序/序列图支持,默认关闭,
                    imageUpload: true,
                    imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
                    imageUploadURL: "{% url 'admin:upload_markdown_image' %}",
                    onload: function () {
                        console.log('onload', this);

                    },
                    /**设置主题颜色 把这些注释去掉主题就是黑色的了*/
                    {#editorTheme: "pastel-on-dark",#}
                    {#theme: "dark",#}
                    {#previewTheme: "dark"#}
                });
            });
        });

    </script>
    <script src="{% static 'node_modules/@baiducloud/sdk/dist/baidubce-sdk.bundle.min.js' %}"></script>
    <script src="{% static 'js/admin/plugins/image_upload.js' %}"></script>
    <script src="{% static 'js/admin/plugins/video_upload.js' %}"></script>
    <script src="{% static 'js/admin/course/course_pub.js' %}"></script>
{% endblock %}


四、课程视频发布功能实现

views逻辑处理
# 课程发布
class CoursePubView(PermissionRequiredMixin,View):
    """
    course video add page
    route:/admin/course/pub/
    permissions:view_course/add_course/
    """
    permission_required = ("course.view_course","course.add_course")
    raise_exception = True

    def get(self, request):
        teacher = Teacher.objects.only("id", "name").filter(is_delete=False)
        category = CourseCategory.objects.only("id", "name").filter(is_delete=False)
        data = {
            "teachers": teacher,
            "categories": category,

        }
        return render(request, 'admin/course/course_edit.html', context=data)

    def post(self, request):
        # 从前端获取到数据
        try:
            json_data = request.body
            if not json_data:
                return to_json_data(errno=Code.PARAMERR, errmsg=error_map[Code.PARAMERR])
            dict_data = json.loads(json_data.decode("utf-8"))
        except Exception as e:
            logger.info("课程发布获取失败:{}".format(e))
            return to_json_data(errno=Code.UNKOWNERR, errmsg=error_map[Code.UNKOWNERR])

        # 表单校验并保存到数据库
        form = CourseEditForm(dict_data)
        if form.is_valid():
            course = form.save(commit=False)
            course.save()
            return to_json_data(errmsg="课程发布成功")
        else:
            err_msg_list = []

            for item in form.errors.values():
                err_msg_list.append(item[0])
            err_str = "/".join(err_msg_list)

            return to_json_data(errno=Code.PARAMERR, errmsg=err_str)

js和html使用课程视频更新共用的

五、上传视频到百度云

由于百度云上传视频没有python的jdk,因此使用javascript来上传视频

javascript插件地址需要将此插件下载后放到static内才能使用

js实现
$(function () {
    let sdk = baidubce.sdk;
    let VodClient = sdk.VodClient;

    const CONFIG = {
        endpoint: 'http://vod.bj.baidubce.com',	// 默认区域名
        credentials: {
            ak: 'ak',	 // 填写你的百度云中ak和sk
            sk: 'sk'    // 在百度vod 安全认证里面
        }
    };

    // 百度云vod域名在多媒体/全局设置/发布设置里面
    let BAIDU_VOD_DOMAIN = '域名';	// 百度云VOD域名

    const CLIENT = new VodClient(CONFIG);


    $(function () {

        // 上传s视频至服务器
        let sUploadVideo = $("#upload-video");
        let sInputVideoUrl = $("#video-url");
        sUploadVideo.change(function () {

            // 判断课程名是否为空
            let sName = $("#course-name").val();
            if (!sName) {
                message.showError('请填写课程名!');
                return
            }

            // 判断课程简介是否为空
            let sBrief = $("#course-brief").val();
            if (!sBrief) {
                message.showError('请填写课程简介!');
                return
            }


            let sVideoFile = this.files[0];   // 获取文件
            let sVideoFileType = sVideoFile.type;

            // 调用百度云VOD接口
            let blob = new Blob([sVideoFile], {type: sVideoFileType});

            CLIENT.createMediaResource(sName, sBrief, blob)
            // Node.js中<data>可以为一个Stream、<pathToFile>;在浏览器中<data>为一个Blob对象
                .then(function (response) {
                    // 上传完成
                    message.showSuccess("视频上传成功");
                    let sMediaId = response.body.mediaId;
                    console.log('媒资ID为:', sMediaId);
                    let sVideoUrl = 'http://' + BAIDU_VOD_DOMAIN + '/' + sMediaId + '/' + sMediaId + '.m3u8';
                    sInputVideoUrl.val('');
                    sInputVideoUrl.val(sVideoUrl);

                })
                .catch(function (error) {
                    console.log(error);   // 上传错误
                    message.showError(error)
                });

        });

    });

});



发布了32 篇原创文章 · 获赞 3 · 访问量 1115

猜你喜欢

转载自blog.csdn.net/hl120841/article/details/104968640