diff --git a/CHANGES.md b/CHANGES.md index 4579470..9407b30 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,16 @@ ## 版本更新记录 +### v.0.5.0 2020-05-02 + +- MrDoc正式中文取名:觅道文档; +- 文档编辑器添加Markdown折叠功能; +- 思维导图支持图形高度设置; +- 优化文集导出EPUB文件功能; +- 新增PDF文件导出功能; +- 新增一个广告位; +- 优化文集名称字符验证; + + ### v0.4.2 2020-04-20 - 添加思维导图功能的支持,可以在文档编辑器通过图标和`mindmap`标识代码块来创建脑图; diff --git a/MrDoc/settings.py b/MrDoc/settings.py index 974d434..2485796 100644 --- a/MrDoc/settings.py +++ b/MrDoc/settings.py @@ -1,3 +1,4 @@ +# coding:utf-8 """ Django settings for MrDoc project. @@ -25,7 +26,7 @@ SECRET_KEY = '5&71mt9@^58zdg*_!t(x6g14q*@84d%ptr%%s6e0l50zs0we3d' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False -VERSIONS = '0.4.2' +VERSIONS = '0.5.0' ALLOWED_HOSTS = ['*'] diff --git a/README.md b/README.md index 5da16f2..6729932 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## MrDoc - 记录文档,汇聚思想 +## 觅道文档MrDoc - 记录文档,汇聚思想  @@ -28,13 +28,13 @@ - 支持**思维导图**,以Markdown的语法创建思维导图 - 支持流程图、时序图的绘制; - 两栏式**文档阅读**页面、三级目录层级显示,文档阅读字体缩放,字体类型切换,页面社交分享,移动端阅读优化; - - 支持文集后台**导出打包**`markdown`文本格式`.md`文件、前台导出为`EPUB`等格式文件; + - 支持文集**导出打包**,包括`markdown`文本格式`.md`文件、`EPUB`电子书格式文件和PDF格式文件; - 基于文集进行**文档权限**控制,提供公开、私密、指定用户可见、访问码可见4种权限模式; - 支持基于账户的**`API`接口**,可以借助账户`token`通过`API`获取文集、上传图片和创建文档; - 支持**文集协作**功能,一个文集可以拥有一个创建者和多个协作者,可灵活选择协作权限; - 支持**文档历史版本**功能,可以查看和对比历史版本与现有版本的差异,恢复某个历史版本为当前版本; -当前版本为:**v0.4.2**,版本发布时间为**2020-04-20** +当前版本为:**v0.5.0**,版本发布时间为**2020-05-02** 完整更新记录详见:[CHANGES.md](./CHANGES.md) @@ -57,7 +57,10 @@ pip install -r requirements.txt 默认情况下,MrDoc使用Django的SQLite数据库,如果你使用Sqlite数据库,则无需另外配置数据库。 -如果有配置其他数据库的需求,请在/MrDoc/MrDoc目录下打开settings.py文件,在约80行的位置,将如下代码: +如果有配置其他数据库的需求,请首先按照Django官方的[数据库支持说明](https://docs.djangoproject.com/zh-hans/2.2/ref/databases/),安装特定数据库的Python绑定库, + +然后在/MrDoc/MrDoc目录下打开settings.py文件,在约80行的位置,将如下代码: + ```python DATABASES = { 'default': { @@ -66,7 +69,9 @@ DATABASES = { } } ``` + 按照自己数据库的信息,将其修改如下格式,下面以MySQL为例: + ```python DATABASES = { 'default': { @@ -79,6 +84,7 @@ DATABASES = { } } ``` + ### 3、初始化数据库 在安装完所需的第三方库并配置好数据库信息之后,我们需要对数据库进行初始化。 diff --git a/app_admin/views.py b/app_admin/views.py index 18534b5..8b58400 100644 --- a/app_admin/views.py +++ b/app_admin/views.py @@ -12,19 +12,25 @@ import json,datetime,hashlib,random from app_doc.models import * from app_admin.models import * from app_admin.utils import * +import traceback # 返回验证码图片 def check_code(request): - import io - from . import check_code as CheckCode - stream = io.BytesIO() - # img图片对象,code在图像中写的内容 - img, code = CheckCode.create_validate_code() - img.save(stream, "png") - # 图片页面中显示,立即把session中的CheckCode更改为目前的随机字符串值 - request.session["CheckCode"] = code - return HttpResponse(stream.getvalue()) + try: + import io + from . import check_code as CheckCode + stream = io.BytesIO() + # img图片对象,code在图像中写的内容 + img, code = CheckCode.create_validate_code() + img.save(stream, "png") + # 图片页面中显示,立即把session中的CheckCode更改为目前的随机字符串值 + request.session["CheckCode"] = code + return HttpResponse(stream.getvalue()) + except Exception as e: + if settings.DEBUG: + print(traceback.print_exc()) + return HttpResponse("请求异常:{}".format(repr(e))) # 登录视图 @@ -54,6 +60,8 @@ def log_in(request): errormsg = '用户名或密码错误!' return render(request, 'login.html', locals()) except Exception as e: + if settings.DEBUG: + print(traceback.print_exc()) return HttpResponse('请求出错') @@ -136,8 +144,8 @@ def log_out(request): try: logout(request) except Exception as e: - print(e) - # logger.error(e) + if settings.DEBUG: + print(traceback.print_exc()) return redirect(request.META['HTTP_REFERER']) @@ -165,7 +173,8 @@ def forget_pwd(request): errormsg = "验证码已过期" return render(request, 'forget_pwd.html', locals()) except Exception as e: - print(repr(e)) + if settings.DEBUG: + print(traceback.print_exc()) errormsg = "验证码错误" return render(request,'forget_pwd.html',locals()) @@ -514,7 +523,8 @@ def admin_setting(request): if types == 'basic': close_register = request.POST.get('close_register',None) # 禁止注册 static_code = request.POST.get('static_code',None) # 统计代码 - ad_code = request.POST.get('ad_code',None) # 广告代码 + ad_code = request.POST.get('ad_code',None) # 广告位1 + ad_code_2 = request.POST.get('ad_code_2',None) # 广告位2 beian_code = request.POST.get('beian_code',None) # 备案号 enbale_email = request.POST.get("enable_email",None) # 启用邮箱 enable_register_code = request.POST.get('enable_register_code',None) # 注册邀请码 @@ -534,6 +544,10 @@ def admin_setting(request): name = 'ad_code', defaults={'value':ad_code,'types':'basic'} ) + SysSetting.objects.update_or_create( + name='ad_code_2', + defaults={'value': ad_code_2, 'types': 'basic'} + ) # 更新备案号 SysSetting.objects.update_or_create( name='beian_code', diff --git a/app_doc/migrations/0020_projectreportfile.py b/app_doc/migrations/0020_projectreportfile.py new file mode 100644 index 0000000..d763c11 --- /dev/null +++ b/app_doc/migrations/0020_projectreportfile.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.12 on 2020-04-26 11:15 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('app_doc', '0019_dochistory_create_user'), + ] + + operations = [ + migrations.CreateModel( + name='ProjectReportFile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('file_type', models.CharField(choices=[('epub', 'epub'), ('pdf', 'pdf'), ('docx', 'docx')], max_length=10, verbose_name='文件类型')), + ('file_name', models.CharField(max_length=100, verbose_name='文件名称')), + ('file_path', models.CharField(max_length=250, verbose_name='文件路径')), + ('create_time', models.DateTimeField(auto_now_add=True)), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app_doc.Project')), + ], + options={ + 'verbose_name': '附件管理', + 'verbose_name_plural': '附件管理', + }, + ), + ] diff --git a/app_doc/migrations/0021_projectreport_allow_pdf.py b/app_doc/migrations/0021_projectreport_allow_pdf.py new file mode 100644 index 0000000..80dd2d2 --- /dev/null +++ b/app_doc/migrations/0021_projectreport_allow_pdf.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.12 on 2020-05-01 10:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app_doc', '0020_projectreportfile'), + ] + + operations = [ + migrations.AddField( + model_name='projectreport', + name='allow_pdf', + field=models.IntegerField(default=0, verbose_name='前台导出PDF'), + ), + ] diff --git a/app_doc/models.py b/app_doc/models.py index 9c05ec2..fb4dcd4 100644 --- a/app_doc/models.py +++ b/app_doc/models.py @@ -106,6 +106,7 @@ class ProjectReport(models.Model): project = models.OneToOneField(Project,unique=True,on_delete=models.CASCADE) # 允许导出,默认为0-允许,1-不允许 allow_epub = models.IntegerField(default=0,verbose_name="前台导出EPUB") + allow_pdf = models.IntegerField(default=0, verbose_name="前台导出PDF") def __str__(self): return self.project.name @@ -114,6 +115,22 @@ class ProjectReport(models.Model): verbose_name = '文集导出' verbose_name_plural = verbose_name + +# 文集导出文集模型 +class ProjectReportFile(models.Model): + project = models.ForeignKey(Project, on_delete=models.CASCADE) # 外键关联文集 + file_type = models.CharField(choices=(('epub', 'epub'), ('pdf', 'pdf'), ('docx', 'docx')), verbose_name='文件类型',max_length=10) + file_name = models.CharField(max_length=100, verbose_name='文件名称') + file_path = models.CharField(max_length=250, verbose_name='文件路径') + create_time = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.file_name + + class Meta: + verbose_name = '附件管理' + verbose_name_plural = verbose_name + # 图片分组模型 class ImageGroup(models.Model): user = models.ForeignKey(User,on_delete=models.CASCADE) diff --git a/app_doc/report_utils.py b/app_doc/report_utils.py index 021e9b8..c284e02 100644 --- a/app_doc/report_utils.py +++ b/app_doc/report_utils.py @@ -21,6 +21,125 @@ django.setup() from app_doc.models import * import traceback import time +from pyppeteer import launch +import asyncio +# import PyPDF2 +# from pdfminer import high_level + +# 动态脑图转静态图片 +def genera_mindmap_img(html_path,img_path): + async def main(): + browser = await launch(headless=True,handleSIGINT=False,handleSIGTERM=False,handleSIGHUP=False) + page = await browser.newPage() + await page.goto('file://' + html_path, {'waitUntil': 'networkidle0'}) + element = await page.querySelector('.mindmap') + await element.screenshot({'type': 'jpeg', 'quality': 100, 'path': img_path}) + await browser.close() + + # asyncio.new_event_loop().run_until_complete(main()) + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop = asyncio.get_event_loop() + try: + loop.run_until_complete(main()) + finally: + loop.close() + + +# 公式转图片 +def genera_tex_img(html_path,img_path): + async def main(): + browser = await launch(headless=True,handleSIGINT=False,handleSIGTERM=False,handleSIGHUP=False) + page = await browser.newPage() + await page.goto('file://' + html_path, {'waitUntil': 'networkidle0'}) + element = await page.querySelector('.editormd-tex') + await element.screenshot({'type': 'jpeg', 'quality': 100, 'path': img_path}) + await browser.close() + + # asyncio.new_event_loop().run_until_complete(main()) + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop = asyncio.get_event_loop() + try: + loop.run_until_complete(main()) + finally: + loop.close() + +# 流程图转图片 +def genera_flowchart_img(html_path,img_path): + async def main(): + browser = await launch(headless=True,handleSIGINT=False,handleSIGTERM=False,handleSIGHUP=False) + page = await browser.newPage() + await page.goto('file://' + html_path, {'waitUntil': 'networkidle0'}) + element = await page.querySelector('.flowchart') + await element.screenshot({'type': 'jpeg', 'quality': 100, 'path': img_path}) + await browser.close() + + # asyncio.new_event_loop().run_until_complete(main()) + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop = asyncio.get_event_loop() + try: + loop.run_until_complete(main()) + finally: + loop.close() + +# 时序图转图片 +def genera_seque_img(html_path,img_path): + async def main(): + browser = await launch(headless=True,handleSIGINT=False,handleSIGTERM=False,handleSIGHUP=False) + page = await browser.newPage() + await page.goto('file://' + html_path, {'waitUntil': 'networkidle0'}) + element = await page.querySelector('.sequence-diagram') + await element.screenshot({'type': 'jpeg', 'quality': 100, 'path': img_path}) + await browser.close() + + # asyncio.new_event_loop().run_until_complete(main()) + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop = asyncio.get_event_loop() + try: + loop.run_until_complete(main()) + finally: + loop.close() + +# HTML转PDF +def html_to_pdf(html_path,pdf_path): + async def main(): + browser = await launch( + headless=True, + handleSIGINT=False, + handleSIGTERM=False, + handleSIGHUP=False, + ignoreHTTPSErrors = True, + ) + page = await browser.newPage() + await page.goto('file://' + html_path, {'waitUntil': 'networkidle0'}) + await page.pdf({ + 'path':pdf_path, + 'format':'A4', + 'displayHeaderFooter':True, + 'headerTemplate':'
', + 'footerTemplate':'