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
@@ -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
- columns = [
88
- {
89
- 'data_index': 'time',
90
- 'width': 140,
91
- 'header': 'Дата и время',
92
- 'sortable': True,
93
- 'filter': date_filter.filter
94
- },
95
- {
96
- 'data_index': 'user_name',
97
- 'width': 130,
98
- 'header': 'Пользователь',
99
- 'filter': ff(
100
- 'table__name',
101
- lookup=lambda x: make_name_filter('surname', x)
102
- ) & ff(
103
- 'table__name',
104
- lookup=lambda x: make_name_filter('firstname', x)
105
- ) & ff(
106
- 'table__name',
107
- lookup=lambda x: make_name_filter('patronymic', x)
108
- )
109
- },
110
- {
111
- 'data_index': 'operation',
112
- 'width': 60,
113
- 'header': 'Операция',
114
- 'filter': ff(
115
- 'operation',
116
- ask_before_deleting=False
117
- ),
118
- },
119
- {
120
- 'data_index': 'model_name',
121
- 'width': 220,
122
- 'header': 'Модель объекта',
123
- 'filter': ff(
124
- 'table',
125
- control_creator=lambda: make_combo_box(
126
- data=get_model_choices(),
127
- ask_before_deleting=False,
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
- 'data_index': 'object_id',
133
- 'width': 50,
134
- 'header': 'Код объекта',
135
- 'filter': ff('object_id'),
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
- super(AuditLogPack, self).__init__()
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(AuditLogPack, self).configure_grid(grid)
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
- params = super(AuditLogPack, self).get_edit_window_params(
182
- params, request, context
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
- params = super(AuditLogPack, self).get_list_window_params(
189
- params, request, context
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
- return super(AuditLogPack, self).get_rows_query(
196
- request, context
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
- result = super(ViewChangeAction, self).context_declaration()
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 = LogProxy.objects.get(id=object_id).diff
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)})
@@ -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
- ioc.get('main_controller').packs.extend((
14
- AuditLogPack(),
15
- PostgreSQLErrorPack(),
16
- ))
10
+ """Регистрация паков и экшенов."""
11
+ ioc.get('main_controller').packs.extend(
12
+ (
13
+ PostgreSQLErrorPack(),
14
+ )
15
+ )
@@ -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
- return '.'.join((
39
- self.name,
40
- self.__class__.__name__,
41
- self._configure_audit_log.__name__,
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
- "{0} - Ошибка подключения к сервисной базе через "
92
+ '{0} - Ошибка подключения к сервисной базе через '
81
93
  "postgres_fdw. Необходимо убедится что 'Журнал изменений' "
82
- "настроен корректно.".format(error_message)
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(Critical(
138
- msg.format(dbname),
139
- hint.format(indent=indent, dbname=dbname),
140
- id=message_id,
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')
@@ -21,8 +21,11 @@ EXCLUDED_TABLES = (
21
21
  )
22
22
 
23
23
  # Папка с sql файлами
24
- SQL_FILES_DIR = os.path.abspath(os.path.join(
25
- os.path.dirname(__file__), 'sql',
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)
@@ -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
- '--clear_audit_logs',
42
- action='store_true',
43
- default=False,
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
- ('user_id', models.IntegerField(null=True, verbose_name='\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c')),
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
- ('time', models.DateTimeField(auto_now_add=True, verbose_name='\u0414\u0430\u0442\u0430, \u0432\u0440\u0435\u043c\u044f')),
28
- ('level', models.CharField(max_length=50, verbose_name='\u0423\u0440\u043e\u0432\u0435\u043d\u044c \u043e\u0448\u0438\u0431\u043a\u0438')),
29
- ('text', models.TextField(verbose_name='\u0422\u0435\u043a\u0441\u0442 \u043e\u0448\u0438\u0431\u043a\u0438')),
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
- ('user_id', models.IntegerField(null=True, verbose_name='\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c', db_index=True)),
42
- ('user_type_id', models.IntegerField(null=True, verbose_name='\u0422\u0438\u043f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f', db_index=True)),
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
- ('time', models.DateTimeField(auto_now_add=True, verbose_name='\u0414\u0430\u0442\u0430, \u0432\u0440\u0435\u043c\u044f', db_index=True)),
45
- ('object_id', models.IntegerField(verbose_name='\u041e\u0431\u044a\u0435\u043a\u0442 \u043c\u043e\u0434\u0435\u043b\u0438', db_index=True)),
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
- ('changes', HStoreField(null=True, verbose_name='\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f')),
48
- ('operation', models.SmallIntegerField(verbose_name='\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435', choices=[(1, '\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435'), (2, '\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435'), (3, '\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435')])),
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
- ('name', models.CharField(max_length=250, verbose_name='\u0418\u043c\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u044b')),
60
- ('schema', models.CharField(max_length=250, verbose_name='\u0421\u0445\u0435\u043c\u0430 \u0442\u0430\u0431\u043b\u0438\u0446\u044b')),
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(verbose_name='\u0422\u0430\u0431\u043b\u0438\u0446\u0430', to='audit_log.Table', on_delete=models.CASCADE),
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
  ]