educommon 3.19.1__py3-none-any.whl → 3.21.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.
@@ -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 $$;
@@ -20,6 +20,8 @@ class BaseEntity:
20
20
 
21
21
  # Проводимая операция с записью сущности
22
22
  operation: int
23
+ # Id выгружаемой модели
24
+ id: str
23
25
 
24
26
  @classmethod
25
27
  @abstractmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: educommon
3
- Version: 3.19.1
3
+ Version: 3.21.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
@@ -198,7 +201,7 @@ educommon/importer/validators.py,sha256=8g9PPd25UXDh8ifjna-xVAca4iv63Lq-0SDC7KtA
198
201
  educommon/integration_entities/README.rst,sha256=Vc5BYEHY8KfRZVHJg4GAAk0aKvJn52PJuShSxvDMa8g,158
199
202
  educommon/integration_entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
200
203
  educommon/integration_entities/consts.py,sha256=XMSR-9Q5cLSXe6Bn0JWFQQ8UfTyJfs7LYT1klj0Xvsc,448
201
- educommon/integration_entities/entities.py,sha256=v4QQPb8NeQJMdgxjfEcY9_NmPGasQbRPoA10gZEMAjU,2005
204
+ educommon/integration_entities/entities.py,sha256=Qbi3Zl4cYsX81brVLTZPl4mSxW7tDcE_frbj1Xyw0zs,2062
202
205
  educommon/integration_entities/enums.py,sha256=MPS3F402TMUP9wDJXBgNHoktVOf-zM8lAD22QphnLtU,335
203
206
  educommon/integration_entities/helpers.py,sha256=KEdLGfuBWHfMoPKP93sy2FewK6tsWvS9jFG6Gj8YtlQ,9892
204
207
  educommon/integration_entities/mixins.py,sha256=pTZbuLMMsD4y513j-UoRKAjmPjCiwMp8d8I5xWoU33M,1115
@@ -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.19.1.dist-info/METADATA,sha256=bTmxkUclBTbBh8nVHUJdEpjKH3hzPyKp6WeSHzmOy_U,2380
356
- educommon-3.19.1.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
357
- educommon-3.19.1.dist-info/top_level.txt,sha256=z5fbW7bz_0V1foUm_FGcZ9_MTpW3N1dBN7-kEmMowl4,10
358
- educommon-3.19.1.dist-info/RECORD,,
358
+ educommon-3.21.0.dist-info/METADATA,sha256=ymYMv5xu-FGq3UhMGK7cQPy76COkIvxrvH7_bJH5yZ4,2380
359
+ educommon-3.21.0.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
360
+ educommon-3.21.0.dist-info/top_level.txt,sha256=z5fbW7bz_0V1foUm_FGcZ9_MTpW3N1dBN7-kEmMowl4,10
361
+ educommon-3.21.0.dist-info/RECORD,,