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
@@ -30,8 +30,7 @@ class InitDefaultDatabase(Operation):
|
|
30
30
|
def state_forwards(self, app_label, state):
|
31
31
|
pass
|
32
32
|
|
33
|
-
def database_forwards(self, app_label, schema_editor, from_state,
|
34
|
-
to_state):
|
33
|
+
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
35
34
|
if schema_editor.connection.alias != settings.DEFAULT_DB_ALIAS:
|
36
35
|
return
|
37
36
|
|
@@ -44,8 +43,7 @@ class InitDefaultDatabase(Operation):
|
|
44
43
|
|
45
44
|
schema_editor.execute(sql)
|
46
45
|
|
47
|
-
def database_backwards(self, app_label, schema_editor, from_state,
|
48
|
-
to_state):
|
46
|
+
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
49
47
|
if schema_editor.connection.alias != settings.DEFAULT_DB_ALIAS:
|
50
48
|
return
|
51
49
|
|
@@ -64,24 +62,26 @@ class LoadTableData(Operation):
|
|
64
62
|
def state_forwards(self, app_label, state):
|
65
63
|
pass
|
66
64
|
|
67
|
-
def database_forwards(self, app_label, schema_editor, from_state,
|
68
|
-
to_state):
|
65
|
+
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
69
66
|
Table = to_state.apps.get_model('audit_log', 'Table')
|
70
67
|
|
71
68
|
if self.allow_migrate_model(schema_editor.connection.alias, Table):
|
72
69
|
cursor = connections[settings.DEFAULT_DB_ALIAS].cursor()
|
73
|
-
cursor.execute(
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
70
|
+
cursor.execute(
|
71
|
+
'\n'.join(
|
72
|
+
(
|
73
|
+
'SELECT table_name, table_schema',
|
74
|
+
'FROM information_schema.tables',
|
75
|
+
"WHERE table_schema = 'public'",
|
76
|
+
)
|
77
|
+
)
|
78
|
+
)
|
78
79
|
|
79
80
|
for name, schema in cursor:
|
80
81
|
if (schema, name) not in EXCLUDED_TABLES:
|
81
82
|
Table.objects.get_or_create(name=name, schema=schema)
|
82
83
|
|
83
|
-
def database_backwards(self, app_label, schema_editor, from_state,
|
84
|
-
to_state):
|
84
|
+
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
85
85
|
pass
|
86
86
|
|
87
87
|
|
@@ -4,7 +4,6 @@ from django.db import (
|
|
4
4
|
|
5
5
|
|
6
6
|
class Migration(migrations.Migration):
|
7
|
-
|
8
7
|
dependencies = [
|
9
8
|
('audit_log', '0002_install_audit_log'),
|
10
9
|
]
|
@@ -12,8 +11,7 @@ class Migration(migrations.Migration):
|
|
12
11
|
operations = [
|
13
12
|
migrations.CreateModel(
|
14
13
|
name='LogProxy',
|
15
|
-
fields=[
|
16
|
-
],
|
14
|
+
fields=[],
|
17
15
|
options={
|
18
16
|
'proxy': True,
|
19
17
|
},
|
@@ -8,7 +8,6 @@ from educommon.audit_log.utils.operations import (
|
|
8
8
|
|
9
9
|
|
10
10
|
class Migration(migrations.Migration):
|
11
|
-
|
12
11
|
dependencies = [
|
13
12
|
('audit_log', '0004_reinstall_audit_log'),
|
14
13
|
]
|
@@ -17,6 +16,9 @@ class Migration(migrations.Migration):
|
|
17
16
|
ReinstallAuditLog(),
|
18
17
|
migrations.AlterModelOptions(
|
19
18
|
name='auditlog',
|
20
|
-
options={
|
19
|
+
options={
|
20
|
+
'verbose_name': '\u0417\u0430\u043f\u0438\u0441\u044c \u0436\u0443\u0440\u043d\u0430\u043b\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439',
|
21
|
+
'verbose_name_plural': '\u0417\u0430\u043f\u0438\u0441\u0438 \u0436\u0443\u0440\u043d\u0430\u043b\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439',
|
22
|
+
},
|
21
23
|
),
|
22
24
|
]
|
@@ -8,7 +8,6 @@ from django.db import (
|
|
8
8
|
|
9
9
|
|
10
10
|
class Migration(migrations.Migration):
|
11
|
-
|
12
11
|
dependencies = [
|
13
12
|
('audit_log', '0005_postgresql_error'),
|
14
13
|
]
|
@@ -19,8 +18,8 @@ class Migration(migrations.Migration):
|
|
19
18
|
name='time',
|
20
19
|
field=models.DateTimeField(
|
21
20
|
auto_now_add=True,
|
22
|
-
validators=[django.core.validators.MinValueValidator(
|
23
|
-
|
24
|
-
|
21
|
+
validators=[django.core.validators.MinValueValidator(datetime.datetime(1900, 1, 1, 0, 0))],
|
22
|
+
verbose_name='Дата, время',
|
23
|
+
),
|
25
24
|
),
|
26
25
|
]
|
@@ -13,14 +13,17 @@ def drop_select_table_function(apps, schema_editor):
|
|
13
13
|
return
|
14
14
|
|
15
15
|
cursor = connections[settings.DEFAULT_DB_ALIAS].cursor()
|
16
|
-
cursor.execute(
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
cursor.execute(
|
17
|
+
'\n'.join(
|
18
|
+
(
|
19
|
+
'SELECT',
|
20
|
+
"audit.drop_functions_by_name('set_for_selective_tables_triggers');",
|
21
|
+
)
|
22
|
+
)
|
23
|
+
)
|
20
24
|
|
21
25
|
|
22
26
|
class Migration(migrations.Migration):
|
23
|
-
|
24
27
|
dependencies = [
|
25
28
|
('audit_log', '0006_auto_20200806_1707'),
|
26
29
|
]
|
educommon/audit_log/models.py
CHANGED
@@ -66,15 +66,10 @@ if TYPE_CHECKING:
|
|
66
66
|
|
67
67
|
|
68
68
|
class Table(ReadOnlyMixin, BaseModel):
|
69
|
+
"""Модель для хранения информации о таблицах, отслеживаемых системой аудита."""
|
69
70
|
|
70
|
-
name = models.CharField(
|
71
|
-
|
72
|
-
verbose_name='Имя таблицы'
|
73
|
-
)
|
74
|
-
schema = models.CharField(
|
75
|
-
max_length=250,
|
76
|
-
verbose_name='Схема таблицы'
|
77
|
-
)
|
71
|
+
name = models.CharField(max_length=250, verbose_name='Имя таблицы')
|
72
|
+
schema = models.CharField(max_length=250, verbose_name='Схема таблицы')
|
78
73
|
logged = models.BooleanField(
|
79
74
|
default=True,
|
80
75
|
verbose_name='Отслеживаемость таблицы',
|
@@ -87,6 +82,18 @@ class Table(ReadOnlyMixin, BaseModel):
|
|
87
82
|
|
88
83
|
|
89
84
|
class AuditLog(ReadOnlyMixin, BaseModel):
|
85
|
+
"""Модель для хранения записей журнала изменений.
|
86
|
+
|
87
|
+
Каждая запись описывает изменение конкретного объекта определённой модели,
|
88
|
+
с указанием пользователя, действия, IP-адреса и зафиксированных данных.
|
89
|
+
|
90
|
+
Поля:
|
91
|
+
- table: ссылка на таблицу, в которой произошло изменение;
|
92
|
+
- data: сериализованные значения объекта до изменения;
|
93
|
+
- changes: изменения (только изменённые поля);
|
94
|
+
- operation: тип действия (создание, изменение, удаление);
|
95
|
+
- user_id, user_type_id, ip, time: информация об авторе действия.
|
96
|
+
"""
|
90
97
|
|
91
98
|
OPERATION_CREATE = 1
|
92
99
|
OPERATION_UPDATE = 2
|
@@ -94,7 +101,7 @@ class AuditLog(ReadOnlyMixin, BaseModel):
|
|
94
101
|
OPERATION_CHOICES = (
|
95
102
|
(OPERATION_CREATE, 'Создание'),
|
96
103
|
(OPERATION_UPDATE, 'Изменение'),
|
97
|
-
(OPERATION_DELETE, 'Удаление')
|
104
|
+
(OPERATION_DELETE, 'Удаление'),
|
98
105
|
)
|
99
106
|
|
100
107
|
user_id = models.IntegerField(
|
@@ -107,36 +114,17 @@ class AuditLog(ReadOnlyMixin, BaseModel):
|
|
107
114
|
db_index=True,
|
108
115
|
verbose_name='Тип пользователя',
|
109
116
|
)
|
110
|
-
ip = models.GenericIPAddressField(
|
111
|
-
|
112
|
-
verbose_name='IP адрес'
|
113
|
-
)
|
114
|
-
time = models.DateTimeField(
|
115
|
-
auto_now_add=True,
|
116
|
-
db_index=True,
|
117
|
-
verbose_name='Дата, время'
|
118
|
-
)
|
117
|
+
ip = models.GenericIPAddressField(null=True, verbose_name='IP адрес')
|
118
|
+
time = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Дата, время')
|
119
119
|
table = models.ForeignKey(
|
120
120
|
Table,
|
121
121
|
verbose_name='Таблица',
|
122
122
|
on_delete=models.CASCADE,
|
123
123
|
)
|
124
|
-
object_id = models.IntegerField(
|
125
|
-
|
126
|
-
|
127
|
-
)
|
128
|
-
data = HStoreField(
|
129
|
-
null=True,
|
130
|
-
verbose_name='Объект'
|
131
|
-
)
|
132
|
-
changes = HStoreField(
|
133
|
-
null=True,
|
134
|
-
verbose_name='Изменения'
|
135
|
-
)
|
136
|
-
operation = models.SmallIntegerField(
|
137
|
-
choices=OPERATION_CHOICES,
|
138
|
-
verbose_name='Действие'
|
139
|
-
)
|
124
|
+
object_id = models.IntegerField(db_index=True, verbose_name='Объект модели')
|
125
|
+
data = HStoreField(null=True, verbose_name='Объект')
|
126
|
+
changes = HStoreField(null=True, verbose_name='Изменения')
|
127
|
+
operation = models.SmallIntegerField(choices=OPERATION_CHOICES, verbose_name='Действие')
|
140
128
|
|
141
129
|
@property
|
142
130
|
def transformed_data(self) -> Dict[str, Any]:
|
@@ -153,6 +141,7 @@ class AuditLog(ReadOnlyMixin, BaseModel):
|
|
153
141
|
return True
|
154
142
|
|
155
143
|
def get_read_only_error_message(self, delete):
|
144
|
+
"""Возвращает сообщение об ошибке при попытке изменить или удалить запись."""
|
156
145
|
action_text = 'удалить' if delete else 'изменить'
|
157
146
|
result = 'Нельзя {} запись лога.'.format(action_text)
|
158
147
|
return result
|
@@ -170,10 +159,7 @@ class AuditLog(ReadOnlyMixin, BaseModel):
|
|
170
159
|
"""
|
171
160
|
model = self.model
|
172
161
|
if model:
|
173
|
-
result = {
|
174
|
-
field.get_attname_column()[1]: field
|
175
|
-
for field in model._meta.fields
|
176
|
-
}
|
162
|
+
result = {field.get_attname_column()[1]: field for field in model._meta.fields}
|
177
163
|
return result
|
178
164
|
|
179
165
|
@property
|
@@ -248,10 +234,7 @@ class AuditLog(ReadOnlyMixin, BaseModel):
|
|
248
234
|
|
249
235
|
items = value[1:-1]
|
250
236
|
if items:
|
251
|
-
return [
|
252
|
-
base_field.to_python(item)
|
253
|
-
for item in items.split(',')
|
254
|
-
]
|
237
|
+
return [base_field.to_python(item) for item in items.split(',')]
|
255
238
|
|
256
239
|
class Meta:
|
257
240
|
verbose_name = 'Запись журнала изменений'
|
@@ -290,10 +273,13 @@ class PostgreSQLError(BaseModel):
|
|
290
273
|
|
291
274
|
class LoggableModelMixin(models.Model):
|
292
275
|
"""Делает модель логируемой."""
|
276
|
+
|
293
277
|
need_to_log = True
|
294
278
|
|
295
279
|
class Meta:
|
296
280
|
abstract = True
|
281
|
+
|
282
|
+
|
297
283
|
# -----------------------------------------------------------------------------
|
298
284
|
# Передача параметров контекста журналирования изменений в задания Celery.
|
299
285
|
|
@@ -341,13 +327,21 @@ def _set_audit_log_context_for_task(kwargs, **_):
|
|
341
327
|
|
342
328
|
@task_postrun.connect(dispatch_uid=_package_name + 'unset')
|
343
329
|
def _unset_audit_log_context_for_task(task, kwargs, **_):
|
330
|
+
"""Очищает параметры журнала изменений после выполнения Celery-задания."""
|
344
331
|
if hasattr(thread_data, 'audit_log_params'):
|
345
332
|
del thread_data.audit_log_params
|
346
333
|
|
347
334
|
|
348
335
|
@receiver(connection_created, dispatch_uid=_package_name + 'send')
|
349
336
|
def _send_audit_log_context_to_db(**kwargs):
|
337
|
+
"""Передаёт параметры контекста журнала изменений в БД при подключении.
|
338
|
+
|
339
|
+
Используется для установки параметров в PostgreSQL через set_config,
|
340
|
+
чтобы аудит знал, кто инициировал изменения.
|
341
|
+
"""
|
350
342
|
if hasattr(thread_data, 'audit_log_params'):
|
351
343
|
for name, value in thread_data.audit_log_params.items():
|
352
344
|
set_db_param('audit_log.' + name, value)
|
345
|
+
|
346
|
+
|
353
347
|
# -----------------------------------------------------------------------------
|
@@ -8,15 +8,17 @@ PERM__AUDIT_LOG__ERRORS__DELETE = PERM_GROUP__AUDIT_LOG_ERRORS + '/delete'
|
|
8
8
|
|
9
9
|
|
10
10
|
permissions = (
|
11
|
-
(PERM__AUDIT_LOG__VIEW,
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
(
|
18
|
-
|
19
|
-
|
11
|
+
(PERM__AUDIT_LOG__VIEW, 'Просмотр', 'Разрешает просмотр журнала изменений.'),
|
12
|
+
(
|
13
|
+
PERM__AUDIT_LOG__ERRORS__VIEW,
|
14
|
+
'Просмотр журнала ошибок PostgreSQL',
|
15
|
+
'Разрешает просмотр журнала ошибок PostgreSQL.',
|
16
|
+
),
|
17
|
+
(
|
18
|
+
PERM__AUDIT_LOG__ERRORS__DELETE,
|
19
|
+
'Удаление записей журнала ошибок PostgreSQL',
|
20
|
+
'Разрешает удаление записей из журнала ошибок PostgreSQL.',
|
21
|
+
),
|
20
22
|
)
|
21
23
|
|
22
24
|
|
educommon/audit_log/proxies.py
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
import os
|
2
2
|
|
3
|
+
from abc import (
|
4
|
+
ABCMeta,
|
5
|
+
abstractmethod,
|
6
|
+
)
|
7
|
+
|
3
8
|
from django.conf import (
|
4
9
|
settings,
|
5
10
|
)
|
@@ -36,8 +41,12 @@ from educommon.audit_log.models import (
|
|
36
41
|
)
|
37
42
|
|
38
43
|
|
39
|
-
class
|
40
|
-
"""
|
44
|
+
class LogProxyMeta(type(AuditLog), ABCMeta):
|
45
|
+
"""Метакласс для устранения конфликта при наследовании от AuditLog и использовании ABCMeta"""
|
46
|
+
|
47
|
+
|
48
|
+
class LogProxy(AuditLog, metaclass=LogProxyMeta):
|
49
|
+
"""Абстрактный класс прокси-модели для отображения."""
|
41
50
|
|
42
51
|
class Meta:
|
43
52
|
proxy = True
|
@@ -53,16 +62,22 @@ class LogProxy(AuditLog):
|
|
53
62
|
except ContentType.DoesNotExist:
|
54
63
|
result = 'Model:{}, id:{}'.format(user_type_id, user_id)
|
55
64
|
else:
|
56
|
-
|
57
|
-
result = model.objects.get(id=user_id).person.fullname
|
58
|
-
except model.DoesNotExist:
|
59
|
-
result = '{}({})'.format(
|
60
|
-
model._meta.verbose_name, user_id
|
61
|
-
)
|
65
|
+
result = self.user_fullname or f'{model._meta.verbose_name}({user_id})'
|
62
66
|
else:
|
63
67
|
result = ''
|
68
|
+
|
64
69
|
return result
|
65
70
|
|
71
|
+
@property
|
72
|
+
@abstractmethod
|
73
|
+
def user_fullname(self) -> str:
|
74
|
+
"""Полное имя пользователя."""
|
75
|
+
|
76
|
+
@property
|
77
|
+
@abstractmethod
|
78
|
+
def user_organization(self) -> str:
|
79
|
+
"""Название организации, к которой привязан пользователь."""
|
80
|
+
|
66
81
|
@property
|
67
82
|
def model_fullname(self):
|
68
83
|
"""Отображаемое и системное имя модели."""
|
@@ -113,7 +128,7 @@ class LogProxy(AuditLog):
|
|
113
128
|
{
|
114
129
|
'name': self.get_field_string(key),
|
115
130
|
'old': self.convert_field_value(key, data.get(key, '')),
|
116
|
-
'new': self.convert_field_value(key, new_data.get(key, ''))
|
131
|
+
'new': self.convert_field_value(key, new_data.get(key, '')),
|
117
132
|
}
|
118
133
|
for key in keys
|
119
134
|
]
|
@@ -132,10 +147,12 @@ class LogProxy(AuditLog):
|
|
132
147
|
field = self.fields.get(column_name)
|
133
148
|
if field and field.verbose_name:
|
134
149
|
name = force_str(field.verbose_name)
|
150
|
+
|
135
151
|
return name
|
136
152
|
|
137
153
|
def convert_field_value(self, column_name, value):
|
138
154
|
"""Возвращает значение поля."""
|
155
|
+
|
139
156
|
def get_choice(choices, choice_id):
|
140
157
|
if choice_id:
|
141
158
|
choice_id = int(choice_id)
|
@@ -153,18 +170,14 @@ class LogProxy(AuditLog):
|
|
153
170
|
related = get_related(field)
|
154
171
|
model = related.parent_model
|
155
172
|
field_name = related.relation.field_name
|
156
|
-
qs = model._default_manager.filter(
|
157
|
-
**{field_name: value}
|
158
|
-
)[:1]
|
173
|
+
qs = model._default_manager.filter(**{field_name: value})[:1]
|
159
174
|
if qs:
|
160
175
|
value = '{{{}}} {}'.format(
|
161
176
|
qs[0].id,
|
162
177
|
self._get_object_verbose_name(qs[0]),
|
163
178
|
)
|
164
179
|
elif isinstance(field, BooleanField):
|
165
|
-
value_map = {
|
166
|
-
't': 'Да', 'f': 'Нет'
|
167
|
-
}
|
180
|
+
value_map = {'t': 'Да', 'f': 'Нет'}
|
168
181
|
value = value_map.get(value, value)
|
169
182
|
elif isinstance(field, IntegerField) and field.choices:
|
170
183
|
value = get_choice(field.choices, value)
|
@@ -173,11 +186,8 @@ class LogProxy(AuditLog):
|
|
173
186
|
return force_str(value)
|
174
187
|
|
175
188
|
@property
|
176
|
-
def object_string(self):
|
177
|
-
"""Отображаемое имя экземпляра модели.
|
178
|
-
|
179
|
-
:rtype str
|
180
|
-
"""
|
189
|
+
def object_string(self) -> str:
|
190
|
+
"""Отображаемое имя экземпляра модели."""
|
181
191
|
instance = self.instance
|
182
192
|
if instance:
|
183
193
|
return self._get_object_verbose_name(instance)
|
@@ -190,10 +200,7 @@ class LogProxy(AuditLog):
|
|
190
200
|
|
191
201
|
if self.model:
|
192
202
|
result = self.model()
|
193
|
-
fields_dict = {
|
194
|
-
field.name: field for field in
|
195
|
-
self.model._meta.fields
|
196
|
-
}
|
203
|
+
fields_dict = {field.name: field for field in self.model._meta.fields}
|
197
204
|
for key, value in self.data.items():
|
198
205
|
field = fields_dict.get(key)
|
199
206
|
converted_value = value
|
@@ -210,15 +217,13 @@ class LogProxy(AuditLog):
|
|
210
217
|
elif isinstance(field, FloatField):
|
211
218
|
converted_value = float(value)
|
212
219
|
elif isinstance(field, FileField):
|
213
|
-
file_path = os.path.join(
|
214
|
-
settings.MEDIA_ROOT,
|
215
|
-
converted_value
|
216
|
-
)
|
220
|
+
file_path = os.path.join(settings.MEDIA_ROOT, converted_value)
|
217
221
|
if not os.path.exists(file_path):
|
218
222
|
converted_value = None
|
219
223
|
except (ValueError, TypeError):
|
220
224
|
pass
|
221
225
|
setattr(result, key, converted_value)
|
226
|
+
|
222
227
|
return result
|
223
228
|
|
224
229
|
@staticmethod
|
educommon/audit_log/ui.py
CHANGED
@@ -21,7 +21,12 @@ class ViewChangeWindow(BaseWindow):
|
|
21
21
|
"""Окно просмотра изменений."""
|
22
22
|
|
23
23
|
def _init_components(self):
|
24
|
-
|
24
|
+
"""Метод создаёт визуальные компоненты.
|
25
|
+
|
26
|
+
Метод отражает поля модели, но не определяет расположение компонентов в окне.
|
27
|
+
"""
|
28
|
+
super()._init_components()
|
29
|
+
|
25
30
|
self.grid = ExtObjectGrid(region='center')
|
26
31
|
self.grid.add_column(data_index='name', header='Поле')
|
27
32
|
self.grid.add_column(data_index='old', header='Старое значение')
|
@@ -30,38 +35,30 @@ class ViewChangeWindow(BaseWindow):
|
|
30
35
|
|
31
36
|
self.user_field = ExtStringField(label='Пользователь')
|
32
37
|
self.unit_field = ExtStringField(label='Учреждение')
|
33
|
-
self.top_region = ExtContainer(
|
34
|
-
region='north', layout='hbox', height=32
|
35
|
-
)
|
38
|
+
self.top_region = ExtContainer(region='north', layout='hbox', height=32)
|
36
39
|
|
37
40
|
def _do_layout(self):
|
38
|
-
|
41
|
+
"""Метод располагает уже созданные визуальные компоненты на окне."""
|
42
|
+
super()._do_layout()
|
39
43
|
|
40
44
|
self.layout = 'border'
|
41
45
|
self.width, self.height = 750, 400
|
42
46
|
|
43
47
|
self.grid.cls = 'word-wrap-grid'
|
44
48
|
|
45
|
-
self.top_region.items.extend(
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
+
self.top_region.items.extend(
|
50
|
+
(
|
51
|
+
formed(self.user_field, flex=1, style=dict(padding='5px')),
|
52
|
+
formed(self.unit_field, flex=1, style=dict(padding='5px')),
|
53
|
+
)
|
54
|
+
)
|
49
55
|
self.items.extend((self.top_region, self.grid))
|
50
56
|
|
51
57
|
def set_params(self, params):
|
58
|
+
"""Метод принимает словарь, содержащий параметры окна, передаваемые в окно слоем экшнов."""
|
52
59
|
self.grid.action_data = params['grid_action']
|
53
60
|
log_record = params['object']
|
54
|
-
self.title = '{}: {}'.format(
|
55
|
-
log_record.get_operation_display(),
|
56
|
-
log_record.model_name
|
57
|
-
)
|
61
|
+
self.title = '{}: {}'.format(log_record.get_operation_display(), log_record.model_name)
|
58
62
|
if log_record.user:
|
59
|
-
self.user_field.value = '{} / {}'
|
60
|
-
|
61
|
-
log_record.user.person.user.username
|
62
|
-
)
|
63
|
-
unit = getattr(log_record.user, 'unit', None)
|
64
|
-
if unit:
|
65
|
-
self.unit_field.value = unit.short_name
|
66
|
-
else:
|
67
|
-
self.unit_field.value = ''
|
63
|
+
self.user_field.value = f'{log_record.user_fullname} / {log_record.user.username}'
|
64
|
+
self.unit_field.value = log_record.user_organization
|