educommon 3.12.0__py3-none-any.whl → 3.13.2__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.
- educommon/__init__.py +0 -1
- educommon/about/ui/actions.py +16 -30
- educommon/about/ui/ui.py +3 -12
- educommon/about/utils.py +6 -5
- educommon/async_task/__init__.py +0 -1
- educommon/async_task/actions.py +18 -13
- educommon/async_task/apps.py +4 -0
- educommon/async_task/locker.py +2 -5
- educommon/async_task/migrations/0001_initial.py +55 -9
- educommon/async_task/migrations/0002_task_type_and_status_data.py +94 -89
- educommon/async_task/migrations/0003_alter_runningtask_options.py +0 -1
- educommon/async_task/models.py +9 -6
- educommon/async_task/tasks.py +11 -7
- educommon/async_task/ui.py +16 -35
- educommon/async_tasks/__init__.py +0 -1
- educommon/async_tasks/apps.py +4 -0
- educommon/async_tasks/locks.py +11 -21
- educommon/async_tasks/migrations/0001_initial.py +68 -8
- educommon/async_tasks/migrations/0002_load_initial_data.py +0 -1
- educommon/async_tasks/models.py +9 -29
- educommon/async_tasks/tasks.py +25 -54
- educommon/audit_log/__init__.py +1 -0
- educommon/audit_log/actions.py +27 -36
- educommon/audit_log/app_meta.py +7 -4
- educommon/audit_log/apps.py +44 -29
- educommon/audit_log/constants.py +7 -4
- educommon/audit_log/error_log/actions.py +1 -3
- educommon/audit_log/helpers.py +2 -4
- educommon/audit_log/management/commands/reinstall_audit_log.py +11 -7
- educommon/audit_log/migrations/0001_initial.py +91 -16
- educommon/audit_log/migrations/0002_install_audit_log.py +13 -13
- educommon/audit_log/migrations/0003_logproxy.py +1 -3
- educommon/audit_log/migrations/0004_reinstall_audit_log.py +1 -4
- educommon/audit_log/migrations/0005_postgresql_error.py +4 -2
- educommon/audit_log/migrations/0006_auto_20200806_1707.py +3 -4
- educommon/audit_log/migrations/0007_create_selective_tables_function.py +8 -5
- educommon/audit_log/migrations/0008_table_logged.py +0 -1
- educommon/audit_log/migrations/0009_reinstall_audit_log.py +0 -1
- educommon/audit_log/models.py +36 -42
- educommon/audit_log/permissions.py +11 -9
- educommon/audit_log/proxies.py +12 -23
- educommon/audit_log/ui.py +18 -15
- educommon/audit_log/utils/__init__.py +28 -60
- educommon/audit_log/utils/operations.py +16 -2
- educommon/auth/__init__.py +0 -3
- educommon/auth/rbac/__init__.py +2 -4
- educommon/auth/rbac/actions.py +148 -145
- educommon/auth/rbac/app_meta.py +9 -6
- educommon/auth/rbac/backends/base.py +2 -8
- educommon/auth/rbac/backends/caching.py +27 -37
- educommon/auth/rbac/backends/simple.py +1 -4
- educommon/auth/rbac/checker.py +1 -3
- educommon/auth/rbac/management/commands/rbac.py +6 -11
- educommon/auth/rbac/manager.py +18 -47
- educommon/auth/rbac/migrations/0001_initial.py +73 -12
- educommon/auth/rbac/migrations/0002_model_modifier_metaclass_fix.py +7 -6
- educommon/auth/rbac/migrations/0003_permission_hidden.py +1 -5
- educommon/auth/rbac/migrations/0004_auto_20171024_1245.py +26 -19
- educommon/auth/rbac/models.py +63 -68
- educommon/auth/rbac/permissions.py +6 -7
- educommon/auth/rbac/ui.py +83 -84
- educommon/auth/rbac/utils.py +10 -11
- educommon/auth/rbac/validators.py +4 -5
- educommon/auth/simple_auth/__init__.py +1 -5
- educommon/auth/simple_auth/actions.py +79 -92
- educommon/auth/simple_auth/app_meta.py +2 -9
- educommon/auth/simple_auth/checkers.py +3 -3
- educommon/auth/simple_auth/migrations/0001_initial.py +23 -4
- educommon/auth/simple_auth/validators.py +0 -1
- educommon/contingent/actions.py +7 -7
- educommon/contingent/app_meta.py +1 -4
- educommon/contingent/base.py +10 -15
- educommon/contingent/catalogs.py +424 -540
- educommon/contingent/contingent_plugin/actions.py +4 -15
- educommon/contingent/contingent_plugin/apps.py +10 -4
- educommon/contingent/contingent_plugin/migrations/0001_initial.py +5 -6
- educommon/contingent/contingent_plugin/migrations/0002_add_contingent_model_deleted.py +6 -11
- educommon/contingent/contingent_plugin/model_views.py +2 -12
- educommon/contingent/contingent_plugin/models.py +2 -7
- educommon/contingent/contingent_plugin/observer.py +14 -13
- educommon/contingent/contingent_plugin/plugin_meta.py +1 -3
- educommon/contingent/contingent_plugin/storage.py +8 -7
- educommon/contingent/contingent_plugin/utils.py +6 -6
- educommon/django/db/fields.py +72 -86
- educommon/django/db/migration/__init__.py +3 -7
- educommon/django/db/migration/operations.py +29 -51
- educommon/django/db/mixins/__init__.py +16 -10
- educommon/django/db/mixins/date_interval.py +47 -75
- educommon/django/db/mixins/validation.py +26 -26
- educommon/django/db/model_view/__init__.py +18 -22
- educommon/django/db/models.py +9 -8
- educommon/django/db/observer.py +9 -27
- educommon/django/db/partitioning/__init__.py +66 -92
- educommon/django/db/partitioning/management/commands/apply_partitioning.py +3 -13
- educommon/django/db/partitioning/management/commands/clear_table.py +18 -14
- educommon/django/db/partitioning/management/commands/split_table.py +18 -13
- educommon/django/db/routers.py +6 -15
- educommon/django/db/signals.py +149 -2
- educommon/django/db/utils.py +14 -19
- educommon/django/db/validators/__init__.py +1 -0
- educommon/django/db/validators/simple.py +72 -100
- educommon/django/storages/atcfs/api.py +39 -53
- educommon/django/storages/atcfs/app_meta.py +1 -1
- educommon/django/storages/atcfs/management/commands/atcfs_migrate.py +42 -55
- educommon/django/storages/atcfs/models.py +0 -3
- educommon/django/storages/atcfs/monkey_patching.py +18 -12
- educommon/django/storages/atcfs/storage.py +14 -23
- educommon/extjs/fields/input_params.py +15 -45
- educommon/importer/XLSReader.py +143 -241
- educommon/importer/__init__.py +86 -4
- educommon/importer/api.py +53 -84
- educommon/importer/constants.py +4 -14
- educommon/importer/loggers.py +16 -26
- educommon/importer/proxy.py +131 -176
- educommon/importer/proxy_import.py +11 -12
- educommon/importer/report.py +4 -6
- educommon/importer/ui.py +32 -26
- educommon/importer/validators.py +4 -7
- educommon/integration_entities/helpers.py +14 -18
- educommon/ioc/__init__.py +3 -6
- educommon/logger/loggers.py +10 -14
- educommon/m3/__init__.py +20 -38
- educommon/m3/extensions/__init__.py +1 -0
- educommon/m3/extensions/listeners/__init__.py +22 -38
- educommon/m3/extensions/listeners/delete_check/listeners.py +31 -41
- educommon/m3/extensions/listeners/delete_check/mixins.py +20 -25
- educommon/m3/extensions/listeners/delete_check/signals.py +2 -2
- educommon/m3/extensions/listeners/delete_check/ui.py +15 -14
- educommon/m3/extensions/listeners/delete_check/utils.py +9 -11
- educommon/m3/extensions/ui.py +15 -33
- educommon/m3/transaction_context.py +17 -19
- educommon/objectpack/actions.py +70 -88
- educommon/objectpack/apps.py +5 -0
- educommon/objectpack/filters.py +9 -15
- educommon/objectpack/ui.py +59 -77
- educommon/report/__init__.py +9 -5
- educommon/report/actions.py +29 -32
- educommon/report/constructor/__init__.py +5 -8
- educommon/report/constructor/app_meta.py +1 -3
- educommon/report/constructor/apps.py +1 -0
- educommon/report/constructor/base.py +33 -80
- educommon/report/constructor/builders/excel/_base.py +138 -286
- educommon/report/constructor/builders/excel/_header.py +2 -9
- educommon/report/constructor/builders/excel/product.py +13 -34
- educommon/report/constructor/builders/excel/with_merged_cells.py +18 -14
- educommon/report/constructor/config.py +2 -0
- educommon/report/constructor/editor/actions.py +101 -215
- educommon/report/constructor/editor/ui.py +71 -93
- educommon/report/constructor/exceptions.py +6 -12
- educommon/report/constructor/migrations/0001_initial.py +36 -44
- educommon/report/constructor/migrations/0002_report_filters.py +86 -72
- educommon/report/constructor/migrations/0003_reportfilter_exclude.py +5 -5
- educommon/report/constructor/migrations/0004_reportfilter_fields.py +22 -18
- educommon/report/constructor/migrations/0005_reportcolumn_visible.py +5 -4
- educommon/report/constructor/migrations/0006_reportsorting.py +21 -17
- educommon/report/constructor/migrations/0007_include_available_units.py +14 -14
- educommon/report/constructor/migrations/0008_auto_20170407_1318.py +4 -5
- educommon/report/constructor/migrations/0009_auto_20180405_0642.py +1 -4
- educommon/report/constructor/migrations/0010_add_aggregate_fields.py +7 -8
- educommon/report/constructor/mixins.py +14 -15
- educommon/report/constructor/models.py +76 -124
- educommon/report/constructor/utils.py +3 -8
- educommon/report/constructor/validators.py +1 -3
- educommon/report/reporter.py +25 -43
- educommon/report/utils.py +14 -40
- educommon/rest/actions.py +7 -11
- educommon/rest/context.py +6 -16
- educommon/rest/controllers.py +10 -10
- educommon/rest/mixins.py +29 -27
- educommon/secure_media/app_meta.py +9 -9
- educommon/utils/__init__.py +3 -2
- educommon/utils/caching.py +1 -3
- educommon/utils/conversion.py +1 -3
- educommon/utils/crypto.py +1 -2
- educommon/utils/date.py +13 -26
- educommon/utils/db/__init__.py +17 -26
- educommon/utils/db/postgresql.py +1 -4
- educommon/utils/fonts/__init__.py +3 -4
- educommon/utils/licence/__init__.py +5 -16
- educommon/utils/misc.py +9 -18
- educommon/utils/object_grid.py +55 -62
- educommon/utils/phone_number/modelfields.py +1 -3
- educommon/utils/phone_number/phone_number.py +5 -8
- educommon/utils/phone_number/validators.py +8 -23
- educommon/utils/plugins.py +15 -28
- educommon/utils/registry.py +2 -1
- educommon/utils/seqtools.py +1 -3
- educommon/utils/serializer.py +9 -16
- educommon/utils/storage.py +3 -2
- educommon/utils/system.py +1 -3
- educommon/utils/system_app/management/commands/delete_objects.py +17 -34
- educommon/utils/ui.py +87 -84
- educommon/utils/xml/__init__.py +2 -7
- educommon/utils/xml/resolver.py +1 -0
- educommon/ws_log/actions.py +31 -76
- educommon/ws_log/base.py +6 -20
- educommon/ws_log/migrations/0001_initial.py +25 -8
- educommon/ws_log/migrations/0002_auto_20160628_1334.py +0 -1
- educommon/ws_log/migrations/0003_add_fields_to_smev_logs.py +20 -4
- educommon/ws_log/migrations/0004_auto_20160727_1600.py +7 -6
- educommon/ws_log/migrations/0005_auto_20161130_1615.py +14 -4
- educommon/ws_log/migrations/0006_auto_20170327_1027.py +3 -2
- educommon/ws_log/migrations/0007_auto_20180607_1040.py +8 -9
- educommon/ws_log/migrations/0008_auto_20180713_1445.py +23 -10
- educommon/ws_log/migrations/0009_auto_20201130_1553.py +7 -2
- educommon/ws_log/models.py +21 -35
- educommon/ws_log/provider.py +2 -1
- educommon/ws_log/report.py +8 -13
- educommon/ws_log/smev/applications.py +12 -27
- educommon/ws_log/smev/exceptions.py +2 -3
- educommon/ws_log/ui.py +32 -32
- educommon/ws_log/utils.py +1 -3
- educommon-3.13.2.dist-info/METADATA +57 -0
- educommon-3.13.2.dist-info/RECORD +354 -0
- {educommon-3.12.0.dist-info → educommon-3.13.2.dist-info}/WHEEL +1 -1
- educommon/utils/patches.py +0 -27
- educommon/version.conf +0 -11
- educommon-3.12.0.dist-info/METADATA +0 -47
- educommon-3.12.0.dist-info/RECORD +0 -357
- educommon-3.12.0.dist-info/dependency_links.txt +0 -1
- {educommon-3.12.0.dist-info → educommon-3.13.2.dist-info}/top_level.txt +0 -0
educommon/async_task/tasks.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Базовые классы для асинхронных задач."""
|
2
|
+
|
2
3
|
import time
|
3
4
|
from datetime import (
|
4
5
|
datetime,
|
@@ -166,8 +167,14 @@ class AsyncTask(Task):
|
|
166
167
|
kwargs['is_replica'] = True
|
167
168
|
|
168
169
|
async_result = super().apply_async(
|
169
|
-
args=args,
|
170
|
-
|
170
|
+
args=args,
|
171
|
+
kwargs=kwargs,
|
172
|
+
task_id=task_id,
|
173
|
+
producer=producer,
|
174
|
+
link=link,
|
175
|
+
link_error=link_error,
|
176
|
+
shadow=shadow,
|
177
|
+
**options,
|
171
178
|
)
|
172
179
|
|
173
180
|
self.debug(f'Task {self.__name__} (id = {task_id}) added')
|
@@ -190,10 +197,7 @@ class AsyncTask(Task):
|
|
190
197
|
'exc_type': '',
|
191
198
|
'exc_message': '',
|
192
199
|
}
|
193
|
-
self.update_state(
|
194
|
-
state=AsyncTaskStatus.to_state(AsyncTaskStatus.STARTED),
|
195
|
-
meta=self.state
|
196
|
-
)
|
200
|
+
self.update_state(state=AsyncTaskStatus.to_state(AsyncTaskStatus.STARTED), meta=self.state)
|
197
201
|
self.set_read_from_replica(kwargs.get('is_replica', False))
|
198
202
|
|
199
203
|
self.debug(f'Task {self.__name__} (id = {self.request.id}) started')
|
@@ -358,7 +362,7 @@ class UniquePeriodicAsyncTask(PeriodicAsyncTask):
|
|
358
362
|
|
359
363
|
@property
|
360
364
|
def locker_config(self) -> dict:
|
361
|
-
"""Настройки для механизма блокировок.
|
365
|
+
"""Настройки для механизма блокировок."""
|
362
366
|
return {
|
363
367
|
'lock_params': {'task_name': self.name},
|
364
368
|
'lock_message': f'Task [{self.__name__}] is running',
|
educommon/async_task/ui.py
CHANGED
@@ -83,34 +83,19 @@ class AsyncTaskResultViewWindow(BaseWindow):
|
|
83
83
|
"""Инициализация элементов окна."""
|
84
84
|
super()._init_components()
|
85
85
|
|
86
|
-
self.top_region = ExtContainer(
|
87
|
-
region='north', layout='form', height=80, style={'padding': '5px'}
|
88
|
-
)
|
86
|
+
self.top_region = ExtContainer(region='north', layout='form', height=80, style={'padding': '5px'})
|
89
87
|
self.center_region = ExtContainer(region='center', layout='fit', style={'padding': '5px'})
|
90
|
-
self.bottom_region = ExtContainer(
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
)
|
96
|
-
self.task_type_fld = ExtStringField(
|
97
|
-
anchor='100%', label='Тип', read_only=True)
|
98
|
-
self.description_fld = ExtStringField(
|
99
|
-
anchor='100%', label='Описание', read_only=True)
|
100
|
-
self.progress_fld = ExtStringField(
|
101
|
-
anchor='100%', label='Прогресс', read_only=True)
|
102
|
-
self.error_fld = ExtTextArea(
|
103
|
-
anchor='100%', label='Сообщение об ошибке', read_only=True)
|
104
|
-
self.state_fld = ExtStringField(
|
105
|
-
anchor='100%', label='Состояние задачи', read_only=True)
|
88
|
+
self.bottom_region = ExtContainer(region='south', layout='form', height=150, style={'padding': '5px'})
|
89
|
+
self.task_type_fld = ExtStringField(anchor='100%', label='Тип', read_only=True)
|
90
|
+
self.description_fld = ExtStringField(anchor='100%', label='Описание', read_only=True)
|
91
|
+
self.progress_fld = ExtStringField(anchor='100%', label='Прогресс', read_only=True)
|
92
|
+
self.error_fld = ExtTextArea(anchor='100%', label='Сообщение об ошибке', read_only=True)
|
93
|
+
self.state_fld = ExtStringField(anchor='100%', label='Состояние задачи', read_only=True)
|
106
94
|
self.results_grid = ExtObjectGrid(
|
107
95
|
title='Результаты',
|
108
96
|
layout='fit',
|
109
97
|
)
|
110
|
-
self.close_btn = ExtButton(
|
111
|
-
text='Закрыть',
|
112
|
-
handler='function() {win.close()}'
|
113
|
-
)
|
98
|
+
self.close_btn = ExtButton(text='Закрыть', handler='function() {win.close()}')
|
114
99
|
|
115
100
|
# Кнопка "Отмена" не блокируется в режиме "только для чтения"
|
116
101
|
self._mro_exclude_list.append(self.close_btn)
|
@@ -120,19 +105,15 @@ class AsyncTaskResultViewWindow(BaseWindow):
|
|
120
105
|
super()._do_layout()
|
121
106
|
|
122
107
|
self.layout = 'border'
|
123
|
-
self.items[:] = [
|
124
|
-
self.top_region,
|
125
|
-
self.center_region,
|
126
|
-
self.bottom_region
|
127
|
-
]
|
108
|
+
self.items[:] = [self.top_region, self.center_region, self.bottom_region]
|
128
109
|
self.center_region.items.append(self.results_grid)
|
129
|
-
self.bottom_region.items.extend([
|
130
|
-
|
131
|
-
self.
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
110
|
+
self.bottom_region.items.extend([self.progress_fld, self.state_fld, self.error_fld])
|
111
|
+
self.top_region.items.extend([self.task_type_fld, self.description_fld])
|
112
|
+
self.buttons.extend(
|
113
|
+
[
|
114
|
+
self.close_btn,
|
115
|
+
]
|
116
|
+
)
|
136
117
|
|
137
118
|
def _configure_results_grid(self, data: dict):
|
138
119
|
"""Конфигурирование грида с результатами задачи."""
|
educommon/async_tasks/apps.py
CHANGED
educommon/async_tasks/locks.py
CHANGED
@@ -32,12 +32,13 @@ class TaskLocker:
|
|
32
32
|
task_id = None
|
33
33
|
expire_on = DEFAULT_LOCK_EXPIRE
|
34
34
|
|
35
|
-
def __init__(
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
def __init__(
|
36
|
+
self,
|
37
|
+
task_name='',
|
38
|
+
params={},
|
39
|
+
task_id=None,
|
40
|
+
expire_on=DEFAULT_LOCK_EXPIRE,
|
41
|
+
):
|
41
42
|
self.task_name = task_name
|
42
43
|
self.params = params
|
43
44
|
self.task_id = task_id
|
@@ -50,21 +51,14 @@ class TaskLocker:
|
|
50
51
|
@cached_property
|
51
52
|
def lock_id(self) -> str:
|
52
53
|
"""Ключ в кэше."""
|
53
|
-
str_params = ['
|
54
|
-
return self.lock_id_format.format(
|
55
|
-
task_name=self.task_name,
|
56
|
-
params='&'.join(str_params)
|
57
|
-
)
|
54
|
+
str_params = [f'{k}={v}' for k, v in self.params.items()]
|
55
|
+
return self.lock_id_format.format(task_name=self.task_name, params='&'.join(str_params))
|
58
56
|
|
59
57
|
def acquire_lock(self) -> None:
|
60
58
|
"""Установка блокировки."""
|
61
59
|
value = self.task_id or 'true'
|
62
60
|
cache.set(self.lock_id, value, self.expire_on)
|
63
|
-
self.debug(
|
64
|
-
'Lock acquired for Task %s (%s) with value: %s' % (
|
65
|
-
self.task_name, self.params, value
|
66
|
-
)
|
67
|
-
)
|
61
|
+
self.debug(f'Lock acquired for Task {self.task_name} ({self.params}) with value: {value}')
|
68
62
|
|
69
63
|
def delete_lock(self) -> None:
|
70
64
|
"""Удаление блокировки."""
|
@@ -104,9 +98,5 @@ class TaskLocker:
|
|
104
98
|
:raises: educommon.async_task.exceptions.TaskUniqueException
|
105
99
|
"""
|
106
100
|
if self.is_locked():
|
107
|
-
self.debug(
|
108
|
-
'Add failed. Task %s currently locked (%s)' % (
|
109
|
-
self.task_name, self.params
|
110
|
-
)
|
111
|
-
)
|
101
|
+
self.debug(f'Add failed. Task {self.task_name} currently locked ({self.params})')
|
112
102
|
raise TaskUniqueException(message or self.DEFAULT_LOCK_MSG)
|
@@ -5,7 +5,6 @@ from django.db import (
|
|
5
5
|
|
6
6
|
|
7
7
|
class Migration(migrations.Migration):
|
8
|
-
|
9
8
|
dependencies = [
|
10
9
|
('contenttypes', '0001_initial'),
|
11
10
|
]
|
@@ -15,8 +14,21 @@ class Migration(migrations.Migration):
|
|
15
14
|
name='AsyncTaskMeta',
|
16
15
|
fields=[
|
17
16
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
18
|
-
(
|
19
|
-
|
17
|
+
(
|
18
|
+
'description',
|
19
|
+
models.CharField(
|
20
|
+
max_length=400,
|
21
|
+
null=True,
|
22
|
+
verbose_name='\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438',
|
23
|
+
blank=True,
|
24
|
+
),
|
25
|
+
),
|
26
|
+
(
|
27
|
+
'location',
|
28
|
+
models.CharField(
|
29
|
+
max_length=400, verbose_name='\u041f\u0443\u0442\u044c \u043a\u043b\u0430\u0441\u0441\u0430'
|
30
|
+
),
|
31
|
+
),
|
20
32
|
],
|
21
33
|
options={
|
22
34
|
'verbose_name': '\u0414\u0430\u043d\u043d\u044b\u0435 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0438',
|
@@ -42,11 +54,59 @@ class Migration(migrations.Migration):
|
|
42
54
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
43
55
|
('object_id', models.PositiveIntegerField(null=True, blank=True)),
|
44
56
|
('task_id', models.CharField(max_length=36, verbose_name='ID \u0437\u0430\u0434\u0430\u0447\u0438')),
|
45
|
-
(
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
57
|
+
(
|
58
|
+
'status',
|
59
|
+
models.SmallIntegerField(
|
60
|
+
default=1,
|
61
|
+
db_index=True,
|
62
|
+
verbose_name='\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438',
|
63
|
+
choices=[
|
64
|
+
(1, '\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e'),
|
65
|
+
(2, '\u0412 \u043e\u0447\u0435\u0440\u0435\u0434\u0438'),
|
66
|
+
(3, '\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f'),
|
67
|
+
(
|
68
|
+
4,
|
69
|
+
'\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430',
|
70
|
+
),
|
71
|
+
(5, '\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0430'),
|
72
|
+
(6, '\u041e\u0448\u0438\u0431\u043a\u0430'),
|
73
|
+
(7, '\u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a'),
|
74
|
+
(8, '\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430'),
|
75
|
+
(9, '\u041e\u0442\u043c\u0435\u043d\u0435\u043d\u0430'),
|
76
|
+
],
|
77
|
+
),
|
78
|
+
),
|
79
|
+
(
|
80
|
+
'queued_on',
|
81
|
+
models.DateTimeField(
|
82
|
+
null=True,
|
83
|
+
verbose_name='\u0421\u0442\u0430\u0440\u0442 \u0437\u0430\u0434\u0430\u0447\u0438',
|
84
|
+
db_index=True,
|
85
|
+
),
|
86
|
+
),
|
87
|
+
(
|
88
|
+
'content_type',
|
89
|
+
models.ForeignKey(blank=True, to='contenttypes.ContentType', null=True, on_delete=models.SET_NULL),
|
90
|
+
),
|
91
|
+
(
|
92
|
+
'task_meta',
|
93
|
+
models.ForeignKey(
|
94
|
+
verbose_name='\u0414\u0430\u043d\u043d\u044b\u0435 \u0437\u0430\u0434\u0430\u0447\u0438',
|
95
|
+
blank=True,
|
96
|
+
to='async.AsyncTaskMeta',
|
97
|
+
null=True,
|
98
|
+
on_delete=models.SET_NULL,
|
99
|
+
),
|
100
|
+
),
|
101
|
+
(
|
102
|
+
'task_type',
|
103
|
+
models.ForeignKey(
|
104
|
+
default=1,
|
105
|
+
verbose_name='\u0422\u0438\u043f \u0437\u0430\u0434\u0430\u0447\u0438',
|
106
|
+
to='async.AsyncTaskType',
|
107
|
+
on_delete=models.CASCADE,
|
108
|
+
),
|
109
|
+
),
|
50
110
|
],
|
51
111
|
options={
|
52
112
|
'verbose_name': '\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430',
|
educommon/async_tasks/models.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Модели для асинхронных задач Celery."""
|
2
|
+
|
2
3
|
from django.contrib.contenttypes.models import (
|
3
4
|
ContentType,
|
4
5
|
)
|
@@ -40,19 +41,11 @@ class AsyncTaskType(ModelValidationMixin, BaseObjectModel):
|
|
40
41
|
class AsyncTaskMeta(ModelValidationMixin, BaseObjectModel):
|
41
42
|
"""Модель данных асинхронных задач."""
|
42
43
|
|
43
|
-
description = models.CharField(
|
44
|
-
max_length=400,
|
45
|
-
verbose_name='Описание задачи',
|
46
|
-
null=True,
|
47
|
-
blank=True
|
48
|
-
)
|
44
|
+
description = models.CharField(max_length=400, verbose_name='Описание задачи', null=True, blank=True)
|
49
45
|
|
50
46
|
# путь класса от корня проекта, содержит имя модуля и название класса
|
51
47
|
# пример для ЭДО: 'extedu.person.merge.tasks.MergeTask'
|
52
|
-
location = models.CharField(
|
53
|
-
max_length=400,
|
54
|
-
verbose_name='Путь класса'
|
55
|
-
)
|
48
|
+
location = models.CharField(max_length=400, verbose_name='Путь класса')
|
56
49
|
|
57
50
|
class Meta:
|
58
51
|
verbose_name = 'Данные асинхронной задачи'
|
@@ -67,12 +60,9 @@ class RunningTask(ModelValidationMixin, BaseObjectModel):
|
|
67
60
|
# отображение пользователя, если пользователь не задан
|
68
61
|
DEFAULT_USER = 'Система'
|
69
62
|
|
70
|
-
MSG_TASK_NOT_FOUND =
|
71
|
-
'Информации не найдено! Возможно задача была удалена!')
|
63
|
+
MSG_TASK_NOT_FOUND = 'Информации не найдено! Возможно задача была удалена!'
|
72
64
|
|
73
|
-
content_type = models.ForeignKey(
|
74
|
-
ContentType, null=True, blank=True, on_delete=models.SET_NULL
|
75
|
-
)
|
65
|
+
content_type = models.ForeignKey(ContentType, null=True, blank=True, on_delete=models.SET_NULL)
|
76
66
|
object_id = models.PositiveIntegerField(null=True, blank=True)
|
77
67
|
user = GenericForeignKey()
|
78
68
|
task_id = models.CharField(
|
@@ -80,28 +70,18 @@ class RunningTask(ModelValidationMixin, BaseObjectModel):
|
|
80
70
|
verbose_name='ID задачи',
|
81
71
|
)
|
82
72
|
task_type = models.ForeignKey(
|
83
|
-
AsyncTaskType,
|
84
|
-
default=AsyncTaskType.TASK_UNKNOWN,
|
85
|
-
verbose_name='Тип задачи',
|
86
|
-
on_delete=models.CASCADE
|
73
|
+
AsyncTaskType, default=AsyncTaskType.TASK_UNKNOWN, verbose_name='Тип задачи', on_delete=models.CASCADE
|
87
74
|
)
|
88
75
|
task_meta = models.ForeignKey(
|
89
|
-
AsyncTaskMeta,
|
90
|
-
verbose_name='Данные задачи',
|
91
|
-
null=True,
|
92
|
-
blank=True,
|
93
|
-
on_delete=models.SET_NULL
|
76
|
+
AsyncTaskMeta, verbose_name='Данные задачи', null=True, blank=True, on_delete=models.SET_NULL
|
94
77
|
)
|
95
78
|
status = models.SmallIntegerField(
|
96
|
-
choices=statuses.STATUS_CHOICES,
|
97
|
-
default=statuses.STATUS_PENDING,
|
98
|
-
verbose_name='Состояние задачи',
|
99
|
-
db_index=True
|
79
|
+
choices=statuses.STATUS_CHOICES, default=statuses.STATUS_PENDING, verbose_name='Состояние задачи', db_index=True
|
100
80
|
)
|
101
81
|
queued_on = models.DateTimeField(
|
102
82
|
verbose_name='Старт задачи',
|
103
83
|
db_index=True,
|
104
|
-
null=True # null - если задача ещё не в работе
|
84
|
+
null=True, # null - если задача ещё не в работе
|
105
85
|
)
|
106
86
|
|
107
87
|
class Meta:
|
educommon/async_tasks/tasks.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Базовые классы для асинхронных задач."""
|
2
|
+
|
2
3
|
import logging
|
3
4
|
from collections import (
|
4
5
|
OrderedDict,
|
@@ -39,8 +40,7 @@ class AsyncTask(Task):
|
|
39
40
|
logger = logging.getLogger('educommon.async')
|
40
41
|
logger.debug(*args, **kwargs)
|
41
42
|
|
42
|
-
def apply_async(self, args=None, kwargs=None, task_id=None, producer=None,
|
43
|
-
link=None, link_error=None, **options):
|
43
|
+
def apply_async(self, args=None, kwargs=None, task_id=None, producer=None, link=None, link_error=None, **options):
|
44
44
|
"""Постановка задачи в очередь.
|
45
45
|
|
46
46
|
автор задачи задаётся снаружи через 2 поля в словаре kwargs:
|
@@ -49,35 +49,27 @@ class AsyncTask(Task):
|
|
49
49
|
if kwargs is None:
|
50
50
|
kwargs = {}
|
51
51
|
|
52
|
-
async_result = super(
|
53
|
-
args=args, kwargs=kwargs, task_id=task_id, producer=producer,
|
54
|
-
link=link, link_error=link_error, **options
|
52
|
+
async_result = super().apply_async(
|
53
|
+
args=args, kwargs=kwargs, task_id=task_id, producer=producer, link=link, link_error=link_error, **options
|
55
54
|
)
|
56
55
|
|
57
56
|
location = self.__class__.__module__ + '.' + self.__class__.__name__
|
58
|
-
task_meta = models.AsyncTaskMeta.objects.create(
|
59
|
-
location=location,
|
60
|
-
description=self.description
|
61
|
-
)
|
57
|
+
task_meta = models.AsyncTaskMeta.objects.create(location=location, description=self.description)
|
62
58
|
params = dict(
|
63
59
|
task_meta=task_meta,
|
64
60
|
status=statuses.STATUS_RECEIVED,
|
65
61
|
object_id=kwargs.get('object_id'),
|
66
62
|
content_type=kwargs.get('content_type'),
|
67
|
-
task_type_id=kwargs.get(
|
68
|
-
'task_type', models.AsyncTaskType.TASK_UNKNOWN
|
69
|
-
),
|
63
|
+
task_type_id=kwargs.get('task_type', models.AsyncTaskType.TASK_UNKNOWN),
|
70
64
|
# время начала задачи
|
71
65
|
# для случая, когда celery успевает начать задачу ещё до
|
72
66
|
# коммита в БД создаваемой здесь models.RunningTask
|
73
|
-
queued_on=datetime.now()
|
67
|
+
queued_on=datetime.now(),
|
74
68
|
)
|
75
69
|
|
76
|
-
models.RunningTask.objects.get_or_create(
|
77
|
-
task_id=async_result.task_id, defaults=params
|
78
|
-
)
|
70
|
+
models.RunningTask.objects.get_or_create(task_id=async_result.task_id, defaults=params)
|
79
71
|
|
80
|
-
self.debug(
|
72
|
+
self.debug(f'Task {self.__name__} added')
|
81
73
|
|
82
74
|
return async_result
|
83
75
|
|
@@ -90,23 +82,19 @@ class AsyncTask(Task):
|
|
90
82
|
# описание результата
|
91
83
|
'description': self.description,
|
92
84
|
# прогресс выполнения задачи
|
93
|
-
'progress': 'Неизвестно'
|
85
|
+
'progress': 'Неизвестно',
|
94
86
|
}
|
95
|
-
self.update_state(
|
96
|
-
|
97
|
-
|
98
|
-
)
|
99
|
-
self.debug("Task %s run (task_id = %s )" % (
|
100
|
-
self.__name__, self.request.id))
|
87
|
+
self.update_state(state=statuses.get_state_str(statuses.STATUS_STARTED), meta=self.state)
|
88
|
+
self.debug(f'Task {self.__name__} run (task_id = {self.request.id} )')
|
89
|
+
|
101
90
|
return {}
|
102
91
|
|
103
92
|
def after_return(self, status, retval, task_id, args, kwargs, einfo):
|
104
93
|
"""Завершение задачи."""
|
105
|
-
self.debug(
|
94
|
+
self.debug(f'Task {self.__name__} completed')
|
106
95
|
|
107
96
|
if isinstance(retval, dict):
|
108
|
-
self.update_state(
|
109
|
-
state=retval.get('task_state', status), meta=retval)
|
97
|
+
self.update_state(state=retval.get('task_state', status), meta=retval)
|
110
98
|
else:
|
111
99
|
self.update_state(state=status, meta=retval)
|
112
100
|
|
@@ -120,19 +108,15 @@ class AsyncTask(Task):
|
|
120
108
|
if task_id is None:
|
121
109
|
task_id = self.request.id
|
122
110
|
|
123
|
-
running_task = models.RunningTask.objects.filter(
|
124
|
-
task_id=task_id
|
125
|
-
).order_by('queued_on').first()
|
111
|
+
running_task = models.RunningTask.objects.filter(task_id=task_id).order_by('queued_on').first()
|
126
112
|
|
127
113
|
if not running_task:
|
128
114
|
return
|
129
115
|
|
130
116
|
if state == states.SUCCESS:
|
131
|
-
self.state['values']['Время выполения'] = (
|
132
|
-
datetime.now() - running_task.queued_on)
|
117
|
+
self.state['values']['Время выполения'] = datetime.now() - running_task.queued_on
|
133
118
|
|
134
|
-
super(
|
135
|
-
task_id=task_id, state=state, meta=meta)
|
119
|
+
super().update_state(task_id=task_id, state=state, meta=meta)
|
136
120
|
|
137
121
|
running_task.status = statuses.get_status_idx(state)
|
138
122
|
|
@@ -141,8 +125,7 @@ class AsyncTask(Task):
|
|
141
125
|
running_task.queued_on = datetime.now()
|
142
126
|
running_task.clean_and_save()
|
143
127
|
|
144
|
-
def set_progress(self, task_id=None, task_state=states.STARTED,
|
145
|
-
progress=None, values=None):
|
128
|
+
def set_progress(self, task_id=None, task_state=states.STARTED, progress=None, values=None):
|
146
129
|
"""Обновление состояния выполнения задачи.
|
147
130
|
|
148
131
|
:param str task_id: id задачи celery
|
@@ -236,11 +219,12 @@ class LockableAsyncTask(AsyncTask):
|
|
236
219
|
# UUID задачи
|
237
220
|
task_id,
|
238
221
|
# время жизни
|
239
|
-
lock_expire
|
222
|
+
lock_expire,
|
240
223
|
)
|
241
224
|
locker.raise_if_locked(message)
|
242
225
|
|
243
226
|
locker.acquire_lock()
|
227
|
+
|
244
228
|
return locker.lock_id
|
245
229
|
|
246
230
|
def _validate_lock_data(self, lock_data):
|
@@ -250,16 +234,7 @@ class LockableAsyncTask(AsyncTask):
|
|
250
234
|
"""
|
251
235
|
pass
|
252
236
|
|
253
|
-
def apply_async(
|
254
|
-
self,
|
255
|
-
args=None,
|
256
|
-
kwargs={},
|
257
|
-
task_id=None,
|
258
|
-
producer=None,
|
259
|
-
link=None,
|
260
|
-
link_error=None,
|
261
|
-
**options
|
262
|
-
):
|
237
|
+
def apply_async(self, args=None, kwargs={}, task_id=None, producer=None, link=None, link_error=None, **options):
|
263
238
|
"""Постановка в очередь.
|
264
239
|
|
265
240
|
Для задач, реализующих уникальность, метод выбрасывает исключение.
|
@@ -279,21 +254,17 @@ class LockableAsyncTask(AsyncTask):
|
|
279
254
|
kwargs.update(lock_id=lock_id)
|
280
255
|
|
281
256
|
async_result = super().apply_async(
|
282
|
-
args=args, kwargs=kwargs, task_id=task_id, producer=producer,
|
283
|
-
link=link, link_error=link_error, **options
|
257
|
+
args=args, kwargs=kwargs, task_id=task_id, producer=producer, link=link, link_error=link_error, **options
|
284
258
|
)
|
285
259
|
|
286
260
|
return async_result
|
287
261
|
|
288
262
|
def after_return(self, status, retval, task_id, args, kwargs, einfo):
|
289
263
|
"""Завершение задачи"""
|
290
|
-
super().after_return(
|
291
|
-
status, retval, task_id, args, kwargs, einfo
|
292
|
-
)
|
264
|
+
super().after_return(status, retval, task_id, args, kwargs, einfo)
|
293
265
|
# снимаем блокировку
|
294
266
|
lock_id = kwargs.get('lock_id')
|
295
267
|
if lock_id:
|
296
268
|
# задача завершена, убрать лок
|
297
|
-
self.debug(
|
298
|
-
self.__name__, lock_id))
|
269
|
+
self.debug(f'Unlock task {self.__name__} lock_id = {lock_id}')
|
299
270
|
self._locker_class.delete_lock_by_id(lock_id)
|