educommon 3.13.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 (220) 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 +4 -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.0.dist-info → educommon-3.13.2.dist-info}/METADATA +26 -14
  214. educommon-3.13.2.dist-info/RECORD +354 -0
  215. educommon/utils/patches.py +0 -27
  216. educommon/version.conf +0 -11
  217. educommon-3.13.0.dist-info/RECORD +0 -357
  218. educommon-3.13.0.dist-info/dependency_links.txt +0 -1
  219. {educommon-3.13.0.dist-info → educommon-3.13.2.dist-info}/WHEEL +0 -0
  220. {educommon-3.13.0.dist-info → educommon-3.13.2.dist-info}/top_level.txt +0 -0
@@ -28,6 +28,7 @@
28
28
  Подробнее о партиционировании можно почитать в дкоументации PostgreSQL
29
29
  (раздел 5.9).
30
30
  """
31
+
31
32
  import re
32
33
  from contextlib import (
33
34
  closing,
@@ -86,24 +87,18 @@ _MESSAGE_PREFIX = '[Partitioning] '
86
87
  def _check_system_settings(database_alias):
87
88
  """Проверка конфигурации системы. Перечень проверок:
88
89
 
89
- 1. Указанный алиас БД есть в конфигурации системы.
90
- 2. Указанная БД управляется СУБД PostgreSQL.
90
+ 1. Указанный алиас БД есть в конфигурации системы.
91
+ 2. Указанная БД управляется СУБД PostgreSQL.
91
92
  """
92
93
  if database_alias not in settings.DATABASES:
93
- raise ImproperlyConfigured(
94
- _MESSAGE_PREFIX +
95
- '"{0}" database not found.'.format(database_alias)
96
- )
94
+ raise ImproperlyConfigured(f'{_MESSAGE_PREFIX}"{database_alias}" database not found.')
97
95
 
98
96
  database_engine = settings.DATABASES[database_alias]['ENGINE']
99
97
  if database_engine not in (
100
98
  'django.db.backends.postgresql_psycopg2',
101
99
  'django.db.backends.postgresql',
102
100
  ):
103
- raise ImproperlyConfigured(
104
- _MESSAGE_PREFIX +
105
- 'only PostgreSQL DBMS supported.'
106
- )
101
+ raise ImproperlyConfigured(f'{_MESSAGE_PREFIX}only PostgreSQL DBMS supported.')
107
102
 
108
103
 
109
104
  def is_initialized(database_alias):
@@ -116,9 +111,7 @@ def is_initialized(database_alias):
116
111
  """
117
112
  # Проверка наличия схемы partitioning.
118
113
  with closing(connections[database_alias].cursor()) as cursor:
119
- cursor.execute(
120
- "select 1 from pg_namespace where nspname = 'partitioning'"
121
- )
114
+ cursor.execute("select 1 from pg_namespace where nspname = 'partitioning'")
122
115
  if cursor.fetchone() is None:
123
116
  return False
124
117
 
@@ -127,12 +120,12 @@ def is_initialized(database_alias):
127
120
  for function_name in function_names:
128
121
  with closing(connections[database_alias].cursor()) as cursor:
129
122
  cursor.execute(
130
- "select 1 "
131
- "from pg_proc proc "
132
- "inner join pg_namespace ns on ns.oid = proc.pronamespace "
123
+ 'select 1 '
124
+ 'from pg_proc proc '
125
+ 'inner join pg_namespace ns on ns.oid = proc.pronamespace '
133
126
  "where proc.proname = %s and ns.nspname = 'partitioning' "
134
- "limit 1",
135
- [function_name]
127
+ 'limit 1',
128
+ [function_name],
136
129
  )
137
130
  if cursor.fetchone() is None:
138
131
  return False
@@ -141,6 +134,7 @@ def is_initialized(database_alias):
141
134
 
142
135
 
143
136
  def _execute_sql_file(database_alias, file_name, params=None):
137
+ """Выполняет SQL-скрипт из файла с подстановкой параметров."""
144
138
  cursor = connections[database_alias].cursor()
145
139
 
146
140
  file_path = path.join(path.dirname(__file__), file_name)
@@ -173,16 +167,19 @@ def init(database_alias=DEFAULT_DB_ALIAS, force=False):
173
167
  _check_system_settings(database_alias)
174
168
 
175
169
  if not force and is_initialized(database_alias):
176
- raise ImproperlyConfigured(
177
- _MESSAGE_PREFIX + 'always initialized'
178
- )
179
-
180
- _execute_sql_file(database_alias, 'partitioning.sql', dict(
181
- view_name_suffix=PartitioningObserver.view_name_suffix,
182
- ))
170
+ raise ImproperlyConfigured(f'{_MESSAGE_PREFIX}always initialized')
171
+
172
+ _execute_sql_file(
173
+ database_alias,
174
+ 'partitioning.sql',
175
+ dict(
176
+ view_name_suffix=PartitioningObserver.view_name_suffix,
177
+ ),
178
+ )
183
179
 
184
180
 
185
181
  def _get_model_params(model):
182
+ """Возвращает параметры модели: алиас БД, имя таблицы и имя PK-колонки."""
186
183
  database_alias = router.db_for_write(model)
187
184
  table_name = model._meta.db_table
188
185
  pk_column_name = model._meta.pk.name
@@ -198,16 +195,12 @@ def is_model_partitioned(model):
198
195
  database_alias, table_name, _ = _get_model_params(model)
199
196
 
200
197
  with closing(connections[database_alias].cursor()) as cursor:
201
- cursor.execute(
202
- "select 1 from pg_namespace where nspname = 'partitioning' limit 1"
203
- )
198
+ cursor.execute("select 1 from pg_namespace where nspname = 'partitioning' limit 1")
204
199
  if cursor.fetchone() is None:
205
200
  return False
206
201
 
207
- cursor.execute(
208
- "select partitioning.is_table_partitioned(%s)",
209
- (table_name,)
210
- )
202
+ cursor.execute('select partitioning.is_table_partitioned(%s)', (table_name,))
203
+
211
204
  return cursor.fetchone()[0]
212
205
 
213
206
 
@@ -236,14 +229,12 @@ def set_partitioning_for_model(model, column_name, force=False):
236
229
  ModelOptions(model).get_field(column_name)
237
230
 
238
231
  if not force and not is_initialized(database_alias):
239
- raise ImproperlyConfigured(
240
- _MESSAGE_PREFIX + 'not initialized'
241
- )
232
+ raise ImproperlyConfigured(f'{_MESSAGE_PREFIX}not initialized')
242
233
 
243
234
  _execute_sql_file(database_alias, 'triggers.sql', locals())
244
235
 
245
236
 
246
- def split_table(model, column_name: str, timeout: float = 0, cursor_itersize: Optional[int] = None):
237
+ def split_table(model, column_name: str, timeout: float = 0, cursor_itersize: Optional[int] = None):
247
238
  """Переносит записи из разбиваемой таблицы в ее разделы.
248
239
 
249
240
  Недостающие разделы будут созданы автоматически.
@@ -271,20 +262,13 @@ def split_table(model, column_name: str, timeout: float = 0, cursor_itersize: O
271
262
  ModelOptions(model).get_field(column_name)
272
263
 
273
264
  if not is_initialized(database_alias):
274
- raise ImproperlyConfigured(
275
- _MESSAGE_PREFIX + 'not initialized'
276
- )
265
+ raise ImproperlyConfigured(f'{_MESSAGE_PREFIX}not initialized')
277
266
 
278
267
  if not is_model_partitioned(model):
279
- raise ImproperlyConfigured(
280
- _MESSAGE_PREFIX + 'not applyed for {table_name}'.format(**locals())
281
- )
268
+ raise ImproperlyConfigured(f'{_MESSAGE_PREFIX}not applyed for {table_name}')
282
269
 
283
270
  if settings.DATABASES[database_alias]['DISABLE_SERVER_SIDE_CURSORS']:
284
- raise ImproperlyConfigured(
285
- _MESSAGE_PREFIX + 'split_table does not '
286
- 'support DISABLE_SERVER_SIDE_CURSORS.'
287
- )
271
+ raise ImproperlyConfigured(f'{_MESSAGE_PREFIX}split_table does not support DISABLE_SERVER_SIDE_CURSORS.')
288
272
 
289
273
  connection = connections[database_alias]
290
274
 
@@ -297,9 +281,7 @@ def split_table(model, column_name: str, timeout: float = 0, cursor_itersize: O
297
281
 
298
282
  move_cursor = connection.cursor()
299
283
 
300
- results = cursor_iter(
301
- ids_cursor, connection.features.empty_fetchmany_value, 1, cursor_itersize
302
- )
284
+ results = cursor_iter(ids_cursor, connection.features.empty_fetchmany_value, 1, cursor_itersize)
303
285
  for rows in results:
304
286
  # Если всего одна запись, то используем строку, чтобы не получать ошибку sql-запроса на обновление
305
287
  if len(rows) == 1:
@@ -308,11 +290,13 @@ def split_table(model, column_name: str, timeout: float = 0, cursor_itersize: O
308
290
  pk_column_values = tuple(pkv for (pkv,) in rows)
309
291
  # Этот update выполняется для того, чтобы сработала триггерная
310
292
  # функция partitioning.before_update.
311
- move_cursor.execute((
312
- 'update {table_name} '
313
- 'set {pk_column_name} = {pk_column_name} '
314
- 'where {pk_column_name} in {pk_column_values}'
315
- ).format(**locals()))
293
+ move_cursor.execute(
294
+ (
295
+ 'update {table_name} '
296
+ 'set {pk_column_name} = {pk_column_name} '
297
+ 'where {pk_column_name} in {pk_column_values}'
298
+ ).format(**locals())
299
+ )
316
300
 
317
301
  if timeout:
318
302
  sleep(timeout)
@@ -344,23 +328,22 @@ def clear_table(model, column_name: str, column_value: str, timeout=0, cursor_it
344
328
  connection = connections[database_alias]
345
329
 
346
330
  if settings.DATABASES[database_alias]['DISABLE_SERVER_SIDE_CURSORS']:
347
- raise ImproperlyConfigured(
348
- _MESSAGE_PREFIX + 'clear_table does not '
349
- 'support DISABLE_SERVER_SIDE_CURSORS.'
350
- )
331
+ raise ImproperlyConfigured(f'{_MESSAGE_PREFIX}clear_table does not support DISABLE_SERVER_SIDE_CURSORS.')
351
332
 
352
333
  ids_cursor = connection.chunked_cursor()
353
334
  ids_cursor.execute(
354
335
  # сырой SQL используется для того, чтобы извлечь только записи
355
336
  # из родительской таблицы без записей, уже размещенных в разделах
356
- "select {pk_column_name} from only {table_name} "
357
- "where {column_name} < '{column_value}'".format(**locals())
337
+ "select {pk_column_name} from only {table_name} where {column_name} < '{column_value}'".format(**locals())
358
338
  )
359
339
 
360
340
  delete_cursor = connection.cursor()
361
341
 
362
342
  results = cursor_iter(
363
- ids_cursor, connection.features.empty_fetchmany_value, 1, cursor_itersize,
343
+ ids_cursor,
344
+ connection.features.empty_fetchmany_value,
345
+ 1,
346
+ cursor_itersize,
364
347
  )
365
348
  for rows in results:
366
349
  # Если всего одна запись, то используем строку, чтобы не получать ошибку sql-запроса на удаление
@@ -368,10 +351,9 @@ def clear_table(model, column_name: str, column_value: str, timeout=0, cursor_it
368
351
  pk_column_values = f'{rows[0]}'.replace(',', '')
369
352
  else:
370
353
  pk_column_values = tuple(pkv for (pkv,) in rows)
371
- delete_cursor.execute((
372
- 'delete from {table_name} '
373
- 'where {pk_column_name} in {pk_column_values}'
374
- ).format(**locals()))
354
+ delete_cursor.execute(
355
+ ('delete from {table_name} where {pk_column_name} in {pk_column_values}').format(**locals())
356
+ )
375
357
 
376
358
  if timeout:
377
359
  sleep(timeout)
@@ -391,17 +373,14 @@ def get_model_partitions(model):
391
373
  cursor = connection.cursor()
392
374
 
393
375
  cursor.execute(
394
- "select inhrelid::regclass::text as partition_name "
395
- "from pg_inherits "
376
+ 'select inhrelid::regclass::text as partition_name '
377
+ 'from pg_inherits '
396
378
  "where inhparent = '{}'::regclass::oid "
397
- "order by partition_name"
398
- .format(table_name)
399
- )
400
- return tuple(
401
- partition_name
402
- for (partition_name,) in cursor
379
+ 'order by partition_name'.format(table_name)
403
380
  )
404
381
 
382
+ return tuple(partition_name for (partition_name,) in cursor)
383
+
405
384
 
406
385
  def reset_partition_constraints(model, column_name, partition_name):
407
386
  """Переустанавливает ограничения для указанного раздела.
@@ -422,8 +401,7 @@ def reset_partition_constraints(model, column_name, partition_name):
422
401
  cursor = connection.cursor()
423
402
 
424
403
  cursor.execute(
425
- 'select partitioning.set_partition_constraint(%s, %s, %s, %s)',
426
- (partition_name, column_name, year, month)
404
+ 'select partitioning.set_partition_constraint(%s, %s, %s, %s)', (partition_name, column_name, year, month)
427
405
  )
428
406
 
429
407
  commit_unless_managed(database_alias)
@@ -440,29 +418,27 @@ def drop_partitions_before_date(model, date):
440
418
  if is_model_partitioned(model):
441
419
  database_alias, table_name, _ = _get_model_params(model)
442
420
  all_partitions = get_model_partitions(model)
443
- filter_partition_name = table_name + '_y{}m{}'.format(
444
- date.year, date.strftime('%m')
445
- )
446
- filtered_partitions = filter(
447
- lambda p: (p <= filter_partition_name), all_partitions
448
- )
421
+ filter_partition_name = f'{table_name}_y{date.year}m{date.strftime("%m")}'
422
+ filtered_partitions = filter(lambda p: (p <= filter_partition_name), all_partitions)
449
423
  connection = connections[database_alias]
450
424
  with connection.cursor() as cursor:
451
425
  for partition in filtered_partitions:
452
- cursor.execute(
453
- 'DROP TABLE IF EXISTS {};'.format(partition)
454
- )
426
+ cursor.execute('DROP TABLE IF EXISTS {};'.format(partition))
455
427
 
456
428
 
457
429
  def set_partitioned_function_search_path(database_alias: str, schema_names: Optional[str] = None):
458
- """"Проставляет параметры поиска для существующих функций партицирования.
430
+ """ "Проставляет параметры поиска для существующих функций партицирования.
459
431
 
460
432
  Это необходимо для корректной работы с таблицами к которым обращаются как к внешним через postgres_fdw.
461
433
  """
462
434
  schema_names = schema_names or 'public'
463
- _execute_sql_file(database_alias, 'partitioning_set_search_path.sql', dict(
464
- schema_names=schema_names,
465
- ))
435
+ _execute_sql_file(
436
+ database_alias,
437
+ 'partitioning_set_search_path.sql',
438
+ dict(
439
+ schema_names=schema_names,
440
+ ),
441
+ )
466
442
 
467
443
 
468
444
  class PartitioningObserver(ModelObserverBase):
@@ -492,10 +468,8 @@ class PartitioningObserver(ModelObserverBase):
492
468
 
493
469
  @cached_property
494
470
  def _partitioning_ready(self):
495
- return {
496
- database_alias: is_initialized(database_alias)
497
- for database_alias in connections
498
- }
471
+ """Кэширует информацию о том, проинициализировано ли партиционирование в БД."""
472
+ return {database_alias: is_initialized(database_alias) for database_alias in connections}
499
473
 
500
474
  def _is_observable(self, model):
501
475
  """Возвращает True только для моделей с включенным партиционированием.
@@ -28,8 +28,8 @@ class Command(BaseCommand):
28
28
  для БД, в которой хранится переданная модель, а затем создает необходимые
29
29
  триггеры. Подробнее см. в `educommon.django.db.partitioning.init` и
30
30
  `educommon.django.db.partitioning.set_partitioning_for_model`.
31
-
32
31
  """
32
+
33
33
  help = 'Applies partitioning to the table.' # noqa: A003
34
34
 
35
35
  def add_arguments(self, parser):
@@ -49,18 +49,8 @@ class Command(BaseCommand):
49
49
  type=str,
50
50
  help='Field name. It will be the partition key.',
51
51
  )
52
- parser.add_argument(
53
- '--is_foreign_table',
54
- type=bool,
55
- default=False,
56
- help='Партицирование для внешних таблиц'
57
- )
58
- parser.add_argument(
59
- '--schemas_names',
60
- type=str,
61
- default=None,
62
- help='Cхемы внешних таблиц при партицировании.'
63
- )
52
+ parser.add_argument('--is_foreign_table', type=bool, default=False, help='Партицирование для внешних таблиц')
53
+ parser.add_argument('--schemas_names', type=str, default=None, help='Cхемы внешних таблиц при партицировании.')
64
54
 
65
55
  def handle(self, *args, **options):
66
56
  """Выполнение команды."""
@@ -1,6 +1,3 @@
1
- from django.core.exceptions import (
2
- FieldDoesNotExist,
3
- )
4
1
  from django.core.management.base import (
5
2
  CommandError,
6
3
  )
@@ -24,14 +21,12 @@ class Command(BaseCommand):
24
21
  С помощью данной команды удаляются записи из основной (не секционированной)
25
22
  таблицы, у которых значение в field_name меньше значения из before_value.
26
23
  Подробнее см. в `educommon.django.db.partitioning.clear_table`.
27
-
28
24
  """
29
- help = (
30
- 'Command deletes all the records from database table when '
31
- 'field_name < before_value.'
32
- )
25
+
26
+ help = 'Command deletes all the records from database table when field_name < before_value.'
33
27
 
34
28
  def add_arguments(self, parser):
29
+ """Добавляет аргументы командной строки для команды очистки таблицы."""
35
30
  parser.add_argument(
36
31
  '--app_label',
37
32
  type=str,
@@ -53,19 +48,28 @@ class Command(BaseCommand):
53
48
  help='Deleting rows before this value.',
54
49
  )
55
50
  parser.add_argument(
56
- '--timeout', action='store', dest='timeout',
57
- default=.0, type=float,
58
- help=('Timeout (in seconds) between the data removes iterations. '
59
- 'It used to reduce the database load.')
51
+ '--timeout',
52
+ action='store',
53
+ dest='timeout',
54
+ default=0.0,
55
+ type=float,
56
+ help=('Timeout (in seconds) between the data removes iterations. It used to reduce the database load.'),
60
57
  )
61
58
  parser.add_argument(
62
- '--cursor_itersize', action='store', dest='cursor_itersize',
59
+ '--cursor_itersize',
60
+ action='store',
61
+ dest='cursor_itersize',
63
62
  type=int,
64
63
  default=None,
65
- help='Количество строк загруженных за раз при загрузке строк при работе команды.'
64
+ help='Количество строк загруженных за раз при загрузке строк при работе команды.',
66
65
  )
67
66
 
68
67
  def handle(self, *args, **options):
68
+ """Основная логика команды.
69
+
70
+ Выполняет проверку модели и поля, затем вызывает функцию очистки
71
+ записей по условию field_name < before_value.
72
+ """
69
73
  app_label = options['app_label']
70
74
  model_name = options['model_name']
71
75
  field_name = options['field_name']
@@ -1,6 +1,3 @@
1
- from django.core.exceptions import (
2
- FieldDoesNotExist,
3
- )
4
1
  from django.core.management.base import (
5
2
  CommandError,
6
3
  )
@@ -29,12 +26,11 @@ class Command(BaseCommand):
29
26
  Подробнее см. в `educommon.django.db.partitioning.split_table`.
30
27
 
31
28
  """
32
- help = (
33
- 'Command moves all the records from database table to partitions of '
34
- 'this table.'
35
- )
29
+
30
+ help = 'Command moves all the records from database table to partitions of this table.'
36
31
 
37
32
  def add_arguments(self, parser):
33
+ """Добавляет аргументы командной строки для команды переноса данных в разделы."""
38
34
  parser.add_argument(
39
35
  '--app_label',
40
36
  type=str,
@@ -51,19 +47,28 @@ class Command(BaseCommand):
51
47
  help='Field name. It will be the partition key.',
52
48
  )
53
49
  parser.add_argument(
54
- '--timeout', action='store', dest='timeout',
55
- default=.0, type=float,
56
- help=('Timeout (in seconds) between the data transfer iterations. '
57
- 'It used to reduce the database load.')
50
+ '--timeout',
51
+ action='store',
52
+ dest='timeout',
53
+ default=0.0,
54
+ type=float,
55
+ help=('Timeout (in seconds) between the data transfer iterations. It used to reduce the database load.'),
58
56
  )
59
57
  parser.add_argument(
60
- '--cursor_itersize', action='store', dest='cursor_itersize',
58
+ '--cursor_itersize',
59
+ action='store',
60
+ dest='cursor_itersize',
61
61
  type=int,
62
62
  default=None,
63
- help='Количество строк загруженных за раз при загрузке строк при работе команды.'
63
+ help='Количество строк загруженных за раз при загрузке строк при работе команды.',
64
64
  )
65
65
 
66
66
  def handle(self, *args, **options):
67
+ """Основная логика команды.
68
+
69
+ Проверяет наличие модели и заданного поля, затем запускает
70
+ процесс переноса данных в секции таблицы.
71
+ """
67
72
  app_label = options['app_label']
68
73
  model_name = options['model_name']
69
74
  field_name = options['field_name']
@@ -1,4 +1,5 @@
1
1
  """Роутеры для приложений Django."""
2
+
2
3
  from abc import (
3
4
  ABCMeta,
4
5
  )
@@ -43,19 +44,14 @@ class ServiceDbRouterBase(DatabaseRouterBase, metaclass=ABCMeta):
43
44
  aliases = list(settings.DATABASES)
44
45
  if len(aliases) != 2:
45
46
  # Роутер поддерживает только конфигурации с двумя БД.
46
- raise ImproperlyConfigured(
47
- 'Database router support only two databases'
48
- )
47
+ raise ImproperlyConfigured('Database router support only two databases')
49
48
 
50
49
  self.default_db_alias = DEFAULT_DB_ALIAS
51
50
 
52
51
  alias_index = 1 if aliases[0] == DEFAULT_DB_ALIAS else 0
53
52
  self.service_db_alias = aliases[alias_index]
54
53
 
55
- self.service_db_model_names = {
56
- model_name.lower()
57
- for model_name in self.service_db_model_names
58
- }
54
+ self.service_db_model_names = {model_name.lower() for model_name in self.service_db_model_names}
59
55
 
60
56
  def _db_for_model(self, model, **hints):
61
57
  """Возвращает имя БД для чтения/записи данных из модели *model*."""
@@ -71,6 +67,7 @@ class ServiceDbRouterBase(DatabaseRouterBase, metaclass=ABCMeta):
71
67
  db_for_write = _db_for_model
72
68
 
73
69
  def _allow(self, db, app_label, model_name):
70
+ """Определяет, разрешён ли доступ к модели в указанной БД."""
74
71
  assert db in (self.default_db_alias, self.service_db_alias)
75
72
 
76
73
  if app_label == self.app_name:
@@ -78,12 +75,6 @@ class ServiceDbRouterBase(DatabaseRouterBase, metaclass=ABCMeta):
78
75
  return True
79
76
  else:
80
77
  model_name = model_name.lower()
81
- return (
82
- (
83
- db == self.default_db_alias and
84
- model_name not in self.service_db_model_names
85
- ) or (
86
- db == self.service_db_alias and
87
- model_name in self.service_db_model_names
88
- )
78
+ return (db == self.default_db_alias and model_name not in self.service_db_model_names) or (
79
+ db == self.service_db_alias and model_name in self.service_db_model_names
89
80
  )
@@ -56,7 +56,7 @@ class BaseBeforeMigrateHandler:
56
56
  def __call__(self, sender, *args, **kwargs):
57
57
  migrating_database = self._get_migrating_database(sender)
58
58
  if self._is_accessing_non_migrating_databases(migrating_database):
59
- logger.warning('Предотвращена попытка доступа к базе данных, по которой не производится миграция')
59
+ logger.debug('Предотвращена попытка доступа к базе данных, по которой не производится миграция')
60
60
  return
61
61
 
62
62
  return self.handler(sender, *args, **kwargs)
@@ -104,7 +104,7 @@ class BaseAfterMigrateHandler:
104
104
  def __call__(self, sender, *args, **kwargs):
105
105
  migrating_database = self._get_migrating_database(sender)
106
106
  if self._is_accessing_non_migrating_databases(migrating_database):
107
- logger.warning('Предотвращена попытка доступа к базе данных, по которой не производится миграция')
107
+ logger.debug('Предотвращена попытка доступа к базе данных, по которой не производится миграция')
108
108
  return
109
109
 
110
110
  return self.handler(sender, *args, **kwargs)
@@ -120,6 +120,7 @@ class BeforeHandleMigrateSignal(Signal):
120
120
  weak: bool = True,
121
121
  dispatch_uid: Optional[str] = None,
122
122
  ) -> None:
123
+ """Регистрирует обработчик, если он является допустимым типом."""
123
124
  if not isinstance(receiver, self._handler_base_class):
124
125
  logger.warning(
125
126
  f'Обработчик сигнала before_handle_migrate_signal {receiver} не зарегистрирован, поскольку '
@@ -139,6 +140,7 @@ class AfterHandleMigrateSignal(Signal):
139
140
  weak: bool = True,
140
141
  dispatch_uid: Optional[str] = None,
141
142
  ) -> None:
143
+ """Регистрирует обработчик, если он является допустимым типом."""
142
144
  if not isinstance(receiver, self._handler_base_class):
143
145
  logger.warning(
144
146
  f'Обработчик сигнала after_handle_migrate_signal {receiver} не зарегистрирован, поскольку '
@@ -68,11 +68,10 @@ def model_modifier_metaclass(meta_base=ModelBase, **params):
68
68
  class Meta:
69
69
  verbose_name = 'Образец справочника'
70
70
  """
71
+
71
72
  class ModifiedModelBase(meta_base):
72
73
  def __new__(cls, name, bases, attrs):
73
- model = super(ModifiedModelBase, cls).__new__(
74
- cls, name, bases, attrs
75
- )
74
+ model = super(ModifiedModelBase, cls).__new__(cls, name, bases, attrs)
76
75
 
77
76
  # Переопределения имен атрибутов (см. Field.deconstruct).
78
77
  attr_overrides = {
@@ -167,39 +166,31 @@ class LazyModel:
167
166
  def model(self):
168
167
  return self._model.get_model()
169
168
 
169
+
170
170
  mp1 = ModelProcessor('person.Person')
171
171
  mp2 = ModelProcessor(('person', 'Person'))
172
172
  mp3 = ModelProcessor(Person)
173
173
  """
174
174
 
175
175
  def __init__(self, model):
176
- if (
177
- isinstance(model, str) and
178
- '.' in model and model.index('.') == model.rindex('.')
179
- ):
176
+ if isinstance(model, str) and '.' in model and model.index('.') == model.rindex('.'):
180
177
  self.app_label, self.model_name = model.split('.')
181
178
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
182
- elif (
183
- isinstance(model, tuple) and
184
- len(model) == 2 and
185
- all(isinstance(s, str) for s in model)
186
- ):
179
+ elif isinstance(model, tuple) and len(model) == 2 and all(isinstance(s, str) for s in model):
187
180
  self.app_label, self.model_name = model
188
181
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
189
182
  elif (
190
- isclass(model) and
191
- hasattr(model, '_meta') and
192
- hasattr(model._meta, 'app_label') and
193
- hasattr(model._meta, 'model_name')
183
+ isclass(model)
184
+ and hasattr(model, '_meta')
185
+ and hasattr(model._meta, 'app_label')
186
+ and hasattr(model._meta, 'model_name')
194
187
  ):
195
188
  self._model = model
196
189
  self.app_label = model._meta.app_label
197
190
  self.model_name = model.__name__
198
191
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
199
192
  else:
200
- raise ValueError(
201
- '"model" argument has invalid value: ' + repr(model)
202
- )
193
+ raise ValueError(f'"model" argument has invalid value: {repr(model)}')
203
194
 
204
195
  def get_model(self):
205
196
  """Возвращает класс модели, заданной при инициализации."""
@@ -214,6 +205,7 @@ class SmartExact(Func):
214
205
 
215
206
  template = "TRANSLATE(%(expressions)s, 'ёЁ ', 'еЕ')"
216
207
 
208
+
217
209
  class SmartExactLookup(Lookup):
218
210
  """Удаляет пробелы из строки и заменяет буквы ё на е."""
219
211
 
@@ -228,11 +220,13 @@ class SmartExactLookup(Lookup):
228
220
 
229
221
  return sql % (lhs, rhs), lhs_params + rhs_params
230
222
 
223
+
231
224
  class SmartIExact(Func):
232
225
  """Переводит в верхний регистр, удаляет пробелы, заменяет Ё на Е."""
233
226
 
234
227
  template = "TRANSLATE(UPPER(%(expressions)s), 'Ё ', 'Е')"
235
228
 
229
+
236
230
  class SmartIExactLookup(Lookup):
237
231
  """Переводит в верхний регистр, удаляет пробелы, заменяет Ё на Е."""
238
232
 
@@ -247,6 +241,7 @@ class SmartIExactLookup(Lookup):
247
241
 
248
242
  return sql % (lhs, rhs), lhs_params + rhs_params
249
243
 
244
+
250
245
  class SmartIContainsLookup(Lookup):
251
246
  """Регистронезависимый поиск.
252
247
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  Документация: http://djbook.ru/rel1.4/ref/validators.html
4
4
  """
5
+
5
6
  from abc import (
6
7
  ABCMeta,
7
8
  abstractmethod,