educommon 3.13.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 +4 -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.0.dist-info → educommon-3.13.2.dist-info}/METADATA +26 -14
- educommon-3.13.2.dist-info/RECORD +354 -0
- educommon/utils/patches.py +0 -27
- educommon/version.conf +0 -11
- educommon-3.13.0.dist-info/RECORD +0 -357
- educommon-3.13.0.dist-info/dependency_links.txt +0 -1
- {educommon-3.13.0.dist-info → educommon-3.13.2.dist-info}/WHEEL +0 -0
- {educommon-3.13.0.dist-info → educommon-3.13.2.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Расширение поведения экшнов."""
|
2
|
+
|
2
3
|
from m3.actions import (
|
3
4
|
OperationResult,
|
4
5
|
)
|
@@ -18,11 +19,10 @@ from educommon.m3.extensions.ui import (
|
|
18
19
|
|
19
20
|
|
20
21
|
class DeclareContextListener:
|
21
|
-
"""
|
22
|
-
Предок листенеров.
|
22
|
+
"""Предок листенеров.
|
23
23
|
|
24
24
|
Предоставляет возможность задекларировать дополнительный контекст
|
25
|
-
выполнения
|
25
|
+
выполнения экшна.
|
26
26
|
"""
|
27
27
|
|
28
28
|
def before(self, request, context):
|
@@ -64,15 +64,14 @@ class DeclareContextListener:
|
|
64
64
|
# если контекст неправильный, то возвращаем
|
65
65
|
# фейльный результат операции
|
66
66
|
return OperationResult.by_message(
|
67
|
-
'Не удалось выполнить операцию. '
|
68
|
-
'Не задан обязательный<br>параметр: ' + e.reason
|
67
|
+
f'Не удалось выполнить операцию. Не задан обязательный<br>параметр: {e.reason}'
|
69
68
|
)
|
69
|
+
|
70
70
|
return context
|
71
71
|
|
72
72
|
|
73
73
|
class BaseEditWinListener(DeclareContextListener):
|
74
|
-
"""
|
75
|
-
Базовый листенер для расширения окна редактирования доп.полями и данными.
|
74
|
+
"""Базовый листенер для расширения окна редактирования доп.полями и данными.
|
76
75
|
|
77
76
|
Окно редактирования связано с определенной моделью, она - расширяемая и ее
|
78
77
|
данные требуется дополнить с помощью доп.полей.
|
@@ -85,38 +84,32 @@ class BaseEditWinListener(DeclareContextListener):
|
|
85
84
|
parent_model_field = None
|
86
85
|
|
87
86
|
def _get_id(self, context):
|
88
|
-
"""
|
89
|
-
Получение из контекста параметра, определяющего id расширяемой модели.
|
87
|
+
"""Получение из контекста параметра, определяющего id расширяемой модели.
|
90
88
|
|
91
89
|
:rype: int
|
92
90
|
"""
|
93
91
|
raise NotImplementedError()
|
94
92
|
|
95
93
|
def _get_instance(self, row_id):
|
96
|
-
"""
|
97
|
-
Получение модели, которая расширяет основную ``ui_extender_cls.model``.
|
94
|
+
"""Получение модели, которая расширяет основную ``ui_extender_cls.model``.
|
98
95
|
|
99
96
|
:param row_id: идентификатор расширяемой модели
|
100
97
|
:rtype: object
|
101
98
|
"""
|
102
99
|
try:
|
103
|
-
instance = self.ui_extender_cls.model.objects.get(**{
|
104
|
-
self.parent_model_field: row_id
|
105
|
-
})
|
100
|
+
instance = self.ui_extender_cls.model.objects.get(**{self.parent_model_field: row_id})
|
106
101
|
except self.ui_extender_cls.model.DoesNotExist:
|
107
102
|
# расширяющая модель еще только создается
|
108
103
|
return
|
104
|
+
|
109
105
|
return instance
|
110
106
|
|
111
107
|
def _get_params(self, instance, context):
|
112
108
|
"""Дополнение параметров."""
|
113
|
-
return {
|
114
|
-
'instance': instance
|
115
|
-
}
|
109
|
+
return {'instance': instance}
|
116
110
|
|
117
111
|
def after(self, request, context, response):
|
118
|
-
"""
|
119
|
-
Получение истанса модели, которая расширяет и биндинг ее в окно.
|
112
|
+
"""Получение истанса модели, которая расширяет и биндинг ее в окно.
|
120
113
|
|
121
114
|
:param request: Request
|
122
115
|
:type request: django.http.HttpRequest
|
@@ -126,8 +119,7 @@ class BaseEditWinListener(DeclareContextListener):
|
|
126
119
|
if not isinstance(response, ExtUIScriptResult):
|
127
120
|
# не окно
|
128
121
|
return
|
129
|
-
assert self.parent_model_field,
|
130
|
-
'No parent model field defined in listener!')
|
122
|
+
assert self.parent_model_field, 'No parent model field defined in listener!'
|
131
123
|
assert issubclass(self.ui_extender_cls, BaseEditWinExtender)
|
132
124
|
|
133
125
|
# расширение интерфейса
|
@@ -151,8 +143,7 @@ class BaseEditWinListener(DeclareContextListener):
|
|
151
143
|
|
152
144
|
|
153
145
|
class BaseSaveListener(DeclareContextListener):
|
154
|
-
"""
|
155
|
-
Базовый класс листенеров сохранения доп.данных.
|
146
|
+
"""Базовый класс листенеров сохранения доп.данных.
|
156
147
|
|
157
148
|
Обращение к данному листенеру из экшна делается через
|
158
149
|
|
@@ -170,8 +161,7 @@ class BaseSaveListener(DeclareContextListener):
|
|
170
161
|
parent_model_field = None
|
171
162
|
|
172
163
|
def _get_instance(self, parent_model_instance, context):
|
173
|
-
"""
|
174
|
-
Получение инстанса расширяющей модели.
|
164
|
+
"""Получение инстанса расширяющей модели.
|
175
165
|
|
176
166
|
Метод вынесен для возможности инстанцирования дополнительных
|
177
167
|
зависимых моделей (относительно расширяющей модели ``instance``)
|
@@ -182,26 +172,21 @@ class BaseSaveListener(DeclareContextListener):
|
|
182
172
|
:returns: инстанс расширяющей модели
|
183
173
|
"""
|
184
174
|
try:
|
185
|
-
instance = self.ui_extender_cls.model.objects.get(**{
|
186
|
-
self.parent_model_field: parent_model_instance
|
187
|
-
})
|
175
|
+
instance = self.ui_extender_cls.model.objects.get(**{self.parent_model_field: parent_model_instance})
|
188
176
|
except self.ui_extender_cls.model.DoesNotExist:
|
189
|
-
instance = self.ui_extender_cls.model(**{
|
190
|
-
|
191
|
-
})
|
177
|
+
instance = self.ui_extender_cls.model(**{self.parent_model_field: parent_model_instance})
|
178
|
+
|
192
179
|
return instance
|
193
180
|
|
194
181
|
def post_save(self, arguments):
|
195
|
-
"""
|
196
|
-
Точка входа для расширения из экшна self.handle('post_save', *).
|
182
|
+
"""Точка входа для расширения из экшна self.handle('post_save', *).
|
197
183
|
|
198
184
|
:param parent_model_instance: инстанс расширяемой модели
|
199
185
|
:param context: Context
|
200
186
|
:type context: m3.actions.context.DeclarativeActionContext
|
201
187
|
"""
|
202
188
|
parent_model_instance, context = arguments
|
203
|
-
assert self.parent_model_field,
|
204
|
-
'No parent model field defined in listener!')
|
189
|
+
assert self.parent_model_field, 'No parent model field defined in listener!'
|
205
190
|
assert issubclass(self.ui_extender_cls, BaseEditWinExtender)
|
206
191
|
|
207
192
|
instance = self._get_instance(parent_model_instance, context)
|
@@ -212,10 +197,9 @@ class BaseSaveListener(DeclareContextListener):
|
|
212
197
|
return parent_model_instance, context
|
213
198
|
|
214
199
|
def _save_instance(self, instance):
|
215
|
-
"""
|
216
|
-
Сохранение расширяющей модели.
|
200
|
+
"""Сохранение расширяющей модели.
|
217
201
|
|
218
|
-
Вынесено для возможности сохранения связанных/зависимых
|
202
|
+
Вынесено для возможности сохранения связанных/зависимых сущностей.
|
219
203
|
"""
|
220
204
|
instance.full_clean()
|
221
205
|
instance.save()
|
@@ -54,13 +54,9 @@ class DeleteCheck:
|
|
54
54
|
|
55
55
|
def _get_message(self, objects):
|
56
56
|
"""Возвращает сообщение в правильном склонении."""
|
57
|
-
return (
|
58
|
-
'объекта' if len(objects) == 1 else 'объектов',
|
59
|
-
'него' if len(objects) == 1 else 'них'
|
60
|
-
)
|
57
|
+
return ('объекта' if len(objects) == 1 else 'объектов', 'него' if len(objects) == 1 else 'них')
|
61
58
|
|
62
|
-
def _setup_window(self, objects, related_objects, blocked=True,
|
63
|
-
grid_id=None):
|
59
|
+
def _setup_window(self, objects, related_objects, blocked=True, grid_id=None):
|
64
60
|
"""Возвращает окно с сообщением о связанных объектах.
|
65
61
|
|
66
62
|
:param objects: удаляемые объекты
|
@@ -70,28 +66,26 @@ class DeleteCheck:
|
|
70
66
|
:return: CancelConfirmWindow
|
71
67
|
"""
|
72
68
|
if blocked:
|
73
|
-
title = (
|
74
|
-
'Удаление {} невозможно, т.к. на {} есть ссылки:'
|
75
|
-
.format(*self._get_message(objects))
|
76
|
-
)
|
69
|
+
title = 'Удаление {} невозможно, т.к. на {} есть ссылки:'.format(*self._get_message(objects))
|
77
70
|
else:
|
78
|
-
title = (
|
79
|
-
'При удалении {} будут удалены следующие связи:'
|
80
|
-
.format(self._get_message(objects)[0])
|
81
|
-
)
|
71
|
+
title = 'При удалении {} будут удалены следующие связи:'.format(self._get_message(objects)[0])
|
82
72
|
|
83
73
|
win = CancelConfirmWindow()
|
84
|
-
win.set_params(
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
74
|
+
win.set_params(
|
75
|
+
dict(
|
76
|
+
title=title,
|
77
|
+
objects=objects,
|
78
|
+
related_objects=related_objects,
|
79
|
+
blocked=blocked,
|
80
|
+
grid_id=grid_id,
|
81
|
+
)
|
82
|
+
)
|
91
83
|
win.pack_action_url = self.action.get_absolute_url()
|
84
|
+
|
92
85
|
return win
|
93
86
|
|
94
87
|
def _get_objects(self, context):
|
88
|
+
"""Получает удаляемые объекты из контекста."""
|
95
89
|
model = self.action.parent.model
|
96
90
|
if issubclass(model, ModelProxy):
|
97
91
|
model = model.model
|
@@ -101,6 +95,11 @@ class DeleteCheck:
|
|
101
95
|
return model.objects.filter(pk__in=object_ids)
|
102
96
|
|
103
97
|
def before(self, request, context):
|
98
|
+
"""Обрабатывает удаление с проверкой на наличие связанных объектов.
|
99
|
+
|
100
|
+
Если удаление невозможно или требует подтверждения, формирует окно
|
101
|
+
с предупреждением или подтверждением.
|
102
|
+
"""
|
104
103
|
if not isinstance(self.action, ObjectDeleteAction):
|
105
104
|
return
|
106
105
|
|
@@ -111,11 +110,9 @@ class DeleteCheck:
|
|
111
110
|
return
|
112
111
|
|
113
112
|
if hasattr(self.action.parent, 'skip_field'):
|
113
|
+
|
114
114
|
def skip_function(field):
|
115
|
-
return (
|
116
|
-
self.action.parent.skip_field(field) or
|
117
|
-
CascadeDeleteMixin.skip_field(field)
|
118
|
-
)
|
115
|
+
return self.action.parent.skip_field(field) or CascadeDeleteMixin.skip_field(field)
|
119
116
|
else:
|
120
117
|
skip_function = CascadeDeleteMixin.skip_field
|
121
118
|
|
@@ -128,19 +125,15 @@ class DeleteCheck:
|
|
128
125
|
|
129
126
|
# Проверка блокирующих удаление связей
|
130
127
|
for obj in objects:
|
131
|
-
for related_obj in get_related_instances_proxy(
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
)
|
128
|
+
for related_obj in get_related_instances_proxy(obj, skip_function):
|
129
|
+
key = '.'.join(
|
130
|
+
(
|
131
|
+
related_obj._meta.app_label,
|
132
|
+
related_obj._meta.object_name,
|
133
|
+
)
|
134
|
+
)
|
138
135
|
related_objects[key].append(related_obj)
|
139
|
-
self.collect_implicit.send(
|
140
|
-
self.action.parent.model,
|
141
|
-
objects_to_delete=objects,
|
142
|
-
related_objects=related_objects
|
143
|
-
)
|
136
|
+
self.collect_implicit.send(self.action.parent.model, objects_to_delete=objects, related_objects=related_objects)
|
144
137
|
|
145
138
|
if related_objects:
|
146
139
|
win = self._setup_window(objects, related_objects)
|
@@ -148,10 +141,7 @@ class DeleteCheck:
|
|
148
141
|
# Проверка не блокирующих удаление связей
|
149
142
|
for obj in objects:
|
150
143
|
related_objects.update(get_non_block_relations(obj))
|
151
|
-
win = self._setup_window(
|
152
|
-
objects, related_objects, blocked=False,
|
153
|
-
grid_id=getattr(context, 'grid_id', None)
|
154
|
-
)
|
144
|
+
win = self._setup_window(objects, related_objects, blocked=False, grid_id=getattr(context, 'grid_id', None))
|
155
145
|
|
156
146
|
if not related_objects:
|
157
147
|
return
|
@@ -16,7 +16,6 @@ from educommon.m3.extensions.listeners.delete_check.utils import (
|
|
16
16
|
|
17
17
|
|
18
18
|
class CascadeDeletePackMixin:
|
19
|
-
|
20
19
|
"""Mixin определяющий исключаемые поля модели при проверке DeleteCheck.
|
21
20
|
|
22
21
|
Поскольку DeleteCheck срабатывает до вызова delete_row возникают ситуации,
|
@@ -26,21 +25,18 @@ class CascadeDeletePackMixin:
|
|
26
25
|
|
27
26
|
.. code-block:: python
|
28
27
|
|
29
|
-
cascade_delete_objects = (
|
30
|
-
( 'parent', 'parentportfolio', 'person'),
|
31
|
-
)
|
28
|
+
cascade_delete_objects = (('parent', 'parentportfolio', 'person'),)
|
32
29
|
"""
|
30
|
+
|
33
31
|
# содержит кортежи формата
|
34
32
|
# (имя приложения, имя модели приложения, имя поля)
|
35
33
|
cascade_delete_objects = ()
|
36
34
|
|
37
35
|
def _cascade_delete(self, obj_id, request, context):
|
38
|
-
|
39
|
-
|
36
|
+
"""Удаляет связанные объекты, указанные в cascade_delete_objects."""
|
37
|
+
for app_label, model_name, field_name in self.__class__.cascade_delete_objects:
|
40
38
|
model = get_model(app_label, model_name)
|
41
|
-
lookup = {
|
42
|
-
field_name + '_id': obj_id
|
43
|
-
}
|
39
|
+
lookup = {f'{field_name}_id': obj_id}
|
44
40
|
for obj in model.objects.filter(**lookup):
|
45
41
|
if hasattr(obj, 'safe_delete') and callable(obj.safe_delete):
|
46
42
|
obj.safe_delete()
|
@@ -49,19 +45,20 @@ class CascadeDeletePackMixin:
|
|
49
45
|
|
50
46
|
@classmethod
|
51
47
|
def skip_field(cls, field):
|
48
|
+
"""Определяет, следует ли исключить поле из проверки DeleteCheck."""
|
52
49
|
if cls.cascade_delete_objects:
|
53
|
-
for app_label, model_name, field_name in
|
54
|
-
cls.cascade_delete_objects):
|
50
|
+
for app_label, model_name, field_name in cls.cascade_delete_objects:
|
55
51
|
model = get_model(app_label, model_name)
|
56
52
|
if field.name == field_name and field.model == model:
|
57
53
|
return True
|
54
|
+
|
58
55
|
return False
|
59
56
|
|
60
57
|
@atomic
|
61
58
|
def delete_row(self, obj_id, request, context):
|
62
59
|
self._cascade_delete(obj_id, request, context)
|
63
|
-
|
64
|
-
|
60
|
+
|
61
|
+
return super().delete_row(obj_id, request, context)
|
65
62
|
|
66
63
|
|
67
64
|
class CascadeDeleteMixin:
|
@@ -81,19 +78,19 @@ class CascadeDeleteMixin:
|
|
81
78
|
:caption: Пример использования
|
82
79
|
|
83
80
|
class ModelA(CascadeDeleteMixin, models.Model):
|
84
|
-
|
85
81
|
field = models.TextField()
|
86
82
|
|
87
|
-
class ModelB(models.Model):
|
88
83
|
|
84
|
+
class ModelB(models.Model):
|
89
85
|
link = models.ForeignKey(ModelA)
|
90
86
|
cascade_delete_for = (link,)
|
91
87
|
|
92
|
-
class ModelC(models.Model):
|
93
88
|
|
89
|
+
class ModelC(models.Model):
|
94
90
|
link = models.ForeignKey(ModelA, on_delete=models.SET_NULL)
|
95
91
|
cascade_delete_for = (link,)
|
96
92
|
|
93
|
+
|
97
94
|
def some_func(obj):
|
98
95
|
obj.link = ModelA()
|
99
96
|
obj.link.save()
|
@@ -101,8 +98,8 @@ class CascadeDeleteMixin:
|
|
101
98
|
# В случае, если ModelA и ModelD наследуются от ModelValidationMixin
|
102
99
|
# то вместо вызова `save` должен быть метод `clean_and_save`
|
103
100
|
|
104
|
-
class ModelD(models.Model):
|
105
101
|
|
102
|
+
class ModelD(models.Model):
|
106
103
|
link = models.ForeignKey(ModelA, on_delete=models.SET_NULL)
|
107
104
|
cascade_delete_for = {link: {'on_delete': some_func}}
|
108
105
|
|
@@ -128,11 +125,10 @@ class CascadeDeleteMixin:
|
|
128
125
|
:caption: Пример использования сингала pre_cascade
|
129
126
|
|
130
127
|
class ModelA(CascadeDeleteMixin, models.Model):
|
131
|
-
|
132
128
|
field = models.TextField()
|
133
129
|
|
134
|
-
class ModelB(ReadOnlyMixin, models.Model):
|
135
130
|
|
131
|
+
class ModelB(ReadOnlyMixin, models.Model):
|
136
132
|
# Объекты изменяются только при изменениях, вызванных в
|
137
133
|
# обработчиках сигналов на моделях-источниках
|
138
134
|
_changed_from_signal = False
|
@@ -144,6 +140,7 @@ class CascadeDeleteMixin:
|
|
144
140
|
|
145
141
|
link = models.ForeignKey(ModelA)
|
146
142
|
|
143
|
+
|
147
144
|
@receiver(pre_cascade, sender=ModelB)
|
148
145
|
def pre_cascade_delete(instance, **_):
|
149
146
|
# Каскадное удаление по умолчанию удаление по сигналу.
|
@@ -152,9 +149,11 @@ class CascadeDeleteMixin:
|
|
152
149
|
|
153
150
|
@staticmethod
|
154
151
|
def skip_field(field):
|
152
|
+
"""Проверяет, включено ли поле в каскадное удаление."""
|
155
153
|
cascade_delete_for = getattr(field.model, 'cascade_delete_for', ())
|
156
154
|
if isinstance(cascade_delete_for, dict):
|
157
155
|
cascade_delete_for = tuple(cascade_delete_for.keys())
|
156
|
+
|
158
157
|
return field in cascade_delete_for
|
159
158
|
|
160
159
|
@atomic
|
@@ -163,13 +162,9 @@ class CascadeDeleteMixin:
|
|
163
162
|
for obj, handler in get_related_instances_and_handlers(
|
164
163
|
self, skip_func=lambda field: not self.skip_field(field)
|
165
164
|
):
|
166
|
-
params = dict(
|
167
|
-
sender=self._meta.model,
|
168
|
-
instance=obj,
|
169
|
-
initiator=self
|
170
|
-
)
|
165
|
+
params = dict(sender=self._meta.model, instance=obj, initiator=self)
|
171
166
|
pre_cascade.send(**params)
|
172
167
|
handler(obj)
|
173
168
|
post_cascade.send(**params)
|
174
169
|
|
175
|
-
return super(
|
170
|
+
return super().safe_delete()
|
@@ -6,5 +6,5 @@ from django.db.models.signals import (
|
|
6
6
|
# Сигналы перед и после каскадного удаления объекта (CascadeDeleteMixin)
|
7
7
|
# - instance - удаляемый объект
|
8
8
|
# - initiator - объект, который стал инициатором удаления instance
|
9
|
-
pre_cascade = ModelSignal(providing_args=[
|
10
|
-
post_cascade = ModelSignal(providing_args=[
|
9
|
+
pre_cascade = ModelSignal(providing_args=['instance', 'initiator'])
|
10
|
+
post_cascade = ModelSignal(providing_args=['instance', 'initiator'])
|
@@ -21,7 +21,6 @@ from educommon.utils.ui import (
|
|
21
21
|
|
22
22
|
|
23
23
|
class RelatedObjectsWindow(BaseWindow):
|
24
|
-
|
25
24
|
"""Окно с информацией о зависимых объектах.
|
26
25
|
|
27
26
|
Используется для отображения информации об объектах, зависящих от
|
@@ -40,7 +39,7 @@ class RelatedObjectsWindow(BaseWindow):
|
|
40
39
|
return registries['related_objects']
|
41
40
|
|
42
41
|
def _init_components(self):
|
43
|
-
super(
|
42
|
+
super()._init_components()
|
44
43
|
|
45
44
|
self.panel = ExtPanel(
|
46
45
|
auto_scroll=True,
|
@@ -49,7 +48,8 @@ class RelatedObjectsWindow(BaseWindow):
|
|
49
48
|
self.items.append(self.panel)
|
50
49
|
|
51
50
|
def _do_layout(self):
|
52
|
-
super(
|
51
|
+
super()._do_layout()
|
52
|
+
|
53
53
|
self.width = 800
|
54
54
|
self.height = 600
|
55
55
|
|
@@ -57,8 +57,7 @@ class RelatedObjectsWindow(BaseWindow):
|
|
57
57
|
|
58
58
|
def _get_html(self, title, objects, related_objects):
|
59
59
|
views = tuple(
|
60
|
-
self.model_view_registry.get(model).get_view(objects)
|
61
|
-
for model, objects in related_objects.items()
|
60
|
+
self.model_view_registry.get(model).get_view(objects) for model, objects in related_objects.items()
|
62
61
|
)
|
63
62
|
|
64
63
|
return render_to_string(
|
@@ -70,7 +69,7 @@ class RelatedObjectsWindow(BaseWindow):
|
|
70
69
|
)
|
71
70
|
|
72
71
|
def set_params(self, params):
|
73
|
-
super(
|
72
|
+
super().set_params(params)
|
74
73
|
|
75
74
|
self.title = 'Внимание!'
|
76
75
|
|
@@ -82,7 +81,6 @@ class RelatedObjectsWindow(BaseWindow):
|
|
82
81
|
|
83
82
|
|
84
83
|
class CancelConfirmWindow(RelatedObjectsWindow):
|
85
|
-
|
86
84
|
"""Окно подтверждения удаления объекта и связанных с ним объектов.
|
87
85
|
|
88
86
|
Содержит информацию об объектах, которые будут удалены при удалении
|
@@ -90,7 +88,7 @@ class CancelConfirmWindow(RelatedObjectsWindow):
|
|
90
88
|
"""
|
91
89
|
|
92
90
|
def _init_components(self):
|
93
|
-
super(
|
91
|
+
super()._init_components()
|
94
92
|
|
95
93
|
self.button__confirm = ext.ExtButton(
|
96
94
|
text='Удалить',
|
@@ -102,15 +100,18 @@ class CancelConfirmWindow(RelatedObjectsWindow):
|
|
102
100
|
)
|
103
101
|
|
104
102
|
def _do_layout(self):
|
105
|
-
super(
|
103
|
+
super()._do_layout()
|
106
104
|
|
107
|
-
self.buttons.extend(
|
108
|
-
|
109
|
-
|
110
|
-
|
105
|
+
self.buttons.extend(
|
106
|
+
(
|
107
|
+
self.button__confirm,
|
108
|
+
self.button__cancel,
|
109
|
+
)
|
110
|
+
)
|
111
111
|
|
112
112
|
def set_params(self, params):
|
113
|
-
super(
|
113
|
+
super().set_params(params)
|
114
|
+
|
114
115
|
self.template_globals = local_template('cancel-confirm-window.js')
|
115
116
|
|
116
117
|
self.blocked = params.get('blocked', True)
|
@@ -24,9 +24,7 @@ def get_related_instances_and_handlers(obj, skip_func=None):
|
|
24
24
|
related_model = field.remote_field.through
|
25
25
|
field_name = field.m2m_reverse_name()
|
26
26
|
|
27
|
-
related_objects = related_model.objects.filter(
|
28
|
-
**{field_name: obj}
|
29
|
-
)
|
27
|
+
related_objects = related_model.objects.filter(**{field_name: obj})
|
30
28
|
|
31
29
|
on_delete_function = get_on_relation_delete_function(field)
|
32
30
|
for related_obj in related_objects.iterator():
|
@@ -41,24 +39,19 @@ def get_on_relation_delete_function(field):
|
|
41
39
|
возвращается функция, соответсвующая поведению, указанному в атрибуте
|
42
40
|
`on_delete` поля.
|
43
41
|
"""
|
44
|
-
return (
|
45
|
-
get_custom_on_delete_function(field) or
|
46
|
-
get_field_on_delete_function(field)
|
47
|
-
)
|
42
|
+
return get_custom_on_delete_function(field) or get_field_on_delete_function(field)
|
48
43
|
|
49
44
|
|
50
45
|
def get_custom_on_delete_function(field):
|
51
46
|
"""Возвращает функцию, указанную в cascade_delete_for для поля."""
|
52
|
-
if (
|
53
|
-
not hasattr(field.model, 'cascade_delete_for') or
|
54
|
-
not isinstance(field.model.cascade_delete_for, dict)
|
55
|
-
):
|
47
|
+
if not hasattr(field.model, 'cascade_delete_for') or not isinstance(field.model.cascade_delete_for, dict):
|
56
48
|
return None
|
57
49
|
|
58
50
|
on_delete_params = {}
|
59
51
|
for _field, _field_params in field.model.cascade_delete_for.items():
|
60
52
|
if _field == field:
|
61
53
|
on_delete_params = _field_params
|
54
|
+
|
62
55
|
return on_delete_params.get('on_delete')
|
63
56
|
|
64
57
|
|
@@ -71,18 +64,23 @@ def get_field_on_delete_function(field):
|
|
71
64
|
return safe_delete
|
72
65
|
|
73
66
|
if on_delete_function == SET_NULL:
|
67
|
+
|
74
68
|
def set_null(obj):
|
75
69
|
setattr(obj, field.name, None)
|
76
70
|
getattr(obj, 'clean_and_save', obj.save)()
|
71
|
+
|
77
72
|
return set_null
|
78
73
|
|
79
74
|
if on_delete_function == SET_DEFAULT:
|
75
|
+
|
80
76
|
def set_default(obj):
|
81
77
|
setattr(obj, field.name, field.default)
|
82
78
|
getattr(obj, 'clean_and_save', obj.save)()
|
79
|
+
|
83
80
|
return set_default
|
84
81
|
|
85
82
|
# По-умолчанию остается поведение с безопасным удалением
|
86
83
|
if hasattr(model, 'safe_delete') and callable(model.safe_delete):
|
87
84
|
return model.safe_delete
|
85
|
+
|
88
86
|
return safe_delete
|