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.
Files changed (221) hide show
  1. educommon/__init__.py +0 -1
  2. educommon/about/ui/actions.py +16 -30
  3. educommon/about/ui/ui.py +3 -12
  4. educommon/about/utils.py +6 -5
  5. educommon/async_task/__init__.py +0 -1
  6. educommon/async_task/actions.py +18 -13
  7. educommon/async_task/apps.py +4 -0
  8. educommon/async_task/locker.py +2 -5
  9. educommon/async_task/migrations/0001_initial.py +55 -9
  10. educommon/async_task/migrations/0002_task_type_and_status_data.py +94 -89
  11. educommon/async_task/migrations/0003_alter_runningtask_options.py +0 -1
  12. educommon/async_task/models.py +9 -6
  13. educommon/async_task/tasks.py +11 -7
  14. educommon/async_task/ui.py +16 -35
  15. educommon/async_tasks/__init__.py +0 -1
  16. educommon/async_tasks/apps.py +4 -0
  17. educommon/async_tasks/locks.py +11 -21
  18. educommon/async_tasks/migrations/0001_initial.py +68 -8
  19. educommon/async_tasks/migrations/0002_load_initial_data.py +0 -1
  20. educommon/async_tasks/models.py +9 -29
  21. educommon/async_tasks/tasks.py +25 -54
  22. educommon/audit_log/README.rst +64 -0
  23. educommon/audit_log/__init__.py +1 -0
  24. educommon/audit_log/actions.py +108 -89
  25. educommon/audit_log/app_meta.py +6 -7
  26. educommon/audit_log/apps.py +44 -29
  27. educommon/audit_log/constants.py +7 -4
  28. educommon/audit_log/error_log/actions.py +1 -3
  29. educommon/audit_log/helpers.py +2 -4
  30. educommon/audit_log/management/commands/reinstall_audit_log.py +11 -7
  31. educommon/audit_log/migrations/0001_initial.py +91 -16
  32. educommon/audit_log/migrations/0002_install_audit_log.py +13 -13
  33. educommon/audit_log/migrations/0003_logproxy.py +1 -3
  34. educommon/audit_log/migrations/0004_reinstall_audit_log.py +1 -4
  35. educommon/audit_log/migrations/0005_postgresql_error.py +4 -2
  36. educommon/audit_log/migrations/0006_auto_20200806_1707.py +3 -4
  37. educommon/audit_log/migrations/0007_create_selective_tables_function.py +8 -5
  38. educommon/audit_log/migrations/0008_table_logged.py +0 -1
  39. educommon/audit_log/migrations/0009_reinstall_audit_log.py +0 -1
  40. educommon/audit_log/models.py +36 -42
  41. educommon/audit_log/permissions.py +11 -9
  42. educommon/audit_log/proxies.py +33 -28
  43. educommon/audit_log/ui.py +19 -22
  44. educommon/audit_log/utils/__init__.py +27 -81
  45. educommon/audit_log/utils/operations.py +16 -2
  46. educommon/auth/__init__.py +0 -3
  47. educommon/auth/rbac/__init__.py +2 -4
  48. educommon/auth/rbac/actions.py +148 -145
  49. educommon/auth/rbac/app_meta.py +9 -6
  50. educommon/auth/rbac/backends/base.py +2 -8
  51. educommon/auth/rbac/backends/caching.py +27 -37
  52. educommon/auth/rbac/backends/simple.py +1 -4
  53. educommon/auth/rbac/checker.py +1 -3
  54. educommon/auth/rbac/management/commands/rbac.py +6 -11
  55. educommon/auth/rbac/manager.py +18 -47
  56. educommon/auth/rbac/migrations/0001_initial.py +73 -12
  57. educommon/auth/rbac/migrations/0002_model_modifier_metaclass_fix.py +7 -6
  58. educommon/auth/rbac/migrations/0003_permission_hidden.py +1 -5
  59. educommon/auth/rbac/migrations/0004_auto_20171024_1245.py +26 -19
  60. educommon/auth/rbac/models.py +63 -68
  61. educommon/auth/rbac/permissions.py +6 -7
  62. educommon/auth/rbac/ui.py +83 -84
  63. educommon/auth/rbac/utils.py +10 -11
  64. educommon/auth/rbac/validators.py +4 -5
  65. educommon/auth/simple_auth/__init__.py +1 -5
  66. educommon/auth/simple_auth/actions.py +79 -92
  67. educommon/auth/simple_auth/app_meta.py +2 -9
  68. educommon/auth/simple_auth/checkers.py +3 -3
  69. educommon/auth/simple_auth/migrations/0001_initial.py +23 -4
  70. educommon/auth/simple_auth/validators.py +0 -1
  71. educommon/contingent/actions.py +7 -7
  72. educommon/contingent/app_meta.py +1 -4
  73. educommon/contingent/base.py +10 -15
  74. educommon/contingent/catalogs.py +424 -540
  75. educommon/contingent/contingent_plugin/actions.py +4 -15
  76. educommon/contingent/contingent_plugin/apps.py +10 -4
  77. educommon/contingent/contingent_plugin/migrations/0001_initial.py +5 -6
  78. educommon/contingent/contingent_plugin/migrations/0002_add_contingent_model_deleted.py +6 -11
  79. educommon/contingent/contingent_plugin/model_views.py +2 -12
  80. educommon/contingent/contingent_plugin/models.py +2 -7
  81. educommon/contingent/contingent_plugin/observer.py +14 -13
  82. educommon/contingent/contingent_plugin/plugin_meta.py +1 -3
  83. educommon/contingent/contingent_plugin/storage.py +8 -7
  84. educommon/contingent/contingent_plugin/utils.py +6 -6
  85. educommon/django/db/fields.py +72 -86
  86. educommon/django/db/migration/__init__.py +3 -7
  87. educommon/django/db/migration/operations.py +29 -51
  88. educommon/django/db/mixins/__init__.py +16 -10
  89. educommon/django/db/mixins/date_interval.py +47 -75
  90. educommon/django/db/mixins/validation.py +26 -26
  91. educommon/django/db/model_view/__init__.py +18 -22
  92. educommon/django/db/models.py +9 -8
  93. educommon/django/db/observer.py +9 -27
  94. educommon/django/db/partitioning/__init__.py +66 -92
  95. educommon/django/db/partitioning/management/commands/apply_partitioning.py +3 -13
  96. educommon/django/db/partitioning/management/commands/clear_table.py +18 -14
  97. educommon/django/db/partitioning/management/commands/split_table.py +18 -13
  98. educommon/django/db/routers.py +6 -15
  99. educommon/django/db/signals.py +4 -2
  100. educommon/django/db/utils.py +14 -19
  101. educommon/django/db/validators/__init__.py +1 -0
  102. educommon/django/db/validators/simple.py +72 -100
  103. educommon/django/storages/atcfs/api.py +39 -53
  104. educommon/django/storages/atcfs/app_meta.py +1 -1
  105. educommon/django/storages/atcfs/management/commands/atcfs_migrate.py +42 -55
  106. educommon/django/storages/atcfs/models.py +0 -3
  107. educommon/django/storages/atcfs/monkey_patching.py +18 -12
  108. educommon/django/storages/atcfs/storage.py +14 -23
  109. educommon/extjs/fields/input_params.py +15 -45
  110. educommon/importer/XLSReader.py +143 -241
  111. educommon/importer/__init__.py +86 -4
  112. educommon/importer/api.py +53 -84
  113. educommon/importer/constants.py +4 -14
  114. educommon/importer/loggers.py +16 -26
  115. educommon/importer/proxy.py +131 -176
  116. educommon/importer/proxy_import.py +11 -12
  117. educommon/importer/report.py +4 -6
  118. educommon/importer/ui.py +32 -26
  119. educommon/importer/validators.py +4 -7
  120. educommon/integration_entities/helpers.py +14 -18
  121. educommon/ioc/__init__.py +3 -6
  122. educommon/logger/loggers.py +10 -14
  123. educommon/m3/__init__.py +20 -38
  124. educommon/m3/extensions/__init__.py +1 -0
  125. educommon/m3/extensions/listeners/__init__.py +22 -38
  126. educommon/m3/extensions/listeners/delete_check/listeners.py +31 -41
  127. educommon/m3/extensions/listeners/delete_check/mixins.py +20 -25
  128. educommon/m3/extensions/listeners/delete_check/signals.py +2 -2
  129. educommon/m3/extensions/listeners/delete_check/ui.py +15 -14
  130. educommon/m3/extensions/listeners/delete_check/utils.py +9 -11
  131. educommon/m3/extensions/ui.py +15 -33
  132. educommon/m3/transaction_context.py +17 -19
  133. educommon/objectpack/actions.py +70 -88
  134. educommon/objectpack/apps.py +5 -0
  135. educommon/objectpack/filters.py +9 -15
  136. educommon/objectpack/ui.py +59 -77
  137. educommon/report/__init__.py +9 -5
  138. educommon/report/actions.py +29 -32
  139. educommon/report/constructor/__init__.py +5 -8
  140. educommon/report/constructor/app_meta.py +1 -3
  141. educommon/report/constructor/apps.py +1 -0
  142. educommon/report/constructor/base.py +33 -80
  143. educommon/report/constructor/builders/excel/_base.py +138 -286
  144. educommon/report/constructor/builders/excel/_header.py +2 -9
  145. educommon/report/constructor/builders/excel/product.py +13 -34
  146. educommon/report/constructor/builders/excel/with_merged_cells.py +18 -14
  147. educommon/report/constructor/config.py +2 -0
  148. educommon/report/constructor/editor/actions.py +101 -215
  149. educommon/report/constructor/editor/ui.py +71 -93
  150. educommon/report/constructor/exceptions.py +6 -12
  151. educommon/report/constructor/migrations/0001_initial.py +36 -44
  152. educommon/report/constructor/migrations/0002_report_filters.py +86 -72
  153. educommon/report/constructor/migrations/0003_reportfilter_exclude.py +5 -5
  154. educommon/report/constructor/migrations/0004_reportfilter_fields.py +22 -18
  155. educommon/report/constructor/migrations/0005_reportcolumn_visible.py +5 -4
  156. educommon/report/constructor/migrations/0006_reportsorting.py +21 -17
  157. educommon/report/constructor/migrations/0007_include_available_units.py +14 -14
  158. educommon/report/constructor/migrations/0008_auto_20170407_1318.py +4 -5
  159. educommon/report/constructor/migrations/0009_auto_20180405_0642.py +1 -4
  160. educommon/report/constructor/migrations/0010_add_aggregate_fields.py +7 -8
  161. educommon/report/constructor/mixins.py +14 -15
  162. educommon/report/constructor/models.py +76 -124
  163. educommon/report/constructor/utils.py +3 -8
  164. educommon/report/constructor/validators.py +1 -3
  165. educommon/report/reporter.py +25 -43
  166. educommon/report/utils.py +14 -40
  167. educommon/rest/actions.py +7 -11
  168. educommon/rest/context.py +6 -16
  169. educommon/rest/controllers.py +10 -10
  170. educommon/rest/mixins.py +29 -27
  171. educommon/secure_media/app_meta.py +9 -9
  172. educommon/utils/__init__.py +3 -2
  173. educommon/utils/caching.py +1 -3
  174. educommon/utils/conversion.py +1 -3
  175. educommon/utils/crypto.py +1 -2
  176. educommon/utils/date.py +13 -26
  177. educommon/utils/db/__init__.py +17 -26
  178. educommon/utils/db/postgresql.py +1 -4
  179. educommon/utils/fonts/__init__.py +3 -4
  180. educommon/utils/licence/__init__.py +5 -16
  181. educommon/utils/misc.py +9 -18
  182. educommon/utils/object_grid.py +55 -62
  183. educommon/utils/phone_number/modelfields.py +1 -3
  184. educommon/utils/phone_number/phone_number.py +5 -8
  185. educommon/utils/phone_number/validators.py +8 -23
  186. educommon/utils/plugins.py +15 -28
  187. educommon/utils/registry.py +2 -1
  188. educommon/utils/seqtools.py +1 -3
  189. educommon/utils/serializer.py +9 -16
  190. educommon/utils/storage.py +3 -2
  191. educommon/utils/system.py +1 -3
  192. educommon/utils/system_app/management/commands/delete_objects.py +17 -34
  193. educommon/utils/ui.py +87 -84
  194. educommon/utils/xml/__init__.py +2 -7
  195. educommon/utils/xml/resolver.py +1 -0
  196. educommon/ws_log/actions.py +31 -76
  197. educommon/ws_log/base.py +6 -20
  198. educommon/ws_log/migrations/0001_initial.py +25 -8
  199. educommon/ws_log/migrations/0002_auto_20160628_1334.py +0 -1
  200. educommon/ws_log/migrations/0003_add_fields_to_smev_logs.py +20 -4
  201. educommon/ws_log/migrations/0004_auto_20160727_1600.py +7 -6
  202. educommon/ws_log/migrations/0005_auto_20161130_1615.py +14 -4
  203. educommon/ws_log/migrations/0006_auto_20170327_1027.py +3 -2
  204. educommon/ws_log/migrations/0007_auto_20180607_1040.py +8 -9
  205. educommon/ws_log/migrations/0008_auto_20180713_1445.py +23 -10
  206. educommon/ws_log/migrations/0009_auto_20201130_1553.py +7 -2
  207. educommon/ws_log/models.py +21 -35
  208. educommon/ws_log/provider.py +2 -1
  209. educommon/ws_log/report.py +8 -13
  210. educommon/ws_log/smev/applications.py +12 -27
  211. educommon/ws_log/smev/exceptions.py +2 -3
  212. educommon/ws_log/ui.py +32 -32
  213. educommon/ws_log/utils.py +1 -3
  214. {educommon-3.13.0.dist-info → educommon-3.14.0.dist-info}/METADATA +26 -14
  215. educommon-3.14.0.dist-info/RECORD +355 -0
  216. educommon/utils/patches.py +0 -27
  217. educommon/version.conf +0 -11
  218. educommon-3.13.0.dist-info/RECORD +0 -357
  219. educommon-3.13.0.dist-info/dependency_links.txt +0 -1
  220. {educommon-3.13.0.dist-info → educommon-3.14.0.dist-info}/WHEEL +0 -0
  221. {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('\n'.join((
74
- "SELECT table_name, table_schema",
75
- "FROM information_schema.tables",
76
- "WHERE table_schema = 'public'",
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,11 +8,8 @@ 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', '0003_logproxy'),
14
13
  ]
15
14
 
16
- operations = [
17
- ReinstallAuditLog()
18
- ]
15
+ operations = [ReinstallAuditLog()]
@@ -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={'verbose_name': '\u0417\u0430\u043f\u0438\u0441\u044c \u0436\u0443\u0440\u043d\u0430\u043b\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439', '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'},
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
- datetime.datetime(1900, 1, 1, 0, 0))],
24
- verbose_name='Дата, время'),
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('\n'.join((
17
- "SELECT",
18
- "audit.drop_functions_by_name('set_for_selective_tables_triggers');",
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
  ]
@@ -7,7 +7,6 @@ from django.db import (
7
7
 
8
8
 
9
9
  class Migration(migrations.Migration):
10
-
11
10
  dependencies = [
12
11
  ('audit_log', '0007_create_selective_tables_function'),
13
12
  ]
@@ -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', '0008_table_logged'),
14
13
  ]
@@ -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
- max_length=250,
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
- null=True,
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
- db_index=True,
126
- verbose_name='Объект модели'
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
- (PERM__AUDIT_LOG__ERRORS__VIEW,
15
- 'Просмотр журнала ошибок PostgreSQL',
16
- 'Разрешает просмотр журнала ошибок PostgreSQL.'),
17
- (PERM__AUDIT_LOG__ERRORS__DELETE,
18
- 'Удаление записей журнала ошибок PostgreSQL',
19
- 'Разрешает удаление записей из журнала ошибок PostgreSQL.'),
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
 
@@ -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 LogProxy(AuditLog):
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
- try:
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
- super(ViewChangeWindow, self)._init_components()
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
- super(ViewChangeWindow, self)._do_layout()
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
- formed(self.user_field, flex=1, style=dict(padding='5px')),
47
- formed(self.unit_field, flex=1, style=dict(padding='5px'))
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 = '{} / {}'.format(
60
- log_record.user.person.fullname,
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