0.5.3版本,更新记录详见changes.md

This commit is contained in:
yangjian 2020-06-13 20:41:17 +08:00
parent ece4477d1b
commit d080ed7760
14 changed files with 397 additions and 33 deletions

View File

@ -5,6 +5,12 @@
- 添加编辑器引用JS的版本号
- 修复开启全站登录时登录注册页面样式丢失的问题
- 完善图片上传对图片大写后缀格式的支持
- 优化个人中心文档管理,支持按文集筛选文档
- 优化移动端阅读体验
- 修复编辑器行号挤压的问题
- 完善删除文档时的权限验证
- 文档页面添加图片放大镜功能
- 添加文档回收站功能
### v0.5.2 2020-05-24

View File

@ -40,7 +40,7 @@ SECRET_KEY = '5&71mt9@^58zdg*_!t(x6g14q*@84d%ptr%%s6e0l50zs0we3d'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = CONFIG.getboolean('site','debug')
VERSIONS = '0.5.2'
VERSIONS = '0.5.3'
ALLOWED_HOSTS = ['*']

View File

@ -44,7 +44,7 @@
- 支持**文集协作**功能,一个文集可以拥有一个创建者和多个协作者,可灵活选择协作权限;
- 支持**文档历史版本**功能,可以查看和对比历史版本与现有版本的差异,恢复某个历史版本为当前版本;
当前版本为:**v0.5.2**
当前版本为:**v0.5.3**
完整更新记录详见:[CHANGES.md](./CHANGES.md)
@ -54,7 +54,7 @@
`MrDoc`基于`Python`语言的`Django Web`框架配合前端的`LayUI``JQuery`等库进行开发。
`MrDoc``Python 3.6` + `Django 2.2`上进行开发并且在Django 2.1、2.2和Python3.5、3.6、3.7上测试运行良好在其他环境下运行MrDoc不排除有未知的异常。
`MrDoc``Python 3.6` + `Django 2.2`上进行开发并且在Django 2.1、2.2和Python3.5、3.6、3.7上测试运行良好在其他环境下运行MrDoc不排除有未知的异常。
## 简明安装教程

View File

@ -45,7 +45,7 @@ You can register, create project and documents. The account can be cleared from
- Supports the project collaboration function. A project can have one Creator and multiple collaborators, and can flexibly select collaboration permissions;
- It supports the function of document historical version to view and compare the differences between the historical version and the existing version, and restore a historical version to the current version;
Current Version : v0.5.2
Current Version : v0.5.3
Update Record : [CHANGES.md](./CHANGES.md)

View File

@ -566,13 +566,27 @@ class DocView(APIView):
# 查询文档
try:
doc = Doc.objects.get(id=doc_id)
project = Project.objects.get(id=doc.top_doc) # 查询文档所属的文集
# 获取文档所属文集的协作信息
pro_colla = ProjectCollaborator.objects.filter(project=project, user=request.user) #
if pro_colla.exists():
colla_user_role = pro_colla[0].role
else:
colla_user_role = 0
except ObjectDoesNotExist:
return Response({'code': 1, 'data': '文档不存在'})
if request.user == doc.create_user:
# 删除
doc.delete()
# 修改其子文档为顶级文档
Doc.objects.filter(parent_doc=doc_id).update(parent_doc=0)
if (request.user == doc.create_user) or (colla_user_role == 1) or (request.user == project.create_user):
# 修改状态为删除
doc.status = 3
doc.modify_time = datetime.datetime.now()
doc.save()
# 修改其下级所有文档状态为删除
chr_doc = Doc.objects.filter(parent_doc=doc_id) # 获取下级文档
chr_doc_ids = chr_doc.values_list('id', flat=True) # 提取下级文档的ID
chr_doc.update(status=3, modify_time=datetime.datetime.now()) # 修改下级文档的状态为删除
Doc.objects.filter(parent_doc__in=chr_doc_ids).update(status=3,
modify_time=datetime.datetime.now()) # 修改下级文档的下级文档状态
return Response({'code': 0, 'data': '删除完成'})
else:
return Response({'code': 2, 'data': '非法请求'})

View File

@ -53,7 +53,7 @@ class Doc(models.Model):
create_user = models.ForeignKey(User,on_delete=models.CASCADE)
create_time = models.DateTimeField(auto_now_add=True)
modify_time = models.DateTimeField(auto_now=True)
# 文档状态说明0表示草稿状态1表示发布状态
# 文档状态说明0表示草稿状态1表示发布状态2表示删除状态
status = models.IntegerField(choices=((0,0),(1,1)),default=1,verbose_name='文档状态')
def __str__(self):

View File

@ -9,7 +9,7 @@ register = template.Library()
# 获取文集下的文档数量
@register.filter(name='get_doc_count')
def get_doc_count(value):
return Doc.objects.filter(top_doc=int(value)).count()
return Doc.objects.filter(top_doc=int(value),status__in=[0,1]).count()
# 获取文集下最新的文档及其修改时间
@register.filter(name='get_new_doc')

View File

@ -28,6 +28,8 @@ urlpatterns = [
path('diff_doc/<int:doc_id>-<int:his_id>/',views.diff_doc,name='diff_doc'), # 对比文档历史版本
path('manage_doc_history/<int:doc_id>/',views.manage_doc_history,name='manage_doc_history'), # 管理文档历史版本
path('move_doc/', views.move_doc, name='move_doc'), # 移动文档
path('doc_recycle/', views.doc_recycle,name='doc_recycle'), # 文档回收站
path('fast_pub_doc/',views.fast_publish_doc,name='fast_pub_doc'), # 一键发布文档
#################文档模板相关
path('manage_doctemp/',views.manage_doctemp,name='manage_doctemp'), # 文档模板列表
path('create_doctemp/',views.create_doctemp,name="create_doctemp"), # 创建文档模板

View File

@ -213,7 +213,7 @@ def project_index(request,pro_id):
else:
colla_user = 0
# 获取问价文集前台下载权限
# 获取文集前台下载权限
try:
allow_download = ProjectReport.objects.get(project=project)
except ObjectDoesNotExist:
@ -687,7 +687,7 @@ def modify_doc(request,doc_id):
return JsonResponse({'status':False,'data':'请求出错'})
# 删除文档
# 删除文档 - 软删除 - 进入回收站
@login_required()
@require_http_methods(["POST"])
def del_doc(request):
@ -698,14 +698,27 @@ def del_doc(request):
# 查询文档
try:
doc = Doc.objects.get(id=doc_id)
project = Project.objects.get(id=doc.top_doc) # 查询文档所属的文集
# 获取文档所属文集的协作信息
pro_colla = ProjectCollaborator.objects.filter(project=project,user=request.user) #
if pro_colla.exists():
colla_user_role = pro_colla[0].role
else:
colla_user_role = 0
except ObjectDoesNotExist:
return JsonResponse({'status': False, 'data': '文档不存在'})
# 如果请求用户为文档创建者或为高级权限的协作者,可以删除
if request.user == doc.create_user:
# 删除
doc.delete()
# 修改其子文档为顶级文档
Doc.objects.filter(parent_doc=doc_id).update(parent_doc=0)
# 如果请求用户为文档创建者、高级权限的协作者、文集的创建者,可以删除
if (request.user == doc.create_user) or (colla_user_role == 1) or (request.user == project.create_user):
# 修改状态为删除
doc.status = 3
doc.modify_time = datetime.datetime.now()
doc.save()
# 修改其下级所有文档状态为删除
chr_doc = Doc.objects.filter(parent_doc=doc_id) # 获取下级文档
chr_doc_ids = chr_doc.values_list('id',flat=True) # 提取下级文档的ID
chr_doc.update(status=3,modify_time=datetime.datetime.now()) # 修改下级文档的状态为删除
Doc.objects.filter(parent_doc__in=chr_doc_ids).update(status=3,modify_time=datetime.datetime.now()) # 修改下级文档的下级文档状态
return JsonResponse({'status': True, 'data': '删除完成'})
else:
return JsonResponse({'status': False, 'data': '非法请求'})
@ -734,11 +747,15 @@ def manage_doc(request):
# 无搜索 - 无状态 - 无文集
if (is_search is False) and (is_status == 'all') and (is_project is False):
doc_list = Doc.objects.filter(create_user=request.user).order_by('-modify_time')
doc_list = Doc.objects.filter(create_user=request.user,status__in=[0,1]).order_by('-modify_time')
# 无搜索 - 无状态 - 有文集
elif (is_search is False) and (is_status == 'all') and (is_project):
doc_list = Doc.objects.filter(create_user=request.user,top_doc=int(doc_pro_id)).order_by('-modify_time')
doc_list = Doc.objects.filter(
create_user=request.user,
top_doc=int(doc_pro_id),
status__in=[0,1]
).order_by('-modify_time')
# 无搜索 - 有状态 - 无文集
elif (is_search is False) and (is_status != 'all') and (is_project is False):
@ -749,7 +766,7 @@ def manage_doc(request):
elif doc_status == 'draft':
doc_list = Doc.objects.filter(create_user=request.user, status=0).order_by('-modify_time')
else:
doc_list = Doc.objects.filter(create_user=request.user).order_by('-modify_time')
doc_list = Doc.objects.filter(create_user=request.user, status__in=[0,1]).order_by('-modify_time')
# 无搜索 - 有状态 - 有文集
elif (is_search is False) and (is_status != 'all') and (is_project):
@ -768,20 +785,25 @@ def manage_doc(request):
top_doc = int(doc_pro_id)
).order_by('-modify_time')
else:
doc_list = Doc.objects.filter(create_user=request.user,top_doc=int(doc_pro_id)).order_by('-modify_time')
doc_list = Doc.objects.filter(
create_user=request.user,
top_doc=int(doc_pro_id),
status__in=[0,1]
).order_by('-modify_time')
# 有搜索 - 无状态 - 无文集
elif (is_search) and (is_status == 'all') and (is_project is False):
doc_list = Doc.objects.filter(
Q(content__icontains=search_kw) | Q(name__icontains=search_kw), # 文本或文档标题包含搜索词
create_user=request.user,
status__in=[0,1]
).order_by('-modify_time')
# 有搜索 - 无状态 - 有文集
elif (is_search) and (is_status == 'all') and (is_project):
doc_list = Doc.objects.filter(
Q(content__icontains=search_kw) | Q(name__icontains=search_kw), # 文本或文档标题包含搜索词
create_user=request.user,top_doc=int(doc_pro_id)
create_user=request.user,top_doc=int(doc_pro_id),status__in=[0,1]
).order_by('-modify_time')
# 有搜索 - 有状态 - 无文集
@ -801,7 +823,7 @@ def manage_doc(request):
else:
doc_list = Doc.objects.filter(
Q(content__icontains=search_kw) | Q(name__icontains=search_kw), # 文本或文档标题包含搜索词
create_user=request.user,
create_user=request.user,status__in=[0,1]
).order_by('-modify_time')
# 有搜索 - 有状态 - 有文集
@ -824,7 +846,7 @@ def manage_doc(request):
doc_list = Doc.objects.filter(
Q(content__icontains=search_kw) | Q(name__icontains=search_kw), # 文本或文档标题包含搜索词
create_user=request.user,
top_doc=int(doc_pro_id)
top_doc=int(doc_pro_id),status__in=[0,1]
).order_by('-modify_time')
# 文集列表
@ -927,7 +949,6 @@ def move_doc(request):
return JsonResponse({'status':False,'data':'移动类型错误'})
# 查看对比文档历史版本
@login_required()
@require_http_methods(['GET',"POST"])
@ -998,6 +1019,105 @@ def manage_doc_history(request,doc_id):
return JsonResponse({'status':False,'data':'出现异常'})
# 文档回收站
@login_required()
@require_http_methods(['GET','POST'])
def doc_recycle(request):
if request.method == 'GET':
# 获取状态为删除的文档
doc_list = Doc.objects.filter(status=3,create_user=request.user).order_by('-modify_time')
# 分页处理
paginator = Paginator(doc_list, 15)
page = request.GET.get('page', 1)
try:
docs = paginator.page(page)
except PageNotAnInteger:
docs = paginator.page(1)
except EmptyPage:
docs = paginator.page(paginator.num_pages)
return render(request,'app_doc/manage_doc_recycle.html',locals())
elif request.method == 'POST':
try:
# 获取参数
doc_id = request.POST.get('doc_id', None) # 文档ID
types = request.POST.get('type',None) # 操作类型
if doc_id:
# 查询文档
try:
doc = Doc.objects.get(id=doc_id)
project = Project.objects.get(id=doc.top_doc) # 查询文档所属的文集
# 获取文档所属文集的协作信息
pro_colla = ProjectCollaborator.objects.filter(project=project, user=request.user) #
if pro_colla.exists():
colla_user_role = pro_colla[0].role
else:
colla_user_role = 0
except ObjectDoesNotExist:
return JsonResponse({'status': False, 'data': '文档不存在'})
# 如果请求用户为文档创建者、高级权限的协作者、文集的创建者,可以操作
if (request.user == doc.create_user) or (colla_user_role == 1) or (request.user == project.create_user):
# 还原文档
if types == 'restore':
# 修改状态为草稿
doc.status = 0
doc.modify_time = datetime.datetime.now()
doc.save()
# 删除文档
elif types == 'del':
# 删除文档
doc.delete()
else:
return JsonResponse({'status':False,'data':'无效请求'})
return JsonResponse({'status': True, 'data': '删除完成'})
else:
return JsonResponse({'status': False, 'data': '非法请求'})
# 清空回收站
elif types == 'empty':
docs = Doc.objects.filter(status=3,create_user=request.user)
docs.delete()
return JsonResponse({'status': True, 'data': '清空成功'})
# 还原回收站
elif types == 'restoreAll':
Doc.objects.filter(status=3,create_user=request.user).update(status=0)
return JsonResponse({'status': True, 'data': '还原成功'})
else:
return JsonResponse({'status': False, 'data': '参数错误'})
except Exception as e:
logger.exception("处理文档出错")
return JsonResponse({'status': False, 'data': '请求出错'})
# 一键发布文档
@login_required()
@require_http_methods(['POST'])
def fast_publish_doc(request):
doc_id = request.POST.get('doc_id',None)
# 查询文档
try:
doc = Doc.objects.get(id=doc_id)
project = Project.objects.get(id=doc.top_doc) # 查询文档所属的文集
# 获取文档所属文集的协作信息
pro_colla = ProjectCollaborator.objects.filter(project=project, user=request.user) #
if pro_colla.exists():
colla_user_role = pro_colla[0].role
else:
colla_user_role = 0
except ObjectDoesNotExist:
return JsonResponse({'status': False, 'data': '文档不存在'})
# 判断请求者是否有权限(文档创建者、文集创建者、文集高级协作者)
# 如果请求用户为文档创建者、高级权限的协作者、文集的创建者,可以删除
if (request.user == doc.create_user) or (colla_user_role == 1) or (request.user == project.create_user):
try:
doc.status = 1
doc.modify_time = datetime.datetime.now()
doc.save()
return JsonResponse({'status':True,'data':'发布成功'})
except:
logger.exception("文档一键发布失败")
return JsonResponse({'status':False,'data':'发布失败'})
else:
return JsonResponse({'status':False,'data':'非法请求'})
# 创建文档模板
@login_required()
@require_http_methods(['GET',"POST"])
@ -1138,7 +1258,7 @@ def get_pro_doc(request):
pro_id = request.POST.get('pro_id','')
if pro_id != '':
# 获取文集所有文档的id、name和parent_doc3个字段
doc_list = Doc.objects.filter(top_doc=int(pro_id)).values_list('id','name','parent_doc').order_by('parent_doc')
doc_list = Doc.objects.filter(top_doc=int(pro_id),status=1).values_list('id','name','parent_doc').order_by('parent_doc')
item_list = []
# 遍历文档
for doc in doc_list:

View File

@ -426,7 +426,8 @@
imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
imageUploadURL : "{% url 'upload_doc_img' %}",
onchange:function(){
md_changed = true
md_changed = true;
//console.log("字符数:",this.getMarkdown().length)
},
onload : function() {
// this.insertValue(" ")

View File

@ -95,11 +95,14 @@
<li class="layui-nav-item layui-nav-itemed">
<a href="{% url 'manage_image' %}"><i class="layui-icon layui-icon-picture"></i> 图片素材管理</a>
</li>
<li class="layui-nav-item layui-nav-itemed">
<li class="layui-nav-item layui-nav-itemed">
<a href="{% url 'manage_attachment' %}"><i class="layui-icon layui-icon-export"></i> 附件管理</a>
</li>
<li class="layui-nav-item layui-nav-itemed">
<a href="{% url 'manage_token' %}"><i class="layui-icon layui-icon-key"></i> 用户Token管理</a>
<a href="{% url 'manage_token' %}"><i class="layui-icon layui-icon-key"></i> Token管理</a>
</li>
<li class="layui-nav-item layui-nav-itemed">
<a href="{% url 'doc_recycle' %}"><i class="layui-icon layui-icon-delete"></i> 文档回收站</a>
</li>
<li class="layui-nav-item layui-nav-itemed">
<a href="http://mrdoc.zmister.com/project-54/" target="_blank"><i class="layui-icon layui-icon-help"></i> 使用手册</a>

View File

@ -82,6 +82,7 @@
{% else %}
<td>
<a href="{% url 'modify_doc' doc.id %}" target="_blank" title="修改文档:{{doc.name}}">{{ doc.name }} <i class="layui-icon layui-icon-menu-fill" style="color:#FF5722"></i></a>
<button class="layui-btn layui-btn-xs layui-btn-normal" onclick="fastPubDoc('{{doc.id}}')">一键发布</button>
</td>
{% endif %}
<td>{{ doc.parent_doc|get_doc_parent }}</td>
@ -159,7 +160,24 @@
})
},
});
}
};
// 一键发布文档
fastPubDoc = function(doc_id){
data = {
'doc_id':doc_id,
}
$.post("{% url 'fast_pub_doc' %}",data,function(r){
layer.closeAll('loading')
if(r.status){
//发布成功
window.location.reload();
}else{
//发布失败,提示
console.log(r)
layer.msg(r.data)
}
})
};
//筛选文集
form.on('select(project)', function(data){
console.log('选择文集:',data.value); //得到被选中的值

View File

@ -0,0 +1,200 @@
{% extends 'app_doc/manage_base.html' %}
{% load staticfiles %}
{% block title %}文档回收站管理{% endblock %}
{% block content %}
<div class="layui-card-header" style="margin-bottom: 10px;">
<div class="layui-row">
<span style="font-size:18px;">文档回收站管理</span>
</div>
</div>
<div class="layui-row">
<button class="layui-btn layui-btn-normal layui-btn-xs" onclick="emptyDoc()"><i class="layui-icon layui-icon-delete" ></i>清空回收站</button>
<button class="layui-btn layui-btn-normal layui-btn-xs" onclick="restoreAll()"><i class="layui-icon layui-icon-refresh"></i>还原所有</button>
</div>
<div class="layui-row" lay-skin="">
<table class="layui-table" id="doctemp-list" lay-skin="" lay-even>
<thead>
<tr>
<th>文档名称</th>
<th>上级文档</th>
<th>所属文集</th>
<th>创建时间</th>
<th>删除时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% load doc_filter %}
{% for doc in docs %}
<tr>
<td>{{ doc.name }}</td>
<td>{{ doc.parent_doc|get_doc_parent }}</td>
<td>
{{ doc.top_doc|is_colla_pro:doc.create_user }}{{ doc.top_doc|get_doc_top }}
</td>
<td>{{ doc.create_time }}</td>
<td>{{ doc.modify_time }}</td>
<td>
<button class="layui-btn layui-btn-xs layui-btn-normal" onclick="restoreDoc('{{doc.id}}')">
<i class="layui-icon layui-icon-refresh"></i>还原
</button>
<button onclick="delDoc('{{doc.id}}');" class="layui-btn layui-btn-xs layui-btn-normal">
<i class="layui-icon layui-icon-delete"></i>删除
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- 分页 -->
<div class="layui-row">
<div class="layui-box layui-laypage layui-laypage-default">
<!-- 上一页 -->
{% if docs.has_previous %}
<a href="?page={{ docs.previous_page_number }}&kw={{docs.kw}}&status={{docs.status}}&pid={{docs.pid}}" class="layui-btn layui-btn-xs layui-btn-normal">上一页</a>
{% else %}
<a href="javascript:;" class="layui-btn layui-btn-xs layui-btn-disabled">上一页</a>
{% endif %}
<!-- 当前页 -->
<span class="layui-laypage-curr">
<em class="layui-laypage-em"></em>
<em>{{ docs.number }}/{{ docs.paginator.num_pages }}</em>
</span>
<!-- 下一页 -->
{% if docs.has_next %}
<a href="?page={{ docs.next_page_number }}&kw={{docs.kw}}&status={{docs.status}}&pid={{docs.pid}}" class="layui-btn layui-btn-xs layui-btn-normal">下一页</a>
{% else %}
<a class="layui-btn layui-btn-xs layui-btn-disabled">下一页</a>
{% endif %}
</div>
</div>
{% endblock %}
{% block custom_script %}
<script>
// 删除文档
delDoc = function(doc_id){
layer.open({
type:1,
title:'删除文档',
area:'300px;',
id:'delPro',//配置ID
content:'<div style="margin-left:10px;">警告:此操作将彻底删除此文档!</div>',
btn:['确定','取消'], //添加按钮
btnAlign:'c', //按钮居中
yes:function (index,layero) {
layer.load(1);
data = {
'doc_id':doc_id,
'type':'del',
}
$.post("{% url 'doc_recycle' %}",data,function(r){
layer.closeAll('loading')
if(r.status){
//删除成功
window.location.reload();
//layer.close(index)
}else{
//删除失败,提示
console.log(r)
layer.msg(r.data)
}
})
},
});
};
// 还原文档
restoreDoc = function(doc_id){
layer.open({
type:1,
title:'还原文档',
area:'300px;',
id:'delPro',//配置ID
content:'<div style="margin-left:10px;">提示:文档将还原为草稿状态!</div>',
btn:['确定','取消'], //添加按钮
btnAlign:'c', //按钮居中
yes:function (index,layero) {
layer.load(1);
data = {
'doc_id':doc_id,
'type':'restore',
}
$.post("{% url 'doc_recycle' %}",data,function(r){
layer.closeAll('loading')
if(r.status){
//还原成功
window.location.reload();
//layer.close(index)
}else{
//还原失败,提示
console.log(r)
layer.msg(r.data)
}
})
},
});
};
// 清空回收站
emptyDoc = function(){
layer.open({
type:1,
title:'清空回收站',
area:'300px;',
id:'delPro',//配置ID
content:'<div style="margin-left:10px;">警告:此操作将彻底删除回收站所有文档!</div>',
btn:['确定','取消'], //添加按钮
btnAlign:'c', //按钮居中
yes:function (index,layero) {
layer.load(1);
data = {
'type':'empty',
}
$.post("{% url 'doc_recycle' %}",data,function(r){
layer.closeAll('loading')
if(r.status){
//清空成功
window.location.reload();
//layer.close(index)
}else{
//清空失败,提示
console.log(r)
layer.msg(r.data)
}
})
},
});
};
// 还原所有
restoreAll = function(){
layer.open({
type:1,
title:'还原回收站',
area:'300px;',
id:'delPro',//配置ID
content:'<div style="margin-left:10px;">警告:此操作将还原回收站所有文档为草稿状态!</div>',
btn:['确定','取消'], //添加按钮
btnAlign:'c', //按钮居中
yes:function (index,layero) {
layer.load(1);
data = {
'type':'restoreAll',
}
$.post("{% url 'doc_recycle' %}",data,function(r){
layer.closeAll('loading')
if(r.status){
//还原成功
window.location.reload();
//layer.close(index)
}else{
//还原失败,提示
console.log(r)
layer.msg(r.data)
}
})
},
});
};
</script>
{% endblock %}

View File

@ -170,7 +170,7 @@
//zoomable:false,//缩放
button:false,//关闭按钮
};
const viewer = new Viewer(document.getElementById('images'), options);
var viewer = new Viewer(document.getElementById('images'), options);
//创建图片分组
createImgGroup = function(){
layer.open({