后台管理站点 -- 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("文档不存在!")