educommon 3.19.1__py3-none-any.whl → 3.20.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/audit_log/management/commands/removing_false_logs.py +162 -0
- educommon/audit_log/sql/new_audit_log_removing_false_logs.sql +25 -0
- educommon/audit_log/sql/old_audit_log_removing_false_logs.sql +78 -0
- {educommon-3.19.1.dist-info → educommon-3.20.0.dist-info}/METADATA +1 -1
- {educommon-3.19.1.dist-info → educommon-3.20.0.dist-info}/RECORD +7 -4
- {educommon-3.19.1.dist-info → educommon-3.20.0.dist-info}/WHEEL +0 -0
- {educommon-3.19.1.dist-info → educommon-3.20.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,162 @@
|
|
1
|
+
"""
|
2
|
+
Команда удаления ложных логов в таблицах audit_log_auditlog и state_log_all.
|
3
|
+
"""
|
4
|
+
import codecs
|
5
|
+
import os
|
6
|
+
|
7
|
+
import psycopg2
|
8
|
+
from django.apps import (
|
9
|
+
apps,
|
10
|
+
)
|
11
|
+
from django.core.management.base import (
|
12
|
+
BaseCommand,
|
13
|
+
)
|
14
|
+
from django.utils.functional import (
|
15
|
+
cached_property,
|
16
|
+
)
|
17
|
+
|
18
|
+
from psycopg2.extras import (
|
19
|
+
Json,
|
20
|
+
)
|
21
|
+
|
22
|
+
from educommon.audit_log.constants import (
|
23
|
+
SQL_FILES_DIR,
|
24
|
+
)
|
25
|
+
from educommon.audit_log.utils import (
|
26
|
+
get_db_connection_params,
|
27
|
+
get_auto_now_fields_by_model,
|
28
|
+
)
|
29
|
+
|
30
|
+
|
31
|
+
NEW_AUDIT_LOG_REMOVING_FALSE_LOGS_SQL_FILE_NAME = 'new_audit_log_removing_false_logs.sql'
|
32
|
+
OLD_AUDIT_LOG_REMOVING_FALSE_LOGS_SQL_FILE_NAME = 'old_audit_log_removing_false_logs.sql'
|
33
|
+
NEW_AUDIT_LOG_TABLE_NAME = 'audit_log_auditlog'
|
34
|
+
OLD_AUDIT_LOG_TABLE_NAME = 'state_log_all'
|
35
|
+
|
36
|
+
|
37
|
+
class Command(BaseCommand):
|
38
|
+
"""Команда удаления ложных логов в таблицах audit_log_auditlog и state_log_all."""
|
39
|
+
|
40
|
+
help = 'Команда удаления ложных логов в таблицах audit_log_auditlog и state_log_all..'
|
41
|
+
|
42
|
+
@cached_property
|
43
|
+
def auto_now_fields_new_audit_log(self) -> dict[str, list[str]]:
|
44
|
+
"""Возвращает словарь с полями, имеющими auto_now=True, для моделей с флагом need_to_log = True.
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
Словарь, где ключ это название таблицы, а значение это список названий полей имеющими auto_now=True.
|
48
|
+
"""
|
49
|
+
|
50
|
+
return get_auto_now_fields_by_model()
|
51
|
+
|
52
|
+
@cached_property
|
53
|
+
def auto_now_fields_old_audit_log(self) -> dict[str, list[str]]:
|
54
|
+
"""Возвращает словарь с полями, имеющими auto_now=True, для моделей с флагом need_to_log = True.
|
55
|
+
|
56
|
+
Returns:
|
57
|
+
Словарь, где ключ это название модели, а значение это список названий полей имеющими auto_now=True.
|
58
|
+
"""
|
59
|
+
|
60
|
+
return {
|
61
|
+
model.__name__: fields
|
62
|
+
for model in apps.get_models()
|
63
|
+
if (fields := self.auto_now_fields_new_audit_log.get(model._meta.db_table))
|
64
|
+
}
|
65
|
+
|
66
|
+
def _check_exists_table(self, cursor, table_name: str) -> bool:
|
67
|
+
"""Запуск SQL-скрипта для проверки существования таблицы.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
cursor: Объект-курсор для работы с базой данных.
|
71
|
+
table_name: Название таблицы БД.
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
Флаг существования таблицы.
|
75
|
+
"""
|
76
|
+
exists_table = """
|
77
|
+
SELECT 1
|
78
|
+
FROM INFORMATION_SCHEMA.TABLES
|
79
|
+
WHERE TABLE_SCHEMA = 'public' AND TABLE_NAME = '{table_name}';
|
80
|
+
""".format(table_name=table_name)
|
81
|
+
|
82
|
+
cursor.execute(exists_table)
|
83
|
+
|
84
|
+
return bool(cursor.fetchone())
|
85
|
+
|
86
|
+
def _delete_log(self, cursor, table_name: str, sql_file_name: str, auto_now_fields) -> None:
|
87
|
+
"""Запуск SQL-скрипта по удалению ложных логов.
|
88
|
+
|
89
|
+
Args:
|
90
|
+
cursor: Объект-курсор для работы с базой данных.
|
91
|
+
table_name: Название таблицы БД.
|
92
|
+
sql_file_name: Название SQL файла.
|
93
|
+
auto_now_fields: Словарь с полями, имеющими auto_now=True, для моделей с флагом need_to_log = True.
|
94
|
+
"""
|
95
|
+
if self._check_exists_table(cursor, table_name):
|
96
|
+
self.stdout.write(f'Удаление ложных записей в таблице {table_name}.')
|
97
|
+
cursor.execute(self._prepare_sql(sql_file_name, auto_now_fields, table_name))
|
98
|
+
self.stdout.write(self.style.SUCCESS(f'Успешно удалены ложные записи в таблице {table_name}.'))
|
99
|
+
else:
|
100
|
+
self.stdout.write(self.style.ERROR(f'Таблица {table_name} не найдена.'))
|
101
|
+
|
102
|
+
def _read_sql(self, sql_file_name: str) -> str:
|
103
|
+
"""Чтение SQL-кода из файла.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
sql_file_name: Название SQL файла.
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
SQL-кода из файла.
|
110
|
+
"""
|
111
|
+
sql_file_path = os.path.join(SQL_FILES_DIR, sql_file_name)
|
112
|
+
|
113
|
+
with codecs.open(sql_file_path, 'r', 'utf-8') as sql_file:
|
114
|
+
sql = sql_file.read()
|
115
|
+
|
116
|
+
self.stdout.write(f'Выполнение {sql_file_name}.')
|
117
|
+
|
118
|
+
return sql
|
119
|
+
|
120
|
+
def _prepare_sql(self, sql_file_name: str, auto_now_fields: dict[str, list[str]], table_name: str) -> str:
|
121
|
+
"""Подготовка SQL-кода.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
sql_file_name: Название SQL файла.
|
125
|
+
auto_now_fields: Словарь с полями, имеющими auto_now=True, для моделей с флагом need_to_log = True.
|
126
|
+
table_name: Название таблицы БД.
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
SQL-кода из файла, с добавленными параметрами.
|
130
|
+
"""
|
131
|
+
self.stdout.write(f'Подготовка SQL-кода для удаления ложных логов в таблице {table_name}.')
|
132
|
+
|
133
|
+
params = {
|
134
|
+
'auto_now_fields': Json(auto_now_fields),
|
135
|
+
}
|
136
|
+
|
137
|
+
return self._read_sql(sql_file_name).format(**params)
|
138
|
+
|
139
|
+
def handle(self, *args, **options) -> None:
|
140
|
+
"""Формирование SQL-кода и его исполнение."""
|
141
|
+
self.stdout.write('Начало работы команды.')
|
142
|
+
|
143
|
+
connection = psycopg2.connect(**get_db_connection_params())
|
144
|
+
cursor = connection.cursor()
|
145
|
+
|
146
|
+
# Удаление ложных записей в таблице audit_log_auditlog.
|
147
|
+
self._delete_log(
|
148
|
+
cursor=cursor,
|
149
|
+
table_name=NEW_AUDIT_LOG_TABLE_NAME,
|
150
|
+
sql_file_name=NEW_AUDIT_LOG_REMOVING_FALSE_LOGS_SQL_FILE_NAME,
|
151
|
+
auto_now_fields=self.auto_now_fields_new_audit_log,
|
152
|
+
)
|
153
|
+
# Удаление ложных записей в таблице state_log_all.
|
154
|
+
self._delete_log(
|
155
|
+
cursor=cursor,
|
156
|
+
table_name=OLD_AUDIT_LOG_TABLE_NAME,
|
157
|
+
sql_file_name=OLD_AUDIT_LOG_REMOVING_FALSE_LOGS_SQL_FILE_NAME,
|
158
|
+
auto_now_fields=self.auto_now_fields_old_audit_log,
|
159
|
+
)
|
160
|
+
|
161
|
+
connection.commit()
|
162
|
+
self.stdout.write(self.style.SUCCESS('Выполнение команды завершено.'))
|
@@ -0,0 +1,25 @@
|
|
1
|
+
DO $$
|
2
|
+
DECLARE
|
3
|
+
chunk_size INT := 1000;
|
4
|
+
deleted_count INT := 0;
|
5
|
+
BEGIN
|
6
|
+
LOOP
|
7
|
+
DELETE FROM audit_log_auditlog
|
8
|
+
WHERE id IN (
|
9
|
+
SELECT a.id
|
10
|
+
FROM audit_log_auditlog a
|
11
|
+
LEFT JOIN audit_log_table t ON a.table_id = t.id
|
12
|
+
WHERE EXISTS (
|
13
|
+
SELECT 1
|
14
|
+
FROM jsonb_each({auto_now_fields}) AS mapping(table_name, keys)
|
15
|
+
WHERE
|
16
|
+
t.name = mapping.table_name::text AND
|
17
|
+
akeys(a.changes) <@ ARRAY(SELECT jsonb_array_elements_text(mapping.keys))
|
18
|
+
)
|
19
|
+
LIMIT chunk_size
|
20
|
+
);
|
21
|
+
|
22
|
+
GET DIAGNOSTICS deleted_count = ROW_COUNT;
|
23
|
+
EXIT WHEN deleted_count = 0;
|
24
|
+
END LOOP;
|
25
|
+
END $$;
|
@@ -0,0 +1,78 @@
|
|
1
|
+
-- Функция нахождения разницы между двумя jsonb.
|
2
|
+
CREATE OR REPLACE FUNCTION jsonb_diff(val1 JSONB, val2 JSONB)
|
3
|
+
RETURNS JSONB AS $$
|
4
|
+
DECLARE
|
5
|
+
result JSONB;
|
6
|
+
BEGIN
|
7
|
+
SELECT jsonb_object_agg(
|
8
|
+
COALESCE(a.key, b.key),
|
9
|
+
COALESCE(b.value, a.value)
|
10
|
+
)
|
11
|
+
INTO result
|
12
|
+
FROM (
|
13
|
+
SELECT key, value FROM jsonb_each(val1)
|
14
|
+
) a
|
15
|
+
FULL JOIN (
|
16
|
+
SELECT key, value FROM jsonb_each(val2)
|
17
|
+
) b ON a.key = b.key
|
18
|
+
WHERE a.value IS DISTINCT FROM b.value
|
19
|
+
OR a.value IS NULL
|
20
|
+
OR b.value IS NULL;
|
21
|
+
|
22
|
+
RETURN result;
|
23
|
+
END;
|
24
|
+
$$ LANGUAGE plpgsql;
|
25
|
+
|
26
|
+
-- Функция проверки, что текстовое поле можно преобразовать в jsonb.
|
27
|
+
CREATE OR REPLACE FUNCTION is_valid_jsonb(input_text TEXT)
|
28
|
+
RETURNS BOOLEAN AS $$
|
29
|
+
BEGIN
|
30
|
+
PERFORM input_text::jsonb;
|
31
|
+
RETURN TRUE;
|
32
|
+
EXCEPTION
|
33
|
+
WHEN others THEN
|
34
|
+
RETURN FALSE;
|
35
|
+
END;
|
36
|
+
$$ LANGUAGE plpgsql IMMUTABLE;
|
37
|
+
|
38
|
+
DO $$
|
39
|
+
DECLARE
|
40
|
+
chunk_size INT := 1000;
|
41
|
+
deleted_count INT := 0;
|
42
|
+
BEGIN
|
43
|
+
LOOP
|
44
|
+
DELETE FROM state_log_all
|
45
|
+
WHERE id IN (
|
46
|
+
SELECT object_previous_and_current.id
|
47
|
+
FROM (
|
48
|
+
SELECT *,
|
49
|
+
LAG(object) OVER (
|
50
|
+
PARTITION BY model_id, model
|
51
|
+
ORDER BY date
|
52
|
+
) AS previous_object
|
53
|
+
FROM state_log_all
|
54
|
+
) AS object_previous_and_current
|
55
|
+
WHERE previous_object IS NOT NULL
|
56
|
+
AND EXISTS (
|
57
|
+
SELECT 1
|
58
|
+
FROM jsonb_each({auto_now_fields}) AS mapping(table_name, keys)
|
59
|
+
WHERE
|
60
|
+
is_valid_jsonb(previous_object) AND
|
61
|
+
is_valid_jsonb(object) AND
|
62
|
+
model = mapping.table_name::text AND
|
63
|
+
ARRAY(
|
64
|
+
SELECT jsonb_object_keys(
|
65
|
+
jsonb_diff(
|
66
|
+
COALESCE(previous_object::jsonb -> 0 -> 'fields', jsonb_build_object()),
|
67
|
+
COALESCE(object::jsonb -> 0 -> 'fields', jsonb_build_object())
|
68
|
+
)
|
69
|
+
)
|
70
|
+
) <@ ARRAY(SELECT jsonb_array_elements_text(mapping.keys))
|
71
|
+
)
|
72
|
+
LIMIT chunk_size
|
73
|
+
);
|
74
|
+
|
75
|
+
GET DIAGNOSTICS deleted_count = ROW_COUNT;
|
76
|
+
EXIT WHEN deleted_count = 0;
|
77
|
+
END LOOP;
|
78
|
+
END $$;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: educommon
|
3
|
-
Version: 3.
|
3
|
+
Version: 3.20.0
|
4
4
|
Summary: Общая кодовая база для проектов БЦ Образование
|
5
5
|
Author-email: BARS Group <education_dev@bars-open.ru>
|
6
6
|
Project-URL: Homepage, https://stash.bars-open.ru/projects/EDUBASE/repos/educommon/browse
|
@@ -59,6 +59,7 @@ educommon/audit_log/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
|
|
59
59
|
educommon/audit_log/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
60
60
|
educommon/audit_log/management/commands/audit_log_migrate_data.py,sha256=J_ZIpamGVBcotc-6cYsz37AJADEEyuiUs-VVyKON47o,12695
|
61
61
|
educommon/audit_log/management/commands/reinstall_audit_log.py,sha256=xr56lxpMtxR-KB3lKOmqYVmJ0aUOqzYSeod_6XYQmjc,3722
|
62
|
+
educommon/audit_log/management/commands/removing_false_logs.py,sha256=xPZom-wUopJNsDKdCrAWvbFea8wZ2BE71CnVEVMHzgg,6578
|
62
63
|
educommon/audit_log/migrations/0001_initial.py,sha256=HDhvBNyVSx_NlFmyA-t_ooFo_TiKf0UHNCZp1GOpLA8,6115
|
63
64
|
educommon/audit_log/migrations/0002_install_audit_log.py,sha256=kAhtd1Xz8b6g33wmxBQyRBJIl-LGJdOc7yFy5yhoRJ8,2825
|
64
65
|
educommon/audit_log/migrations/0003_logproxy.py,sha256=fx6nCoDtr3bZTtlwiOKP4QTVPxnnqyYDPMAT20Bv4n8,391
|
@@ -72,6 +73,8 @@ educommon/audit_log/migrations/0010_alter_auditlog_time.py,sha256=XmujmdgNADMy4O
|
|
72
73
|
educommon/audit_log/migrations/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
73
74
|
educommon/audit_log/sql/configure_audit_log.sql,sha256=M3QxNKTZbn-uNRxGDvNxE9iJh1EOQUTIho7rvc3yhlY,1511
|
74
75
|
educommon/audit_log/sql/install_audit_log.sql,sha256=EhyhGMYn8aYCGEBNC2AC7kuG5vfJiAPxhwTMq_2NADE,14505
|
76
|
+
educommon/audit_log/sql/new_audit_log_removing_false_logs.sql,sha256=s58p9L1TF9GIC957X7PSfhz60YIt60aNxcIZcoEB1Kc,739
|
77
|
+
educommon/audit_log/sql/old_audit_log_removing_false_logs.sql,sha256=7hA5y7-xc1N7cJHpENl07NMmZ48XLPOquI_gqPvupGg,2425
|
75
78
|
educommon/audit_log/utils/__init__.py,sha256=FKktT7EeO3M1wqYYSnsgEQ6pSyY4OEzSAXP87ZitI5Q,17239
|
76
79
|
educommon/audit_log/utils/operations.py,sha256=skxL7wE4Jx1XlNdPx-Pl3SfiZ1G9jBmcZrXKSQDUGzw,2555
|
77
80
|
educommon/auth/__init__.py,sha256=xkGJgqQ5QaEU86n6tJV77ux-2bMTAKbjpVYBCDhcS0E,79
|
@@ -352,7 +355,7 @@ educommon/ws_log/smev/exceptions.py,sha256=VNfzNHlj5Pz8D4979d_msTkxC-RQVoMctsgoJ
|
|
352
355
|
educommon/ws_log/templates/report/smev_logs.xlsx,sha256=nnYgB0Z_ix8HoxsRICjsZfFRQBdra-5Gd8nWhCxTjYg,10439
|
353
356
|
educommon/ws_log/templates/ui-js/smev-logs-list-window.js,sha256=AGup3D8GTJSY9WdDPj0zBJeYQBFOmGgcbxPOJbKK-nY,513
|
354
357
|
educommon/ws_log/templates/ui-js/smev-logs-report-setting-window.js,sha256=nQ7QYK9frJcE7g7kIt6INg9TlEEJAPPayBJgRaoTePA,1103
|
355
|
-
educommon-3.
|
356
|
-
educommon-3.
|
357
|
-
educommon-3.
|
358
|
-
educommon-3.
|
358
|
+
educommon-3.20.0.dist-info/METADATA,sha256=yEQhLXuSRQAuPw8IKtuLn8qatkf4TYjcLxRa9mIp2K0,2380
|
359
|
+
educommon-3.20.0.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
360
|
+
educommon-3.20.0.dist-info/top_level.txt,sha256=z5fbW7bz_0V1foUm_FGcZ9_MTpW3N1dBN7-kEmMowl4,10
|
361
|
+
educommon-3.20.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|