19 文档 + 在线课堂相关功能

文档功能

后端功能

apps/admin/views.py

  • 添加如下代码:
import requests
import json
import logging
import qiniu

from datetime import datetime
from urllib.parse import urlencode

from django.core.paginator import Paginator, EmptyPage
from django.db.models import Count
from django.shortcuts import render
from django.views import View
from django.http import JsonResponse

from apps.news import models
from apps.admin import constants
from apps.doc.models import Doc
from apps.course.models import Course,Teacher,CourseCategory
from . import forms

from web_prv import settings
from utils.json_fun import to_json_data
from utils.res_code import Code, error_map
from utils import paginator_script
from utils.fastdfs.fdfs import FDFS_Client
from utils.secrets import qiniu_secret_info

logger = logging.getLogger('django')


class DocsManageView(View):
    """

    """
    def get(self,request):
        docs = Doc.objects.only('title','update_time').filter(is_delete=False)
        return render(request,'admin/doc/docs_manage.html',locals())


class DocsEditView(View):
    """
    /admin/docs/<int:doc_id>/
    """
    def get(self,request,doc_id):
        doc = Doc.objects.filter(is_delete=False,id=doc_id).first()
        if not doc:
            return to_json_data(errno=Code.NODATA,errmsg='需要删除的文档不存在')
        else:
            return render(request,'admin/doc/docs_pub.html',locals())

    def delete(self,request,doc_id):
        doc = Doc.objects.filter(is_delete=False,id=doc_id).first()
        if not doc:
            return to_json_data(errno=Code.NODATA,errmsg='需要删除的文档不存在!')
        else:
            doc.is_delete = True
            doc.save(update_fields=['is_delete','update_time'])
            return to_json_data(errmsg='文档删除成功!')

    def put(self,request,doc_id):
        doc = Doc.objects.filter(is_delete=False,id=doc_id).first()
        if not doc:
            return to_json_data(errno=Code.NODATA,errmsg='需要删除的文档不存在')
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        #将 json 转化为 dict
        dict_data = json.loads(json_data.decode('utf8'))
        form = forms.DocsPubForm(data=dict_data)
        if form.is_valid():
            #form.cleaned_data 是 dict 对象
            for attr, value in form.cleaned_data.items():
                #对 doc 对象的 attr 属性赋值 value
                setattr(doc,attr,value)
            doc.save()
            return to_json_data(errmsg='文档更新成功')
        else:
            #定义一个错误信息列表
            err_msg_list = []
            for item in form.errors.get_json_data().values():
                err_msg_list.append(item[0].get('message'))
            err_msg_str = ''.join(err_msg_list)     #拼接错误信息为一个字符串
            return to_json_data(errno=Code.PARAMERR,errmsg=err_msg_str)


class DocsUploadFile(View):
    """

    """
    def post(self,request):
        text_file = request.FILES.get('text_file')
        if not text_file:
            logger.info('从前端获取文件失败')
            return to_json_data(errno=Code.NODATA,errmsg='从前端获取文件失败')
        if text_file.content_type not in ('application/octet-stream','application/pdf','application/zip',\
              'text/plain','application/x-rar','application/msword'):
            print(text_file.content_type)
            return to_json_data(errno=Code.DATAERR,errmsg='不能上传的文件类型')
        try:
            text_ext_name = text_file.name.split('.')[-1]
        except Exception as e:
            logger.info('文件拓展名异常:{}'.format(e))
            text_ext_name = 'pdf'
        try:
            upload_res = FDFS_Client.upload_by_buffer(text_file.read(),file_ext_name=text_ext_name)
        except Exception as e:
            logger.error('文件上传出现异常:{}'.format(e))
            return to_json_data(errno=Code.UNKOWNERR,errmsg='文件上传异常')
        else:
            text_name = upload_res.get('Remote file_id')
            text_url = settings.FASTDFS_SERVER_DOMAIN + text_name
            return to_json_data(data={'text_file':text_url},errmsg='文件上传成功')


class DocsPubView(View):
    """
    /admin/docs/pub/
    """
    def get(self,request):
        return render(request,'admin/doc/docs_pub.html')

    def post(self,request):
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        #将 json 转换为 dict
        dict_data = json.loads(json_data.decode('utf8'))
        form = forms.DocsPubForm(data=dict_data)
        if form.is_valid():
            doc_instance = form.save(commit=False)
            doc_instance.author = request.user
            doc_instance.save()
            return to_json_data(errmsg='文档更新成功')
        else:
            #定义一个错误信息列表
            err_msg_list = []
            for item in form.errors.get_json_data().values():
                err_msg_list.append(item[0].get('message'))
            err_msg_str = ''.join(err_msg_list)     #拼接错误信息为一个字符串
            return to_json_data(errno=Code.PARAMERR,errmsg=err_msg_str)

apps/admin/forms.py

  • 添加如下代码
from django import forms

from apps.doc.models import Doc


class DocsPubForm(forms.ModelForm):
    """

    """
    #重写
    image_url = forms.URLField(label='文档缩略图url',error_messages={'required':'文档缩略图url不能为空'})
    file_url = forms.URLField(label='文档url',error_messages={'required':'文档url不能为空'})

    class Meta:
        model = Doc #与数据库模型关联
        #需要关联的字段
        #exclude 排除
        fields = ['title','desc','file_url','image_url']
        error_messages = {
            'title': {
                'max_length':'文档标题长度不能超过150',
                'min_length':'文档标题长度大于1',
                'required':'文档标题不能为空',
            },
            'desc': {
                'max_length':'文档描述长度不能超过200',
                'min_length':'文档描述长度大于1',
                'required':'文档描述不能为空',
            },
        }

apps/admin/urls.py

urlpatterns = [
	path('docs/',views.DocsManageView.as_view(),name='docs_manage'),
	path('docs/<int:doc_id>/',views.DocsEditView.as_view(),name='docs_edit'),
	path('docs/pub/',views.DocsPubView.as_view(),name='docs_pub'),
	path('docs/files/',views.DocsUploadFile.as_view(),name='upload_text'),
]

前端功能实现

templates/admin/doc/docs_manage.html

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

{% load static %}
{% block title %}
  文档管理页
{% endblock %}

{% block content_header %}
  文档管理
{% endblock %}

{% block header_option_desc %}
  文档管理
{% endblock %}


{% block content %}
  <div class="row">
    <div class="col-md-12 col-xs-12 col-sm-12">
      <div class="box box-primary">
        <div class="box-body">
          <table class="table table-bordered table-hover">
            <thead>
            <tr>
              <th>文档标题</th>
              <th>更新时间</th>
              <th>操作</th>
            </tr>
            </thead>
            <tbody id="tbody">
            {% for one_doc in docs %}
              <tr data-id="{{ one_doc.id }}" data-name="{{ one_doc.title }}">
                <td>{{ one_doc.title }}</td>
                <td>{{ one_doc.update_time|date:'Y年m月d日' }}</td>
                <td>
                  <a href="{% url 'admin:docs_edit' one_doc.id %}" class="btn btn-xs btn-warning btn-edit">编辑</a>
                  <button class="btn btn-xs btn-danger btn-del">删除</button>
                </td>
              </tr>
            {% endfor %}


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

{% block script %}
  <script src="{% static 'js/admin/doc/docs_manage.js' %}"></script>
{% endblock %}

templates/admin/doc/docs_pub.html


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

{% block title %}
  文档发布页
{% endblock %}

{% block content_header %}
  文档发布
{% endblock %}

{% block header_option_desc %}
  书是人类进步的阶梯
{% endblock %}


{% block content %}
  <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" style="margin-top: 30px;">
            <label for="news-title">文档标题(150个字以内)</label>
            {% if doc %}
              <input type="text" class="form-control" id="news-title" name="news-title" placeholder="请输入文档标题"
                     value="{{ doc.title }}">
            {% else %}
              <input type="text" class="form-control" id="news-title" name="news-title" placeholder="请输入文档标题"
                     autofocus>
            {% endif %}
          </div>

          <div class="form-group" id="container">
            <label for="news-thumbnail-url">文档缩略图</label>
            <div class="input-group">
              {% if doc %}
                <input type="text" class="form-control" id="news-thumbnail-url" name="news-thumbnail-url"
                       placeholder="请上传图片或输入文档缩略图地址" value="{{ doc.image_url }}">
              {% else %}
                <input type="text" class="form-control" id="news-thumbnail-url" name="news-thumbnail-url"
                       placeholder="请上传图片或输入文档缩略图地址">
              {% endif %}

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

          <div class="form-group">
            <div class="progress-bar" style="display: none">
              <div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 0;">0%</div>
            </div>
          </div>


          <div class="form-group">
            <label for="news-desc">文档描述</label>
            {% if doc %}
              <textarea name="news-desc" id="news-desc" placeholder="请输入文档描述" class="form-control"
                        style="height: 8rem; resize: none;">{{ doc.desc }}</textarea>
            {% else %}
              <textarea name="news-desc" id="news-desc" placeholder="请输入文档描述" class="form-control"
                        style="height: 8rem; resize: none;"></textarea>
            {% endif %}
          </div>

          <div class="form-group">
            <label for="docs-file-url">文档URL地址</label>
            <div class="input-group">
              {% if doc %}
                <input type="text" class="form-control" id="docs-file-url" name="docs-file-url"
                       placeholder="请上传文档或输入文档地址" value="{{ doc.file_url }}">
              {% else %}
                <input type="text" class="form-control" id="docs-file-url" name="docs-file-url"
                       placeholder="请上传文档或输入文档地址">
              {% endif %}

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


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

{% block script %}

  <!-- 七牛云 客户端 并不经过服务端 服务器需要提供 token -->
  <script src="https://cdn.bootcss.com/plupload/2.1.9/moxie.min.js"></script>
  <script src="https://cdn.bootcss.com/plupload/2.1.9/plupload.dev.js"></script>
  <script src="https://cdn.bootcss.com/qiniu-js/1.0.17.1/qiniu.min.js"></script>
  <!--一定要在下面 js 文件顺序很重要 -->
  <script src="{% static 'js/admin/base/fqiniu.js' %}"></script>
  <script src="{% static 'js/admin/doc/docs_pub.js' %}"></script>
{% endblock %}

static/js/admin/doc/docs_manage.js

$(function () {

  // 删除标签
  let $docDel = $(".btn-del");  // 1. 获取删除按钮
  $docDel.click(function () {   // 2. 点击触发事件
    let _this = this;
    let sDocId = $(this).parents('tr').data('id');
    let sDocTile = $(this).parents('tr').data('name');
    fAlert.alertConfirm({
      title: "确定删除文档吗?",
      type: "error",
      confirmText: "确认删除",
      cancelText: "取消删除",
      confirmCallback: function confirmCallback() {

        $.ajax({
          url: "/admin/docs/" + sDocId + "/",  // url尾部需要添加/
          // 请求方式
          type: "DELETE",
          dataType: "json",
        })
          .done(function (res) {
            if (res.errno === "0") {
              message.showSuccess("文档删除成功");
              $(_this).parents('tr').remove();
            } else {
              swal.showInputError(res.errmsg);
            }
          })
          .fail(function () {
            message.showError('服务器超时,请重试!');
          });
      }
    });
  });


  // get cookie using jQuery
  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));
  }

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

});

static/js/admin/doc/docs_pub.js

$(function () {

  let $thumbnailUrl = $("#news-thumbnail-url");   // 获取缩略图输入框元素
  let $docFileUrl = $("#docs-file-url");    // 获取文档地址输入框元素

  // ================== 上传图片文件至服务器 ================
  let $upload_image_server = $("#upload-image-server");
  $upload_image_server.change(function () {
    // let _this = this;
    let file = this.files[0];   // 获取文件
    let oFormData = new FormData();  // 创建一个 FormData
    oFormData.append("image_file", file); // 把文件添加进去
    // 发送请求
    $.ajax({
      url: "/admin/news/images/",
      method: "POST",
      data: oFormData,
      processData: false,   // 定义文件的传输
      contentType: false,
    })
      .done(function (res) {
        if (res.errno === "0") {
          message.showSuccess("图片上传成功");
          let sImageUrl = res.data.image_url;
          $thumbnailUrl.val('');
          $thumbnailUrl.val(sImageUrl);

        } else {
          message.showError(res.errmsg)
        }
      })
      .fail(function () {
        message.showError('服务器超时,请重试!');
      });

  });
  // ================== 上传文件至服务器 ================
  let $upload_file_server = $("#upload-file-server");
  $upload_file_server.change(function () {
    // let _this = this;
    let file = this.files[0];   // 获取文件
    let oFormData = new FormData();  // 创建一个 FormData
    oFormData.append("text_file", file); // 把文件添加进去
    // 发送请求
    $.ajax({
      url: "/admin/docs/files/",
      method: "POST",
      data: oFormData,
      processData: false,   // 定义文件的传输
      contentType: false,
    })
      .done(function (res) {
        if (res.errno === "0") {
          message.showSuccess("文件上传成功");
          let sTextFileUrl = res.data.text_file;
          $docFileUrl.val('');
          $docFileUrl.val(sTextFileUrl);

        } else {
          message.showError(res.errmsg)
        }
      })
      .fail(function () {
        message.showError('服务器超时,请重试!');
      });

  });


  // ================== 上传图片至七牛(云存储平台) ================
  let $progressBar = $(".progress-bar");
  QINIU.upload({
    "domain": "http://prbzcij9n.bkt.clouddn.com/",  // 七牛空间域名
    // 后台返回 token的地址 (后台返回的 url 地址) 不可能成功
    "uptoken_url": "/admin/token/",
    // 按钮
    "browse_btn": "upload-image-btn",
    // 成功
    "success": function (up, file, info) {
      let domain = up.getOption('domain');
      let res = JSON.parse(info);
      let filePath = domain + res.key;
      console.log(filePath);  // 打印文件路径
      $thumbnailUrl.val('');
      $thumbnailUrl.val(filePath);
    },
    // 失败
    "error": function (up, err, errTip) {
      // console.log('error');
      console.log(up);
      console.log(err);
      console.log(errTip);
      // console.log('error');
      message.showError(errTip);
    },
    "progress": function (up, file) {
      let percent = file.percent;
      $progressBar.parent().css("display", 'block');
      $progressBar.css("width", percent + '%');
      $progressBar.text(parseInt(percent) + '%');
    },
    // 完成后 去掉进度条
    "complete": function () {
      $progressBar.parent().css("display", 'none');
      $progressBar.css("width", '0%');
      $progressBar.text('0%');
    }
  });



  // ================== 发布文章 ================
  let $docsBtn = $("#btn-pub-news");
  $docsBtn.click(function () {
    // 判断文档标题是否为空
    let sTitle = $("#news-title").val();  // 获取文件标题
    if (!sTitle) {
        message.showError('请填写文档标题!');
        return
    }

    // 判断文档缩略图url是否为空
    let sThumbnailUrl = $thumbnailUrl.val();
    if (!sThumbnailUrl) {
      message.showError('请上传文档缩略图');
      return
    }

    // 判断文档描述是否为空
    let sDesc = $("#news-desc").val();  // 获取文档描述
    if (!sDesc) {
        message.showError('请填写文档描述!');
        return
    }

    // 判断文档url是否为空
    let sDocFileUrl = $docFileUrl.val();
    if (!sDocFileUrl) {
      message.showError('请上传文档或输入文档地址');
      return
    }

    // 获取docsId 存在表示更新 不存在表示发表
    let docsId = $(this).data("news-id");
    let url = docsId ? '/admin/docs/' + docsId + '/' : '/admin/docs/pub/';
    let data = {
      "title": sTitle,
      "desc": sDesc,
      "image_url": sThumbnailUrl,
      "file_url": sDocFileUrl,
    };

    $.ajax({
      // 请求地址
      url: url,
      // 请求方式
      type: docsId ? 'PUT' : 'POST',
      data: JSON.stringify(data),
      // 请求内容的数据类型(前端发给后端的格式)
      contentType: "application/json; charset=utf-8",
      // 响应数据的格式(后端返回给前端的格式)
      dataType: "json",
    })
      .done(function (res) {
        if (res.errno === "0") {
          if (docsId) {
            fAlert.alertNewsSuccessCallback("文档更新成功", '跳到文档管理页', function () {
              window.location.href = '../../../../apps/admin/docs/'
            });

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

  });


  // get cookie using jQuery
  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));
  }

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

});

在线课堂相关功能

后端功能实现

apps/admin/views.py

  • 添加如下代码
import requests
import json
import logging
import qiniu

from datetime import datetime
from urllib.parse import urlencode

from django.core.paginator import Paginator, EmptyPage
from django.db.models import Count
from django.shortcuts import render
from django.views import View
from django.http import JsonResponse

from apps.news import models
from apps.admin import constants
from apps.doc.models import Doc
from apps.course.models import Course,Teacher,CourseCategory
from . import forms

from web_prv import settings
from utils.json_fun import to_json_data
from utils.res_code import Code, error_map
from utils import paginator_script
from utils.fastdfs.fdfs import FDFS_Client
from utils.secrets import qiniu_secret_info

logger = logging.getLogger('django')


class CoursesManageView(View):
    """

    """
    def get(self,request):
        courses = Course.objects.select_related('category','teacher').only('title','category__name',\
                  'teacher__name').filter(is_delete=False)
        return render(request,'admin/course/courses_manage.html',locals())


class CoursesEditView(View):
    """
    /admin/courses/<int:course_id>/
    """
    def get(self,request,course_id):
        course = Course.objects.filter(is_delete=False,id=course_id).first()
        if course:
            teachers = Teacher.objects.only('name').filter(is_delete=False)
            categories = CourseCategory.objects.only('name').filter(is_delete=False)
            return render(request,'admin/course/courses_pub.html',locals())
        else:
            return to_json_data(errno=Code.NODATA,errmsg='需要更新的课程不存在')

    def delete(self,request,course_id):
        course = Course.objects.filter(is_delete=False,id=course_id).first()
        if course:
            course.is_delete = True
            course.save(update_fields=['is_delete','update_time'])
            return to_json_data(errmsg='课程删除成功')
        else:
            return to_json_data(errno=Code.PARAMERR,errmsg='需要删除的课程不存在')

    def put(self,request,course_id):
        course = Course.objects.filter(is_delete=False,id=course_id).first()
        if not course:
            return to_json_data(errno=Code.NODATA,errmsg='需要更新的课程不存在')
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        #将 json 格式转换为 dict
        dict_data = json.loads(json_data.decode('utf8'))
        form = forms.CoursesPubForm(data=dict_data)
        if form.is_valid():
            for attr,value in form.cleaned_data.items():
                setattr(course,attr,value)
            course.save()
            return to_json_data(errmsg='课程更新成功')
        else:
            #定义一个错误信息列表
            err_msg_list = []
            for item in form.errors.get_json_data().values():
                err_msg_list.append(item[0].get('message'))
            err_msg_str = ''.join(err_msg_list)     #拼接错误信息为一个字符串
            return to_json_data(errno=Code.PARAMERR,errmsg=err_msg_str)


class CoursesPubView(View):
    """

    """
    def get(self,request):
        teachers = Teacher.objects.only('name').filter(is_delete=False)
        categories = CourseCategory.objects.only('name').filter(is_delete=False)
        return render(request,'admin/course/courses_pub.html',locals())

    def post(self,request):
        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('utf8'))
        form = forms.CoursesPubForm(data=dict_data)
        if form.is_valid():
            #此处模型里需要的字段都传了,所以不需要先缓存,直接存即可
            courses_instance = form.save()
            return to_json_data(errmsg='课程发布成功')
        else:
            #定义一个错误信息列表
            err_msg_list = []
            for item in form.errors.get_json_data().values():
                err_msg_list.append(item[0].get('message'))
            err_msg_str = ''.join(err_msg_list)     #拼接错误信息为一个字符串

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

apps/admin/forms.py

  • 添加如下代码
from django import forms

from apps.news.models import News, Tag
from apps.doc.models import Doc
from apps.course.models import Course


class CoursesPubForm(forms.ModelForm):
    """

    """
    cover_url = forms.URLField(label='封面图url',error_messages={'required':'封面图url不能为空'})
    video_url = forms.URLField(label='视频url',error_messages={'required':'视频url不能为空'})

    class Meta:
        model = Course  #与数据库模型关联
        #需要关联的字段
        #exclude 排除
        exclude = ['is_delete','create_time','update_time']
        error_messages = {
            'title': {
                'max_length':'视频标题长度不能超过150',
                'min_length':'视频标题长度大于1',
                'required':'视频标题不能为空',
            },
        }

apps/admin/urls.py

  • 添加如下代码
from apps.admin import views
from django.urls import path


app_name = 'admin'

urlpatterns = [
    path('courses/',views.CoursesManageView.as_view(),name='courses_manage'),
    path('courses/<int:course_id>/',views.CoursesEditView.as_view(),name='courses_edit'),
    path('courses/pub/',views.CoursesPubView.as_view(),name='courses_pub'),
]

前端功能实现

templates/admin/course/courses_manage.html

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

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

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

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


{% block content %}
  <div class="row">
    <div class="col-md-12 col-xs-12 col-sm-12">
      <div class="box box-primary">
{#        <div class="box-header">#}
{#          <button class="btn btn-primary pull-right" id="btn-add-tag">添加课程</button>#}
{#        </div>#}
        <div class="box-body">
          <table class="table table-bordered table-hover">
            <thead>
            <tr>
              <th>课程标题</th>
              <th>课程类别</th>
              <th>讲师名</th>
              <th>操作</th>
            </tr>
            </thead>
            <tbody id="tbody">
            {% for one_course in courses %}
              <tr data-id="{{ one_course.id }}" data-name="{{ one_course.title }}">
                <td>{{ one_course.title }}</td>
                <td>{{ one_course.category.name }}</td>
                <td>{{ one_course.teacher.name }}</td>
                <td>
{#                  <button class="btn btn-xs btn-warning btn-edit">编辑</button>#}
                  <a href="{% url 'admin:courses_edit' one_course.id %}" class="btn btn-xs btn-warning btn-edit">编辑</a>
                  <button class="btn btn-xs btn-danger btn-del">删除</button>
                </td>
              </tr>
            {% endfor %}


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

{% block script %}
  <script src="{% static 'js/admin/course/courses_manage.js' %}"></script>
{% endblock %}

templates/admin/course/course_pub.html

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

{% load static %}
{% block title %}
  课程发布页
{% endblock %}

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



{% block content_header %}
  课程发布
{% endblock %}

{% block header_option_desc %}
  书是人类进步的阶梯
{% endblock %}


{% block content %}
  <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" style="margin-top: 30px;">
            <label for="news-title">课程标题(150个字以内)</label>
            {% if course %}
              <input type="text" class="form-control" id="news-title" name="news-title" placeholder="请输入课程标题"
                     value="{{ course.title }}">
            {% else %}
              <input type="text" class="form-control" id="news-title" name="news-title" placeholder="请输入课程标题"
                     autofocus>
            {% endif %}
          </div>

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


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

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

          <div class="form-group">
            <div class="progress-bar" style="display: none">
              <div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 0;">0%</div>
            </div>
          </div>


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

              <div class="input-group-btn">
                <label class="btn btn-default btn-file">
                  上传至服务器 <input type="file" id="upload-file-server">
                </label>
                {#                <button class="btn btn-info">上传至七牛云</button>#}
              </div>
            </div>
          </div>


          <div class="form-group">
            <label for="course-time">课程时长(单位:分钟)</label>
            {% if course %}
              <input type="text" class="form-control" id="course-time" name="course-time"
                     placeholder="请输入课程时长" value="{{ course.duration }}">
            {% else %}
              <input type="text" class="form-control" id="course-time" name="course-time"
                     placeholder="请输入课程时长" autofocus>
            {% endif %}
          </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 %}
                {% 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">
            <label for="course-category">课程分类</label>
            <select name="course-category" id="course-category" class="form-control">
              <option value="0">-- 请选择分类 --</option>
              {% for category in categories %}
                {% 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-outline">课程大纲</label>
            {% if course %}
              <div id="course-outline">
                    <textarea name="content" id="content">{{ course.outline|safe }}</textarea>
              </div>
            {% else %}
              <div id="course-outline">
                    <textarea name="content" id="content"></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-news"
               data-news-id="{{ course.id }}">更新课程 </a>
          {% else %}
            <a href="javascript:void (0);" class="btn btn-primary pull-right" id="btn-pub-news">发布课程 </a>
          {% endif %}
        </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", {
          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:markdown_image_upload' %}",
{#          onload: function () {#}
{#            console.log('onload', this);#}
{##}
{#          },#}
          /**设置主题颜色 把这些注释去掉主题就是黑色的了*/
{#          editorTheme: "pastel-on-dark",#}
{#          theme: "dark",#}
{#          previewTheme: "dark"#}
        });
      });
    });

  </script>

{#  <script src="{% static 'js/admin/news/wangEditor.min.js' %}"></script>#}
  <!-- 七牛云 客户端 并不经过服务端 服务器需要提供 token -->
  <script src="https://cdn.bootcss.com/plupload/2.1.9/moxie.min.js"></script>
  <script src="https://cdn.bootcss.com/plupload/2.1.9/plupload.dev.js"></script>
  <script src="https://cdn.bootcss.com/qiniu-js/1.0.17.1/qiniu.min.js"></script>
  <!--一定要在下面 js 文件顺序很重要 -->
  <script src="{% static 'js/admin/base/fqiniu.js' %}"></script>
  <script src="{% static 'js/admin/course/courses_pub.js' %}"></script>
  <script src="{% static 'node_modules/@baiducloud/sdk/dist/baidubce-sdk.bundle.min.js' %}"></script>
{% endblock %}

static/js/admin/course/courses_manage.js

$(function () {

  // 删除标签
  let $courseDel = $(".btn-del");  // 1. 获取删除按钮
  $courseDel.click(function () {   // 2. 点击触发事件
    let _this = this;
    let sCourseId = $(this).parents('tr').data('id');
    // let sCourseTitle = $(this).parents('tr').data('name');
    fAlert.alertConfirm({
      title: "确定删除文档吗?",
      type: "error",
      confirmText: "确认删除",
      cancelText: "取消删除",
      confirmCallback: function confirmCallback() {

        $.ajax({
          url: "/admin/courses/" + sCourseId + "/",  // url尾部需要添加/
          // 请求方式
          type: "DELETE",
          dataType: "json",
        })
          .done(function (res) {
            if (res.errno === "0") {
              message.showSuccess("文档删除成功");
              $(_this).parents('tr').remove();
            } else {
              swal.showInputError(res.errmsg);
            }
          })
          .fail(function () {
            message.showError('服务器超时,请重试!');
          });
      }
    });
  });


  // get cookie using jQuery
  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));
  }

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

});

static/js/admin/course/course_pub.js

$(function () {
  let $thumbnailUrl = $("#news-thumbnail-url");   // 获取缩略图输入框元素
  let $courseFileUrl = $("#docs-file-url");    // 获取课程地址输入框元素

  // ================== 上传图片文件至服务器 ================
  let $upload_image_server = $("#upload-image-server");
  $upload_image_server.change(function () {
    // let _this = this;
    let file = this.files[0];   // 获取文件
    let oFormData = new FormData();  // 创建一个 FormData
    oFormData.append("image_file", file); // 把文件添加进去
    // 发送请求
    $.ajax({
      url: "/admin/news/images/",
      method: "POST",
      data: oFormData,
      processData: false,   // 定义文件的传输
      contentType: false,
    })
      .done(function (res) {
        if (res.errno === "0") {
          message.showSuccess("图片上传成功");
          let sImageUrl = res.data.image_url;
          // let $inpuUrl = $(_this).parents('.input-group').find('input:nth-child(1)');
          $thumbnailUrl.val('');
          $thumbnailUrl.val(sImageUrl);

        } else {
          message.showError(res.errmsg)
        }
      })
      .fail(function () {
        message.showError('服务器超时,请重试!');
      });

  });


  // ================== 上传图片至七牛(云存储平台) ================
  let $progressBar = $(".progress-bar");
  QINIU.upload({
    "domain": "http://ppe3ld884.bkt.clouddn.com/",  // 七牛空间域名
    // 后台返回 token的地址 (后台返回的 url 地址) 不可能成功
    "uptoken_url": "/admin/token/",
    // 按钮
    "browse_btn": "upload-image-btn",
    // 成功
    "success": function (up, file, info) {
      let domain = up.getOption('domain');
      let res = JSON.parse(info);
      let filePath = domain + res.key;
      console.log(filePath);  // 打印文件路径
      $thumbnailUrl.val('');
      $thumbnailUrl.val(filePath);
    },
    // 失败
    "error": function (up, err, errTip) {
      // console.log('error');
      console.log(up);
      console.log(err);
      console.log(errTip);
      // console.log('error');
      message.showError(errTip);
    },
    "progress": function (up, file) {
      let percent = file.percent;
      $progressBar.parent().css("display", 'block');
      $progressBar.css("width", percent + '%');
      $progressBar.text(parseInt(percent) + '%');
    },
    // 完成后 去掉进度条
    "complete": function () {
      $progressBar.parent().css("display", 'none');
      $progressBar.css("width", '0%');
      $progressBar.text('0%');
    }
  });


  let sdk = baidubce.sdk;
  let VodClient = sdk.VodClient;

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

  let BAIDU_VOD_DOMAIN = '\n' +
      '';	// 你的百度云VOD域名

  const CLIENT = new VodClient(CONFIG);




  $(function () {
  	// 其他js代码省略
    // ......

  // ================== 上传文件至服务器 ================
  let $upload_file_server = $("#upload-file-server");
  $upload_file_server.change(function () {

    // 先判断课程标题是否为空
    let sTitle = $("#news-title").val();  // 获取课程标题
    if (!sTitle) {
      message.showError('请先填写课程标题之后,再上传视频!');
      return
    }

    // 判断课程简介是否为空
    let sDesc = $("#news-desc").val();  // 获取课程简介
    if (!sDesc) {
      message.showError('请先填写课程描述之后,再上传视频!');
      return
    }

    let video_file = this.files[0];   // 获取文件
    let video_file_type = video_file.type;

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

    CLIENT.createMediaResource(sTitle, sDesc, 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';
        $courseFileUrl.val('');
        $courseFileUrl.val(sVideoUrl);

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

  });


});



  // ================== 发布文章 ================
  let $docsBtn = $("#btn-pub-news");
  $docsBtn.click(function () {
    // 判断课程标题是否为空
    let sTitle = $("#news-title").val();  // 获取文件标题
    if (!sTitle) {
      message.showError('请填写课程标题!');
      return
    }

    // 判断课程简介是否为空
    let sDesc = $("#news-desc").val();  // 获取课程简介
    if (!sDesc) {
      message.showError('请填写课程描述!');
      return
    }

    // 判断课程缩略图url是否为空
    let sThumbnailUrl = $thumbnailUrl.val();
    if (!sThumbnailUrl) {
      message.showError('请上传课程缩略图');
      return
    }

    // 判断课程url是否为空
    let sCourseFileUrl = $courseFileUrl.val();
    if (!sCourseFileUrl) {
      message.showError('请上传视频或输入视频地址');
      return
    }

    // 判断视频时长是否为空
    let sCourseTime = $('#course-time').val();  // 获取视频时长
    if (!sCourseTime) {
      message.showError('请填写视频时长!');
      return
    }

    // 判断是否选择讲师
    let sTeacherId = $("#course-teacher").val();
    if (!sTeacherId || sTeacherId === '0') {
      message.showError('请选择讲师');
      return
    }

    // 判断是否选择课程分类
    let sCategoryId = $("#course-category").val();
    if (!sCategoryId || sCategoryId === '0') {
      message.showError('请选择课程分类');
      return
    }

    // 判断课程大纲是否为空
    let sContentHtml = $(".markdown-body").html();
    // let sContentHtml = window.editor.txt.html();
    // let sContentText = window.editor.txt.text();
    if (!sContentHtml || sContentHtml === '<p><br></p>') {
        message.showError('请填写课程大纲!');
        return
    }

    // 获取coursesId 存在表示更新 不存在表示发表
    let coursesId = $(this).data("news-id");
    let url = coursesId ? '/admin/courses/' + coursesId + '/' : '/admin/courses/pub/';
    let data = {
      "title": sTitle,
      "profile": sDesc,
      "cover_url": sThumbnailUrl,
      "video_url": sCourseFileUrl,
      "duration": sCourseTime,
      "outline": sContentHtml,
      "teacher": sTeacherId,
      "category": sCategoryId

    };

    $.ajax({
      // 请求地址
      url: url,
      // 请求方式
      type: coursesId ? 'PUT' : 'POST',
      data: JSON.stringify(data),
      // 请求内容的数据类型(前端发给后端的格式)
      contentType: "application/json; charset=utf-8",
      // 响应数据的格式(后端返回给前端的格式)
      dataType: "json",
    })
      .done(function (res) {
        if (res.errno === "0") {
          if (coursesId) {
            fAlert.alertNewsSuccessCallback("课程更新成功", '跳到课程管理页', function () {
              window.location.href = '../../../../apps/admin/courses/'
            });

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

  });


  // get cookie using jQuery
  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));
  }

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

});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值