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.
- educommon/__init__.py +0 -1
- educommon/about/ui/actions.py +16 -30
- educommon/about/ui/ui.py +3 -12
- educommon/about/utils.py +6 -5
- educommon/async_task/__init__.py +0 -1
- educommon/async_task/actions.py +18 -13
- educommon/async_task/apps.py +4 -0
- educommon/async_task/locker.py +2 -5
- educommon/async_task/migrations/0001_initial.py +55 -9
- educommon/async_task/migrations/0002_task_type_and_status_data.py +94 -89
- educommon/async_task/migrations/0003_alter_runningtask_options.py +0 -1
- educommon/async_task/models.py +9 -6
- educommon/async_task/tasks.py +11 -7
- educommon/async_task/ui.py +16 -35
- educommon/async_tasks/__init__.py +0 -1
- educommon/async_tasks/apps.py +4 -0
- educommon/async_tasks/locks.py +11 -21
- educommon/async_tasks/migrations/0001_initial.py +68 -8
- educommon/async_tasks/migrations/0002_load_initial_data.py +0 -1
- educommon/async_tasks/models.py +9 -29
- educommon/async_tasks/tasks.py +25 -54
- educommon/audit_log/__init__.py +1 -0
- educommon/audit_log/actions.py +27 -36
- educommon/audit_log/app_meta.py +7 -4
- educommon/audit_log/apps.py +44 -29
- educommon/audit_log/constants.py +7 -4
- educommon/audit_log/error_log/actions.py +1 -3
- educommon/audit_log/helpers.py +2 -4
- educommon/audit_log/management/commands/reinstall_audit_log.py +11 -7
- educommon/audit_log/migrations/0001_initial.py +91 -16
- educommon/audit_log/migrations/0002_install_audit_log.py +13 -13
- educommon/audit_log/migrations/0003_logproxy.py +1 -3
- educommon/audit_log/migrations/0004_reinstall_audit_log.py +1 -4
- educommon/audit_log/migrations/0005_postgresql_error.py +4 -2
- educommon/audit_log/migrations/0006_auto_20200806_1707.py +3 -4
- educommon/audit_log/migrations/0007_create_selective_tables_function.py +8 -5
- educommon/audit_log/migrations/0008_table_logged.py +0 -1
- educommon/audit_log/migrations/0009_reinstall_audit_log.py +0 -1
- educommon/audit_log/models.py +36 -42
- educommon/audit_log/permissions.py +11 -9
- educommon/audit_log/proxies.py +12 -23
- educommon/audit_log/ui.py +18 -15
- educommon/audit_log/utils/__init__.py +28 -60
- educommon/audit_log/utils/operations.py +16 -2
- educommon/auth/__init__.py +0 -3
- educommon/auth/rbac/__init__.py +2 -4
- educommon/auth/rbac/actions.py +148 -145
- educommon/auth/rbac/app_meta.py +9 -6
- educommon/auth/rbac/backends/base.py +2 -8
- educommon/auth/rbac/backends/caching.py +27 -37
- educommon/auth/rbac/backends/simple.py +1 -4
- educommon/auth/rbac/checker.py +1 -3
- educommon/auth/rbac/management/commands/rbac.py +6 -11
- educommon/auth/rbac/manager.py +18 -47
- educommon/auth/rbac/migrations/0001_initial.py +73 -12
- educommon/auth/rbac/migrations/0002_model_modifier_metaclass_fix.py +7 -6
- educommon/auth/rbac/migrations/0003_permission_hidden.py +1 -5
- educommon/auth/rbac/migrations/0004_auto_20171024_1245.py +26 -19
- educommon/auth/rbac/models.py +63 -68
- educommon/auth/rbac/permissions.py +6 -7
- educommon/auth/rbac/ui.py +83 -84
- educommon/auth/rbac/utils.py +10 -11
- educommon/auth/rbac/validators.py +4 -5
- educommon/auth/simple_auth/__init__.py +1 -5
- educommon/auth/simple_auth/actions.py +79 -92
- educommon/auth/simple_auth/app_meta.py +2 -9
- educommon/auth/simple_auth/checkers.py +3 -3
- educommon/auth/simple_auth/migrations/0001_initial.py +23 -4
- educommon/auth/simple_auth/validators.py +0 -1
- educommon/contingent/actions.py +7 -7
- educommon/contingent/app_meta.py +1 -4
- educommon/contingent/base.py +10 -15
- educommon/contingent/catalogs.py +424 -540
- educommon/contingent/contingent_plugin/actions.py +4 -15
- educommon/contingent/contingent_plugin/apps.py +10 -4
- educommon/contingent/contingent_plugin/migrations/0001_initial.py +5 -6
- educommon/contingent/contingent_plugin/migrations/0002_add_contingent_model_deleted.py +6 -11
- educommon/contingent/contingent_plugin/model_views.py +2 -12
- educommon/contingent/contingent_plugin/models.py +2 -7
- educommon/contingent/contingent_plugin/observer.py +14 -13
- educommon/contingent/contingent_plugin/plugin_meta.py +1 -3
- educommon/contingent/contingent_plugin/storage.py +8 -7
- educommon/contingent/contingent_plugin/utils.py +6 -6
- educommon/django/db/fields.py +72 -86
- educommon/django/db/migration/__init__.py +3 -7
- educommon/django/db/migration/operations.py +29 -51
- educommon/django/db/mixins/__init__.py +16 -10
- educommon/django/db/mixins/date_interval.py +47 -75
- educommon/django/db/mixins/validation.py +26 -26
- educommon/django/db/model_view/__init__.py +18 -22
- educommon/django/db/models.py +9 -8
- educommon/django/db/observer.py +9 -27
- educommon/django/db/partitioning/__init__.py +66 -92
- educommon/django/db/partitioning/management/commands/apply_partitioning.py +3 -13
- educommon/django/db/partitioning/management/commands/clear_table.py +18 -14
- educommon/django/db/partitioning/management/commands/split_table.py +18 -13
- educommon/django/db/routers.py +6 -15
- educommon/django/db/signals.py +4 -2
- educommon/django/db/utils.py +14 -19
- educommon/django/db/validators/__init__.py +1 -0
- educommon/django/db/validators/simple.py +72 -100
- educommon/django/storages/atcfs/api.py +39 -53
- educommon/django/storages/atcfs/app_meta.py +1 -1
- educommon/django/storages/atcfs/management/commands/atcfs_migrate.py +42 -55
- educommon/django/storages/atcfs/models.py +0 -3
- educommon/django/storages/atcfs/monkey_patching.py +18 -12
- educommon/django/storages/atcfs/storage.py +14 -23
- educommon/extjs/fields/input_params.py +15 -45
- educommon/importer/XLSReader.py +143 -241
- educommon/importer/__init__.py +86 -4
- educommon/importer/api.py +53 -84
- educommon/importer/constants.py +4 -14
- educommon/importer/loggers.py +16 -26
- educommon/importer/proxy.py +131 -176
- educommon/importer/proxy_import.py +11 -12
- educommon/importer/report.py +4 -6
- educommon/importer/ui.py +32 -26
- educommon/importer/validators.py +4 -7
- educommon/integration_entities/helpers.py +14 -18
- educommon/ioc/__init__.py +3 -6
- educommon/logger/loggers.py +10 -14
- educommon/m3/__init__.py +20 -38
- educommon/m3/extensions/__init__.py +1 -0
- educommon/m3/extensions/listeners/__init__.py +22 -38
- educommon/m3/extensions/listeners/delete_check/listeners.py +31 -41
- educommon/m3/extensions/listeners/delete_check/mixins.py +20 -25
- educommon/m3/extensions/listeners/delete_check/signals.py +2 -2
- educommon/m3/extensions/listeners/delete_check/ui.py +15 -14
- educommon/m3/extensions/listeners/delete_check/utils.py +9 -11
- educommon/m3/extensions/ui.py +15 -33
- educommon/m3/transaction_context.py +17 -19
- educommon/objectpack/actions.py +70 -88
- educommon/objectpack/apps.py +5 -0
- educommon/objectpack/filters.py +9 -15
- educommon/objectpack/ui.py +59 -77
- educommon/report/__init__.py +9 -5
- educommon/report/actions.py +29 -32
- educommon/report/constructor/__init__.py +5 -8
- educommon/report/constructor/app_meta.py +1 -3
- educommon/report/constructor/apps.py +1 -0
- educommon/report/constructor/base.py +33 -80
- educommon/report/constructor/builders/excel/_base.py +138 -286
- educommon/report/constructor/builders/excel/_header.py +2 -9
- educommon/report/constructor/builders/excel/product.py +13 -34
- educommon/report/constructor/builders/excel/with_merged_cells.py +18 -14
- educommon/report/constructor/config.py +2 -0
- educommon/report/constructor/editor/actions.py +101 -215
- educommon/report/constructor/editor/ui.py +71 -93
- educommon/report/constructor/exceptions.py +6 -12
- educommon/report/constructor/migrations/0001_initial.py +36 -44
- educommon/report/constructor/migrations/0002_report_filters.py +86 -72
- educommon/report/constructor/migrations/0003_reportfilter_exclude.py +5 -5
- educommon/report/constructor/migrations/0004_reportfilter_fields.py +22 -18
- educommon/report/constructor/migrations/0005_reportcolumn_visible.py +5 -4
- educommon/report/constructor/migrations/0006_reportsorting.py +21 -17
- educommon/report/constructor/migrations/0007_include_available_units.py +14 -14
- educommon/report/constructor/migrations/0008_auto_20170407_1318.py +4 -5
- educommon/report/constructor/migrations/0009_auto_20180405_0642.py +1 -4
- educommon/report/constructor/migrations/0010_add_aggregate_fields.py +7 -8
- educommon/report/constructor/mixins.py +14 -15
- educommon/report/constructor/models.py +76 -124
- educommon/report/constructor/utils.py +3 -8
- educommon/report/constructor/validators.py +1 -3
- educommon/report/reporter.py +25 -43
- educommon/report/utils.py +14 -40
- educommon/rest/actions.py +7 -11
- educommon/rest/context.py +6 -16
- educommon/rest/controllers.py +10 -10
- educommon/rest/mixins.py +29 -27
- educommon/secure_media/app_meta.py +9 -9
- educommon/utils/__init__.py +3 -2
- educommon/utils/caching.py +1 -3
- educommon/utils/conversion.py +1 -3
- educommon/utils/crypto.py +1 -2
- educommon/utils/date.py +13 -26
- educommon/utils/db/__init__.py +17 -26
- educommon/utils/db/postgresql.py +1 -4
- educommon/utils/fonts/__init__.py +3 -4
- educommon/utils/licence/__init__.py +5 -16
- educommon/utils/misc.py +9 -18
- educommon/utils/object_grid.py +55 -62
- educommon/utils/phone_number/modelfields.py +1 -3
- educommon/utils/phone_number/phone_number.py +5 -8
- educommon/utils/phone_number/validators.py +8 -23
- educommon/utils/plugins.py +15 -28
- educommon/utils/registry.py +2 -1
- educommon/utils/seqtools.py +1 -3
- educommon/utils/serializer.py +9 -16
- educommon/utils/storage.py +3 -2
- educommon/utils/system.py +1 -3
- educommon/utils/system_app/management/commands/delete_objects.py +17 -34
- educommon/utils/ui.py +87 -84
- educommon/utils/xml/__init__.py +2 -7
- educommon/utils/xml/resolver.py +1 -0
- educommon/ws_log/actions.py +31 -76
- educommon/ws_log/base.py +6 -20
- educommon/ws_log/migrations/0001_initial.py +25 -8
- educommon/ws_log/migrations/0002_auto_20160628_1334.py +0 -1
- educommon/ws_log/migrations/0003_add_fields_to_smev_logs.py +20 -4
- educommon/ws_log/migrations/0004_auto_20160727_1600.py +7 -6
- educommon/ws_log/migrations/0005_auto_20161130_1615.py +14 -4
- educommon/ws_log/migrations/0006_auto_20170327_1027.py +3 -2
- educommon/ws_log/migrations/0007_auto_20180607_1040.py +8 -9
- educommon/ws_log/migrations/0008_auto_20180713_1445.py +23 -10
- educommon/ws_log/migrations/0009_auto_20201130_1553.py +7 -2
- educommon/ws_log/models.py +21 -35
- educommon/ws_log/provider.py +2 -1
- educommon/ws_log/report.py +8 -13
- educommon/ws_log/smev/applications.py +12 -27
- educommon/ws_log/smev/exceptions.py +2 -3
- educommon/ws_log/ui.py +32 -32
- educommon/ws_log/utils.py +1 -3
- {educommon-3.13.0.dist-info → educommon-3.13.2.dist-info}/METADATA +26 -14
- educommon-3.13.2.dist-info/RECORD +354 -0
- educommon/utils/patches.py +0 -27
- educommon/version.conf +0 -11
- educommon-3.13.0.dist-info/RECORD +0 -357
- educommon-3.13.0.dist-info/dependency_links.txt +0 -1
- {educommon-3.13.0.dist-info → educommon-3.13.2.dist-info}/WHEEL +0 -0
- {educommon-3.13.0.dist-info → educommon-3.13.2.dist-info}/top_level.txt +0 -0
@@ -24,8 +24,7 @@ from django.db.models import (
|
|
24
24
|
)
|
25
25
|
|
26
26
|
|
27
|
-
def get_objects_from_fixture(file_path, file_type=None,
|
28
|
-
use_natural_foreign_keys=False):
|
27
|
+
def get_objects_from_fixture(file_path, file_type=None, use_natural_foreign_keys=False):
|
29
28
|
"""Возвращает генератор объектов из файла фикстуры.
|
30
29
|
|
31
30
|
:param basestring file_path: Путь к файлу с данными.
|
@@ -36,16 +35,14 @@ def get_objects_from_fixture(file_path, file_type=None,
|
|
36
35
|
:rtype: generator
|
37
36
|
"""
|
38
37
|
if file_type is None:
|
39
|
-
file_type = file_path[file_path.rfind('.') + 1:]
|
38
|
+
file_type = file_path[file_path.rfind('.') + 1 :]
|
40
39
|
if file_type not in ('json', 'xml', 'yaml'):
|
41
40
|
raise ValueError('Неподдерживаемый тип файла ' + file_path)
|
42
41
|
|
43
42
|
with open(file_path, 'r') as infile:
|
44
|
-
with closing(
|
45
|
-
file_type,
|
46
|
-
|
47
|
-
use_natural_foreign_keys=use_natural_foreign_keys
|
48
|
-
)) as objects:
|
43
|
+
with closing(
|
44
|
+
deserialize(file_type, infile.read(), use_natural_foreign_keys=use_natural_foreign_keys)
|
45
|
+
) as objects:
|
49
46
|
for obj in objects:
|
50
47
|
yield obj
|
51
48
|
|
@@ -69,11 +66,12 @@ def correct_sequence_value(model, field='id', conn=connection):
|
|
69
66
|
cursor = conn.cursor()
|
70
67
|
|
71
68
|
cursor.execute(
|
72
|
-
|
69
|
+
'SELECT setval(pg_get_serial_sequence(%s,%s), %s)',
|
70
|
+
(
|
73
71
|
model._meta.db_table,
|
74
72
|
model._meta.get_field(field).column,
|
75
73
|
max_id,
|
76
|
-
)
|
74
|
+
),
|
77
75
|
)
|
78
76
|
|
79
77
|
|
@@ -86,8 +84,7 @@ class LoadFixture(Operation):
|
|
86
84
|
|
87
85
|
atomic = True
|
88
86
|
|
89
|
-
def __init__(self, file_path, force=False, file_type=None,
|
90
|
-
use_natural_foreign_keys=False):
|
87
|
+
def __init__(self, file_path, force=False, file_type=None, use_natural_foreign_keys=False):
|
91
88
|
"""Инициализация экземпляра класса.
|
92
89
|
|
93
90
|
:param str file_path: Путь к файлу.
|
@@ -105,8 +102,9 @@ class LoadFixture(Operation):
|
|
105
102
|
def state_forwards(self, app_label, state):
|
106
103
|
pass
|
107
104
|
|
108
|
-
def database_forwards(self, app_label, schema_editor, from_state,
|
109
|
-
|
105
|
+
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
106
|
+
"""Выполняет миграцию для выбранного приложения."""
|
107
|
+
|
110
108
|
@contextmanager
|
111
109
|
def replace_model_loader():
|
112
110
|
_get_model = python_serializer_module._get_model
|
@@ -142,8 +140,8 @@ class CorrectSequence(Operation):
|
|
142
140
|
def state_forwards(self, app_label, state):
|
143
141
|
pass
|
144
142
|
|
145
|
-
def database_forwards(self, app_label, schema_editor, from_state,
|
146
|
-
|
143
|
+
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
144
|
+
"""Выполняет миграцию для выбранного приложения."""
|
147
145
|
db_alias = schema_editor.connection.alias
|
148
146
|
model = to_state.apps.get_model(app_label, self.model_name)
|
149
147
|
if self.allow_migrate_model(db_alias, model) or self.force:
|
@@ -155,8 +153,7 @@ class CreateSchema(Operation):
|
|
155
153
|
|
156
154
|
reversible = True
|
157
155
|
|
158
|
-
def __init__(self, schema_name, owner=None, aliases=None,
|
159
|
-
cascade_drop=False):
|
156
|
+
def __init__(self, schema_name, owner=None, aliases=None, cascade_drop=False):
|
160
157
|
"""Инициализация экземпляра.
|
161
158
|
|
162
159
|
:param schema_name: Имя схемы.
|
@@ -178,16 +175,16 @@ class CreateSchema(Operation):
|
|
178
175
|
def state_backward(self, app_label, state):
|
179
176
|
pass
|
180
177
|
|
181
|
-
def database_forwards(self, app_label, schema_editor, from_state,
|
182
|
-
|
178
|
+
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
179
|
+
"""Выполняет миграцию для выбранного приложения."""
|
183
180
|
if not self.aliases or schema_editor.connection.alias in self.aliases:
|
184
181
|
sql = 'CREATE SCHEMA IF NOT EXISTS ' + self.schema_name
|
185
182
|
if self.owner:
|
186
183
|
sql += ' AUTHORIZATION ' + self.owner
|
187
184
|
schema_editor.execute(sql)
|
188
185
|
|
189
|
-
def database_backwards(self, app_label, schema_editor, from_state,
|
190
|
-
|
186
|
+
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
187
|
+
"""Откатывает миграцию для выбранного приложения."""
|
191
188
|
if not self.aliases or schema_editor.connection.alias in self.aliases:
|
192
189
|
sql = 'DROP SCHEMA IF EXISTS ' + self.schema_name
|
193
190
|
if self.cascade_drop:
|
@@ -196,41 +193,25 @@ class CreateSchema(Operation):
|
|
196
193
|
|
197
194
|
|
198
195
|
class _AnotherAppMixin:
|
199
|
-
|
200
196
|
def __init__(self, *args, **kwargs):
|
201
197
|
self.__app_label = kwargs.pop('app_label', None)
|
202
198
|
|
203
199
|
super().__init__(*args, **kwargs)
|
204
200
|
|
205
201
|
def state_forwards(self, app_label, state):
|
206
|
-
|
207
|
-
|
208
|
-
state
|
209
|
-
)
|
210
|
-
|
211
|
-
def database_forwards(
|
212
|
-
self, app_label, schema_editor, from_state, to_state
|
213
|
-
):
|
214
|
-
super().database_forwards(
|
215
|
-
self.__app_label or app_label,
|
216
|
-
schema_editor,
|
217
|
-
from_state,
|
218
|
-
to_state
|
219
|
-
)
|
220
|
-
|
221
|
-
def database_backwards(
|
222
|
-
self, app_label, schema_editor, from_state, to_state
|
223
|
-
):
|
224
|
-
super().database_backwards(
|
225
|
-
self.__app_label or app_label,
|
226
|
-
schema_editor,
|
227
|
-
from_state,
|
228
|
-
to_state
|
229
|
-
)
|
202
|
+
"""Применяет изменение к заданному приложению или текущему."""
|
203
|
+
super().state_forwards(self.__app_label or app_label, state)
|
230
204
|
|
205
|
+
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
206
|
+
"""Выполняет миграцию для выбранного приложения."""
|
207
|
+
super().database_forwards(self.__app_label or app_label, schema_editor, from_state, to_state)
|
208
|
+
|
209
|
+
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
210
|
+
"""Откатывает миграцию для выбранного приложения."""
|
211
|
+
super().database_backwards(self.__app_label or app_label, schema_editor, from_state, to_state)
|
231
212
|
|
232
|
-
class AddField(_AnotherAppMixin, AddField):
|
233
213
|
|
214
|
+
class AddField(_AnotherAppMixin, AddField):
|
234
215
|
"""Операция добавления поля в модель с поддержкой других приложений.
|
235
216
|
|
236
217
|
Имя приложения можно указать в аргументе :arg:`app_label`.
|
@@ -238,7 +219,6 @@ class AddField(_AnotherAppMixin, AddField):
|
|
238
219
|
|
239
220
|
|
240
221
|
class AlterField(_AnotherAppMixin, AlterField):
|
241
|
-
|
242
222
|
"""Операция изменения поля в модели с поддержкой других приложений.
|
243
223
|
|
244
224
|
Имя приложения можно указать в аргументе :arg:`app_label`.
|
@@ -246,7 +226,6 @@ class AlterField(_AnotherAppMixin, AlterField):
|
|
246
226
|
|
247
227
|
|
248
228
|
class RemoveField(_AnotherAppMixin, RemoveField):
|
249
|
-
|
250
229
|
"""Операция удаления поля в модели с поддержкой других приложений.
|
251
230
|
|
252
231
|
Имя приложения можно указать в аргументе :arg:`app_label`.
|
@@ -254,7 +233,6 @@ class RemoveField(_AnotherAppMixin, RemoveField):
|
|
254
233
|
|
255
234
|
|
256
235
|
class RenameField(_AnotherAppMixin, RenameField):
|
257
|
-
|
258
236
|
"""Операция переименования поля в модели с поддержкой других приложений.
|
259
237
|
|
260
238
|
Имя приложения можно указать в аргументе :arg:`app_label`.
|
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Классы-примеси для моделей Django."""
|
2
|
+
|
2
3
|
from django.db import (
|
3
4
|
models,
|
4
5
|
)
|
@@ -12,7 +13,6 @@ from m3_django_compat.exceptions import (
|
|
12
13
|
|
13
14
|
|
14
15
|
class DeferredActionsMixin(models.Model):
|
15
|
-
|
16
16
|
"""Класс-примесь для выполнения отложенных действий в моделях.
|
17
17
|
|
18
18
|
Позволяет выполнять действия, оформленные в виде callable-объектов,
|
@@ -36,7 +36,7 @@ class DeferredActionsMixin(models.Model):
|
|
36
36
|
abstract = True
|
37
37
|
|
38
38
|
def __init__(self, *args, **kwargs): # noqa: D107
|
39
|
-
super(
|
39
|
+
super().__init__(*args, **kwargs)
|
40
40
|
|
41
41
|
self.__pre_save_actions = []
|
42
42
|
self.__post_save_actions = []
|
@@ -74,7 +74,9 @@ class DeferredActionsMixin(models.Model):
|
|
74
74
|
соответственно.
|
75
75
|
"""
|
76
76
|
self.__execute_actions(self.__pre_save_actions)
|
77
|
-
|
77
|
+
|
78
|
+
super().save(*args, **kwargs)
|
79
|
+
|
78
80
|
self.__execute_actions(self.__post_save_actions)
|
79
81
|
|
80
82
|
@atomic
|
@@ -86,8 +88,11 @@ class DeferredActionsMixin(models.Model):
|
|
86
88
|
соответственно.
|
87
89
|
"""
|
88
90
|
self.__execute_actions(self.__pre_delete_actions)
|
89
|
-
|
91
|
+
|
92
|
+
result = super().delete(*args, **kwargs)
|
93
|
+
|
90
94
|
self.__execute_actions(self.__post_delete_actions)
|
95
|
+
|
91
96
|
return result
|
92
97
|
|
93
98
|
@atomic
|
@@ -99,13 +104,15 @@ class DeferredActionsMixin(models.Model):
|
|
99
104
|
соответственно.
|
100
105
|
"""
|
101
106
|
self.__execute_actions(self.__pre_delete_actions)
|
102
|
-
|
107
|
+
|
108
|
+
result = super().safe_delete(*args, **kwargs)
|
109
|
+
|
103
110
|
self.__execute_actions(self.__post_delete_actions)
|
111
|
+
|
104
112
|
return result
|
105
113
|
|
106
114
|
|
107
115
|
class DeleteOnSaveMixin(models.Model):
|
108
|
-
|
109
116
|
"""Примесь для удаления объектов до/после сохранения модели.
|
110
117
|
|
111
118
|
Функционал данной примеси актуален при валидации моделей. В некоторых
|
@@ -120,7 +127,7 @@ class DeleteOnSaveMixin(models.Model):
|
|
120
127
|
"""
|
121
128
|
|
122
129
|
def __init__(self, *args, **kwargs):
|
123
|
-
super(
|
130
|
+
super().__init__(*args, **kwargs)
|
124
131
|
|
125
132
|
# Список объектов, подлежащих удалению после сохранения self
|
126
133
|
self.__objects_for_delete_before_save = set()
|
@@ -176,7 +183,7 @@ class DeleteOnSaveMixin(models.Model):
|
|
176
183
|
|
177
184
|
delete_objects(self.__objects_for_delete_before_save)
|
178
185
|
|
179
|
-
super(
|
186
|
+
super().save(*args, **kwargs)
|
180
187
|
|
181
188
|
delete_objects(self.__objects_for_delete_after_save)
|
182
189
|
|
@@ -185,7 +192,6 @@ class DeleteOnSaveMixin(models.Model):
|
|
185
192
|
|
186
193
|
|
187
194
|
class StringFieldsCleanerMixin(models.Model):
|
188
|
-
|
189
195
|
"""Примесь для удаления из строковых полей модели лишних пробелов.
|
190
196
|
|
191
197
|
Во всех текстовых полях модели удаляет пробельные символы в начале и конце
|
@@ -222,7 +228,7 @@ class StringFieldsCleanerMixin(models.Model):
|
|
222
228
|
|
223
229
|
setattr(self, field.attname, field_value)
|
224
230
|
|
225
|
-
return super(
|
231
|
+
return super().clean_fields(exclude)
|
226
232
|
|
227
233
|
class Meta:
|
228
234
|
abstract = True
|
@@ -30,12 +30,12 @@ from educommon.utils.date import (
|
|
30
30
|
|
31
31
|
|
32
32
|
class BaseIntervalMeta(models.base.ModelBase):
|
33
|
-
|
34
33
|
"""Базовый метакласс для примесей *IntervalMixin.
|
35
34
|
|
36
35
|
Добавляет к создаваемой модели поля, содержащие границы интервала. Имена
|
37
36
|
полей задаются в атрибуте interval_field_names.
|
38
37
|
"""
|
38
|
+
|
39
39
|
# тип поля модели, определяется в потомке
|
40
40
|
_interval_bound_field_type = None
|
41
41
|
|
@@ -62,42 +62,34 @@ class BaseIntervalMeta(models.base.ModelBase):
|
|
62
62
|
return result
|
63
63
|
|
64
64
|
def __new__(cls, name, bases, attrs):
|
65
|
-
if (
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
# абстрактная.
|
70
|
-
'Meta' not in attrs or
|
71
|
-
not hasattr(attrs['Meta'], 'abstract') or
|
72
|
-
not attrs['Meta'].abstract
|
73
|
-
)
|
65
|
+
if cls._mixin_stay(bases) and (
|
66
|
+
# поля с границами интервала создаются только если модель не
|
67
|
+
# абстрактная.
|
68
|
+
'Meta' not in attrs or not hasattr(attrs['Meta'], 'abstract') or not attrs['Meta'].abstract
|
74
69
|
):
|
75
|
-
interval_field_names = cls.__get_attr(
|
76
|
-
'interval_field_names', attrs, bases, cls.interval_field_names
|
77
|
-
)
|
70
|
+
interval_field_names = cls.__get_attr('interval_field_names', attrs, bases, cls.interval_field_names)
|
78
71
|
|
79
72
|
# Добавление полей, хранящих границы интервала
|
80
|
-
for field_name, verbose_name in zip(
|
81
|
-
interval_field_names, ('Начало интервала', 'Конец интервала')
|
82
|
-
):
|
73
|
+
for field_name, verbose_name in zip(interval_field_names, ('Начало интервала', 'Конец интервала')):
|
83
74
|
field = cls.__get_attr(field_name, attrs, bases)
|
84
75
|
if field is Undefined:
|
85
76
|
attrs[field_name] = cls._interval_bound_field_type(
|
86
77
|
name=field_name,
|
87
|
-
blank=True,
|
78
|
+
blank=True,
|
79
|
+
null=True,
|
88
80
|
verbose_name=verbose_name,
|
89
81
|
)
|
90
82
|
|
91
|
-
return super(
|
83
|
+
return super().__new__(cls, name, bases, attrs)
|
92
84
|
|
93
85
|
|
94
86
|
class DateIntervalMeta(BaseIntervalMeta):
|
95
|
-
|
96
87
|
"""Метакласс для примеси DateIntervalMixin.
|
97
88
|
|
98
89
|
Добавляет к создаваемой модели поля дат, содержащие границы интервала.
|
99
90
|
Имена полей задаются в атрибуте interval_field_names.
|
100
91
|
"""
|
92
|
+
|
101
93
|
# тип полей модели, которые будет созданы
|
102
94
|
_interval_bound_field_type = models.DateField
|
103
95
|
# названия полей по умолчанию
|
@@ -109,19 +101,16 @@ class DateIntervalMeta(BaseIntervalMeta):
|
|
109
101
|
|
110
102
|
:rtype bool
|
111
103
|
"""
|
112
|
-
return (
|
113
|
-
'DateIntervalMixin' in globals() and
|
114
|
-
any(issubclass(base, DateIntervalMixin) for base in bases)
|
115
|
-
)
|
104
|
+
return 'DateIntervalMixin' in globals() and any(issubclass(base, DateIntervalMixin) for base in bases)
|
116
105
|
|
117
106
|
|
118
107
|
class DateTimeIntervalMeta(BaseIntervalMeta):
|
119
|
-
|
120
108
|
"""Метакласс для примеси DateTimeIntervalMixin.
|
121
109
|
|
122
110
|
Добавляет к создаваемой модели поля дат со временем, содержащие
|
123
111
|
границы интервала. Имена полей задаются в атрибуте interval_field_names.
|
124
112
|
"""
|
113
|
+
|
125
114
|
# тип полей модели, которые будет созданы
|
126
115
|
_interval_bound_field_type = models.DateTimeField
|
127
116
|
# названия полей по умолчанию
|
@@ -133,14 +122,10 @@ class DateTimeIntervalMeta(BaseIntervalMeta):
|
|
133
122
|
|
134
123
|
:rtype bool
|
135
124
|
"""
|
136
|
-
return (
|
137
|
-
'DateTimeIntervalMixin' in globals() and
|
138
|
-
any(issubclass(base, DateTimeIntervalMixin) for base in bases)
|
139
|
-
)
|
125
|
+
return 'DateTimeIntervalMixin' in globals() and any(issubclass(base, DateTimeIntervalMixin) for base in bases)
|
140
126
|
|
141
127
|
|
142
128
|
class BaseIntervalMixin(models.Model):
|
143
|
-
|
144
129
|
"""Базовый класс для примесей к моделям, добавляющих интервал дат.
|
145
130
|
|
146
131
|
Содержит два поля: date_from (дата начала интервала) и date_to (дата
|
@@ -165,18 +150,17 @@ class BaseIntervalMixin(models.Model):
|
|
165
150
|
|
166
151
|
Используется для проверки пересечения диапазонов.
|
167
152
|
"""
|
153
|
+
|
168
154
|
@classmethod
|
169
155
|
def get_current_date(cls):
|
170
156
|
"""Возвращает текущую дату.
|
171
157
|
|
172
158
|
:rtype: datetime.datetime or datetime.date
|
173
159
|
"""
|
174
|
-
raise
|
160
|
+
raise NotImplementedError
|
175
161
|
|
176
162
|
@classmethod
|
177
|
-
def get_date_in_intervals_filter(cls, day=None, lookup=None,
|
178
|
-
include_lower_bound=True,
|
179
|
-
include_upper_bound=True):
|
163
|
+
def get_date_in_intervals_filter(cls, day=None, lookup=None, include_lower_bound=True, include_upper_bound=True):
|
180
164
|
"""Возвращает фильтр для выборки записей по указанной дате.
|
181
165
|
|
182
166
|
Условия фильтра включают записи, в интервал которых входит
|
@@ -225,6 +209,7 @@ class BaseIntervalMixin(models.Model):
|
|
225
209
|
to_filter |= Q(**{to_lookup + '__isnull': True})
|
226
210
|
|
227
211
|
result = from_filter & to_filter
|
212
|
+
|
228
213
|
return result
|
229
214
|
|
230
215
|
@classmethod
|
@@ -233,12 +218,7 @@ class BaseIntervalMixin(models.Model):
|
|
233
218
|
|
234
219
|
@classmethod
|
235
220
|
def get_intersection_daterange_filter(
|
236
|
-
cls,
|
237
|
-
date_begin=None,
|
238
|
-
date_end=None,
|
239
|
-
lookup=None,
|
240
|
-
include_lower_bound=True,
|
241
|
-
include_upper_bound=True
|
221
|
+
cls, date_begin=None, date_end=None, lookup=None, include_lower_bound=True, include_upper_bound=True
|
242
222
|
):
|
243
223
|
"""Метод возвращает фильтр для выборки записей, попадающих в интервал.
|
244
224
|
|
@@ -268,11 +248,7 @@ class BaseIntervalMixin(models.Model):
|
|
268
248
|
|
269
249
|
.. code-block:: python
|
270
250
|
:caption: Пример использования
|
271
|
-
query = model.objects.filter(
|
272
|
-
model.get_intersection_daterange_filter(
|
273
|
-
date_begin, date_end
|
274
|
-
)
|
275
|
-
)
|
251
|
+
query = model.objects.filter(model.get_intersection_daterange_filter(date_begin, date_end))
|
276
252
|
|
277
253
|
:params date_begin: Дата начала интервала.
|
278
254
|
:type date_begin: datetime.datetime
|
@@ -305,9 +281,7 @@ class BaseIntervalMixin(models.Model):
|
|
305
281
|
# Предполагается, что в интервалах дат дата окончания находится
|
306
282
|
# справа по оси времени от даты начала или равна. Если будет наоборот,
|
307
283
|
# то результат может оказаться не верным
|
308
|
-
assert date_end >= date_begin,
|
309
|
-
'Дата окончания должна быть больше или равна дате начала'
|
310
|
-
)
|
284
|
+
assert date_end >= date_begin, 'Дата окончания должна быть больше или равна дате начала'
|
311
285
|
|
312
286
|
from_name, to_name = cls.interval_field_names
|
313
287
|
if lookup is not None:
|
@@ -331,6 +305,7 @@ class BaseIntervalMixin(models.Model):
|
|
331
305
|
to_filter |= Q(**{to_lookup + '__isnull': True})
|
332
306
|
|
333
307
|
result = from_filter & to_filter
|
308
|
+
|
334
309
|
return result
|
335
310
|
|
336
311
|
def is_date_in_interval(self, day):
|
@@ -343,12 +318,19 @@ class BaseIntervalMixin(models.Model):
|
|
343
318
|
assert isinstance(day, (date, datetime)), type(day)
|
344
319
|
date_from, date_to = self.interval_range
|
345
320
|
result = (
|
346
|
-
date_from is None
|
347
|
-
|
348
|
-
date_from is
|
349
|
-
|
350
|
-
|
321
|
+
date_from is None
|
322
|
+
and date_to is None
|
323
|
+
or date_from is None
|
324
|
+
and date_to is not None
|
325
|
+
and day <= date_to
|
326
|
+
or date_from is not None
|
327
|
+
and date_to is None
|
328
|
+
and day >= date_from
|
329
|
+
or date_from is not None
|
330
|
+
and date_to is not None
|
331
|
+
and date_from <= day <= date_to
|
351
332
|
)
|
333
|
+
|
352
334
|
return result
|
353
335
|
|
354
336
|
def is_intersected_with(self, date_from, date_to):
|
@@ -400,6 +382,7 @@ class BaseIntervalMixin(models.Model):
|
|
400
382
|
opts.get_field(date_from_name).verbose_name.capitalize(),
|
401
383
|
opts.get_field(date_to_name).verbose_name.lower(),
|
402
384
|
)
|
385
|
+
|
403
386
|
return result
|
404
387
|
|
405
388
|
def interval_intersected_error_message(self, others=None):
|
@@ -419,9 +402,9 @@ class BaseIntervalMixin(models.Model):
|
|
419
402
|
|
420
403
|
date_from = getattr(self, date_from_name)
|
421
404
|
|
422
|
-
prev_intervals = self.__class__.objects.filter(
|
423
|
-
|
424
|
-
)
|
405
|
+
prev_intervals = self.__class__.objects.filter(**{'{}__lt'.format(date_to_name): date_from}).order_by(
|
406
|
+
'-{}'.format(date_to_name)
|
407
|
+
)
|
425
408
|
|
426
409
|
return prev_intervals[0] if prev_intervals else None
|
427
410
|
|
@@ -466,9 +449,9 @@ class BaseIntervalMixin(models.Model):
|
|
466
449
|
|
467
450
|
date_to = getattr(self, date_to_name)
|
468
451
|
|
469
|
-
next_intervals = self.__class__.objects.filter(
|
470
|
-
|
471
|
-
)
|
452
|
+
next_intervals = self.__class__.objects.filter(**{'{}__gt'.format(date_from_name): date_to}).order_by(
|
453
|
+
date_from_name
|
454
|
+
)
|
472
455
|
|
473
456
|
return next_intervals[0] if next_intervals else None
|
474
457
|
|
@@ -477,28 +460,20 @@ class BaseIntervalMixin(models.Model):
|
|
477
460
|
errors = {}
|
478
461
|
|
479
462
|
try:
|
480
|
-
super(
|
463
|
+
super().clean()
|
481
464
|
except ValidationError as error:
|
482
465
|
errors = error.update_error_dict(errors)
|
483
466
|
|
484
467
|
date_from, date_to = self.interval_range
|
485
468
|
|
486
469
|
# Дата начала интервала должна быть раньше даты окончания
|
487
|
-
if
|
488
|
-
|
489
|
-
date_to is not None and
|
490
|
-
date_from > date_to
|
491
|
-
):
|
492
|
-
errors.setdefault(NON_FIELD_ERRORS, []).append(
|
493
|
-
self.interval_dates_error_message()
|
494
|
-
)
|
470
|
+
if date_from is not None and date_to is not None and date_from > date_to:
|
471
|
+
errors.setdefault(NON_FIELD_ERRORS, []).append(self.interval_dates_error_message())
|
495
472
|
else:
|
496
473
|
# Проверка пересения интервалов
|
497
474
|
others = self.get_intersected_query()
|
498
475
|
if others:
|
499
|
-
errors.setdefault(NON_FIELD_ERRORS, []).append(
|
500
|
-
self.interval_intersected_error_message(others)
|
501
|
-
)
|
476
|
+
errors.setdefault(NON_FIELD_ERRORS, []).append(self.interval_intersected_error_message(others))
|
502
477
|
|
503
478
|
if errors:
|
504
479
|
raise ValidationError(errors)
|
@@ -582,7 +557,6 @@ class DateTimeIntervalMixin(BaseIntervalMixin, metaclass=DateTimeIntervalMeta):
|
|
582
557
|
|
583
558
|
|
584
559
|
class ActualObjectsManager(Manager):
|
585
|
-
|
586
560
|
"""Менеджер для интервальной модели.
|
587
561
|
|
588
562
|
Отфильтровывает все объекты, в интервал которых входит текущая дата.
|
@@ -591,9 +565,7 @@ class ActualObjectsManager(Manager):
|
|
591
565
|
def contribute_to_class(self, model, name):
|
592
566
|
assert issubclass(model, BaseIntervalMixin), type(model)
|
593
567
|
|
594
|
-
super(
|
568
|
+
super().contribute_to_class(model, name)
|
595
569
|
|
596
570
|
def get_queryset(self):
|
597
|
-
return super(
|
598
|
-
self.model.get_date_in_intervals_filter()
|
599
|
-
)
|
571
|
+
return super().get_queryset().filter(self.model.get_date_in_intervals_filter())
|