educommon 3.13.0__py3-none-any.whl → 3.14.0__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/README.rst +64 -0
- educommon/audit_log/__init__.py +1 -0
- educommon/audit_log/actions.py +108 -89
- educommon/audit_log/app_meta.py +6 -7
- 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 +33 -28
- educommon/audit_log/ui.py +19 -22
- educommon/audit_log/utils/__init__.py +27 -81
- 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.14.0.dist-info}/METADATA +26 -14
- educommon-3.14.0.dist-info/RECORD +355 -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.14.0.dist-info}/WHEEL +0 -0
- {educommon-3.13.0.dist-info → educommon-3.14.0.dist-info}/top_level.txt +0 -0
educommon/audit_log/actions.py
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
from abc import (
|
2
|
+
ABCMeta,
|
3
|
+
abstractmethod,
|
4
|
+
)
|
1
5
|
from datetime import (
|
2
6
|
date,
|
3
7
|
timedelta,
|
@@ -45,7 +49,6 @@ from educommon.audit_log.ui import (
|
|
45
49
|
from educommon.audit_log.utils import (
|
46
50
|
get_model_choices,
|
47
51
|
make_hstore_filter,
|
48
|
-
make_name_filter,
|
49
52
|
)
|
50
53
|
from educommon.m3 import (
|
51
54
|
PackValidationMixin,
|
@@ -59,7 +62,7 @@ from educommon.utils.ui import (
|
|
59
62
|
)
|
60
63
|
|
61
64
|
|
62
|
-
class AuditLogPack(ViewWindowPackMixin, PackValidationMixin, ObjectPack):
|
65
|
+
class AuditLogPack(ViewWindowPackMixin, PackValidationMixin, ObjectPack, metaclass=ABCMeta):
|
63
66
|
"""Журнал изменений."""
|
64
67
|
|
65
68
|
title = 'Журнал изменений'
|
@@ -79,86 +82,86 @@ class AuditLogPack(ViewWindowPackMixin, PackValidationMixin, ObjectPack):
|
|
79
82
|
|
80
83
|
# Фильтр интервала дат
|
81
84
|
date_filter = DatetimeFilterCreator(
|
82
|
-
model, 'time',
|
83
|
-
get_from=lambda: date.today() - timedelta(days=2),
|
84
|
-
get_to=date.today
|
85
|
+
model, 'time', get_from=lambda: date.today() - timedelta(days=2), get_to=date.today
|
85
86
|
)
|
86
87
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
'
|
101
|
-
lookup=lambda x:
|
102
|
-
|
103
|
-
'table__name',
|
104
|
-
|
105
|
-
|
106
|
-
'
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
'
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
'
|
125
|
-
|
126
|
-
|
127
|
-
|
88
|
+
def _generate_columns(self):
|
89
|
+
"""Формирует наполнение столбцов."""
|
90
|
+
columns = [
|
91
|
+
{
|
92
|
+
'data_index': 'time',
|
93
|
+
'width': 140,
|
94
|
+
'header': 'Дата и время',
|
95
|
+
'sortable': True,
|
96
|
+
'filter': self.date_filter.filter
|
97
|
+
},
|
98
|
+
{
|
99
|
+
'data_index': 'user_name',
|
100
|
+
'width': 130,
|
101
|
+
'header': 'Пользователь',
|
102
|
+
'filter': self.ff('table__name', lookup=lambda x: self._make_name_filter('surname', x))
|
103
|
+
& self.ff('table__name', lookup=lambda x: self._make_name_filter('firstname', x))
|
104
|
+
& self.ff('table__name', lookup=lambda x: self._make_name_filter('patronymic', x)),
|
105
|
+
},
|
106
|
+
{
|
107
|
+
'data_index': 'operation',
|
108
|
+
'width': 60,
|
109
|
+
'header': 'Операция',
|
110
|
+
'filter': self.ff('operation', ask_before_deleting=False),
|
111
|
+
},
|
112
|
+
{
|
113
|
+
'data_index': 'model_name',
|
114
|
+
'width': 220,
|
115
|
+
'header': 'Модель объекта',
|
116
|
+
'filter': self.ff(
|
117
|
+
'table',
|
118
|
+
control_creator=lambda: make_combo_box(
|
119
|
+
data=get_model_choices(),
|
120
|
+
ask_before_deleting=False,
|
121
|
+
),
|
122
|
+
),
|
123
|
+
},
|
124
|
+
{
|
125
|
+
'data_index': 'object_id',
|
126
|
+
'width': 50,
|
127
|
+
'header': 'Код объекта',
|
128
|
+
'filter': self.ff('object_id'),
|
129
|
+
},
|
130
|
+
{
|
131
|
+
'data_index': 'ip',
|
132
|
+
'width': 60,
|
133
|
+
'header': 'IP',
|
134
|
+
'filter': self.ff(
|
135
|
+
'ip',
|
136
|
+
parser_map=(GenericIPAddressField, 'str', '%s__contains'),
|
137
|
+
control_creator=ExtStringField,
|
138
|
+
),
|
139
|
+
},
|
140
|
+
{
|
141
|
+
'data_index': 'object_string',
|
142
|
+
'width': 180,
|
143
|
+
'header': 'Объект',
|
144
|
+
'filter': self.ff(
|
145
|
+
'data',
|
146
|
+
parser_map=(HStoreField, 'str', '%s__values__icontains'),
|
147
|
+
lookup=lambda x: make_hstore_filter('data', x),
|
148
|
+
control_creator=ExtStringField,
|
128
149
|
),
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
'data_index': 'ip',
|
139
|
-
'width': 60,
|
140
|
-
'header': 'IP',
|
141
|
-
'filter': ff(
|
142
|
-
'ip',
|
143
|
-
parser_map=(GenericIPAddressField, 'str', '%s__contains'),
|
144
|
-
control_creator=ExtStringField,
|
145
|
-
),
|
146
|
-
},
|
147
|
-
{
|
148
|
-
'data_index': 'object_string',
|
149
|
-
'width': 180,
|
150
|
-
'header': 'Объект',
|
151
|
-
'filter': ff(
|
152
|
-
'data',
|
153
|
-
parser_map=(HStoreField, 'str', '%s__values__icontains'),
|
154
|
-
lookup=lambda x: make_hstore_filter('data', x),
|
155
|
-
control_creator=ExtStringField,
|
156
|
-
),
|
157
|
-
},
|
158
|
-
]
|
150
|
+
},
|
151
|
+
{
|
152
|
+
'data_index': '',
|
153
|
+
'width': 40,
|
154
|
+
'header': 'Имитация',
|
155
|
+
},
|
156
|
+
]
|
157
|
+
|
158
|
+
return columns
|
159
159
|
|
160
160
|
def __init__(self):
|
161
|
-
|
161
|
+
self.columns = self._generate_columns()
|
162
|
+
|
163
|
+
super().__init__()
|
164
|
+
|
162
165
|
self.view_changes_action = ViewChangeAction()
|
163
166
|
self.actions.append(self.view_changes_action)
|
164
167
|
|
@@ -174,46 +177,62 @@ class AuditLogPack(ViewWindowPackMixin, PackValidationMixin, ObjectPack):
|
|
174
177
|
Устанавливает интервал дат фильтрации по умолчанию
|
175
178
|
в параметрах запроса.
|
176
179
|
"""
|
177
|
-
super(
|
180
|
+
super().configure_grid(grid)
|
181
|
+
|
178
182
|
grid.store.base_params = self.date_filter.base_params
|
179
183
|
|
180
184
|
def get_edit_window_params(self, params, request, context):
|
181
|
-
|
182
|
-
|
183
|
-
|
185
|
+
"""Возвращает словарь параметров, которые будут переданы окну редактирования."""
|
186
|
+
params = super().get_edit_window_params(params, request, context)
|
187
|
+
|
184
188
|
params['grid_action'] = self.view_changes_action
|
189
|
+
|
185
190
|
return params
|
186
191
|
|
187
192
|
def get_list_window_params(self, params, request, context):
|
188
|
-
|
189
|
-
|
190
|
-
|
193
|
+
"""Возвращает словарь параметров, которые будут переданы окну списка."""
|
194
|
+
params = super().get_list_window_params(params, request, context)
|
195
|
+
|
191
196
|
params['maximized'] = True
|
197
|
+
|
192
198
|
return params
|
193
199
|
|
194
200
|
def get_rows_query(self, request, context):
|
195
|
-
|
196
|
-
|
197
|
-
).prefetch_related('table')
|
201
|
+
"""Возвращает выборку из БД для получения списка данных."""
|
202
|
+
return super().get_rows_query(request, context).prefetch_related('table')
|
198
203
|
|
199
204
|
def extend_menu(self, menu):
|
205
|
+
"""Расширение главного меню."""
|
200
206
|
return menu.administry(
|
201
207
|
menu.Item(self.title, self.list_window_action),
|
202
208
|
)
|
203
209
|
|
210
|
+
@abstractmethod
|
211
|
+
def _make_name_filter(self, field, value):
|
212
|
+
"""Создает lookup фильтра по фамилии/имени/отчеству пользователя.
|
213
|
+
|
214
|
+
:param str field: название поля ('firstname', 'surname', 'patronymic').
|
215
|
+
:param str value: значение, по которому фильтруется queryset.
|
216
|
+
"""
|
217
|
+
|
204
218
|
|
205
219
|
class ViewChangeAction(BaseAction):
|
206
220
|
"""Action для просмотра изменений."""
|
207
221
|
|
208
222
|
def context_declaration(self):
|
209
|
-
|
223
|
+
"""Делегирует декларацию контекста в пак."""
|
224
|
+
result = super().context_declaration()
|
225
|
+
|
210
226
|
result[self.parent.id_param_name] = dict(type='int')
|
227
|
+
|
211
228
|
return result
|
212
229
|
|
213
230
|
def run(self, request, context):
|
231
|
+
"""Тело Action, вызывается при обработке запроса к серверу."""
|
214
232
|
object_id = getattr(context, self.parent.id_param_name)
|
215
233
|
if object_id:
|
216
|
-
rows =
|
234
|
+
rows = self.parent.model.objects.get(id=object_id).diff
|
217
235
|
else:
|
218
236
|
rows = []
|
237
|
+
|
219
238
|
return PreJsonResult({'rows': rows, 'total': len(rows)})
|
educommon/audit_log/app_meta.py
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
from educommon import (
|
2
2
|
ioc,
|
3
3
|
)
|
4
|
-
from educommon.audit_log.actions import (
|
5
|
-
AuditLogPack,
|
6
|
-
)
|
7
4
|
from educommon.audit_log.error_log.actions import (
|
8
5
|
PostgreSQLErrorPack,
|
9
6
|
)
|
10
7
|
|
11
8
|
|
12
9
|
def register_actions():
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
"""Регистрация паков и экшенов."""
|
11
|
+
ioc.get('main_controller').packs.extend(
|
12
|
+
(
|
13
|
+
PostgreSQLErrorPack(),
|
14
|
+
)
|
15
|
+
)
|
educommon/audit_log/apps.py
CHANGED
@@ -30,16 +30,30 @@ from educommon.utils.db.postgresql import (
|
|
30
30
|
|
31
31
|
|
32
32
|
class AppConfig(AppConfig):
|
33
|
+
"""Конфигурация подсистемы логирования изменений в БД.
|
34
|
+
|
35
|
+
При инициализации приложения:
|
36
|
+
- подключает обработку сигнала post_migrate;
|
37
|
+
- создаёт необходимые расширения PostgreSQL (hstore, postgres_fdw);
|
38
|
+
- проверяет и настраивает подключение к сервисной БД через FDW;
|
39
|
+
- выполняет установку и проверку инфраструктуры AuditLog.
|
40
|
+
"""
|
33
41
|
|
34
42
|
name = __name__.rpartition('.')[0]
|
35
43
|
|
36
44
|
@property
|
37
45
|
def _dispatch_uid(self):
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
46
|
+
"""Уникальный идентификатор для подключения сигнала post_migrate.
|
47
|
+
|
48
|
+
Используется для предотвращения дублирующего подключения обработчиков.
|
49
|
+
"""
|
50
|
+
return '.'.join(
|
51
|
+
(
|
52
|
+
self.name,
|
53
|
+
self.__class__.__name__,
|
54
|
+
self._configure_audit_log.__name__,
|
55
|
+
)
|
56
|
+
)
|
43
57
|
|
44
58
|
def _create_postgresql_extensions(self):
|
45
59
|
"""Создает в БД необходимые расширения PostgreSQL.
|
@@ -52,13 +66,9 @@ class AppConfig(AppConfig):
|
|
52
66
|
if not is_extension_exists(alias, 'postgres_fdw'):
|
53
67
|
if create_extension(alias, 'postgres_fdw', quite=True):
|
54
68
|
with closing(connections[alias].cursor()) as cursor:
|
55
|
-
cursor.execute(
|
56
|
-
'GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw '
|
57
|
-
'TO PUBLIC'
|
58
|
-
)
|
69
|
+
cursor.execute('GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw TO PUBLIC')
|
59
70
|
|
60
|
-
for alias in (settings.DEFAULT_DB_ALIAS,
|
61
|
-
settings.SERVICE_DB_ALIAS):
|
71
|
+
for alias in (settings.DEFAULT_DB_ALIAS, settings.SERVICE_DB_ALIAS):
|
62
72
|
if not is_extension_exists(alias, 'hstore'):
|
63
73
|
create_extension(alias, 'hstore', quite=True)
|
64
74
|
|
@@ -68,32 +78,41 @@ class AppConfig(AppConfig):
|
|
68
78
|
from educommon.audit_log.utils import (
|
69
79
|
configure,
|
70
80
|
)
|
81
|
+
|
71
82
|
configure()
|
72
83
|
|
73
84
|
# Проверка подключения подключения к сервисной БД через FDW.
|
74
85
|
from educommon.audit_log.utils import (
|
75
86
|
check_connection_fdw,
|
76
87
|
)
|
88
|
+
|
77
89
|
success, error_message = check_connection_fdw()
|
78
90
|
if not success:
|
79
91
|
raise ImproperlyConfigured(
|
80
|
-
|
92
|
+
'{0} - Ошибка подключения к сервисной базе через '
|
81
93
|
"postgres_fdw. Необходимо убедится что 'Журнал изменений' "
|
82
|
-
|
94
|
+
'настроен корректно.'.format(error_message)
|
83
95
|
)
|
84
96
|
|
85
97
|
def _configure_db(self, **kwargs):
|
98
|
+
"""Инициализирует расширения PostgreSQL и настраивает AuditLog.
|
99
|
+
|
100
|
+
Вызывается после применения миграций. Если AuditLog уже инициализирован,
|
101
|
+
выполняется его настройка.
|
102
|
+
"""
|
86
103
|
from educommon.audit_log.utils import (
|
87
104
|
is_initialized,
|
88
105
|
)
|
89
106
|
|
90
107
|
self._create_postgresql_extensions()
|
91
108
|
if is_initialized(settings.DEFAULT_DB_ALIAS):
|
92
|
-
self._configure_audit_log(
|
93
|
-
connections[settings.DEFAULT_DB_ALIAS]
|
94
|
-
)
|
109
|
+
self._configure_audit_log(connections[settings.DEFAULT_DB_ALIAS])
|
95
110
|
|
96
111
|
def ready(self):
|
112
|
+
"""Вызывается при готовности приложения.
|
113
|
+
|
114
|
+
Подключает обработчик _configure_db к сигналу post_migrate.
|
115
|
+
"""
|
97
116
|
post_migrate.connect(self._configure_db, sender=self)
|
98
117
|
|
99
118
|
|
@@ -104,10 +123,7 @@ def check_postgres_fdw(app_configs, **kwargs):
|
|
104
123
|
|
105
124
|
if not is_extension_exists(settings.DEFAULT_DB_ALIAS, 'postgres_fdw'):
|
106
125
|
dbname = settings.DATABASES[settings.DEFAULT_DB_ALIAS]['NAME']
|
107
|
-
msg = (
|
108
|
-
"'postgres_fdw' PostgreSQL extension not installed in '{}' "
|
109
|
-
"database."
|
110
|
-
).format(dbname)
|
126
|
+
msg = ("'postgres_fdw' PostgreSQL extension not installed in '{}' database.").format(dbname)
|
111
127
|
hint = (
|
112
128
|
"Execute this SQL in '{dbname}' database:\n"
|
113
129
|
'{indent}CREATE EXTENSION postgres_fdw;\n'
|
@@ -125,20 +141,19 @@ def check_hstore(app_configs, **kwargs):
|
|
125
141
|
errors = []
|
126
142
|
|
127
143
|
msg = "'hstore' PostgreSQL extension not installed in '{}' database."
|
128
|
-
hint =
|
129
|
-
"Execute this SQL in '{dbname}' database:\n"
|
130
|
-
'{indent}CREATE EXTENSION hstore;'
|
131
|
-
)
|
144
|
+
hint = "Execute this SQL in '{dbname}' database:\n{indent}CREATE EXTENSION hstore;"
|
132
145
|
indent = ' ' * 14
|
133
146
|
|
134
147
|
def check(alias, message_id):
|
135
148
|
dbname = settings.DATABASES[alias]['NAME']
|
136
149
|
if not is_extension_exists(alias, 'hstore'):
|
137
|
-
errors.append(
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
150
|
+
errors.append(
|
151
|
+
Critical(
|
152
|
+
msg.format(dbname),
|
153
|
+
hint.format(indent=indent, dbname=dbname),
|
154
|
+
id=message_id,
|
155
|
+
)
|
156
|
+
)
|
142
157
|
|
143
158
|
check(settings.DEFAULT_DB_ALIAS, 'audit_log.C002')
|
144
159
|
check(settings.SERVICE_DB_ALIAS, 'audit_log.C003')
|
educommon/audit_log/constants.py
CHANGED
@@ -21,8 +21,11 @@ EXCLUDED_TABLES = (
|
|
21
21
|
)
|
22
22
|
|
23
23
|
# Папка с sql файлами
|
24
|
-
SQL_FILES_DIR = os.path.abspath(
|
25
|
-
os.path.
|
26
|
-
)
|
24
|
+
SQL_FILES_DIR = os.path.abspath(
|
25
|
+
os.path.join(
|
26
|
+
os.path.dirname(__file__),
|
27
|
+
'sql',
|
28
|
+
)
|
29
|
+
)
|
27
30
|
|
28
|
-
INSTALL_AUDIT_LOG_SQL_FILE_NAME = 'install_audit_log.sql'
|
31
|
+
INSTALL_AUDIT_LOG_SQL_FILE_NAME = 'install_audit_log.sql'
|
@@ -72,9 +72,7 @@ class PostgreSQLErrorPack(PackValidationMixin, ObjectPack):
|
|
72
72
|
)
|
73
73
|
|
74
74
|
def get_list_window_params(self, params, request, context):
|
75
|
-
result = super(PostgreSQLErrorPack, self).get_list_window_params(
|
76
|
-
params, request, context
|
77
|
-
)
|
75
|
+
result = super(PostgreSQLErrorPack, self).get_list_window_params(params, request, context)
|
78
76
|
|
79
77
|
result['maximized'] = True
|
80
78
|
result['read_only'] = not self.delete_action.has_perm(request)
|
educommon/audit_log/helpers.py
CHANGED
@@ -20,11 +20,9 @@ if TYPE_CHECKING:
|
|
20
20
|
|
21
21
|
|
22
22
|
def get_models_table_ids(models: Union['Model', IterableType['Model']]) -> List[int]:
|
23
|
-
"""
|
24
|
-
Возвращает перечень id таблиц из AuditLog соответствующих указанным моделям.
|
25
|
-
"""
|
23
|
+
"""Возвращает перечень id таблиц из AuditLog соответствующих указанным моделям."""
|
26
24
|
if not isinstance(models, Iterable):
|
27
|
-
models = (models,
|
25
|
+
models = (models,)
|
28
26
|
|
29
27
|
table_ids = Table.objects.filter(
|
30
28
|
name__in=(model._meta.db_table for model in models),
|
@@ -37,14 +37,18 @@ class Command(BaseCommand):
|
|
37
37
|
|
38
38
|
def add_arguments(self, parser):
|
39
39
|
"""Добавление аргументов команды."""
|
40
|
+
(
|
41
|
+
parser.add_argument(
|
42
|
+
'--clear_audit_logs',
|
43
|
+
action='store_true',
|
44
|
+
default=False,
|
45
|
+
help='Удалить записи из audit_log для неотслеживаемых таблиц',
|
46
|
+
),
|
47
|
+
)
|
40
48
|
parser.add_argument(
|
41
|
-
'--
|
42
|
-
|
43
|
-
default=
|
44
|
-
help='Удалить записи из audit_log для неотслеживаемых таблиц',
|
45
|
-
),
|
46
|
-
parser.add_argument(
|
47
|
-
'--chunk_size', type=int, default=DEFAULT_QUERYSET_CHUNK_SIZE,
|
49
|
+
'--chunk_size',
|
50
|
+
type=int,
|
51
|
+
default=DEFAULT_QUERYSET_CHUNK_SIZE,
|
48
52
|
help='Кол-во единовременно удаляемых записей',
|
49
53
|
)
|
50
54
|
|
@@ -12,9 +12,7 @@ from educommon.django.db.migration.operations import (
|
|
12
12
|
|
13
13
|
|
14
14
|
class Migration(migrations.Migration):
|
15
|
-
|
16
|
-
dependencies = [
|
17
|
-
]
|
15
|
+
dependencies = []
|
18
16
|
|
19
17
|
operations = [
|
20
18
|
CreateSchema('audit', aliases=('default',)),
|
@@ -22,11 +20,33 @@ class Migration(migrations.Migration):
|
|
22
20
|
name='PostgreSQLError',
|
23
21
|
fields=[
|
24
22
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
25
|
-
(
|
23
|
+
(
|
24
|
+
'user_id',
|
25
|
+
models.IntegerField(
|
26
|
+
null=True,
|
27
|
+
verbose_name='\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c',
|
28
|
+
),
|
29
|
+
),
|
26
30
|
('ip', models.GenericIPAddressField(null=True, verbose_name='IP \u0430\u0434\u0440\u0435\u0441')),
|
27
|
-
(
|
28
|
-
|
29
|
-
|
31
|
+
(
|
32
|
+
'time',
|
33
|
+
models.DateTimeField(
|
34
|
+
auto_now_add=True, verbose_name='\u0414\u0430\u0442\u0430, \u0432\u0440\u0435\u043c\u044f'
|
35
|
+
),
|
36
|
+
),
|
37
|
+
(
|
38
|
+
'level',
|
39
|
+
models.CharField(
|
40
|
+
max_length=50,
|
41
|
+
verbose_name='\u0423\u0440\u043e\u0432\u0435\u043d\u044c \u043e\u0448\u0438\u0431\u043a\u0438',
|
42
|
+
),
|
43
|
+
),
|
44
|
+
(
|
45
|
+
'text',
|
46
|
+
models.TextField(
|
47
|
+
verbose_name='\u0422\u0435\u043a\u0441\u0442 \u043e\u0448\u0438\u0431\u043a\u0438'
|
48
|
+
),
|
49
|
+
),
|
30
50
|
],
|
31
51
|
options={
|
32
52
|
'db_table': 'audit"."postgresql_errors',
|
@@ -38,14 +58,54 @@ class Migration(migrations.Migration):
|
|
38
58
|
name='AuditLog',
|
39
59
|
fields=[
|
40
60
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
41
|
-
(
|
42
|
-
|
61
|
+
(
|
62
|
+
'user_id',
|
63
|
+
models.IntegerField(
|
64
|
+
null=True,
|
65
|
+
verbose_name='\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c',
|
66
|
+
db_index=True,
|
67
|
+
),
|
68
|
+
),
|
69
|
+
(
|
70
|
+
'user_type_id',
|
71
|
+
models.IntegerField(
|
72
|
+
null=True,
|
73
|
+
verbose_name='\u0422\u0438\u043f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f',
|
74
|
+
db_index=True,
|
75
|
+
),
|
76
|
+
),
|
43
77
|
('ip', models.GenericIPAddressField(null=True, verbose_name='IP \u0430\u0434\u0440\u0435\u0441')),
|
44
|
-
(
|
45
|
-
|
78
|
+
(
|
79
|
+
'time',
|
80
|
+
models.DateTimeField(
|
81
|
+
auto_now_add=True,
|
82
|
+
verbose_name='\u0414\u0430\u0442\u0430, \u0432\u0440\u0435\u043c\u044f',
|
83
|
+
db_index=True,
|
84
|
+
),
|
85
|
+
),
|
86
|
+
(
|
87
|
+
'object_id',
|
88
|
+
models.IntegerField(
|
89
|
+
verbose_name='\u041e\u0431\u044a\u0435\u043a\u0442 \u043c\u043e\u0434\u0435\u043b\u0438',
|
90
|
+
db_index=True,
|
91
|
+
),
|
92
|
+
),
|
46
93
|
('data', HStoreField(null=True, verbose_name='\u041e\u0431\u044a\u0435\u043a\u0442')),
|
47
|
-
(
|
48
|
-
|
94
|
+
(
|
95
|
+
'changes',
|
96
|
+
HStoreField(null=True, verbose_name='\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f'),
|
97
|
+
),
|
98
|
+
(
|
99
|
+
'operation',
|
100
|
+
models.SmallIntegerField(
|
101
|
+
verbose_name='\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435',
|
102
|
+
choices=[
|
103
|
+
(1, '\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435'),
|
104
|
+
(2, '\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435'),
|
105
|
+
(3, '\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435'),
|
106
|
+
],
|
107
|
+
),
|
108
|
+
),
|
49
109
|
],
|
50
110
|
options={
|
51
111
|
'abstract': False,
|
@@ -56,8 +116,19 @@ class Migration(migrations.Migration):
|
|
56
116
|
name='Table',
|
57
117
|
fields=[
|
58
118
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
59
|
-
(
|
60
|
-
|
119
|
+
(
|
120
|
+
'name',
|
121
|
+
models.CharField(
|
122
|
+
max_length=250, verbose_name='\u0418\u043c\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u044b'
|
123
|
+
),
|
124
|
+
),
|
125
|
+
(
|
126
|
+
'schema',
|
127
|
+
models.CharField(
|
128
|
+
max_length=250,
|
129
|
+
verbose_name='\u0421\u0445\u0435\u043c\u0430 \u0442\u0430\u0431\u043b\u0438\u0446\u044b',
|
130
|
+
),
|
131
|
+
),
|
61
132
|
],
|
62
133
|
options={
|
63
134
|
'verbose_name': '\u041b\u043e\u0433\u0438\u0440\u0443\u0435\u043c\u0430\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u0430',
|
@@ -72,7 +143,11 @@ class Migration(migrations.Migration):
|
|
72
143
|
migrations.AddField(
|
73
144
|
model_name='auditlog',
|
74
145
|
name='table',
|
75
|
-
field=models.ForeignKey(
|
146
|
+
field=models.ForeignKey(
|
147
|
+
verbose_name='\u0422\u0430\u0431\u043b\u0438\u0446\u0430',
|
148
|
+
to='audit_log.Table',
|
149
|
+
on_delete=models.CASCADE,
|
150
|
+
),
|
76
151
|
preserve_default=True,
|
77
152
|
),
|
78
153
|
]
|