pythonDjango+小程序

后台管理站点 -- 6.文档在线管理

2019-03-13  本文已影响8人  爱修仙的道友

文档的增删改查

from docs.models import Doc

class DocsManageView(PermissionRequiredMixin, View):
    """
    route: /admin/docs/
    """
    permission_required = ('doc.view_doc', 'doc.add_doc')
    raise_exception = True

    def handle_no_permission(self):
        if self.request.method.lower() != 'get':
            return to_json_data(errno=Code.ROLEERR, errmsg='没有操作权限')
        else:
            return super(DocsManageView, self).handle_no_permission()

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


class DocsEditView(PermissionRequiredMixin, View):
    """
    route: /admin/docs/<int:doc_id>/
    """
    permission_required = ('doc.change_doc', 'doc.delete_doc')
    raise_exception = True

    def handle_no_permission(self):
        if self.request.method.lower() != 'get':
            return to_json_data(errno=Code.ROLEERR, errmsg='没有操作权限')
        else:
            return super(DocsEditView, self).handle_no_permission()

    def get(self, request, doc_id):
        """
        """
        doc = Doc.objects.filter(is_delete=False, id=doc_id).first()
        if doc:
            tags = Doc.objects.only('id', 'name').filter(is_delete=False)
            context = {
                'doc': doc
            }
            return render(request, 'admin/doc/docs_pub.html', context=context)
        else:
            raise Http404('需要更新的文章不存在!')

    def delete(self, request, doc_id):
        doc = Doc.objects.filter(is_delete=False, id=doc_id).first()
        if doc:
            doc.is_delete = True
            doc.save(update_fields=['is_delete'])
            return to_json_data(errmsg="文档删除成功")
        else:
            return to_json_data(errno=Code.PARAMERR, 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():
            doc.title = form.cleaned_data.get('title')
            doc.desc = form.cleaned_data.get('desc')
            doc.file_url = form.cleaned_data.get('file_url')
            doc.image_url = form.cleaned_data.get('image_url')
            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 DocsPubView(PermissionRequiredMixin, View):
    """
    route: /admin/news/pub/
    """
    permission_required = ('doc.add_doc', 'news.view_doc')
    raise_exception = True

    def handle_no_permission(self):
        if self.request.method.lower() != 'get':
            return to_json_data(errno=Code.ROLEERR, errmsg='没有操作权限')
        else:
            return super(DocsPubView, self).handle_no_permission()

    def get(self, request):

        return render(request, 'admin/doc/docs_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])
        # 将json转化为dict
        dict_data = json.loads(json_data.decode('utf8'))

        form = forms.DocsPubForm(data=dict_data)
        if form.is_valid():
            docs_instance = form.save(commit=False)
            docs_instance.author_id = request.user.id
            docs_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)


class DocsUploadFile(PermissionRequiredMixin, View):
    """route: /admin/docs/files/
    """
    permission_required = ('doc.add_doc',)

    def handle_no_permission(self):
        return to_json_data(errno=Code.ROLEERR, errmsg='没有上传文件的权限')

    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'):
            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:
            FDFS_Client = Fdfs_client('utils/fastdfs/client.conf')
            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:
            if upload_res.get('Status') != 'Upload successed.':
                logger.info('文件上传到FastDFS服务器失败')
                return to_json_data(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='文件上传成功')

路由分配

    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' %}


{% 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.create_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 %}

// 创建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 === "200") {
              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'));
      }
    }
  });

});

<!-- 创建templates/admin/doc/docs_pub.html文件 -->

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


{% 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_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 === "200") {
          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 === "200") {
          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://pn0e9dbwu.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 === "200") {
          if (docsId) {
            fAlert.alertNewsSuccessCallback("文档更新成功", '跳到文档管理页', function () {
              window.location.href = '/admin/docs/'
            });

          } else {
            fAlert.alertNewsSuccessCallback("文档发表成功", '跳到文档管理页', function () {
              window.location.href = '/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'));
      }
    }
  });

});
# docs/views.py
class DocDownloadView(View):
    """

    """

    def get(self, request, doc_id):
        doc = Doc.objects.only('file_url').filter(is_delete=False, id=doc_id).first()
        if doc:
            doc_url = doc.file_url

            doc_name = doc.title
            try:
                res = FileResponse(requests.get(doc_url, stream=True))
                # 仅测试的话可以这样子设置
                # res = FileResponse(open(doc.file_url, 'rb'))
            except Exception as e:
                logger.info("获取文档内容出现异常:\n{}".format(e))
                raise Http404("文档下载异常!")

            ex_name = doc_url.split('.')[-1]
            # https://stackoverflow.com/questions/23714383/what-are-all-the-possible-values-for-http-content-type-header
            # http://www.iana.org/assignments/media-types/media-types.xhtml#image
            if not ex_name:
                raise Http404("文档url异常!")
            else:
                ex_name = ex_name.lower()

            if ex_name == "pdf":
                res["Content-type"] = "application/pdf"
            elif ex_name == "zip":
                res["Content-type"] = "application/zip"
            elif ex_name == "doc":
                res["Content-type"] = "application/msword"
            elif ex_name == "xls":
                res["Content-type"] = "application/vnd.ms-excel"
            elif ex_name == "docx":
                res["Content-type"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
            elif ex_name == "ppt":
                res["Content-type"] = "application/vnd.ms-powerpoint"
            elif ex_name == "pptx":
                res["Content-type"] = "application/vnd.openxmlformats-officedocument.presentationml.presentation"

            else:
                raise Http404("文档格式不正确!")
            # 将文件名进行中文编码
            doc_filename = escape_uri_path(doc_name+'.'+ex_name)
            # 设置为inline,下载后会直接打开(预览操作)  设置为attachment,会直接下载
            # 这个头信息代表这,点击下载会进行怎样的处理
            # filename*=UTF-8''{}" 显示下载文件的中文名
            res["Content-Disposition"] = "attachment; filename*=UTF-8''{}".format(doc_filename)
            return res

        else:
            raise Http404("文档不存在!")
上一篇 下一篇

猜你喜欢

热点阅读