educommon 3.12.0__py3-none-any.whl → 3.13.2__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/__init__.py +1 -0
  23. educommon/audit_log/actions.py +27 -36
  24. educommon/audit_log/app_meta.py +7 -4
  25. educommon/audit_log/apps.py +44 -29
  26. educommon/audit_log/constants.py +7 -4
  27. educommon/audit_log/error_log/actions.py +1 -3
  28. educommon/audit_log/helpers.py +2 -4
  29. educommon/audit_log/management/commands/reinstall_audit_log.py +11 -7
  30. educommon/audit_log/migrations/0001_initial.py +91 -16
  31. educommon/audit_log/migrations/0002_install_audit_log.py +13 -13
  32. educommon/audit_log/migrations/0003_logproxy.py +1 -3
  33. educommon/audit_log/migrations/0004_reinstall_audit_log.py +1 -4
  34. educommon/audit_log/migrations/0005_postgresql_error.py +4 -2
  35. educommon/audit_log/migrations/0006_auto_20200806_1707.py +3 -4
  36. educommon/audit_log/migrations/0007_create_selective_tables_function.py +8 -5
  37. educommon/audit_log/migrations/0008_table_logged.py +0 -1
  38. educommon/audit_log/migrations/0009_reinstall_audit_log.py +0 -1
  39. educommon/audit_log/models.py +36 -42
  40. educommon/audit_log/permissions.py +11 -9
  41. educommon/audit_log/proxies.py +12 -23
  42. educommon/audit_log/ui.py +18 -15
  43. educommon/audit_log/utils/__init__.py +28 -60
  44. educommon/audit_log/utils/operations.py +16 -2
  45. educommon/auth/__init__.py +0 -3
  46. educommon/auth/rbac/__init__.py +2 -4
  47. educommon/auth/rbac/actions.py +148 -145
  48. educommon/auth/rbac/app_meta.py +9 -6
  49. educommon/auth/rbac/backends/base.py +2 -8
  50. educommon/auth/rbac/backends/caching.py +27 -37
  51. educommon/auth/rbac/backends/simple.py +1 -4
  52. educommon/auth/rbac/checker.py +1 -3
  53. educommon/auth/rbac/management/commands/rbac.py +6 -11
  54. educommon/auth/rbac/manager.py +18 -47
  55. educommon/auth/rbac/migrations/0001_initial.py +73 -12
  56. educommon/auth/rbac/migrations/0002_model_modifier_metaclass_fix.py +7 -6
  57. educommon/auth/rbac/migrations/0003_permission_hidden.py +1 -5
  58. educommon/auth/rbac/migrations/0004_auto_20171024_1245.py +26 -19
  59. educommon/auth/rbac/models.py +63 -68
  60. educommon/auth/rbac/permissions.py +6 -7
  61. educommon/auth/rbac/ui.py +83 -84
  62. educommon/auth/rbac/utils.py +10 -11
  63. educommon/auth/rbac/validators.py +4 -5
  64. educommon/auth/simple_auth/__init__.py +1 -5
  65. educommon/auth/simple_auth/actions.py +79 -92
  66. educommon/auth/simple_auth/app_meta.py +2 -9
  67. educommon/auth/simple_auth/checkers.py +3 -3
  68. educommon/auth/simple_auth/migrations/0001_initial.py +23 -4
  69. educommon/auth/simple_auth/validators.py +0 -1
  70. educommon/contingent/actions.py +7 -7
  71. educommon/contingent/app_meta.py +1 -4
  72. educommon/contingent/base.py +10 -15
  73. educommon/contingent/catalogs.py +424 -540
  74. educommon/contingent/contingent_plugin/actions.py +4 -15
  75. educommon/contingent/contingent_plugin/apps.py +10 -4
  76. educommon/contingent/contingent_plugin/migrations/0001_initial.py +5 -6
  77. educommon/contingent/contingent_plugin/migrations/0002_add_contingent_model_deleted.py +6 -11
  78. educommon/contingent/contingent_plugin/model_views.py +2 -12
  79. educommon/contingent/contingent_plugin/models.py +2 -7
  80. educommon/contingent/contingent_plugin/observer.py +14 -13
  81. educommon/contingent/contingent_plugin/plugin_meta.py +1 -3
  82. educommon/contingent/contingent_plugin/storage.py +8 -7
  83. educommon/contingent/contingent_plugin/utils.py +6 -6
  84. educommon/django/db/fields.py +72 -86
  85. educommon/django/db/migration/__init__.py +3 -7
  86. educommon/django/db/migration/operations.py +29 -51
  87. educommon/django/db/mixins/__init__.py +16 -10
  88. educommon/django/db/mixins/date_interval.py +47 -75
  89. educommon/django/db/mixins/validation.py +26 -26
  90. educommon/django/db/model_view/__init__.py +18 -22
  91. educommon/django/db/models.py +9 -8
  92. educommon/django/db/observer.py +9 -27
  93. educommon/django/db/partitioning/__init__.py +66 -92
  94. educommon/django/db/partitioning/management/commands/apply_partitioning.py +3 -13
  95. educommon/django/db/partitioning/management/commands/clear_table.py +18 -14
  96. educommon/django/db/partitioning/management/commands/split_table.py +18 -13
  97. educommon/django/db/routers.py +6 -15
  98. educommon/django/db/signals.py +149 -2
  99. educommon/django/db/utils.py +14 -19
  100. educommon/django/db/validators/__init__.py +1 -0
  101. educommon/django/db/validators/simple.py +72 -100
  102. educommon/django/storages/atcfs/api.py +39 -53
  103. educommon/django/storages/atcfs/app_meta.py +1 -1
  104. educommon/django/storages/atcfs/management/commands/atcfs_migrate.py +42 -55
  105. educommon/django/storages/atcfs/models.py +0 -3
  106. educommon/django/storages/atcfs/monkey_patching.py +18 -12
  107. educommon/django/storages/atcfs/storage.py +14 -23
  108. educommon/extjs/fields/input_params.py +15 -45
  109. educommon/importer/XLSReader.py +143 -241
  110. educommon/importer/__init__.py +86 -4
  111. educommon/importer/api.py +53 -84
  112. educommon/importer/constants.py +4 -14
  113. educommon/importer/loggers.py +16 -26
  114. educommon/importer/proxy.py +131 -176
  115. educommon/importer/proxy_import.py +11 -12
  116. educommon/importer/report.py +4 -6
  117. educommon/importer/ui.py +32 -26
  118. educommon/importer/validators.py +4 -7
  119. educommon/integration_entities/helpers.py +14 -18
  120. educommon/ioc/__init__.py +3 -6
  121. educommon/logger/loggers.py +10 -14
  122. educommon/m3/__init__.py +20 -38
  123. educommon/m3/extensions/__init__.py +1 -0
  124. educommon/m3/extensions/listeners/__init__.py +22 -38
  125. educommon/m3/extensions/listeners/delete_check/listeners.py +31 -41
  126. educommon/m3/extensions/listeners/delete_check/mixins.py +20 -25
  127. educommon/m3/extensions/listeners/delete_check/signals.py +2 -2
  128. educommon/m3/extensions/listeners/delete_check/ui.py +15 -14
  129. educommon/m3/extensions/listeners/delete_check/utils.py +9 -11
  130. educommon/m3/extensions/ui.py +15 -33
  131. educommon/m3/transaction_context.py +17 -19
  132. educommon/objectpack/actions.py +70 -88
  133. educommon/objectpack/apps.py +5 -0
  134. educommon/objectpack/filters.py +9 -15
  135. educommon/objectpack/ui.py +59 -77
  136. educommon/report/__init__.py +9 -5
  137. educommon/report/actions.py +29 -32
  138. educommon/report/constructor/__init__.py +5 -8
  139. educommon/report/constructor/app_meta.py +1 -3
  140. educommon/report/constructor/apps.py +1 -0
  141. educommon/report/constructor/base.py +33 -80
  142. educommon/report/constructor/builders/excel/_base.py +138 -286
  143. educommon/report/constructor/builders/excel/_header.py +2 -9
  144. educommon/report/constructor/builders/excel/product.py +13 -34
  145. educommon/report/constructor/builders/excel/with_merged_cells.py +18 -14
  146. educommon/report/constructor/config.py +2 -0
  147. educommon/report/constructor/editor/actions.py +101 -215
  148. educommon/report/constructor/editor/ui.py +71 -93
  149. educommon/report/constructor/exceptions.py +6 -12
  150. educommon/report/constructor/migrations/0001_initial.py +36 -44
  151. educommon/report/constructor/migrations/0002_report_filters.py +86 -72
  152. educommon/report/constructor/migrations/0003_reportfilter_exclude.py +5 -5
  153. educommon/report/constructor/migrations/0004_reportfilter_fields.py +22 -18
  154. educommon/report/constructor/migrations/0005_reportcolumn_visible.py +5 -4
  155. educommon/report/constructor/migrations/0006_reportsorting.py +21 -17
  156. educommon/report/constructor/migrations/0007_include_available_units.py +14 -14
  157. educommon/report/constructor/migrations/0008_auto_20170407_1318.py +4 -5
  158. educommon/report/constructor/migrations/0009_auto_20180405_0642.py +1 -4
  159. educommon/report/constructor/migrations/0010_add_aggregate_fields.py +7 -8
  160. educommon/report/constructor/mixins.py +14 -15
  161. educommon/report/constructor/models.py +76 -124
  162. educommon/report/constructor/utils.py +3 -8
  163. educommon/report/constructor/validators.py +1 -3
  164. educommon/report/reporter.py +25 -43
  165. educommon/report/utils.py +14 -40
  166. educommon/rest/actions.py +7 -11
  167. educommon/rest/context.py +6 -16
  168. educommon/rest/controllers.py +10 -10
  169. educommon/rest/mixins.py +29 -27
  170. educommon/secure_media/app_meta.py +9 -9
  171. educommon/utils/__init__.py +3 -2
  172. educommon/utils/caching.py +1 -3
  173. educommon/utils/conversion.py +1 -3
  174. educommon/utils/crypto.py +1 -2
  175. educommon/utils/date.py +13 -26
  176. educommon/utils/db/__init__.py +17 -26
  177. educommon/utils/db/postgresql.py +1 -4
  178. educommon/utils/fonts/__init__.py +3 -4
  179. educommon/utils/licence/__init__.py +5 -16
  180. educommon/utils/misc.py +9 -18
  181. educommon/utils/object_grid.py +55 -62
  182. educommon/utils/phone_number/modelfields.py +1 -3
  183. educommon/utils/phone_number/phone_number.py +5 -8
  184. educommon/utils/phone_number/validators.py +8 -23
  185. educommon/utils/plugins.py +15 -28
  186. educommon/utils/registry.py +2 -1
  187. educommon/utils/seqtools.py +1 -3
  188. educommon/utils/serializer.py +9 -16
  189. educommon/utils/storage.py +3 -2
  190. educommon/utils/system.py +1 -3
  191. educommon/utils/system_app/management/commands/delete_objects.py +17 -34
  192. educommon/utils/ui.py +87 -84
  193. educommon/utils/xml/__init__.py +2 -7
  194. educommon/utils/xml/resolver.py +1 -0
  195. educommon/ws_log/actions.py +31 -76
  196. educommon/ws_log/base.py +6 -20
  197. educommon/ws_log/migrations/0001_initial.py +25 -8
  198. educommon/ws_log/migrations/0002_auto_20160628_1334.py +0 -1
  199. educommon/ws_log/migrations/0003_add_fields_to_smev_logs.py +20 -4
  200. educommon/ws_log/migrations/0004_auto_20160727_1600.py +7 -6
  201. educommon/ws_log/migrations/0005_auto_20161130_1615.py +14 -4
  202. educommon/ws_log/migrations/0006_auto_20170327_1027.py +3 -2
  203. educommon/ws_log/migrations/0007_auto_20180607_1040.py +8 -9
  204. educommon/ws_log/migrations/0008_auto_20180713_1445.py +23 -10
  205. educommon/ws_log/migrations/0009_auto_20201130_1553.py +7 -2
  206. educommon/ws_log/models.py +21 -35
  207. educommon/ws_log/provider.py +2 -1
  208. educommon/ws_log/report.py +8 -13
  209. educommon/ws_log/smev/applications.py +12 -27
  210. educommon/ws_log/smev/exceptions.py +2 -3
  211. educommon/ws_log/ui.py +32 -32
  212. educommon/ws_log/utils.py +1 -3
  213. educommon-3.13.2.dist-info/METADATA +57 -0
  214. educommon-3.13.2.dist-info/RECORD +354 -0
  215. {educommon-3.12.0.dist-info → educommon-3.13.2.dist-info}/WHEEL +1 -1
  216. educommon/utils/patches.py +0 -27
  217. educommon/version.conf +0 -11
  218. educommon-3.12.0.dist-info/METADATA +0 -47
  219. educommon-3.12.0.dist-info/RECORD +0 -357
  220. educommon-3.12.0.dist-info/dependency_links.txt +0 -1
  221. {educommon-3.12.0.dist-info → educommon-3.13.2.dist-info}/top_level.txt +0 -0
@@ -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
 
@@ -56,11 +56,10 @@ class LogProxy(AuditLog):
56
56
  try:
57
57
  result = model.objects.get(id=user_id).person.fullname
58
58
  except model.DoesNotExist:
59
- result = '{}({})'.format(
60
- model._meta.verbose_name, user_id
61
- )
59
+ result = f'{model._meta.verbose_name}({user_id})'
62
60
  else:
63
61
  result = ''
62
+
64
63
  return result
65
64
 
66
65
  @property
@@ -113,7 +112,7 @@ class LogProxy(AuditLog):
113
112
  {
114
113
  'name': self.get_field_string(key),
115
114
  'old': self.convert_field_value(key, data.get(key, '')),
116
- 'new': self.convert_field_value(key, new_data.get(key, ''))
115
+ 'new': self.convert_field_value(key, new_data.get(key, '')),
117
116
  }
118
117
  for key in keys
119
118
  ]
@@ -132,10 +131,12 @@ class LogProxy(AuditLog):
132
131
  field = self.fields.get(column_name)
133
132
  if field and field.verbose_name:
134
133
  name = force_str(field.verbose_name)
134
+
135
135
  return name
136
136
 
137
137
  def convert_field_value(self, column_name, value):
138
138
  """Возвращает значение поля."""
139
+
139
140
  def get_choice(choices, choice_id):
140
141
  if choice_id:
141
142
  choice_id = int(choice_id)
@@ -153,18 +154,14 @@ class LogProxy(AuditLog):
153
154
  related = get_related(field)
154
155
  model = related.parent_model
155
156
  field_name = related.relation.field_name
156
- qs = model._default_manager.filter(
157
- **{field_name: value}
158
- )[:1]
157
+ qs = model._default_manager.filter(**{field_name: value})[:1]
159
158
  if qs:
160
159
  value = '{{{}}} {}'.format(
161
160
  qs[0].id,
162
161
  self._get_object_verbose_name(qs[0]),
163
162
  )
164
163
  elif isinstance(field, BooleanField):
165
- value_map = {
166
- 't': 'Да', 'f': 'Нет'
167
- }
164
+ value_map = {'t': 'Да', 'f': 'Нет'}
168
165
  value = value_map.get(value, value)
169
166
  elif isinstance(field, IntegerField) and field.choices:
170
167
  value = get_choice(field.choices, value)
@@ -173,11 +170,8 @@ class LogProxy(AuditLog):
173
170
  return force_str(value)
174
171
 
175
172
  @property
176
- def object_string(self):
177
- """Отображаемое имя экземпляра модели.
178
-
179
- :rtype str
180
- """
173
+ def object_string(self) -> str:
174
+ """Отображаемое имя экземпляра модели."""
181
175
  instance = self.instance
182
176
  if instance:
183
177
  return self._get_object_verbose_name(instance)
@@ -190,10 +184,7 @@ class LogProxy(AuditLog):
190
184
 
191
185
  if self.model:
192
186
  result = self.model()
193
- fields_dict = {
194
- field.name: field for field in
195
- self.model._meta.fields
196
- }
187
+ fields_dict = {field.name: field for field in self.model._meta.fields}
197
188
  for key, value in self.data.items():
198
189
  field = fields_dict.get(key)
199
190
  converted_value = value
@@ -210,15 +201,13 @@ class LogProxy(AuditLog):
210
201
  elif isinstance(field, FloatField):
211
202
  converted_value = float(value)
212
203
  elif isinstance(field, FileField):
213
- file_path = os.path.join(
214
- settings.MEDIA_ROOT,
215
- converted_value
216
- )
204
+ file_path = os.path.join(settings.MEDIA_ROOT, converted_value)
217
205
  if not os.path.exists(file_path):
218
206
  converted_value = None
219
207
  except (ValueError, TypeError):
220
208
  pass
221
209
  setattr(result, key, converted_value)
210
+
222
211
  return result
223
212
 
224
213
  @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,35 +35,33 @@ 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
63
  self.user_field.value = '{} / {}'.format(
60
- log_record.user.person.fullname,
61
- log_record.user.person.user.username
64
+ log_record.user.person.fullname, log_record.user.person.user.username
62
65
  )
63
66
  unit = getattr(log_record.user, 'unit', None)
64
67
  if unit:
@@ -1,3 +1,4 @@
1
+ # TODO - EDUSCHL-23454
1
2
  import os
2
3
  from contextlib import (
3
4
  closing,
@@ -72,11 +73,7 @@ def configure(force_update_triggers: bool = False):
72
73
  params['need_to_update_triggers'] = force_update_triggers or changed_table
73
74
  params['lock_id'] = PG_LOCK_ID
74
75
 
75
- execute_sql_file(
76
- settings.DEFAULT_DB_ALIAS,
77
- os.path.join(SQL_FILES_DIR, 'configure_audit_log.sql'),
78
- params
79
- )
76
+ execute_sql_file(settings.DEFAULT_DB_ALIAS, os.path.join(SQL_FILES_DIR, 'configure_audit_log.sql'), params)
80
77
 
81
78
 
82
79
  def get_all_table_names(db_alias: str, schema: str) -> Set[str]:
@@ -97,11 +94,7 @@ def get_all_table_names(db_alias: str, schema: str) -> Set[str]:
97
94
 
98
95
  def get_need_to_log_table_names() -> Set[str]:
99
96
  """Возвращает перечень наименований таблиц моделей, которые отмечены как отслеживаемые."""
100
- table_names = {
101
- model._meta.db_table
102
- for model in apps.get_models()
103
- if getattr(model, 'need_to_log', False)
104
- }
97
+ table_names = {model._meta.db_table for model in apps.get_models() if getattr(model, 'need_to_log', False)}
105
98
 
106
99
  return table_names
107
100
 
@@ -114,9 +107,7 @@ def update_or_create_tables(need_to_log_table_names: Iterable[str]) -> bool:
114
107
  Table = apps.get_model('audit_log', 'Table')
115
108
 
116
109
  need_to_log_table_names = set(need_to_log_table_names)
117
- existed_table_names = set(
118
- Table.objects.filter(schema='public').values_list('name', flat=True)
119
- )
110
+ existed_table_names = set(Table.objects.filter(schema='public').values_list('name', flat=True))
120
111
 
121
112
  to_create_table_names = need_to_log_table_names - existed_table_names
122
113
  to_disable_table_names = existed_table_names - need_to_log_table_names
@@ -141,10 +132,7 @@ def update_or_create_tables(need_to_log_table_names: Iterable[str]) -> bool:
141
132
 
142
133
  # Создание записей таблиц, которые теперь отслеживаемые
143
134
  Table.objects.bulk_create(
144
- objs=[
145
- Table(name=table, schema='public')
146
- for table in to_create_table_names
147
- ],
135
+ objs=[Table(name=table, schema='public') for table in to_create_table_names],
148
136
  )
149
137
 
150
138
  if to_create_table_names or enabled_count or disabled_count:
@@ -229,10 +217,8 @@ def is_initialized(database_alias):
229
217
  # Проверка наличия таблицы postgresql_errors
230
218
  with closing(connections[database_alias].cursor()) as cursor:
231
219
  cursor.execute(
232
- 'select 1 '
233
- 'from information_schema.tables '
234
- 'where table_schema = %s and table_name = %s',
235
- ('audit', 'postgresql_errors')
220
+ 'select 1 from information_schema.tables where table_schema = %s and table_name = %s',
221
+ ('audit', 'postgresql_errors'),
236
222
  )
237
223
  if cursor.fetchone() is None:
238
224
  return False
@@ -251,12 +237,12 @@ def is_initialized(database_alias):
251
237
  for function_name in function_names:
252
238
  with closing(connections[database_alias].cursor()) as cursor:
253
239
  cursor.execute(
254
- "select 1 "
255
- "from pg_proc proc "
256
- "inner join pg_namespace ns on ns.oid = proc.pronamespace "
257
- "where proc.proname = %s and ns.nspname = %s "
258
- "limit 1",
259
- [function_name, 'audit']
240
+ 'select 1 '
241
+ 'from pg_proc proc '
242
+ 'inner join pg_namespace ns on ns.oid = proc.pronamespace '
243
+ 'where proc.proname = %s and ns.nspname = %s '
244
+ 'limit 1',
245
+ [function_name, 'audit'],
260
246
  )
261
247
  if cursor.fetchone() is None:
262
248
  return False
@@ -304,7 +290,7 @@ def get_db_connection_params():
304
290
  dbname=target_db_conf['NAME'],
305
291
  port=target_db_conf['PORT'],
306
292
  user=target_db_conf['USER'],
307
- password=target_db_conf['PASSWORD']
293
+ password=target_db_conf['PASSWORD'],
308
294
  )
309
295
 
310
296
 
@@ -355,10 +341,7 @@ def make_hstore_filter(field, value):
355
341
  :param str value: значение, по которому фильтруется queryset.
356
342
  Если строка, то разбивается на отдельные слова.
357
343
  """
358
- result = reduce(
359
- and_,
360
- (Q(**{'%s__values__icontains' % field: x}) for x in value.split(' '))
361
- )
344
+ result = reduce(and_, (Q(**{f'{field}__values__icontains': x}) for x in value.split(' ')))
362
345
  return result
363
346
 
364
347
 
@@ -375,11 +358,7 @@ def make_name_filter(field, value):
375
358
  result = None
376
359
  for model in (Employee, SysAdmin):
377
360
  type_id = ContentType.objects.get_for_model(model).id
378
- user_ids = list(
379
- model.objects.filter(
380
- **{'person__{}__icontains'.format(field): value}
381
- ).values_list('id', flat=True)
382
- )
361
+ user_ids = list(model.objects.filter(**{f'person__{field}__icontains': value}).values_list('id', flat=True))
383
362
  qobj = Q(user_id__in=user_ids, user_type_id=type_id)
384
363
  if result:
385
364
  result |= qobj
@@ -389,6 +368,13 @@ def make_name_filter(field, value):
389
368
 
390
369
 
391
370
  class ModelRegistry:
371
+ """Реестр моделей Django по имени таблицы.
372
+
373
+ Позволяет получать класс модели по имени таблицы из базы данных.
374
+ Использует кэшируемое свойство для построения соответствия
375
+ между именами таблиц и их моделями, включая автоматически
376
+ создаваемые модели, но исключая proxy-модели.
377
+ """
392
378
 
393
379
  @cached_property
394
380
  def table_model(self):
@@ -415,16 +401,9 @@ def get_model_choices(excluded=None):
415
401
  table_class = apps.get_model('audit_log', 'Table')
416
402
  if excluded:
417
403
  total_exclude += tuple(excluded)
418
- tables = (
419
- table
420
- for table in table_class.objects.iterator()
421
- if (table.schema, table.name) not in total_exclude
422
- )
404
+ tables = (table for table in table_class.objects.iterator() if (table.schema, table.name) not in total_exclude)
423
405
 
424
- result = sorted(
425
- ((table.id, get_table_name(table)) for table in tables),
426
- key=lambda x: x[1]
427
- )
406
+ result = sorted(((table.id, get_table_name(table)) for table in tables), key=lambda x: x[1])
428
407
 
429
408
  return tuple(result)
430
409
 
@@ -435,12 +414,7 @@ def _get_m2m_model_fields(model):
435
414
  :return Два поля типа ForeignKey или None, если таблица не
436
415
  соответствует автоматически созданной.
437
416
  """
438
- result = [
439
- field
440
- for field
441
- in model._meta.get_fields()
442
- if isinstance(field, related.ForeignKey)
443
- ]
417
+ result = [field for field in model._meta.get_fields() if isinstance(field, related.ForeignKey)]
444
418
  if len(result) == 2:
445
419
  return result
446
420
 
@@ -454,16 +428,10 @@ def get_table_name(table):
454
428
  if model._meta.auto_created:
455
429
  fields = _get_m2m_model_fields(model)
456
430
  if fields:
457
- names = [
458
- get_related(f).parent_model._meta.verbose_name
459
- for f in fields
460
- ]
431
+ names = [get_related(f).parent_model._meta.verbose_name for f in fields]
461
432
  verbose_name = 'Связь {}, {}'.format(names[0], names[1])
462
433
 
463
- return '{} - {}'.format(
464
- verbose_name,
465
- class_name
466
- )
434
+ return f'{verbose_name} - {class_name}'
467
435
  else:
468
436
  return table.name
469
437
 
@@ -32,6 +32,7 @@ class ReinstallAuditLog(Operation):
32
32
 
33
33
  @staticmethod
34
34
  def _read_sql(filename):
35
+ """Читает SQL-файл и экранирует знаки процента."""
35
36
  sql_file_path = os.path.join(SQL_FILES_DIR, filename)
36
37
  with codecs.open(sql_file_path, 'r', 'utf-8') as sql_file:
37
38
  sql = sql_file.read().replace('%', '%%')
@@ -39,17 +40,30 @@ class ReinstallAuditLog(Operation):
39
40
 
40
41
  @property
41
42
  def _install_sql(self):
43
+ """Генерирует SQL-скрипт для установки схемы audit_log.
44
+
45
+ Подставляет параметры подключения к БД и lock_id
46
+ в шаблон SQL-файла install_audit_log.sql.
47
+ """
42
48
  params = get_db_connection_params()
43
49
  params['lock_id'] = PG_LOCK_ID
44
50
  return self._read_sql('install_audit_log.sql').format(**params)
45
51
 
46
52
  def state_forwards(self, app_label, state):
53
+ """Не изменяет состояние проекта в памяти.
54
+
55
+ Метод требуется по контракту абстрактного базового класса Operation.
56
+ """
47
57
  pass
48
58
 
49
- def database_forwards(self, app_label, schema_editor, from_state,
50
- to_state):
59
+ def database_forwards(self, app_label, schema_editor, from_state, to_state):
60
+ """Применяет SQL-скрипт установки audit_log, если используется основная БД."""
51
61
  if schema_editor.connection.alias == settings.DEFAULT_DB_ALIAS:
52
62
  schema_editor.execute(self._install_sql)
53
63
 
54
64
  def database_backwards(self, app_label, schema_editor, from_state, to_state):
65
+ """Откат миграции не реализован (no-op).
66
+
67
+ Метод присутствует, чтобы соответствовать контракту Django.
68
+ """
55
69
  return None
@@ -1,4 +1 @@
1
1
  """Средства аутентификации и авторизации."""
2
- from __future__ import (
3
- absolute_import,
4
- )
@@ -3,12 +3,10 @@
3
3
  .. note::
4
4
  RBAC - Role Based Access Control.
5
5
  """
6
+
6
7
  from django import (
7
8
  VERSION as _django_version,
8
9
  )
9
10
 
10
11
 
11
- assert _django_version >= (1, 8), (
12
- "{} app doesn't support Django {}.{}."
13
- .format(__name__, *_django_version[:2])
14
- )
12
+ assert _django_version >= (1, 8), "{} app doesn't support Django {}.{}.".format(__name__, *_django_version[:2])