diff --git a/README.md b/README.md index c964844..82f4a4a 100644 --- a/README.md +++ b/README.md @@ -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、刷新页面,重新登录系统 +``` ## 感谢 diff --git a/apps/common/templates/common/backup.html b/apps/common/templates/common/backup.html index 89083b4..99dd577 100644 --- a/apps/common/templates/common/backup.html +++ b/apps/common/templates/common/backup.html @@ -1,74 +1,136 @@ -
- -
{% csrf_token %} - +
-
- - + +
+
- - -
-
+
+ +
-
-
说明
-
-
备份恢复数据 -
+ + + + \ No newline at end of file diff --git a/apps/common/urls/views_urls.py b/apps/common/urls/views_urls.py index c0a349e..fcc0b1a 100644 --- a/apps/common/urls/views_urls.py +++ b/apps/common/urls/views_urls.py @@ -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), diff --git a/apps/common/views.py b/apps/common/views.py index 75d3449..6b8d3e5 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -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): """ diff --git a/apps/common/views_request.py b/apps/common/views_request.py index 6eefce0..e66d2c5 100644 --- a/apps/common/views_request.py +++ b/apps/common/views_request.py @@ -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): diff --git a/apps/rss/migrations/0017_config_name_alter_config_url.py b/apps/rss/migrations/0017_config_name_alter_config_url.py new file mode 100644 index 0000000..c7d7824 --- /dev/null +++ b/apps/rss/migrations/0017_config_name_alter_config_url.py @@ -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地址'), + ), + ] diff --git a/apps/rss/models.py b/apps/rss/models.py index edbef1e..7ffb785 100644 --- a/apps/rss/models.py +++ b/apps/rss/models.py @@ -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 后面可以跟的词语有四个 diff --git a/apps/rss/templates/rss/rssconfig.html b/apps/rss/templates/rss/rssconfig.html index 21f231c..56d77f5 100644 --- a/apps/rss/templates/rss/rssconfig.html +++ b/apps/rss/templates/rss/rssconfig.html @@ -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 } diff --git a/apps/rss/templates/rss/rssconfigadd.html b/apps/rss/templates/rss/rssconfigadd.html index c291fe0..1a6d5d3 100644 --- a/apps/rss/templates/rss/rssconfigadd.html +++ b/apps/rss/templates/rss/rssconfigadd.html @@ -1,6 +1,13 @@
{% csrf_token %} +
+ +
+ +
+
+
@@ -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) { diff --git a/apps/rss/templates/rss/rssconfigedit.html b/apps/rss/templates/rss/rssconfigedit.html index a285808..4d25652 100644 --- a/apps/rss/templates/rss/rssconfigedit.html +++ b/apps/rss/templates/rss/rssconfigedit.html @@ -2,6 +2,13 @@ {% csrf_token %} +
+ +
+ +
+
+
diff --git a/apps/rss/templates/rss/rssrule.html b/apps/rss/templates/rss/rssrule.html index dca43e3..bde55ea 100644 --- a/apps/rss/templates/rss/rssrule.html +++ b/apps/rss/templates/rss/rssrule.html @@ -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: '关键字'}, diff --git a/apps/rss/templates/rss/rssruleadd.html b/apps/rss/templates/rss/rssruleadd.html index 5166190..6b251a4 100644 --- a/apps/rss/templates/rss/rssruleadd.html +++ b/apps/rss/templates/rss/rssruleadd.html @@ -9,7 +9,7 @@
- +
diff --git a/apps/rss/templates/rss/rssruleedit.html b/apps/rss/templates/rss/rssruleedit.html index adb3b04..c3211c5 100644 --- a/apps/rss/templates/rss/rssruleedit.html +++ b/apps/rss/templates/rss/rssruleedit.html @@ -11,7 +11,7 @@
- +
diff --git a/apps/rss/templates/rss/rssseedinfo.html b/apps/rss/templates/rss/rssseedinfo.html index c293d60..1f671cf 100644 --- a/apps/rss/templates/rss/rssseedinfo.html +++ b/apps/rss/templates/rss/rssseedinfo.html @@ -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 '' + d.seed_name + ''; + //return '' + d.seed_name + ''; + return '' + d.seed_name + ''; }, unresize: true }, { field: 'seed_type', sort: true, title: '类型', width: 150 }, diff --git a/apps/rss/views.py b/apps/rss/views.py index 00d2bce..0c452b9 100644 --- a/apps/rss/views.py +++ b/apps/rss/views.py @@ -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() diff --git a/apps/rss/views_request.py b/apps/rss/views_request.py index 8197dd8..597ec2f 100644 --- a/apps/rss/views_request.py +++ b/apps/rss/views_request.py @@ -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) diff --git a/pthelper/settings.py b/pthelper/settings.py index 27ea12f..ecab992 100644 --- a/pthelper/settings.py +++ b/pthelper/settings.py @@ -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)