django-botmanager 0.1.10__py3-none-any.whl → 0.2.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- botmanager/admin.py +48 -2
- botmanager/basetask.py +11 -2
- botmanager/management/commands/bot_manager.py +45 -57
- botmanager/migrations/0011_alter_task_create_dt_alter_task_id.py +24 -0
- botmanager/models.py +22 -21
- botmanager/settings.py +46 -38
- botmanager/templates/botmanager/task/change_form.html +11 -0
- django_botmanager-0.2.23.dist-info/METADATA +17 -0
- {django_botmanager-0.1.10.dist-info → django_botmanager-0.2.23.dist-info}/RECORD +12 -10
- {django_botmanager-0.1.10.dist-info → django_botmanager-0.2.23.dist-info}/WHEEL +1 -1
- django_botmanager-0.1.10.dist-info/METADATA +0 -15
- {django_botmanager-0.1.10.dist-info → django_botmanager-0.2.23.dist-info/licenses}/LICENSE +0 -0
- {django_botmanager-0.1.10.dist-info → django_botmanager-0.2.23.dist-info}/top_level.txt +0 -0
botmanager/admin.py
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
|
|
2
4
|
from django.contrib import admin
|
|
5
|
+
from django.http import HttpResponseNotFound, StreamingHttpResponse
|
|
6
|
+
from django.shortcuts import get_object_or_404
|
|
7
|
+
from django.urls import path
|
|
3
8
|
from django.utils.html import escape
|
|
9
|
+
from django.utils.translation import gettext_lazy as _
|
|
4
10
|
from botmanager.models import Task
|
|
11
|
+
from botmanager import settings
|
|
12
|
+
from botmanager.management.commands.bot_manager import Command
|
|
5
13
|
|
|
6
14
|
|
|
7
15
|
class TaskAdmin(admin.ModelAdmin):
|
|
@@ -9,6 +17,7 @@ class TaskAdmin(admin.ModelAdmin):
|
|
|
9
17
|
'create_dt', 'finish_dt', 'error_field', 'attempt_period', 'attempt_count', 'input_field']
|
|
10
18
|
list_filter = ('is_complete', 'is_failed', 'in_process', 'name')
|
|
11
19
|
search_fields = ('id', 'name')
|
|
20
|
+
change_form_template = 'botmanager/task/change_form.html'
|
|
12
21
|
|
|
13
22
|
def get_queryset(self, request):
|
|
14
23
|
qs = super(TaskAdmin, self).get_queryset(request)
|
|
@@ -28,12 +37,49 @@ class TaskAdmin(admin.ModelAdmin):
|
|
|
28
37
|
else:
|
|
29
38
|
return None
|
|
30
39
|
|
|
31
|
-
error_field.short_description =
|
|
40
|
+
error_field.short_description = _("Ошибка")
|
|
32
41
|
error_field.allow_tags = True
|
|
33
42
|
|
|
34
43
|
def input_field(self, obj):
|
|
35
44
|
return obj.input
|
|
36
45
|
|
|
37
|
-
input_field.short_description = "Вводные данные"
|
|
46
|
+
input_field.short_description = _("Вводные данные")
|
|
47
|
+
|
|
48
|
+
def get_urls(self):
|
|
49
|
+
urls = super().get_urls()
|
|
50
|
+
custom_urls = [
|
|
51
|
+
path("<path:task_id>/open-logfile/",
|
|
52
|
+
self.admin_site.admin_view(self.open_logfile),
|
|
53
|
+
name="botmanager_task_open_logfile"),
|
|
54
|
+
]
|
|
55
|
+
return custom_urls + urls
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def file_generator(file_name):
|
|
59
|
+
with open(file_name, "rb") as file:
|
|
60
|
+
yield from file
|
|
61
|
+
|
|
62
|
+
def open_logfile(self, request, task_id):
|
|
63
|
+
task = get_object_or_404(Task, pk=task_id)
|
|
64
|
+
imported_class = [
|
|
65
|
+
Command.import_from_string(i) for i in settings.MAIN_CONFIG["tasks"].keys()
|
|
66
|
+
if Command.import_from_string(i).name == task.name
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
if not imported_class:
|
|
70
|
+
return HttpResponseNotFound()
|
|
71
|
+
|
|
72
|
+
filename = imported_class[0](task).get_log_file_name()
|
|
73
|
+
folder = imported_class[0].name if imported_class[0].name else ''
|
|
74
|
+
dir = settings.MAIN_CONFIG["logs"]["dir"]
|
|
75
|
+
file_path = os.path.join(dir, folder, filename) + ".log"
|
|
76
|
+
|
|
77
|
+
if os.path.exists(file_path):
|
|
78
|
+
return StreamingHttpResponse(self.file_generator(file_path), content_type="text/event-stream")
|
|
79
|
+
else:
|
|
80
|
+
return HttpResponseNotFound()
|
|
81
|
+
|
|
82
|
+
open_logfile.short_description = _(u"Открыть файл")
|
|
83
|
+
|
|
38
84
|
|
|
39
85
|
admin.site.register(Task, TaskAdmin)
|
botmanager/basetask.py
CHANGED
|
@@ -7,6 +7,7 @@ import json
|
|
|
7
7
|
from botmanager import settings
|
|
8
8
|
from botmanager.models import Task
|
|
9
9
|
from django.conf import settings as project_settings
|
|
10
|
+
from django.db import connection
|
|
10
11
|
from datetime import datetime, timedelta
|
|
11
12
|
from hashlib import md5
|
|
12
13
|
from botmanager import BotManagerTaskError
|
|
@@ -20,6 +21,10 @@ class StopTaskSuccess(BotManagerTaskError):
|
|
|
20
21
|
pass
|
|
21
22
|
|
|
22
23
|
|
|
24
|
+
class TaskTemporarilyException(BotManagerTaskError):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
23
28
|
class BotManagerBaseTask(object):
|
|
24
29
|
|
|
25
30
|
actions = []
|
|
@@ -189,7 +194,10 @@ class BotManagerBaseTask(object):
|
|
|
189
194
|
logging.info(u'Stop actions because StopTaskSuccess caught')
|
|
190
195
|
break
|
|
191
196
|
except Exception as e:
|
|
192
|
-
|
|
197
|
+
|
|
198
|
+
if not isinstance(e, TaskTemporarilyException):
|
|
199
|
+
logging.exception(e)
|
|
200
|
+
|
|
193
201
|
self.task.failed_action = action
|
|
194
202
|
is_error_handled = self._handle_error(e)
|
|
195
203
|
self.task.last_error_dt = timezone.now()
|
|
@@ -230,5 +238,6 @@ class BotManagerBaseTask(object):
|
|
|
230
238
|
is_success = not is_failed or is_error_handled
|
|
231
239
|
self._on_finish(is_success=is_success, is_complete=is_complete)
|
|
232
240
|
self.task.save()
|
|
233
|
-
|
|
241
|
+
logging.info('Closing connection to DB...')
|
|
242
|
+
connection.close()
|
|
234
243
|
logging.info('End run task {} id {}'.format(self.name, self.task.pk))
|
|
@@ -3,9 +3,10 @@ import importlib
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
|
+
import signal
|
|
6
7
|
import sys
|
|
7
8
|
from datetime import datetime, timedelta
|
|
8
|
-
from multiprocessing import Queue, Process
|
|
9
|
+
from multiprocessing import Queue, Process, Event
|
|
9
10
|
from time import sleep, time
|
|
10
11
|
|
|
11
12
|
import psutil
|
|
@@ -30,6 +31,9 @@ except ImportError:
|
|
|
30
31
|
setproctitle = lambda x: x
|
|
31
32
|
|
|
32
33
|
|
|
34
|
+
PROCESS_TO_FINISH_GRACEFULLY_TIMEOUT = 30 # seconds
|
|
35
|
+
|
|
36
|
+
|
|
33
37
|
class BotManagerCommandException(BotManagerBaseCommandException):
|
|
34
38
|
pass
|
|
35
39
|
|
|
@@ -142,7 +146,30 @@ class Command(BotManagerBaseCommand):
|
|
|
142
146
|
current_pid = os.getpid()
|
|
143
147
|
PidsFileController.add_pid(current_pid)
|
|
144
148
|
|
|
149
|
+
shutdown_event = Event()
|
|
150
|
+
|
|
145
151
|
processes = []
|
|
152
|
+
|
|
153
|
+
def signal_handler(signum, frame):
|
|
154
|
+
logging.info('Main process received signal {}'.format(signum))
|
|
155
|
+
logging.info('Start shutting down children processes gracefully...')
|
|
156
|
+
shutdown_event.set()
|
|
157
|
+
|
|
158
|
+
# Wait for processes to finish
|
|
159
|
+
for p in processes:
|
|
160
|
+
p.join(timeout=PROCESS_TO_FINISH_GRACEFULLY_TIMEOUT)
|
|
161
|
+
if p.is_alive():
|
|
162
|
+
logging.info("Process {} didn't terminate gracefully, forcing...".format(p.pid))
|
|
163
|
+
p.terminate()
|
|
164
|
+
|
|
165
|
+
logging.info("All children processes have been terminated. Exiting main process {}.".format(current_pid))
|
|
166
|
+
|
|
167
|
+
sys.exit(0)
|
|
168
|
+
|
|
169
|
+
# Register signal handlers
|
|
170
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
171
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
172
|
+
|
|
146
173
|
queue_dict = {}
|
|
147
174
|
for task_class_string, processes_count in self.config['tasks'].items():
|
|
148
175
|
cls_name = task_class_string.split('.')[-1]
|
|
@@ -158,7 +185,7 @@ class Command(BotManagerBaseCommand):
|
|
|
158
185
|
queue_dict[task_class.name].maxsize = maxsize
|
|
159
186
|
|
|
160
187
|
for i in range(processes_count):
|
|
161
|
-
tm = TaskManager(task_class, queue_dict[task_class.name], i + 1, current_pid)
|
|
188
|
+
tm = TaskManager(task_class, queue_dict[task_class.name], i + 1, current_pid, shutdown_event=shutdown_event)
|
|
162
189
|
p = Process(target=Command.start_service, args=(tm,))
|
|
163
190
|
p.name = tm.process_name
|
|
164
191
|
p.daemon = True
|
|
@@ -166,7 +193,7 @@ class Command(BotManagerBaseCommand):
|
|
|
166
193
|
processes.append(p)
|
|
167
194
|
PidsFileController.add_pid(p.pid)
|
|
168
195
|
|
|
169
|
-
tf = TaskFetcher(queue_dict, current_pid)
|
|
196
|
+
tf = TaskFetcher(queue_dict, current_pid, shutdown_event=shutdown_event)
|
|
170
197
|
p = Process(target=Command.start_service, args=(tf,))
|
|
171
198
|
p.name = tf.process_name
|
|
172
199
|
p.daemon = True
|
|
@@ -175,7 +202,7 @@ class Command(BotManagerBaseCommand):
|
|
|
175
202
|
PidsFileController.add_pid(p.pid)
|
|
176
203
|
|
|
177
204
|
if not options.get('without_sheduller'):
|
|
178
|
-
ts = TaskSheduler(self.config, current_pid)
|
|
205
|
+
ts = TaskSheduler(self.config, current_pid, shutdown_event=shutdown_event)
|
|
179
206
|
p = Process(target=Command.start_service, args=(ts,))
|
|
180
207
|
p.name = ts.process_name
|
|
181
208
|
p.daemon = True
|
|
@@ -234,16 +261,16 @@ class Command(BotManagerBaseCommand):
|
|
|
234
261
|
class TaskSheduler(object):
|
|
235
262
|
SET_PERIOD_SECONDS = 5
|
|
236
263
|
|
|
237
|
-
def __init__(self, config, parent_pid):
|
|
264
|
+
def __init__(self, config, parent_pid, shutdown_event=None):
|
|
238
265
|
self.process_name = "BotManager.TaskSheduler"
|
|
239
266
|
self.config = config
|
|
240
267
|
self.parent_pid = parent_pid
|
|
241
268
|
self.shedule_cache = {}
|
|
269
|
+
self.shutdown_event = shutdown_event
|
|
242
270
|
|
|
243
271
|
def run(self):
|
|
244
272
|
setproctitle(self.process_name)
|
|
245
|
-
while
|
|
246
|
-
|
|
273
|
+
while not self.shutdown_event.is_set():
|
|
247
274
|
if os.getppid() != self.parent_pid:
|
|
248
275
|
logging.info(u"Parent process is die. Exit..")
|
|
249
276
|
break
|
|
@@ -259,6 +286,7 @@ class TaskSheduler(object):
|
|
|
259
286
|
logging.exception(e)
|
|
260
287
|
|
|
261
288
|
sleep(self.SET_PERIOD_SECONDS)
|
|
289
|
+
logging.info(u"TaskSheduler process shutdown gracefully.")
|
|
262
290
|
|
|
263
291
|
def _time_to_set_tasks_for(self, task_class):
|
|
264
292
|
last_shedule_time = self.shedule_cache.get(task_class.name)
|
|
@@ -268,23 +296,12 @@ class TaskSheduler(object):
|
|
|
268
296
|
|
|
269
297
|
class TaskFetcher(object):
|
|
270
298
|
|
|
271
|
-
def __init__(self, queue_dict, parent_pid):
|
|
299
|
+
def __init__(self, queue_dict, parent_pid, shutdown_event=None):
|
|
272
300
|
self.process_name = "BotManager.TaskFetcher"
|
|
273
301
|
self.queue_dict = queue_dict
|
|
274
302
|
self.parent_pid = parent_pid
|
|
275
303
|
self.config = settings.MAIN_CONFIG
|
|
276
|
-
|
|
277
|
-
def _release_old_in_process_tasks(self, old_in_process_task_ids):
|
|
278
|
-
if not old_in_process_task_ids:
|
|
279
|
-
return
|
|
280
|
-
|
|
281
|
-
released_ids = set()
|
|
282
|
-
for task_id in old_in_process_task_ids:
|
|
283
|
-
is_released = self._release_old_in_process_task(task_id)
|
|
284
|
-
if is_released:
|
|
285
|
-
released_ids.add(task_id)
|
|
286
|
-
|
|
287
|
-
old_in_process_task_ids -= released_ids
|
|
304
|
+
self.shutdown_event = shutdown_event
|
|
288
305
|
|
|
289
306
|
def run(self):
|
|
290
307
|
"""
|
|
@@ -300,16 +317,14 @@ class TaskFetcher(object):
|
|
|
300
317
|
5. in_process ставится отдельно на каждую взятую задачу а не разово на всех.
|
|
301
318
|
"""
|
|
302
319
|
setproctitle(self.process_name)
|
|
303
|
-
|
|
304
|
-
while
|
|
320
|
+
Task.objects.filter(in_process=True).update(in_process=False)
|
|
321
|
+
while not self.shutdown_event.is_set():
|
|
305
322
|
try:
|
|
306
323
|
|
|
307
324
|
if os.getppid() != self.parent_pid:
|
|
308
325
|
logging.info(u"Parent process is die. Exit..")
|
|
309
326
|
break
|
|
310
327
|
|
|
311
|
-
self._release_old_in_process_tasks(old_in_process_task_ids)
|
|
312
|
-
|
|
313
328
|
tasks = Task.objects.filter(
|
|
314
329
|
is_complete=False
|
|
315
330
|
).order_by('-priority', 'id')
|
|
@@ -355,19 +370,7 @@ class TaskFetcher(object):
|
|
|
355
370
|
|
|
356
371
|
sleep(self.config['fetch_period'])
|
|
357
372
|
|
|
358
|
-
|
|
359
|
-
def _release_old_in_process_task(self, task_id):
|
|
360
|
-
task = lock_task(task_id)
|
|
361
|
-
if not task:
|
|
362
|
-
return
|
|
363
|
-
|
|
364
|
-
if task.is_complete:
|
|
365
|
-
logging.info('task_id={} already completed'.format(task.id))
|
|
366
|
-
return True
|
|
367
|
-
|
|
368
|
-
task.in_process = False
|
|
369
|
-
task.save(update_fields=['in_process'])
|
|
370
|
-
return True
|
|
373
|
+
logging.info(u"TaskFetcher shutdown gracefully.")
|
|
371
374
|
|
|
372
375
|
def _get_qsize(self, task):
|
|
373
376
|
try:
|
|
@@ -379,40 +382,24 @@ class TaskFetcher(object):
|
|
|
379
382
|
|
|
380
383
|
|
|
381
384
|
class TaskManager(object):
|
|
382
|
-
def __init__(self, task_class, queue, process_num, parent_pid):
|
|
385
|
+
def __init__(self, task_class, queue, process_num, parent_pid, shutdown_event=None):
|
|
383
386
|
self.task_class = task_class
|
|
384
387
|
self.queue = queue
|
|
385
388
|
self.process_num = process_num
|
|
386
389
|
self.process_name = u"BotManager.{0}.{1}".format(self.task_class.name, self.process_num)
|
|
387
390
|
self.parent_pid = parent_pid
|
|
391
|
+
self.shutdown_event = shutdown_event
|
|
388
392
|
|
|
389
393
|
def __str__(self):
|
|
390
394
|
return self.process_name
|
|
391
395
|
|
|
392
|
-
def _on_commit(self):
|
|
393
|
-
logging.info('Closing connection to DB...')
|
|
394
|
-
connection.close()
|
|
395
|
-
|
|
396
|
-
@transaction.atomic
|
|
397
396
|
def run_task(self, task):
|
|
398
397
|
logging.info(u"Start {0}".format(task))
|
|
399
|
-
task = lock_task(task.pk)
|
|
400
|
-
if not task:
|
|
401
|
-
logging.info(u"lock_task is empty")
|
|
402
|
-
return
|
|
403
|
-
|
|
404
|
-
if task.is_complete:
|
|
405
|
-
logging.info('task_id={} already completed'.format(task.id))
|
|
406
|
-
return
|
|
407
|
-
|
|
408
398
|
self.task_class(task).run()
|
|
409
|
-
transaction.on_commit(self._on_commit)
|
|
410
|
-
|
|
411
|
-
logging.info(u"End {0}".format(task))
|
|
412
399
|
|
|
413
400
|
def run(self):
|
|
414
401
|
setproctitle(self.process_name)
|
|
415
|
-
while
|
|
402
|
+
while not self.shutdown_event.is_set():
|
|
416
403
|
try:
|
|
417
404
|
|
|
418
405
|
if os.getppid() != self.parent_pid:
|
|
@@ -435,6 +422,8 @@ class TaskManager(object):
|
|
|
435
422
|
u"Error in queue preparing: %s".format(e)
|
|
436
423
|
)
|
|
437
424
|
|
|
425
|
+
logging.info(u"TaskManager process {} shutdown gracefully.".format(self.process_name))
|
|
426
|
+
|
|
438
427
|
|
|
439
428
|
class PidsFileController(object):
|
|
440
429
|
def __init__(self):
|
|
@@ -503,4 +492,3 @@ class PidsFileController(object):
|
|
|
503
492
|
|
|
504
493
|
if os.path.exists(pids_file_name):
|
|
505
494
|
os.remove(pids_file_name)
|
|
506
|
-
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Generated by Django 4.1.2 on 2023-06-28 11:19
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import django.utils.timezone
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('botmanager', '0010_auto_20170531_1321'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AlterField(
|
|
15
|
+
model_name='task',
|
|
16
|
+
name='create_dt',
|
|
17
|
+
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Дата создания задачи'),
|
|
18
|
+
),
|
|
19
|
+
migrations.AlterField(
|
|
20
|
+
model_name='task',
|
|
21
|
+
name='id',
|
|
22
|
+
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
|
23
|
+
),
|
|
24
|
+
]
|
botmanager/models.py
CHANGED
|
@@ -4,7 +4,7 @@ from django.db import models
|
|
|
4
4
|
from jsonfield import JSONField
|
|
5
5
|
from datetime import timedelta, datetime
|
|
6
6
|
from django.utils import timezone
|
|
7
|
-
|
|
7
|
+
from django.utils.translation import gettext_lazy as _
|
|
8
8
|
|
|
9
9
|
class Task(models.Model):
|
|
10
10
|
|
|
@@ -13,34 +13,35 @@ class Task(models.Model):
|
|
|
13
13
|
STATUS_OK = 'OK'
|
|
14
14
|
|
|
15
15
|
STATUSES = (
|
|
16
|
-
(STATUS_NEW, u'В ожидании'),
|
|
17
|
-
(STATUS_IN_PROCESS, u'Идет загрузка'),
|
|
18
|
-
(STATUS_OK, u'Данные загружены'),
|
|
16
|
+
(STATUS_NEW, _(u'В ожидании')),
|
|
17
|
+
(STATUS_IN_PROCESS, _(u'Идет загрузка')),
|
|
18
|
+
(STATUS_OK, _(u'Данные загружены')),
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
id = models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
|
|
22
|
+
name = models.CharField(max_length=256, verbose_name=_(u'Название'))
|
|
23
|
+
create_dt = models.DateTimeField(verbose_name=_(u"Дата создания задачи"), default=timezone.now)
|
|
24
|
+
finish_dt = models.DateTimeField(verbose_name=_(u"Дата завершения"), null=True, blank=True)
|
|
25
|
+
is_complete = models.BooleanField(verbose_name=_(u"Выполнена"), default=False)
|
|
26
|
+
is_failed = models.BooleanField(verbose_name=_(u"Аварийное завершение"), default=False)
|
|
27
|
+
is_persistent = models.BooleanField(verbose_name=_(u"Выполнять пока не завершится успешно"), default=True)
|
|
28
|
+
in_process = models.BooleanField(verbose_name=_(u"В процессе"), default=False)
|
|
28
29
|
|
|
29
30
|
input = JSONField(null=True, blank=True)
|
|
30
31
|
output = JSONField(null=True, blank=True)
|
|
31
32
|
last_error = JSONField(null=True, blank=True)
|
|
32
33
|
extra_params = JSONField(null=True, blank=True)
|
|
33
34
|
|
|
34
|
-
last_error_dt = models.DateTimeField(verbose_name=u"Дата последнего fail-а", null=True, blank=True)
|
|
35
|
-
queue_key = models.CharField(max_length=32, verbose_name=u'Ключ группировки задач')
|
|
36
|
-
failed_action = models.CharField(max_length=256, verbose_name=u'Action, на котором произошла ошибка', null=True, blank=True)
|
|
37
|
-
attempt_count = models.SmallIntegerField(verbose_name=u'Счетчик попыток', default=0)
|
|
38
|
-
last_attempt_dt = models.DateTimeField(verbose_name=u"Последняя попытка выполнения", null=True, blank=True)
|
|
35
|
+
last_error_dt = models.DateTimeField(verbose_name=_(u"Дата последнего fail-а"), null=True, blank=True)
|
|
36
|
+
queue_key = models.CharField(max_length=32, verbose_name=_(u'Ключ группировки задач'))
|
|
37
|
+
failed_action = models.CharField(max_length=256, verbose_name=_(u'Action, на котором произошла ошибка'), null=True, blank=True)
|
|
38
|
+
attempt_count = models.SmallIntegerField(verbose_name=_(u'Счетчик попыток'), default=0)
|
|
39
|
+
last_attempt_dt = models.DateTimeField(verbose_name=_(u"Последняя попытка выполнения"), null=True, blank=True)
|
|
39
40
|
max_attempt_count = models.IntegerField(
|
|
40
|
-
verbose_name=u"Максимальное кол-во попыток выполнение (если None, то бесконечно)", null=True, blank=True
|
|
41
|
+
verbose_name=_(u"Максимальное кол-во попыток выполнение (если None, то бесконечно)"), null=True, blank=True
|
|
41
42
|
)
|
|
42
|
-
attempt_period = models.DurationField(verbose_name=u'Период между попытками', default=timedelta(hours=1))
|
|
43
|
-
parent = models.ForeignKey('self', verbose_name=u'Родительская задача', null=True, blank=True, related_name='child_tasks', on_delete=models.CASCADE)
|
|
43
|
+
attempt_period = models.DurationField(verbose_name=_(u'Период между попытками'), default=timedelta(hours=1))
|
|
44
|
+
parent = models.ForeignKey('self', verbose_name=_(u'Родительская задача'), null=True, blank=True, related_name='child_tasks', on_delete=models.CASCADE)
|
|
44
45
|
priority = models.SmallIntegerField(default=0)
|
|
45
46
|
|
|
46
47
|
def __unicode__(self):
|
|
@@ -67,5 +68,5 @@ class Task(models.Model):
|
|
|
67
68
|
|
|
68
69
|
class Meta:
|
|
69
70
|
db_table = 'botmanager_task'
|
|
70
|
-
verbose_name = u"Задача для BotManager"
|
|
71
|
-
verbose_name_plural = u"Задачи для BotManager"
|
|
71
|
+
verbose_name = _(u"Задача для BotManager")
|
|
72
|
+
verbose_name_plural = _(u"Задачи для BotManager")
|
botmanager/settings.py
CHANGED
|
@@ -16,24 +16,64 @@ if 'tasks' not in MAIN_CONFIG:
|
|
|
16
16
|
if 'task_logs_separated' not in MAIN_CONFIG['logs']:
|
|
17
17
|
MAIN_CONFIG['logs']['task_logs_separated'] = False
|
|
18
18
|
if MAIN_CONFIG['logs']['task_logs_separated'] and 'logs_life_hours' not in MAIN_CONFIG['logs']:
|
|
19
|
-
MAIN_CONFIG['logs']['logs_life_hours'] = 7*24
|
|
19
|
+
MAIN_CONFIG['logs']['logs_life_hours'] = 7 * 24
|
|
20
20
|
if 'success_tasks_life_hours' not in MAIN_CONFIG['logs']:
|
|
21
|
-
MAIN_CONFIG['logs']['success_tasks_life_hours'] = 7*24
|
|
21
|
+
MAIN_CONFIG['logs']['success_tasks_life_hours'] = 7 * 24
|
|
22
22
|
|
|
23
23
|
DEFAULT_LOG_LEVEL = 'INFO'
|
|
24
24
|
|
|
25
25
|
if 'dir' in MAIN_CONFIG['logs'] and MAIN_CONFIG['logs']['dir'] is not None:
|
|
26
26
|
|
|
27
27
|
log_conf = MAIN_CONFIG['logs']
|
|
28
|
-
max_bytes = log_conf['maxBytes'] if 'maxBytes' in log_conf and log_conf['maxBytes'] else 1024*1024*100
|
|
28
|
+
max_bytes = log_conf['maxBytes'] if 'maxBytes' in log_conf and log_conf['maxBytes'] else 1024 * 1024 * 100
|
|
29
29
|
backupCount = log_conf['backupCount'] if 'backupCount' in log_conf and log_conf['backupCount'] else 10
|
|
30
30
|
level = log_conf['level'] if 'level' in log_conf and log_conf['level'] else DEFAULT_LOG_LEVEL
|
|
31
31
|
sentry_enabled = log_conf.get('sentry_enabled', False)
|
|
32
32
|
|
|
33
33
|
handlers = ['console', 'common_errors', 'common']
|
|
34
34
|
|
|
35
|
+
handlers_config = {
|
|
36
|
+
'mail_admins': {
|
|
37
|
+
'level': 'ERROR',
|
|
38
|
+
'class': 'django.utils.log.AdminEmailHandler',
|
|
39
|
+
},
|
|
40
|
+
'console': {
|
|
41
|
+
'level': level,
|
|
42
|
+
'class': 'logging.StreamHandler',
|
|
43
|
+
'formatter': 'default',
|
|
44
|
+
'stream': sys.stdout
|
|
45
|
+
},
|
|
46
|
+
'common_errors': {
|
|
47
|
+
'level': 'ERROR',
|
|
48
|
+
'class': 'logging.handlers.RotatingFileHandler',
|
|
49
|
+
'filename': os.path.join(log_conf['dir'], 'botmanager_errors.log'),
|
|
50
|
+
'maxBytes': max_bytes,
|
|
51
|
+
'formatter': 'default',
|
|
52
|
+
'encoding': 'utf-8',
|
|
53
|
+
'backupCount': backupCount,
|
|
54
|
+
},
|
|
55
|
+
'common': {
|
|
56
|
+
'level': level,
|
|
57
|
+
'class': 'logging.handlers.RotatingFileHandler',
|
|
58
|
+
'filename': os.path.join(log_conf['dir'], 'botmanager.log'),
|
|
59
|
+
'maxBytes': max_bytes,
|
|
60
|
+
'formatter': 'default',
|
|
61
|
+
'encoding': 'utf-8',
|
|
62
|
+
'backupCount': backupCount,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
35
66
|
if sentry_enabled:
|
|
36
|
-
|
|
67
|
+
try:
|
|
68
|
+
import raven
|
|
69
|
+
|
|
70
|
+
handlers.append('sentry')
|
|
71
|
+
handlers_config['sentry'] = {
|
|
72
|
+
'level': 'ERROR',
|
|
73
|
+
'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
|
|
74
|
+
}
|
|
75
|
+
except ImportError:
|
|
76
|
+
raise ImportError('module "raven" not installed')
|
|
37
77
|
|
|
38
78
|
if 'mail_admins' in log_conf and log_conf['mail_admins']:
|
|
39
79
|
handlers.append('mail_admins')
|
|
@@ -46,40 +86,7 @@ if 'dir' in MAIN_CONFIG['logs'] and MAIN_CONFIG['logs']['dir'] is not None:
|
|
|
46
86
|
'format': '%(asctime)s - %(processName)s - %(levelname)s - %(message)s'
|
|
47
87
|
}
|
|
48
88
|
},
|
|
49
|
-
'handlers':
|
|
50
|
-
'sentry': {
|
|
51
|
-
'level': 'ERROR',
|
|
52
|
-
'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
|
|
53
|
-
},
|
|
54
|
-
'mail_admins': {
|
|
55
|
-
'level': 'ERROR',
|
|
56
|
-
'class': 'django.utils.log.AdminEmailHandler',
|
|
57
|
-
},
|
|
58
|
-
'console': {
|
|
59
|
-
'level': level,
|
|
60
|
-
'class': 'logging.StreamHandler',
|
|
61
|
-
'formatter': 'default',
|
|
62
|
-
'stream': sys.stdout
|
|
63
|
-
},
|
|
64
|
-
'common_errors': {
|
|
65
|
-
'level': 'ERROR',
|
|
66
|
-
'class': 'logging.handlers.RotatingFileHandler',
|
|
67
|
-
'filename': os.path.join(log_conf['dir'], 'botmanager_errors.log'),
|
|
68
|
-
'maxBytes': max_bytes,
|
|
69
|
-
'formatter': 'default',
|
|
70
|
-
'encoding': 'utf-8',
|
|
71
|
-
'backupCount': backupCount,
|
|
72
|
-
},
|
|
73
|
-
'common': {
|
|
74
|
-
'level': level,
|
|
75
|
-
'class': 'logging.handlers.RotatingFileHandler',
|
|
76
|
-
'filename': os.path.join(log_conf['dir'], 'botmanager.log'),
|
|
77
|
-
'maxBytes': max_bytes,
|
|
78
|
-
'formatter': 'default',
|
|
79
|
-
'encoding': 'utf-8',
|
|
80
|
-
'backupCount': backupCount,
|
|
81
|
-
}
|
|
82
|
-
},
|
|
89
|
+
'handlers': handlers_config,
|
|
83
90
|
'loggers': {
|
|
84
91
|
'default': {
|
|
85
92
|
'handlers': handlers,
|
|
@@ -88,5 +95,6 @@ if 'dir' in MAIN_CONFIG['logs'] and MAIN_CONFIG['logs']['dir'] is not None:
|
|
|
88
95
|
},
|
|
89
96
|
}
|
|
90
97
|
}
|
|
98
|
+
|
|
91
99
|
else:
|
|
92
100
|
TASKS_LOGGING = None
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{% extends 'admin/change_form.html' %}
|
|
2
|
+
{% load i18n %}
|
|
3
|
+
|
|
4
|
+
{% block object-tools-items %}
|
|
5
|
+
{{ block.super }}
|
|
6
|
+
<li>
|
|
7
|
+
<a href="{% url 'admin:botmanager_task_open_logfile' original.pk %}" class="open-logfile">
|
|
8
|
+
{% translate "Open LogFile" %}
|
|
9
|
+
</a>
|
|
10
|
+
</li>
|
|
11
|
+
{% endblock %}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-botmanager
|
|
3
|
+
Version: 0.2.23
|
|
4
|
+
Summary: Async tasks for django
|
|
5
|
+
Home-page: https://github.com/dimoha/django-botmanager
|
|
6
|
+
Author: Dimoha
|
|
7
|
+
Author-email: dimoha@controlstyle.ru
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: setproctitle==1.1.10
|
|
10
|
+
Requires-Dist: jsonfield==3.1.0
|
|
11
|
+
Requires-Dist: psutil==5.8.0
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: author-email
|
|
14
|
+
Dynamic: home-page
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
Dynamic: requires-dist
|
|
17
|
+
Dynamic: summary
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
botmanager/__init__.py,sha256=p093xeQ7-p1sMH-FycACIIoomZNXCB7KgpjKBQuDL84,73
|
|
2
|
-
botmanager/admin.py,sha256=
|
|
2
|
+
botmanager/admin.py,sha256=oeLgxc2briLeXZjT1jE3DKWpYh65QY3qBctEpV4ggDU,3159
|
|
3
3
|
botmanager/apps.py,sha256=LeNG6Fr77QcsOSjyBAKEldYqqI2zUOmCFt3P3Sq9pSI,135
|
|
4
|
-
botmanager/basetask.py,sha256=
|
|
5
|
-
botmanager/models.py,sha256=
|
|
6
|
-
botmanager/settings.py,sha256=
|
|
4
|
+
botmanager/basetask.py,sha256=8VqUVMbEyq0Um5nlNSvdoj6NUjBn43TLPH97xGduqcs,7829
|
|
5
|
+
botmanager/models.py,sha256=83QbME1_fVx_RVLCPD1xdMfRIE0DwxbVMJwHYg_pV9Y,3776
|
|
6
|
+
botmanager/settings.py,sha256=saOAublzj8ikIwZ7eJR_P90nKolAv29Kp36QDUYEbbw,3266
|
|
7
7
|
botmanager/utils.py,sha256=4epNJDf5C7MrNQlZ8t2EAKGsFGRc2uXqoY49__YHGjg,1105
|
|
8
8
|
botmanager/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
botmanager/management/commands/__init__.py,sha256=uLLxNF6CEdNFFvWTcPHlaYxKQLscMRHs9sRegH19L2g,542
|
|
10
|
-
botmanager/management/commands/bot_manager.py,sha256=
|
|
10
|
+
botmanager/management/commands/bot_manager.py,sha256=WUybgNhLKOkq40LhMZSjMkWDGI8bMPTS6qBmsya6YNo,19749
|
|
11
11
|
botmanager/migrations/0001_initial.py,sha256=AfYbVMcP171RefENIDUpPkVYQK3xoSxxc_XAr5DIoQs,3357
|
|
12
12
|
botmanager/migrations/0002_auto_20161208_1406.py,sha256=jxfewdTeJ8RpLl-lzxp1zYtJijVPYR54pPXysJeb62U,742
|
|
13
13
|
botmanager/migrations/0003_auto_20161208_1529.py,sha256=FMu3GdUsKqR8oPUPxqsqPfRBjVn-9p_wukgHjsFf5hk,1935
|
|
@@ -18,9 +18,11 @@ botmanager/migrations/0007_task_is_persistent.py,sha256=3xpqICmgjiOsVfXy3QxDZJW_
|
|
|
18
18
|
botmanager/migrations/0008_auto_20170331_1752.py,sha256=SefrHGYtHk_OVP0OqWS9wOWUwhNYToWWDc8gTSEN3Nw,673
|
|
19
19
|
botmanager/migrations/0009_task_extra_params.py,sha256=pt9EuqSXeHp_pE3muwQzhMeC8tMNxStnDeXu4B8qM0M,489
|
|
20
20
|
botmanager/migrations/0010_auto_20170531_1321.py,sha256=VnfOWMBI_fuBGmH1NOuXzA5k4wFed9LWEb0Y_-XVAJU,653
|
|
21
|
+
botmanager/migrations/0011_alter_task_create_dt_alter_task_id.py,sha256=-bR0dQFj_ncNKJLQSVF_aDLIYNkNzkbFcsyWF6vMB2w,706
|
|
21
22
|
botmanager/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
|
|
23
|
-
django_botmanager-0.
|
|
24
|
-
django_botmanager-0.
|
|
25
|
-
django_botmanager-0.
|
|
26
|
-
django_botmanager-0.
|
|
23
|
+
botmanager/templates/botmanager/task/change_form.html,sha256=kwcZ72z_YgNJgWOKNE_jS1WXk7YDvo50r0kdLBXLhmU,277
|
|
24
|
+
django_botmanager-0.2.23.dist-info/licenses/LICENSE,sha256=drrREcwMWVwKpmlpyKh6ytsBFonOm2GbADAJD3WzjKY,1479
|
|
25
|
+
django_botmanager-0.2.23.dist-info/METADATA,sha256=WBTwZp7r9xc17EZTBuYdYnFyPsCtrnKzwzIVtk7It3E,439
|
|
26
|
+
django_botmanager-0.2.23.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
27
|
+
django_botmanager-0.2.23.dist-info/top_level.txt,sha256=gq7opmRFT7PQifLHi-_hCCtsMjAAiH87radGZ13utgM,11
|
|
28
|
+
django_botmanager-0.2.23.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: django-botmanager
|
|
3
|
-
Version: 0.1.10
|
|
4
|
-
Summary: Async tasks for django
|
|
5
|
-
Home-page: https://github.com/dimoha/django-botmanager
|
|
6
|
-
Author: Dimoha
|
|
7
|
-
Author-email: dimoha@controlstyle.ru
|
|
8
|
-
License: UNKNOWN
|
|
9
|
-
Platform: UNKNOWN
|
|
10
|
-
Requires-Dist: setproctitle (==1.1.10)
|
|
11
|
-
Requires-Dist: jsonfield (==1.0.3)
|
|
12
|
-
|
|
13
|
-
UNKNOWN
|
|
14
|
-
|
|
15
|
-
|
|
File without changes
|
|
File without changes
|