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 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 = u"Ошибка"
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
- logging.exception(e)
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 True:
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
- old_in_process_task_ids = set(Task.objects.filter(in_process=True).values_list('pk', flat=True))
304
- while True:
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
- @transaction.atomic
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 True:
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
- name = models.CharField(max_length=256, verbose_name=u'Название')
22
- create_dt = models.DateTimeField(verbose_name=u"Дата создания задачи", auto_now_add=True)
23
- finish_dt = models.DateTimeField(verbose_name=u"Дата завершения", null=True, blank=True)
24
- is_complete = models.BooleanField(verbose_name=u"Выполнена", default=False)
25
- is_failed = models.BooleanField(verbose_name=u"Аварийное завершение", default=False)
26
- is_persistent = models.BooleanField(verbose_name=u"Выполнять пока не завершится успешно", default=True)
27
- in_process = models.BooleanField(verbose_name=u"В процессе", default=False)
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
- handlers.append('sentry')
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=c5SsP_aLp1BVeXD2FBKBxUDVk5Y-8zaUlWN47GMVjgU,1482
2
+ botmanager/admin.py,sha256=oeLgxc2briLeXZjT1jE3DKWpYh65QY3qBctEpV4ggDU,3159
3
3
  botmanager/apps.py,sha256=LeNG6Fr77QcsOSjyBAKEldYqqI2zUOmCFt3P3Sq9pSI,135
4
- botmanager/basetask.py,sha256=EHPgJbKKW0aX0IkFmok7hdjjLQx58KGaLgFg7tW_YJw,7580
5
- botmanager/models.py,sha256=41r8-k8XRXan8zh6rbx7mxIxf37cMzFmI1ECElyFjPg,3557
6
- botmanager/settings.py,sha256=qQWH6mFi-P6Kzb41u8zl1sCANzokPjgQ2jpJeRtVWyY,3184
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=IUx-4O-6jB0IOrB_hgVhveaN6CSe7AfZGghs7Z39Y2k,19567
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
- django_botmanager-0.1.10.dist-info/LICENSE,sha256=drrREcwMWVwKpmlpyKh6ytsBFonOm2GbADAJD3WzjKY,1479
23
- django_botmanager-0.1.10.dist-info/METADATA,sha256=mc70xjzYJEefi5VDMKK6WWty9vng81DWWS-mx7TaYUI,321
24
- django_botmanager-0.1.10.dist-info/WHEEL,sha256=EVRjI69F5qVjm_YgqcTXPnTAv3BfSUr0WVAHuSP3Xoo,92
25
- django_botmanager-0.1.10.dist-info/top_level.txt,sha256=gq7opmRFT7PQifLHi-_hCCtsMjAAiH87radGZ13utgM,11
26
- django_botmanager-0.1.10.dist-info/RECORD,,
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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.35.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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
-