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
@@ -1,4 +1,5 @@
1
1
  """Расширение поведения экшнов."""
2
+
2
3
  from m3.actions import (
3
4
  OperationResult,
4
5
  )
@@ -18,11 +19,10 @@ from educommon.m3.extensions.ui import (
18
19
 
19
20
 
20
21
  class DeclareContextListener:
21
- """
22
- Предок листенеров.
22
+ """Предок листенеров.
23
23
 
24
24
  Предоставляет возможность задекларировать дополнительный контекст
25
- выполнения экшна
25
+ выполнения экшна.
26
26
  """
27
27
 
28
28
  def before(self, request, context):
@@ -64,15 +64,14 @@ class DeclareContextListener:
64
64
  # если контекст неправильный, то возвращаем
65
65
  # фейльный результат операции
66
66
  return OperationResult.by_message(
67
- 'Не удалось выполнить операцию. '
68
- 'Не задан обязательный<br>параметр: ' + e.reason
67
+ f'Не удалось выполнить операцию. Не задан обязательный<br>параметр: {e.reason}'
69
68
  )
69
+
70
70
  return context
71
71
 
72
72
 
73
73
  class BaseEditWinListener(DeclareContextListener):
74
- """
75
- Базовый листенер для расширения окна редактирования доп.полями и данными.
74
+ """Базовый листенер для расширения окна редактирования доп.полями и данными.
76
75
 
77
76
  Окно редактирования связано с определенной моделью, она - расширяемая и ее
78
77
  данные требуется дополнить с помощью доп.полей.
@@ -85,38 +84,32 @@ class BaseEditWinListener(DeclareContextListener):
85
84
  parent_model_field = None
86
85
 
87
86
  def _get_id(self, context):
88
- """
89
- Получение из контекста параметра, определяющего id расширяемой модели.
87
+ """Получение из контекста параметра, определяющего id расширяемой модели.
90
88
 
91
89
  :rype: int
92
90
  """
93
91
  raise NotImplementedError()
94
92
 
95
93
  def _get_instance(self, row_id):
96
- """
97
- Получение модели, которая расширяет основную ``ui_extender_cls.model``.
94
+ """Получение модели, которая расширяет основную ``ui_extender_cls.model``.
98
95
 
99
96
  :param row_id: идентификатор расширяемой модели
100
97
  :rtype: object
101
98
  """
102
99
  try:
103
- instance = self.ui_extender_cls.model.objects.get(**{
104
- self.parent_model_field: row_id
105
- })
100
+ instance = self.ui_extender_cls.model.objects.get(**{self.parent_model_field: row_id})
106
101
  except self.ui_extender_cls.model.DoesNotExist:
107
102
  # расширяющая модель еще только создается
108
103
  return
104
+
109
105
  return instance
110
106
 
111
107
  def _get_params(self, instance, context):
112
108
  """Дополнение параметров."""
113
- return {
114
- 'instance': instance
115
- }
109
+ return {'instance': instance}
116
110
 
117
111
  def after(self, request, context, response):
118
- """
119
- Получение истанса модели, которая расширяет и биндинг ее в окно.
112
+ """Получение истанса модели, которая расширяет и биндинг ее в окно.
120
113
 
121
114
  :param request: Request
122
115
  :type request: django.http.HttpRequest
@@ -126,8 +119,7 @@ class BaseEditWinListener(DeclareContextListener):
126
119
  if not isinstance(response, ExtUIScriptResult):
127
120
  # не окно
128
121
  return
129
- assert self.parent_model_field, (
130
- 'No parent model field defined in listener!')
122
+ assert self.parent_model_field, 'No parent model field defined in listener!'
131
123
  assert issubclass(self.ui_extender_cls, BaseEditWinExtender)
132
124
 
133
125
  # расширение интерфейса
@@ -151,8 +143,7 @@ class BaseEditWinListener(DeclareContextListener):
151
143
 
152
144
 
153
145
  class BaseSaveListener(DeclareContextListener):
154
- """
155
- Базовый класс листенеров сохранения доп.данных.
146
+ """Базовый класс листенеров сохранения доп.данных.
156
147
 
157
148
  Обращение к данному листенеру из экшна делается через
158
149
 
@@ -170,8 +161,7 @@ class BaseSaveListener(DeclareContextListener):
170
161
  parent_model_field = None
171
162
 
172
163
  def _get_instance(self, parent_model_instance, context):
173
- """
174
- Получение инстанса расширяющей модели.
164
+ """Получение инстанса расширяющей модели.
175
165
 
176
166
  Метод вынесен для возможности инстанцирования дополнительных
177
167
  зависимых моделей (относительно расширяющей модели ``instance``)
@@ -182,26 +172,21 @@ class BaseSaveListener(DeclareContextListener):
182
172
  :returns: инстанс расширяющей модели
183
173
  """
184
174
  try:
185
- instance = self.ui_extender_cls.model.objects.get(**{
186
- self.parent_model_field: parent_model_instance
187
- })
175
+ instance = self.ui_extender_cls.model.objects.get(**{self.parent_model_field: parent_model_instance})
188
176
  except self.ui_extender_cls.model.DoesNotExist:
189
- instance = self.ui_extender_cls.model(**{
190
- self.parent_model_field: parent_model_instance
191
- })
177
+ instance = self.ui_extender_cls.model(**{self.parent_model_field: parent_model_instance})
178
+
192
179
  return instance
193
180
 
194
181
  def post_save(self, arguments):
195
- """
196
- Точка входа для расширения из экшна self.handle('post_save', *).
182
+ """Точка входа для расширения из экшна self.handle('post_save', *).
197
183
 
198
184
  :param parent_model_instance: инстанс расширяемой модели
199
185
  :param context: Context
200
186
  :type context: m3.actions.context.DeclarativeActionContext
201
187
  """
202
188
  parent_model_instance, context = arguments
203
- assert self.parent_model_field, (
204
- 'No parent model field defined in listener!')
189
+ assert self.parent_model_field, 'No parent model field defined in listener!'
205
190
  assert issubclass(self.ui_extender_cls, BaseEditWinExtender)
206
191
 
207
192
  instance = self._get_instance(parent_model_instance, context)
@@ -212,10 +197,9 @@ class BaseSaveListener(DeclareContextListener):
212
197
  return parent_model_instance, context
213
198
 
214
199
  def _save_instance(self, instance):
215
- """
216
- Сохранение расширяющей модели.
200
+ """Сохранение расширяющей модели.
217
201
 
218
- Вынесено для возможности сохранения связанных/зависимых сущностей
202
+ Вынесено для возможности сохранения связанных/зависимых сущностей.
219
203
  """
220
204
  instance.full_clean()
221
205
  instance.save()
@@ -54,13 +54,9 @@ class DeleteCheck:
54
54
 
55
55
  def _get_message(self, objects):
56
56
  """Возвращает сообщение в правильном склонении."""
57
- return (
58
- 'объекта' if len(objects) == 1 else 'объектов',
59
- 'него' if len(objects) == 1 else 'них'
60
- )
57
+ return ('объекта' if len(objects) == 1 else 'объектов', 'него' if len(objects) == 1 else 'них')
61
58
 
62
- def _setup_window(self, objects, related_objects, blocked=True,
63
- grid_id=None):
59
+ def _setup_window(self, objects, related_objects, blocked=True, grid_id=None):
64
60
  """Возвращает окно с сообщением о связанных объектах.
65
61
 
66
62
  :param objects: удаляемые объекты
@@ -70,28 +66,26 @@ class DeleteCheck:
70
66
  :return: CancelConfirmWindow
71
67
  """
72
68
  if blocked:
73
- title = (
74
- 'Удаление {} невозможно, т.к. на {} есть ссылки:'
75
- .format(*self._get_message(objects))
76
- )
69
+ title = 'Удаление {} невозможно, т.к. на {} есть ссылки:'.format(*self._get_message(objects))
77
70
  else:
78
- title = (
79
- 'При удалении {} будут удалены следующие связи:'
80
- .format(self._get_message(objects)[0])
81
- )
71
+ title = 'При удалении {} будут удалены следующие связи:'.format(self._get_message(objects)[0])
82
72
 
83
73
  win = CancelConfirmWindow()
84
- win.set_params(dict(
85
- title=title,
86
- objects=objects,
87
- related_objects=related_objects,
88
- blocked=blocked,
89
- grid_id=grid_id,
90
- ))
74
+ win.set_params(
75
+ dict(
76
+ title=title,
77
+ objects=objects,
78
+ related_objects=related_objects,
79
+ blocked=blocked,
80
+ grid_id=grid_id,
81
+ )
82
+ )
91
83
  win.pack_action_url = self.action.get_absolute_url()
84
+
92
85
  return win
93
86
 
94
87
  def _get_objects(self, context):
88
+ """Получает удаляемые объекты из контекста."""
95
89
  model = self.action.parent.model
96
90
  if issubclass(model, ModelProxy):
97
91
  model = model.model
@@ -101,6 +95,11 @@ class DeleteCheck:
101
95
  return model.objects.filter(pk__in=object_ids)
102
96
 
103
97
  def before(self, request, context):
98
+ """Обрабатывает удаление с проверкой на наличие связанных объектов.
99
+
100
+ Если удаление невозможно или требует подтверждения, формирует окно
101
+ с предупреждением или подтверждением.
102
+ """
104
103
  if not isinstance(self.action, ObjectDeleteAction):
105
104
  return
106
105
 
@@ -111,11 +110,9 @@ class DeleteCheck:
111
110
  return
112
111
 
113
112
  if hasattr(self.action.parent, 'skip_field'):
113
+
114
114
  def skip_function(field):
115
- return (
116
- self.action.parent.skip_field(field) or
117
- CascadeDeleteMixin.skip_field(field)
118
- )
115
+ return self.action.parent.skip_field(field) or CascadeDeleteMixin.skip_field(field)
119
116
  else:
120
117
  skip_function = CascadeDeleteMixin.skip_field
121
118
 
@@ -128,19 +125,15 @@ class DeleteCheck:
128
125
 
129
126
  # Проверка блокирующих удаление связей
130
127
  for obj in objects:
131
- for related_obj in get_related_instances_proxy(
132
- obj, skip_function
133
- ):
134
- key = '.'.join((
135
- related_obj._meta.app_label,
136
- related_obj._meta.object_name,
137
- ))
128
+ for related_obj in get_related_instances_proxy(obj, skip_function):
129
+ key = '.'.join(
130
+ (
131
+ related_obj._meta.app_label,
132
+ related_obj._meta.object_name,
133
+ )
134
+ )
138
135
  related_objects[key].append(related_obj)
139
- self.collect_implicit.send(
140
- self.action.parent.model,
141
- objects_to_delete=objects,
142
- related_objects=related_objects
143
- )
136
+ self.collect_implicit.send(self.action.parent.model, objects_to_delete=objects, related_objects=related_objects)
144
137
 
145
138
  if related_objects:
146
139
  win = self._setup_window(objects, related_objects)
@@ -148,10 +141,7 @@ class DeleteCheck:
148
141
  # Проверка не блокирующих удаление связей
149
142
  for obj in objects:
150
143
  related_objects.update(get_non_block_relations(obj))
151
- win = self._setup_window(
152
- objects, related_objects, blocked=False,
153
- grid_id=getattr(context, 'grid_id', None)
154
- )
144
+ win = self._setup_window(objects, related_objects, blocked=False, grid_id=getattr(context, 'grid_id', None))
155
145
 
156
146
  if not related_objects:
157
147
  return
@@ -16,7 +16,6 @@ from educommon.m3.extensions.listeners.delete_check.utils import (
16
16
 
17
17
 
18
18
  class CascadeDeletePackMixin:
19
-
20
19
  """Mixin определяющий исключаемые поля модели при проверке DeleteCheck.
21
20
 
22
21
  Поскольку DeleteCheck срабатывает до вызова delete_row возникают ситуации,
@@ -26,21 +25,18 @@ class CascadeDeletePackMixin:
26
25
 
27
26
  .. code-block:: python
28
27
 
29
- cascade_delete_objects = (
30
- ( 'parent', 'parentportfolio', 'person'),
31
- )
28
+ cascade_delete_objects = (('parent', 'parentportfolio', 'person'),)
32
29
  """
30
+
33
31
  # содержит кортежи формата
34
32
  # (имя приложения, имя модели приложения, имя поля)
35
33
  cascade_delete_objects = ()
36
34
 
37
35
  def _cascade_delete(self, obj_id, request, context):
38
- for app_label, model_name, field_name in (
39
- self.__class__.cascade_delete_objects):
36
+ """Удаляет связанные объекты, указанные в cascade_delete_objects."""
37
+ for app_label, model_name, field_name in self.__class__.cascade_delete_objects:
40
38
  model = get_model(app_label, model_name)
41
- lookup = {
42
- field_name + '_id': obj_id
43
- }
39
+ lookup = {f'{field_name}_id': obj_id}
44
40
  for obj in model.objects.filter(**lookup):
45
41
  if hasattr(obj, 'safe_delete') and callable(obj.safe_delete):
46
42
  obj.safe_delete()
@@ -49,19 +45,20 @@ class CascadeDeletePackMixin:
49
45
 
50
46
  @classmethod
51
47
  def skip_field(cls, field):
48
+ """Определяет, следует ли исключить поле из проверки DeleteCheck."""
52
49
  if cls.cascade_delete_objects:
53
- for app_label, model_name, field_name in (
54
- cls.cascade_delete_objects):
50
+ for app_label, model_name, field_name in cls.cascade_delete_objects:
55
51
  model = get_model(app_label, model_name)
56
52
  if field.name == field_name and field.model == model:
57
53
  return True
54
+
58
55
  return False
59
56
 
60
57
  @atomic
61
58
  def delete_row(self, obj_id, request, context):
62
59
  self._cascade_delete(obj_id, request, context)
63
- return super(CascadeDeletePackMixin, self).delete_row(
64
- obj_id, request, context)
60
+
61
+ return super().delete_row(obj_id, request, context)
65
62
 
66
63
 
67
64
  class CascadeDeleteMixin:
@@ -81,19 +78,19 @@ class CascadeDeleteMixin:
81
78
  :caption: Пример использования
82
79
 
83
80
  class ModelA(CascadeDeleteMixin, models.Model):
84
-
85
81
  field = models.TextField()
86
82
 
87
- class ModelB(models.Model):
88
83
 
84
+ class ModelB(models.Model):
89
85
  link = models.ForeignKey(ModelA)
90
86
  cascade_delete_for = (link,)
91
87
 
92
- class ModelC(models.Model):
93
88
 
89
+ class ModelC(models.Model):
94
90
  link = models.ForeignKey(ModelA, on_delete=models.SET_NULL)
95
91
  cascade_delete_for = (link,)
96
92
 
93
+
97
94
  def some_func(obj):
98
95
  obj.link = ModelA()
99
96
  obj.link.save()
@@ -101,8 +98,8 @@ class CascadeDeleteMixin:
101
98
  # В случае, если ModelA и ModelD наследуются от ModelValidationMixin
102
99
  # то вместо вызова `save` должен быть метод `clean_and_save`
103
100
 
104
- class ModelD(models.Model):
105
101
 
102
+ class ModelD(models.Model):
106
103
  link = models.ForeignKey(ModelA, on_delete=models.SET_NULL)
107
104
  cascade_delete_for = {link: {'on_delete': some_func}}
108
105
 
@@ -128,11 +125,10 @@ class CascadeDeleteMixin:
128
125
  :caption: Пример использования сингала pre_cascade
129
126
 
130
127
  class ModelA(CascadeDeleteMixin, models.Model):
131
-
132
128
  field = models.TextField()
133
129
 
134
- class ModelB(ReadOnlyMixin, models.Model):
135
130
 
131
+ class ModelB(ReadOnlyMixin, models.Model):
136
132
  # Объекты изменяются только при изменениях, вызванных в
137
133
  # обработчиках сигналов на моделях-источниках
138
134
  _changed_from_signal = False
@@ -144,6 +140,7 @@ class CascadeDeleteMixin:
144
140
 
145
141
  link = models.ForeignKey(ModelA)
146
142
 
143
+
147
144
  @receiver(pre_cascade, sender=ModelB)
148
145
  def pre_cascade_delete(instance, **_):
149
146
  # Каскадное удаление по умолчанию удаление по сигналу.
@@ -152,9 +149,11 @@ class CascadeDeleteMixin:
152
149
 
153
150
  @staticmethod
154
151
  def skip_field(field):
152
+ """Проверяет, включено ли поле в каскадное удаление."""
155
153
  cascade_delete_for = getattr(field.model, 'cascade_delete_for', ())
156
154
  if isinstance(cascade_delete_for, dict):
157
155
  cascade_delete_for = tuple(cascade_delete_for.keys())
156
+
158
157
  return field in cascade_delete_for
159
158
 
160
159
  @atomic
@@ -163,13 +162,9 @@ class CascadeDeleteMixin:
163
162
  for obj, handler in get_related_instances_and_handlers(
164
163
  self, skip_func=lambda field: not self.skip_field(field)
165
164
  ):
166
- params = dict(
167
- sender=self._meta.model,
168
- instance=obj,
169
- initiator=self
170
- )
165
+ params = dict(sender=self._meta.model, instance=obj, initiator=self)
171
166
  pre_cascade.send(**params)
172
167
  handler(obj)
173
168
  post_cascade.send(**params)
174
169
 
175
- return super(CascadeDeleteMixin, self).safe_delete()
170
+ return super().safe_delete()
@@ -6,5 +6,5 @@ from django.db.models.signals import (
6
6
  # Сигналы перед и после каскадного удаления объекта (CascadeDeleteMixin)
7
7
  # - instance - удаляемый объект
8
8
  # - initiator - объект, который стал инициатором удаления instance
9
- pre_cascade = ModelSignal(providing_args=["instance", "initiator"])
10
- post_cascade = ModelSignal(providing_args=["instance", "initiator"])
9
+ pre_cascade = ModelSignal(providing_args=['instance', 'initiator'])
10
+ post_cascade = ModelSignal(providing_args=['instance', 'initiator'])
@@ -21,7 +21,6 @@ from educommon.utils.ui import (
21
21
 
22
22
 
23
23
  class RelatedObjectsWindow(BaseWindow):
24
-
25
24
  """Окно с информацией о зависимых объектах.
26
25
 
27
26
  Используется для отображения информации об объектах, зависящих от
@@ -40,7 +39,7 @@ class RelatedObjectsWindow(BaseWindow):
40
39
  return registries['related_objects']
41
40
 
42
41
  def _init_components(self):
43
- super(RelatedObjectsWindow, self)._init_components()
42
+ super()._init_components()
44
43
 
45
44
  self.panel = ExtPanel(
46
45
  auto_scroll=True,
@@ -49,7 +48,8 @@ class RelatedObjectsWindow(BaseWindow):
49
48
  self.items.append(self.panel)
50
49
 
51
50
  def _do_layout(self):
52
- super(RelatedObjectsWindow, self)._do_layout()
51
+ super()._do_layout()
52
+
53
53
  self.width = 800
54
54
  self.height = 600
55
55
 
@@ -57,8 +57,7 @@ class RelatedObjectsWindow(BaseWindow):
57
57
 
58
58
  def _get_html(self, title, objects, related_objects):
59
59
  views = tuple(
60
- self.model_view_registry.get(model).get_view(objects)
61
- for model, objects in related_objects.items()
60
+ self.model_view_registry.get(model).get_view(objects) for model, objects in related_objects.items()
62
61
  )
63
62
 
64
63
  return render_to_string(
@@ -70,7 +69,7 @@ class RelatedObjectsWindow(BaseWindow):
70
69
  )
71
70
 
72
71
  def set_params(self, params):
73
- super(RelatedObjectsWindow, self).set_params(params)
72
+ super().set_params(params)
74
73
 
75
74
  self.title = 'Внимание!'
76
75
 
@@ -82,7 +81,6 @@ class RelatedObjectsWindow(BaseWindow):
82
81
 
83
82
 
84
83
  class CancelConfirmWindow(RelatedObjectsWindow):
85
-
86
84
  """Окно подтверждения удаления объекта и связанных с ним объектов.
87
85
 
88
86
  Содержит информацию об объектах, которые будут удалены при удалении
@@ -90,7 +88,7 @@ class CancelConfirmWindow(RelatedObjectsWindow):
90
88
  """
91
89
 
92
90
  def _init_components(self):
93
- super(CancelConfirmWindow, self)._init_components()
91
+ super()._init_components()
94
92
 
95
93
  self.button__confirm = ext.ExtButton(
96
94
  text='Удалить',
@@ -102,15 +100,18 @@ class CancelConfirmWindow(RelatedObjectsWindow):
102
100
  )
103
101
 
104
102
  def _do_layout(self):
105
- super(CancelConfirmWindow, self)._do_layout()
103
+ super()._do_layout()
106
104
 
107
- self.buttons.extend((
108
- self.button__confirm,
109
- self.button__cancel,
110
- ))
105
+ self.buttons.extend(
106
+ (
107
+ self.button__confirm,
108
+ self.button__cancel,
109
+ )
110
+ )
111
111
 
112
112
  def set_params(self, params):
113
- super(CancelConfirmWindow, self).set_params(params)
113
+ super().set_params(params)
114
+
114
115
  self.template_globals = local_template('cancel-confirm-window.js')
115
116
 
116
117
  self.blocked = params.get('blocked', True)
@@ -24,9 +24,7 @@ def get_related_instances_and_handlers(obj, skip_func=None):
24
24
  related_model = field.remote_field.through
25
25
  field_name = field.m2m_reverse_name()
26
26
 
27
- related_objects = related_model.objects.filter(
28
- **{field_name: obj}
29
- )
27
+ related_objects = related_model.objects.filter(**{field_name: obj})
30
28
 
31
29
  on_delete_function = get_on_relation_delete_function(field)
32
30
  for related_obj in related_objects.iterator():
@@ -41,24 +39,19 @@ def get_on_relation_delete_function(field):
41
39
  возвращается функция, соответсвующая поведению, указанному в атрибуте
42
40
  `on_delete` поля.
43
41
  """
44
- return (
45
- get_custom_on_delete_function(field) or
46
- get_field_on_delete_function(field)
47
- )
42
+ return get_custom_on_delete_function(field) or get_field_on_delete_function(field)
48
43
 
49
44
 
50
45
  def get_custom_on_delete_function(field):
51
46
  """Возвращает функцию, указанную в cascade_delete_for для поля."""
52
- if (
53
- not hasattr(field.model, 'cascade_delete_for') or
54
- not isinstance(field.model.cascade_delete_for, dict)
55
- ):
47
+ if not hasattr(field.model, 'cascade_delete_for') or not isinstance(field.model.cascade_delete_for, dict):
56
48
  return None
57
49
 
58
50
  on_delete_params = {}
59
51
  for _field, _field_params in field.model.cascade_delete_for.items():
60
52
  if _field == field:
61
53
  on_delete_params = _field_params
54
+
62
55
  return on_delete_params.get('on_delete')
63
56
 
64
57
 
@@ -71,18 +64,23 @@ def get_field_on_delete_function(field):
71
64
  return safe_delete
72
65
 
73
66
  if on_delete_function == SET_NULL:
67
+
74
68
  def set_null(obj):
75
69
  setattr(obj, field.name, None)
76
70
  getattr(obj, 'clean_and_save', obj.save)()
71
+
77
72
  return set_null
78
73
 
79
74
  if on_delete_function == SET_DEFAULT:
75
+
80
76
  def set_default(obj):
81
77
  setattr(obj, field.name, field.default)
82
78
  getattr(obj, 'clean_and_save', obj.save)()
79
+
83
80
  return set_default
84
81
 
85
82
  # По-умолчанию остается поведение с безопасным удалением
86
83
  if hasattr(model, 'safe_delete') and callable(model.safe_delete):
87
84
  return model.safe_delete
85
+
88
86
  return safe_delete