新增备份恢复功能,支持sqlite迁移到mysql

This commit is contained in:
sftang
2022-09-22 15:26:03 +08:00
parent dd90ebd1b4
commit ddff62b4d1
18 changed files with 239 additions and 264 deletions

View File

@@ -58,7 +58,7 @@ pip install --no-cache-dir -r requirements.txt -i https://pypi.mirrors.ustc.edu.
```shell
1、开放计划任务配置
2、支持签到失败重试(忽略cookie失效站点),建议失败重试时间设置5分钟以上
2、支持签到失败重试(忽略cookie失效站点),建议失败重试时间设置5分钟以上(由于sqlite单进程模式因此重试和签到不要放在同一个分钟或小时建议差距2分钟以上)
```
# v2.0 说明
@@ -151,6 +151,15 @@ root 数据库用户名
3306 数据库端口
pthelper 数据库名称
```
#sqlite迁移到mysql步骤
```shell
1、系统管理---》备份恢复,点击备份
2、关闭docker
3、修改conf下的env.prod配置你使用的mysql,并创建你配置的数据库
4、启动docker
5、系统管理---》备份恢复,选择要恢复的文件,然后点击恢复
6、刷新页面重新登录系统
```
## 感谢

View File

@@ -1,74 +1,136 @@
<!-- 正文开始 -->
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<!-- 表单开始 -->
<form class="layui-form" id="backuploadForm" lay-filter="backuploadForm" method="POST">{% csrf_token %}
<input name="id" type="hidden" value="{{id}}"/>
<div class="layui-form toolbar">
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-filter="backupSubmit" lay-submit>&emsp;备份&emsp;</button>
<button class="layui-btn layui-btn-normal" lay-filter="loadSubmit" lay-submit>&emsp;恢复&emsp;</button>
<div class="layui-inline">
<button id="exportSubmit" class="layui-btn icon-btn"><i class="layui-icon layui-icon-export"></i>备份数据</button>
</div>
</div>
</form>
<!-- //表单结束 -->
</div>
</div>
</div>
<table class="layui-table" id="backuplistTable" lay-filter="backuplistTable"></table>
<div class="layui-card">
<div class="layui-card-header">说明</div>
<div class="layui-card-body">
<blockquote class="layui-elem-quote layui-quote-nm">备份恢复数据
</blockquote>
</div>
</div>
</div>
<!-- 表格操作列 -->
<script type="text/html" id="backuplistTableBar">
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="import">恢复</a>
</script>
<!-- js部分 -->
<script>
layui.use(['layer', 'form', 'util', 'admin', 'xmSelect', 'formX', 'dropdown'], function () {
layui.use(['layer', 'form', 'table', 'tableX', 'util', 'admin', 'xmSelect', 'formX', 'dropdown'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var table = layui.table;
var tableX = layui.tableX;
var util = layui.util;
var admin = layui.admin;
var formX = layui.formX;
var setter = layui.setter;
var xmSelect = layui.xmSelect;
form.on('submit(backupSubmit)', function (data) {
// 渲染表格
var insTb = tableX.render({
elem: '#backuplistTable',
url: setter.base_server + 'common/backup/list',
method: 'GET',
page: false,
cols: [[
{field: 'name', title: '文件名称', templet: function (d) {
return '<a href="' + d.url + '" class="layui-table-link" lay-event="link" target="_blank">' + d.name + '</a>';
}
},
{align: 'center', toolbar: '#backuplistTableBar', title: '操作', width: 120}
]]
});
// 工具条点击事件
table.on('tool(backuplistTable)', function (obj) {
var data = obj.data;
var layEvent = obj.event;
if (layEvent === 'import') {
layer.confirm('确定恢复此备份文件 ' + data.name + '',{icon: 3, title: '提示', shadeClose: true},
function (i) {
layer.close(i);
layer.load(2);
$.ajax({
url : "common/backup/import",
type: 'POST',
dataType : "json",
data: {'name': data.name, csrfmiddlewaretoken: '{{ csrf_token }}' },
// beforeSend: function(request) {
// //django需要csrf验证,Forbidden (CSRF cookie not set.):
// request.setRequestHeader("X-CSRFToken", csrftoken);
// },
success : function(d) {
layer.closeAll('loading');
if (d.code == 1) {
table.reload('backuplistTable', {});
layer.msg(d.msg, {icon: 1});
} else {
layer.msg(d.msg, {icon: 2});
}
}
});
});
} else if(layEvent === 'del'){
//var csrftoken = getCookie('csrftoken');
layer.confirm('确定删除此备份文件 ' + data.name + '',{icon: 3, title: '警告', shadeClose: true},
function (i) {
layer.close(i);
layer.load(2);
$.ajax({
url : "common/backup/del",
type: 'POST',
dataType : "json",
data: {'name': data.name, csrfmiddlewaretoken: '{{ csrf_token }}' },
// beforeSend: function(request) {
// //django需要csrf验证,Forbidden (CSRF cookie not set.):
// request.setRequestHeader("X-CSRFToken", csrftoken);
// },
success : function(d) {
layer.closeAll('loading');
if (d.code == 1) {
table.reload('backuplistTable', {});
layer.msg(d.msg, {icon: 1});
} else {
layer.msg(d.msg, {icon: 2});
}
}
});
});
}
});
// 备份按钮点击事件
$('#exportSubmit').click(function () {
layer.load(2);
admin.req('common/backup', {'action':'backup'}, function (data) {
admin.req('common/backup/export', function (data) {
layer.closeAll('loading');
if (data.code == 1) {
table.reload('backuplistTable', {});
layer.msg(data.msg, {icon: 1});
} else {
layer.msg(data.msg, {icon: 2});
}
}, $('#backuploadForm').attr('method'));
}, 'POST');
return false;
});
// form.on('submit(backuploadcrtestSubmit)', function (data) {
// layer.load(2);
// admin.req('common/backuploadcrtest', data.field, function (data) {
// layer.closeAll('loading');
// if (data.code == 1) {
// layer.msg(data.msg, {icon: 1});
// } else {
// layer.msg(data.msg, {icon: 2});
// }
// }, $('#backuploadcrForm').attr('method'));
// return false;
// });
});

View File

@@ -1,134 +0,0 @@
<style>
#baiduocrForm {
max-width: 700px;
}
#baiduocrForm .layui-form-item {
margin-bottom: 25px;
}
#img {
width: 25px;
height: 15px;
position: absolute;
right: 5px;
margin-top: 12px;
top: 1px;
text-align: center;
}
</style>
<!-- 正文开始 -->
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<!-- 表单开始 -->
<form class="layui-form" id="baiduocrForm" lay-filter="baiduocrForm" method="POST">{% csrf_token %}
<input name="id" type="hidden" value="{{id}}"/>
<div class="layui-form-item">
<label class="layui-form-label">APP_ID</label>
<div class="layui-input-block">
<input name="app_id" class="layui-input"
lay-verify="required" required value="{{app_id}}"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">API_KEY</label>
<div class="layui-input-block">
<input name="app_key" class="layui-input"
lay-verify="required" required value="{{app_key}}"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">SECRET_KEY</label>
<div class="layui-input-block">
<input type="password" name="secret_key" id="secret_key" class="layui-input" lay-verify="required" required value="{{secret_key}}"/>
<!--在输入框后接img标签-->
<img id="img" onclick="hideShowPsw()"
src="/static/assets/images/icon-visible.png">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-filter="baiduocrSubmit" lay-submit>&emsp;提交&emsp;</button>
<!--
<button class="layui-btn layui-btn-normal" lay-filter="baiduocrtestSubmit" lay-submit>&emsp;测试&emsp;</button>
-->
</div>
</div>
</form>
<!-- //表单结束 -->
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">说明</div>
<div class="layui-card-body">
<blockquote class="layui-elem-quote layui-quote-nm">申请百度云的免费图片和文字识别,用于验证码识别,天空、皇后必须
</blockquote>
</div>
</div>
</div>
<!-- js部分 -->
<script>
layui.use(['layer', 'form', 'util', 'admin', 'xmSelect', 'formX', 'dropdown'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
var util = layui.util;
var admin = layui.admin;
var formX = layui.formX;
var setter = layui.setter;
var xmSelect = layui.xmSelect;
form.on('submit(baiduocrSubmit)', function (data) {
layer.load(2);
admin.req('common/baiduocr', data.field, function (data) {
layer.closeAll('loading');
if (data.code == 1) {
layer.msg(data.msg, {icon: 1});
} else {
layer.msg(data.msg, {icon: 2});
}
}, $('#baiduocrForm').attr('method'));
return false;
});
// form.on('submit(baiduocrtestSubmit)', function (data) {
// layer.load(2);
// admin.req('common/baiduocrtest', data.field, function (data) {
// layer.closeAll('loading');
// if (data.code == 1) {
// layer.msg(data.msg, {icon: 1});
// } else {
// layer.msg(data.msg, {icon: 2});
// }
// }, $('#baiduocrForm').attr('method'));
// return false;
// });
});
var demoImg = document.getElementById("img");
var PWD = document.getElementById("secret_key");
function hideShowPsw() {
if (PWD.type == "password") {
PWD.type = "text";
demoImg.src = "/static/assets/images/icon-invisible.png"; //图片路径(闭眼图片)
} else {
PWD.type = "password";
demoImg.src = "/static/assets/images/icon-visible.png"; // 图片路径(睁眼图片)
}
}
</script>

View File

@@ -13,6 +13,8 @@ urlpatterns = [
path('backup', views.BackupView.as_view()),
path('backup/export', views_request.backupExport),
path('backup/import', views_request.backupImport),
path('backup/list', views_request.backupList),
path('backup/del', views_request.backupDel),
#补签
path('signagain', views_request.signAgain),
path('signcheck', views_request.signCheck),

View File

@@ -5,68 +5,6 @@ from django.http import JsonResponse
from django.conf import settings
from git.repo import Repo
# Create your views here.
#------------------------------
#class BaiDuOcrView(LoginRequiredMixin,TemplateView):
#"""
#百度OCR配置
#"""
#template_name = 'common/baidu_ocr.html'
##显示添加模板
#def get(self, request, *args, **kwargs):
#"""
#得到
#"""
#return super(BaiDuOcrView, self).get(request, *args, **kwargs)
##显示编辑模板
#def get_context_data(self, **kwargs):
##获取记录
#orm_count = BaiDuOcr.objects.count()
#if orm_count == 0:
#context = {}
#else:
##获取一条数据
#ormdata = BaiDuOcr.objects.first()
#context = {
#'id': ormdata.id,
#'app_id': ormdata.app_id,
#'app_key': ormdata.app_key,
#'secret_key': ormdata.secret_key,
#}
#kwargs.update(context)
#return super(BaiDuOcrView, self).get_context_data(**kwargs)
##数据提交接收方法
#def post(self, request, *args, **kwargs):
#"""
#数据提交
#"""
#_id = request.POST.get('id','')
#app_id = request.POST.get("app_id").strip()
#app_key = request.POST.get("app_key").strip()
#secret_key = request.POST.get("secret_key").strip()
#if _id == '':
#ormdata = BaiDuOcr.objects.create(app_id=app_id,
#app_key=app_key,
#secret_key=secret_key,
#)
#else:
#ormdata = BaiDuOcr.objects.get(id=_id)
#ormdata.app_id = app_id
#ormdata.app_key = app_key
#ormdata.secret_key = secret_key
#ormdata.save()
#response_data={"code":1,"msg":"添加成功"}
#return JsonResponse(response_data)
class BackupView(LoginRequiredMixin,TemplateView):
"""

View File

@@ -7,7 +7,8 @@ from django.core.management import call_command
import logging
import tempfile
import os
import shutil
from datetime import datetime
import simplejson as json
from sites.models import SiteInfo,SiteConfig
from cron.models import Log
@@ -22,23 +23,27 @@ def backupExport(request):
logger = logging.getLogger('django')
if request.method == "POST":
action = request.POST.get('action','')
backup_file = os.path.join(settings.BACKUP_DIR,'pthelper.json')
logger.info("Starting backup process.")
#backup_file = os.path.join(settings.BACKUP_DIR,'pthelper.json')
backup_file = os.path.join(settings.BACKUP_DIR,'export_{}.json'.format(datetime.now().strftime("%Y-%m-%d_%H-%M")))
logger.info("开始导出数据.")
with tempfile.TemporaryDirectory() as d:
dump_path = os.path.join(d, 'dump.json')
logger.info("Starting data dump...")
#call_command('dumpdata', '--exclude','cron.Log',natural_foreign=True, output=dump_path)
call_command('dumpdata', exclude=['cron.Log'], natural_foreign=True, output=dump_path)
#call_command('dumpdata', exclude=['cron.Log'], natural_foreign=True, output=dump_path, format='json', indent=4)
call_command('dumpdata', exclude=['authtoken.Token'], natural_foreign=True, natural_primary=True, output=dump_path, format='json', indent=4)
logger.info("Data dumped.")
logger.info("Copying BACKUP_DIR to %s...", backup_file)
#shutil.copytree(settings.MEDIA_ROOT, os.path.join(d, 'media'))
shutil.copyfile(dump_path, backup_file)
logger.info('Copy done.')
logger.info("备份完成.")
logger.info("开始转换备份文件 %s...", backup_file)
#将乱码转换成中文
with open(dump_path) as f:
data = json.load(f)
with open(backup_file, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
logger.info('转换完成.')
#backup_path = os.path.join(settings.BACKUP_DIR, datetime.date.today().strftime("%Y-%m-%d.zip"))
#with zipfile.ZipFile(backup_path, mode='w') as backup_zip:
#for root, dirs, files in os.walk(d):
@@ -49,7 +54,7 @@ def backupExport(request):
#arcname=os.path.relpath(filepath, d))
#logger.info("{} created.".format(backup_path))
response_data={"code":0,"msg":"ok" }
response_data={"code":1,"msg":"备份完成" }
return JsonResponse(response_data)
@@ -59,17 +64,57 @@ def backupImport(request):
"""
数据恢复
"""
logger = logging.getLogger('django')
if request.method == "POST":
action = request.POST.get('action','')
backup_file = os.path.join(settings.BACKUP_DIR,'pthelper.json')
logger.info("Starting load process.")
call_command('loaddata', backup_file)
name = request.POST.get('name')
backup_file = os.path.join(settings.BACKUP_DIR, name)
try:
call_command('loaddata', backup_file)
response_data={"code":1,"msg":"恢复完成" }
except Exception as e:
response_data={"code":0,"msg":"恢复失败" + str(e) }
return JsonResponse(response_data)
#==================
@login_required
def backupList(request):
"""
列出备份数据
"""
data = {}
data['code'] = 0
data['msg'] = ""
data['data'] = []
for file_name in os.listdir(settings.BACKUP_DIR):
data['data'].append({"name":file_name,
"url": "/backups/" + file_name
})
return JsonResponse(data)
#==================
@login_required
def backupDel(request):
"""
删除备份数据
"""
name = request.POST.get('name')
backup_file = os.path.join(settings.BACKUP_DIR, name)
try:
os.remove(backup_file)
response_data={"code":1,"msg":"删除成功" }
except Exception as e:
response_data={"code":0,"msg":"删除失败" + str(e) }
return JsonResponse(response_data)
#==================
@login_required
def signAgain(request):

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.1 on 2022-09-22 15:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rss', '0016_rule_is_paused'),
]
operations = [
migrations.AddField(
model_name='config',
name='name',
field=models.CharField(max_length=50, null=True, verbose_name='自定义配置名称'),
),
migrations.AlterField(
model_name='config',
name='url',
field=models.URLField(blank=True, max_length=350, unique=True, verbose_name='RSS地址'),
),
]

View File

@@ -27,7 +27,8 @@ class FilmType(models.Model):
class Config(models.Model):
"""RSS配置"""
url = models.URLField('RSS地址', blank=True, max_length=350)
name = models.CharField('自定义配置名称', max_length=50, null=True)
url = models.URLField('RSS地址', blank=True, max_length=350, unique=True)
#site_name = models.CharField('网站名简称,英文', max_length=50, unique=True)
#site_name_cn = models.CharField('网站名简称,中文', max_length=50, unique=True)
#on_update 和 on_delete 后面可以跟的词语有四个

View File

@@ -62,6 +62,7 @@
cols: [[
{ type: 'checkbox' },
{ field: 'id', title: 'ID', hide: true },
{ field: 'name', title: '配置名', width: 200 },
{ field: 'site_name', sort: true, title: '站点', width: 180 },
{ field: 'url', sort: true, title: 'RSS地址' },
{ align: 'center', toolbar: '#rssconfigTableBar', title: '操作', width: 150 }

View File

@@ -1,6 +1,13 @@
<!-- 表单弹窗 -->
<form id="rssconfigAddForm" lay-filter="rssconfigAddForm" class="layui-form model-form" method="POST">{% csrf_token %}
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">配置名称</label>
<div class="layui-input-block">
<input name="name" type="text" class="layui-input" lay-verify="required"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">站点名称</label>
<div class="layui-input-block">
@@ -54,7 +61,7 @@
/* 获取所有记录类型 */
var loadIndex = layer.load(2);
admin.req('sites/siteinfo/select/list', function (res) {
admin.req('sites/siteinfo/select/list?flag=1', function (res) {
layer.close(loadIndex);
if (0 === res.code) {
$.each(res.data, function (index, item) {

View File

@@ -2,6 +2,13 @@
<form id="rssconfigEditForm" lay-filter="rssconfigEditForm" class="layui-form model-form" method="POST">{% csrf_token %}
<input name="id" type="hidden" value="{{id}}"/>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">配置名称</label>
<div class="layui-input-block">
<input name="name" type="text" class="layui-input" lay-verify="required" value="{{name}}"/>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">站点名称</label>
<div class="layui-input-block">

View File

@@ -68,7 +68,8 @@
cols: [[
{ type: 'checkbox' },
{ field: 'id', title: 'ID', hide: true },
{ field: 'name', sort: true, title: '名称', width: 120 },
{ field: 'name', sort: true, title: '规则名称', width: 120 },
{ field: 'config_name', sort: true, title: '配置名称', width: 120 },
{ field: 'config_id', sort: true, title: '站点', width: 150 },
{ field: 'tools_id', sort: true, title: '下载器', width: 200 },
{ field: 'keyword', title: '关键字'},

View File

@@ -9,7 +9,7 @@
</div>
<div class="layui-form-item">
<label class="layui-form-label">RSS站点</label>
<label class="layui-form-label">配置名</label>
<div class="layui-input-block">
<div id="config_id"></div>
</div>

View File

@@ -11,7 +11,7 @@
</div>
<div class="layui-form-item">
<label class="layui-form-label">RSS站点</label>
<label class="layui-form-label">配置名</label>
<div class="layui-input-block">
<div id="config_id"></div>
</div>

View File

@@ -61,7 +61,8 @@
{ field: 'siteinfo_id', sort: true, title: '站点', width: 150 },
{ field: 'seed_details_link', title: '种子介绍页', hide: true },
{ field: 'seed_name', title: '名称', templet: function (d) {
return '<a href="' + d.seed_details_link +'" target="_blank" id="test'+d.id+'" onmouseover="show('+ d.id +')"; onmouseleave="closeTips();">' + d.seed_name + '</a>';
//return '<a href="' + d.seed_details_link +'" target="_blank" id="test'+d.id+'" onmouseover="show('+ d.id +')"; onmouseleave="closeTips();">' + d.seed_name + '</a>';
return '<a href="' + d.seed_details_link +'" target="_blank" lay-direction="1" lay-tips="鼠标点击访问种子详情页<br>' + d.seed_name +'">' + d.seed_name + '</a>';
}, unresize: true
},
{ field: 'seed_type', sort: true, title: '类型', width: 150 },

View File

@@ -62,12 +62,14 @@ class RssConfigAddView(LoginRequiredMixin,TemplateView):
"""
数据提交
"""
name = request.POST.get("name","").strip()
siteinfo_id = request.POST.get("siteinfo_id").strip()
url = request.POST.get("url").strip()
ormdata_siteinfo = SiteInfo.objects.get(id=int(siteinfo_id))
ormdata = Config.objects.create(url=url,
ormdata = Config.objects.create(name=name,
url=url,
siteinfo_id=ormdata_siteinfo
)
@@ -101,6 +103,7 @@ class RssConfigEditView(LoginRequiredMixin,TemplateView):
context = {
'id': self._id,
'name': self.ormdata.name,
'url': self.ormdata.url,
'siteinfo_id': self.ormdata.siteinfo_id.id
}
@@ -112,6 +115,7 @@ class RssConfigEditView(LoginRequiredMixin,TemplateView):
"""
数据提交
"""
name = request.POST.get("name","").strip()
url = request.POST.get("url").strip()
siteinfo_id = request.POST.get("siteinfo_id").strip()
@@ -122,6 +126,7 @@ class RssConfigEditView(LoginRequiredMixin,TemplateView):
ormdata = Config.objects.get(id=_id)
ormdata.url = url
ormdata.name = name
ormdata.siteinfo_id=ormdata_siteinfo
ormdata.save()

View File

@@ -54,17 +54,18 @@ def config(request):
ormdata = Config.objects.order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize]
else:
data['count'] = Config.objects.filter(Q(name__icontains=searchValue)|Q(name_cn__icontains=searchValue)).count()
data['count'] = Config.objects.filter(Q(name__icontains=searchValue)|Q(siteconfig_name_cn__icontains=searchValue)).count()
if pageIndex == 1:
ormdata = Config.objects.filter(Q(name__icontains=searchValue)|Q(name_cn__icontains=searchValue)).order_by(order_by)[:pageSize] #offset:limit
ormdata = Config.objects.filter(Q(name__icontains=searchValue)|Q(siteconfig_name_cn__icontains=searchValue)).order_by(order_by)[:pageSize] #offset:limit
else:
ormdata = Config.objects.filter(Q(name__icontains=searchValue)|Q(name_cn__icontains=searchValue)).order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize]
ormdata = Config.objects.filter(Q(name__icontains=searchValue)|Q(siteconfig_name_cn__icontains=searchValue)).order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize]
for i in ormdata:
data['data'].append({"id":i.id,
"site_name":i.siteinfo_id.siteconfig_name + '(' + i.siteinfo_id.siteconfig_name_cn + ')',
"url":i.url,
"name":i.name,
})
#返回json串
#return HttpResponse(json.dumps(data,ensure_ascii = False), "application/json")
@@ -121,7 +122,7 @@ def rule(request):
for i in ormdata:
data['data'].append({"id":i.id,
#"config_id":i.config_id.siteinfo_id.siteconfig_name_cn,
"config_name":i.config_id.name,
"config_id":i.config_id.siteinfo_id.siteconfig_name + '(' + i.config_id.siteinfo_id.siteconfig_name_cn + ')',
"tools_id":i.tools_id.name,
"name":i.name,
@@ -263,7 +264,8 @@ def select_rssconfig(request):
for i in ormdata:
data['data'].append({
"id":i.id,
"name":i.siteinfo_id.siteconfig_name + "(" + i.siteinfo_id.siteconfig_name_cn + ")"
#"name":i.siteinfo_id.siteconfig_name + "(" + i.siteinfo_id.siteconfig_name_cn + ")"
"name":i.name + "-----" +i.siteinfo_id.siteconfig_name + "(" + i.siteinfo_id.siteconfig_name_cn + ")"
})
return JsonResponse(data)

View File

@@ -19,6 +19,10 @@ import environ
import pymysql
pymysql.install_as_MySQLdb()
#在win下解决gbk问题导出数据
import _locale
_locale._getdefaultlocale = (lambda *args: ['en_US', 'utf8'])
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
#BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -233,6 +237,7 @@ APSCHEDULER_RUN_NOW_TIMEOUT = 25 # Seconds
TMP_LOG_DIR = os.path.join(BASE_DIR, "tmp")
if not os.path.exists(TMP_LOG_DIR): os.mkdir(TMP_LOG_DIR)
BACKUP_URL = "backups/"
BACKUP_DIR = os.path.join(BASE_DIR, 'backups')
if not os.path.exists(BACKUP_DIR): os.mkdir(BACKUP_DIR)