From d080ed7760dd6410b7c384b055e9da2e32f6a7f1 Mon Sep 17 00:00:00 2001 From: yangjian Date: Sat, 13 Jun 2020 20:41:17 +0800 Subject: [PATCH] =?UTF-8?q?0.5.3=E7=89=88=E6=9C=AC=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E8=AE=B0=E5=BD=95=E8=AF=A6=E8=A7=81changes.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 6 + MrDoc/settings.py | 2 +- README.md | 4 +- README_ENG.md | 2 +- app_api/views_app.py | 24 ++- app_doc/models.py | 2 +- app_doc/templatetags/project_filter.py | 2 +- app_doc/urls.py | 2 + app_doc/views.py | 154 +++++++++++++++-- template/app_doc/create_base_2.html | 3 +- template/app_doc/manage_base.html | 7 +- template/app_doc/manage_doc.html | 20 ++- template/app_doc/manage_doc_recycle.html | 200 +++++++++++++++++++++++ template/app_doc/manage_image.html | 2 +- 14 files changed, 397 insertions(+), 33 deletions(-) create mode 100644 template/app_doc/manage_doc_recycle.html diff --git a/CHANGES.md b/CHANGES.md index bb214df..8c82f78 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,12 @@ - 添加编辑器引用JS的版本号 - 修复开启全站登录时登录注册页面样式丢失的问题 - 完善图片上传对图片大写后缀格式的支持 +- 优化个人中心文档管理,支持按文集筛选文档 +- 优化移动端阅读体验 +- 修复编辑器行号挤压的问题 +- 完善删除文档时的权限验证 +- 文档页面添加图片放大镜功能 +- 添加文档回收站功能 ### v0.5.2 2020-05-24 diff --git a/MrDoc/settings.py b/MrDoc/settings.py index 3552bbe..e606b86 100644 --- a/MrDoc/settings.py +++ b/MrDoc/settings.py @@ -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 = ['*'] diff --git a/README.md b/README.md index 8008914..51d8eaf 100644 --- a/README.md +++ b/README.md @@ -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不排除有未知的异常。 ## 简明安装教程 diff --git a/README_ENG.md b/README_ENG.md index fb7ed74..c6841fc 100644 --- a/README_ENG.md +++ b/README_ENG.md @@ -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) diff --git a/app_api/views_app.py b/app_api/views_app.py index 90db8bc..0249f2c 100644 --- a/app_api/views_app.py +++ b/app_api/views_app.py @@ -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': '非法请求'}) diff --git a/app_doc/models.py b/app_doc/models.py index fb4dcd4..da4e18b 100644 --- a/app_doc/models.py +++ b/app_doc/models.py @@ -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): diff --git a/app_doc/templatetags/project_filter.py b/app_doc/templatetags/project_filter.py index dadff81..4b1982d 100644 --- a/app_doc/templatetags/project_filter.py +++ b/app_doc/templatetags/project_filter.py @@ -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') diff --git a/app_doc/urls.py b/app_doc/urls.py index 64917cc..ab2edfe 100644 --- a/app_doc/urls.py +++ b/app_doc/urls.py @@ -28,6 +28,8 @@ urlpatterns = [ path('diff_doc/-/',views.diff_doc,name='diff_doc'), # 对比文档历史版本 path('manage_doc_history//',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"), # 创建文档模板 diff --git a/app_doc/views.py b/app_doc/views.py index fa2133e..c7b5d14 100644 --- a/app_doc/views.py +++ b/app_doc/views.py @@ -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: diff --git a/template/app_doc/create_base_2.html b/template/app_doc/create_base_2.html index 096ba0e..4883712 100644 --- a/template/app_doc/create_base_2.html +++ b/template/app_doc/create_base_2.html @@ -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(" ") diff --git a/template/app_doc/manage_base.html b/template/app_doc/manage_base.html index b15b2f3..cb71844 100644 --- a/template/app_doc/manage_base.html +++ b/template/app_doc/manage_base.html @@ -95,11 +95,14 @@
  • 图片素材管理
  • -
  • +
  • 附件管理
  • - 用户Token管理 + Token管理 +
  • +
  • + 文档回收站
  • 使用手册 diff --git a/template/app_doc/manage_doc.html b/template/app_doc/manage_doc.html index 80eb124..5d507a7 100644 --- a/template/app_doc/manage_doc.html +++ b/template/app_doc/manage_doc.html @@ -82,6 +82,7 @@ {% else %} {{ doc.name }} + {% endif %} {{ doc.parent_doc|get_doc_parent }} @@ -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); //得到被选中的值 diff --git a/template/app_doc/manage_doc_recycle.html b/template/app_doc/manage_doc_recycle.html new file mode 100644 index 0000000..f4246ec --- /dev/null +++ b/template/app_doc/manage_doc_recycle.html @@ -0,0 +1,200 @@ +{% extends 'app_doc/manage_base.html' %} +{% load staticfiles %} +{% block title %}文档回收站管理{% endblock %} +{% block content %} +
    +
    + 文档回收站管理 +
    +
    + +
    + + + +
    +
    + + + + + + + + + + + + + {% load doc_filter %} + {% for doc in docs %} + + + + + + + + + {% endfor %} + +
    文档名称上级文档所属文集创建时间删除时间操作
    {{ doc.name }}{{ doc.parent_doc|get_doc_parent }} + {{ doc.top_doc|is_colla_pro:doc.create_user }}{{ doc.top_doc|get_doc_top }} + {{ doc.create_time }}{{ doc.modify_time }} + + +
    +
    + +
    +
    + + {% if docs.has_previous %} + 上一页 + {% else %} + 上一页 + {% endif %} + + + + {{ docs.number }}/{{ docs.paginator.num_pages }} + + + {% if docs.has_next %} + 下一页 + {% else %} + 下一页 + {% endif %} +
    +
    +{% endblock %} +{% block custom_script %} + +{% endblock %} \ No newline at end of file diff --git a/template/app_doc/manage_image.html b/template/app_doc/manage_image.html index c3d83f6..f165dda 100644 --- a/template/app_doc/manage_image.html +++ b/template/app_doc/manage_image.html @@ -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({