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
educommon/utils/serializer.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Инструменты для сериализации моделей."""
|
2
|
+
|
2
3
|
from collections import (
|
3
4
|
defaultdict,
|
4
5
|
)
|
@@ -120,19 +121,15 @@ class SerializeQueue:
|
|
120
121
|
attname = field.attname
|
121
122
|
if attname.endswith('_id'):
|
122
123
|
attname = attname.replace('_id', '')
|
123
|
-
attname = '
|
124
|
+
attname = f'{attname}__in'
|
124
125
|
# добавление фильтра вхождения
|
125
126
|
objects = self._include_conditions.get(rel_model._meta.object_name)
|
126
127
|
if objects:
|
127
|
-
query_set = query_set.filter(
|
128
|
-
**{attname: objects}
|
129
|
-
)
|
128
|
+
query_set = query_set.filter(**{attname: objects})
|
130
129
|
# добавление фильтра исключения
|
131
130
|
objects = self._exclude_conditions.get(rel_model._meta.object_name)
|
132
131
|
if objects:
|
133
|
-
query_set = query_set.exclude(
|
134
|
-
**{attname: objects}
|
135
|
-
)
|
132
|
+
query_set = query_set.exclude(**{attname: objects})
|
136
133
|
|
137
134
|
return query_set
|
138
135
|
|
@@ -144,14 +141,12 @@ class SerializeQueue:
|
|
144
141
|
:return: список кортежей вида [(поле, ссылочная модель), ...]
|
145
142
|
"""
|
146
143
|
return [
|
147
|
-
(field, get_related(field).parent_model) for
|
148
|
-
field in model._meta.fields
|
149
|
-
if isinstance(field, ForeignKey)
|
144
|
+
(field, get_related(field).parent_model) for field in model._meta.fields if isinstance(field, ForeignKey)
|
150
145
|
]
|
151
146
|
|
152
147
|
@staticmethod
|
153
148
|
def related_objects(obj):
|
154
|
-
"""
|
149
|
+
"""Возвращает список кверисетов зависимых моделей
|
155
150
|
|
156
151
|
:param obj:
|
157
152
|
:return:
|
@@ -163,11 +158,9 @@ class SerializeQueue:
|
|
163
158
|
_attname = rel.field.attname
|
164
159
|
if _attname.endswith('_id'):
|
165
160
|
_attname = _attname.replace('_id', '')
|
166
|
-
_attname = '
|
161
|
+
_attname = f'{_attname}__pk'
|
167
162
|
# Пытаемся определить наличие зависимых объектов:
|
168
|
-
rel_qs = rel.field.model.objects.filter(
|
169
|
-
**{str(_attname): obj.id}
|
170
|
-
)
|
163
|
+
rel_qs = rel.field.model.objects.filter(**{str(_attname): obj.id})
|
171
164
|
if rel_qs.exists():
|
172
165
|
result.append(rel_qs)
|
173
166
|
|
@@ -216,6 +209,6 @@ class SerializeQueue:
|
|
216
209
|
|
217
210
|
:param filename: имя файла
|
218
211
|
"""
|
219
|
-
data = serializers.serialize(
|
212
|
+
data = serializers.serialize('json', self._objects, indent=4)
|
220
213
|
with open(filename, 'wb') as json_out:
|
221
214
|
json_out.write(data)
|
educommon/utils/storage.py
CHANGED
@@ -68,8 +68,9 @@ class AbstractInstanceDataStorage(AbstractInstanceStorage):
|
|
68
68
|
|
69
69
|
:param instance: Объект модели
|
70
70
|
"""
|
71
|
-
if
|
72
|
-
|
71
|
+
if not self.registry.is_model_has_handler(instance.__class__) or self._is_instance_already_saved(
|
72
|
+
instance, **kwargs
|
73
|
+
):
|
73
74
|
return
|
74
75
|
|
75
76
|
self._save_instance_data(instance)
|
educommon/utils/system.py
CHANGED
@@ -57,9 +57,7 @@ def get_postgresql_version(connection):
|
|
57
57
|
"""
|
58
58
|
with connection.cursor() as cursor:
|
59
59
|
if cursor.db.vendor != 'postgresql':
|
60
|
-
raise RuntimeError(
|
61
|
-
'Only PostgreSQL RDBMS supported, not ' + cursor.db.vendor
|
62
|
-
)
|
60
|
+
raise RuntimeError(f'Only PostgreSQL RDBMS supported, not {cursor.db.vendor}')
|
63
61
|
|
64
62
|
return (
|
65
63
|
cursor.db.pg_version // 10000,
|
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Management-команда для удаления обьектов."""
|
2
|
+
|
2
3
|
import sys
|
3
4
|
from collections import (
|
4
5
|
defaultdict,
|
@@ -39,7 +40,7 @@ def call_custom_command(command_name, *args, **options):
|
|
39
40
|
try:
|
40
41
|
app_name = get_commands()[command_name]
|
41
42
|
except KeyError:
|
42
|
-
raise CommandError(
|
43
|
+
raise CommandError(f'Unknown command: {command_name!r}')
|
43
44
|
|
44
45
|
if isinstance(app_name, BaseCommand):
|
45
46
|
# If the command is already loaded, use it directly.
|
@@ -52,16 +53,15 @@ def call_custom_command(command_name, *args, **options):
|
|
52
53
|
parser = command.create_parser('', command_name)
|
53
54
|
# Use the `dest` option name from the parser option
|
54
55
|
opt_mapping = {
|
55
|
-
sorted(s_opt.option_strings)[0].lstrip('-').replace(
|
56
|
-
|
57
|
-
|
56
|
+
sorted(s_opt.option_strings)[0].lstrip('-').replace('-', '_'): s_opt.dest
|
57
|
+
for s_opt in parser._actions
|
58
|
+
if s_opt.option_strings
|
58
59
|
}
|
59
|
-
arg_options = {opt_mapping.get(key, key): value
|
60
|
-
for key, value in options.items()}
|
60
|
+
arg_options = {opt_mapping.get(key, key): value for key, value in options.items()}
|
61
61
|
options, un_options = parser.parse_known_args(args)
|
62
62
|
for opt in un_options:
|
63
63
|
if not isinstance(opt, str):
|
64
|
-
opt = opt
|
64
|
+
opt = str(opt)
|
65
65
|
com = opt.split('=')
|
66
66
|
com = com[0]
|
67
67
|
if '__in' in com:
|
@@ -92,7 +92,7 @@ class Command(BaseCommand):
|
|
92
92
|
options, un_options = parser.parse_known_args(argv[2:])
|
93
93
|
for opt in un_options:
|
94
94
|
if not isinstance(opt, str):
|
95
|
-
opt = opt
|
95
|
+
opt = str(opt)
|
96
96
|
com = opt.split('=')
|
97
97
|
com = com[0]
|
98
98
|
if '__in' in com:
|
@@ -116,21 +116,15 @@ class Command(BaseCommand):
|
|
116
116
|
if isinstance(e, SystemCheckError):
|
117
117
|
self.stderr.write(str(e), lambda x: x)
|
118
118
|
else:
|
119
|
-
self.stderr.write('
|
119
|
+
self.stderr.write(f'{e.__class__.__name__}: {e}')
|
120
120
|
sys.exit(1)
|
121
121
|
finally:
|
122
122
|
connections.close_all()
|
123
123
|
|
124
124
|
def add_arguments(self, parser):
|
125
|
-
parser.add_argument(
|
126
|
-
|
127
|
-
)
|
128
|
-
parser.add_argument(
|
129
|
-
'--model', type=str, required=True, help='Модель для удаления'
|
130
|
-
)
|
131
|
-
parser.add_argument(
|
132
|
-
'--data', action='count', help='Вернуть json удаляемых обьектов'
|
133
|
-
)
|
125
|
+
parser.add_argument('--count', action='count', help='Количество удаляемых обьектов')
|
126
|
+
parser.add_argument('--model', type=str, required=True, help='Модель для удаления')
|
127
|
+
parser.add_argument('--data', action='count', help='Вернуть json удаляемых обьектов')
|
134
128
|
|
135
129
|
@staticmethod
|
136
130
|
def find_filter(options, ignore_opt=()):
|
@@ -177,10 +171,7 @@ class Command(BaseCommand):
|
|
177
171
|
try:
|
178
172
|
model = apps.get_model(app_name, model_name)
|
179
173
|
except LookupError:
|
180
|
-
message = (
|
181
|
-
'Модели {} в приложении '
|
182
|
-
'{} не найдено.'.format(model_name, app_name)
|
183
|
-
)
|
174
|
+
message = 'Модели {} в приложении {} не найдено.'.format(model_name, app_name)
|
184
175
|
return model, message
|
185
176
|
for app_config in apps.get_app_configs():
|
186
177
|
try:
|
@@ -191,10 +182,7 @@ class Command(BaseCommand):
|
|
191
182
|
|
192
183
|
if len(models) > 1:
|
193
184
|
model = None
|
194
|
-
message =
|
195
|
-
'Невозможно однозначно определить модель, '
|
196
|
-
'найдены похожие модели: \n'
|
197
|
-
)
|
185
|
+
message = 'Невозможно однозначно определить модель, найдены похожие модели: \n'
|
198
186
|
message += '\n'.join(str(model._meta) for model in models)
|
199
187
|
elif not model:
|
200
188
|
message = 'Модели {} не найдено.'.format(model_name)
|
@@ -206,9 +194,7 @@ class Command(BaseCommand):
|
|
206
194
|
for del_obj in deleted_objects:
|
207
195
|
for model, model_objects in del_obj.get_related_objects():
|
208
196
|
for o in model_objects:
|
209
|
-
uniq_dict[
|
210
|
-
model.__name__ + ' ' + model._meta.verbose_name
|
211
|
-
].add(o)
|
197
|
+
uniq_dict[model.__name__ + ' ' + model._meta.verbose_name].add(o)
|
212
198
|
for model, objs in uniq_dict.items():
|
213
199
|
display_mes = '{} {}'.format(model, len(objs))
|
214
200
|
self.stdout.write(display_mes)
|
@@ -220,8 +206,7 @@ class Command(BaseCommand):
|
|
220
206
|
for rel_model, rel_objs in instance.get_related_objects():
|
221
207
|
for obj in rel_objs:
|
222
208
|
objs.append(obj)
|
223
|
-
data = serializers.serialize(
|
224
|
-
'json', objs)
|
209
|
+
data = serializers.serialize('json', objs)
|
225
210
|
self.stdout.write(data)
|
226
211
|
|
227
212
|
def handle(self, *args, **options):
|
@@ -234,9 +219,7 @@ class Command(BaseCommand):
|
|
234
219
|
if message:
|
235
220
|
raise CommandError(message)
|
236
221
|
# опции которые будут игнорироваться в фильтре
|
237
|
-
ignore_opt = (
|
238
|
-
'count', 'model', 'data', 'verbosity', 'stdout', 'skip_checks'
|
239
|
-
)
|
222
|
+
ignore_opt = ('count', 'model', 'data', 'verbosity', 'stdout', 'skip_checks')
|
240
223
|
lookup = self.find_filter(options, ignore_opt)
|
241
224
|
deleted_objects = model.objects.filter(**lookup)
|
242
225
|
|
educommon/utils/ui.py
CHANGED
@@ -1,10 +1,20 @@
|
|
1
1
|
"""Утилиты для работы с элементами управления (интерфейсами)."""
|
2
|
+
from __future__ import (
|
3
|
+
annotations,
|
4
|
+
)
|
5
|
+
|
2
6
|
import inspect
|
3
7
|
import os
|
4
8
|
from datetime import (
|
9
|
+
date,
|
5
10
|
datetime,
|
6
11
|
time,
|
7
12
|
)
|
13
|
+
from typing import (
|
14
|
+
TYPE_CHECKING,
|
15
|
+
Callable,
|
16
|
+
Type,
|
17
|
+
)
|
8
18
|
|
9
19
|
from django.conf import (
|
10
20
|
settings,
|
@@ -58,26 +68,29 @@ from educommon.utils.misc import (
|
|
58
68
|
)
|
59
69
|
|
60
70
|
|
71
|
+
if TYPE_CHECKING:
|
72
|
+
from django.db.models import (
|
73
|
+
Model,
|
74
|
+
)
|
75
|
+
|
76
|
+
from objectpack.filters import (
|
77
|
+
FilterGroup,
|
78
|
+
)
|
79
|
+
|
80
|
+
|
61
81
|
def anchor100(*elements):
|
62
82
|
"""Установка anchor='100%' для перечня компонент."""
|
63
|
-
return list(
|
64
|
-
obj_anchor100(element)
|
65
|
-
for element in elements
|
66
|
-
)
|
83
|
+
return list(obj_anchor100(element) for element in elements)
|
67
84
|
|
68
85
|
|
69
86
|
def deny_blank(*elements):
|
70
87
|
"""Установка allow_blank=False для перечня компонент."""
|
71
|
-
return list(
|
72
|
-
obj_deny_blank(element)
|
73
|
-
for element in elements
|
74
|
-
)
|
88
|
+
return list(obj_deny_blank(element) for element in elements)
|
75
89
|
|
76
90
|
|
77
91
|
def make_button(title, icon_cls, event, client_id):
|
78
92
|
"""Создает кнопку, оповещающую компонент с client_id на событие event."""
|
79
|
-
handler =
|
80
|
-
client_id, event)
|
93
|
+
handler = f'function() {{Ext.getCmp("{client_id}").fireEvent("{event}");}}'
|
81
94
|
|
82
95
|
return ext.ExtButton(text=title, icon_cls=icon_cls, handler=handler)
|
83
96
|
|
@@ -107,7 +120,7 @@ class ChoicesFilter(CustomFilter):
|
|
107
120
|
self._choices = choices
|
108
121
|
kwargs['xtype'] = 'combo'
|
109
122
|
|
110
|
-
super(
|
123
|
+
super().__init__(*args, **kwargs)
|
111
124
|
|
112
125
|
def get_script(self):
|
113
126
|
"""Генерация кода компонента."""
|
@@ -126,12 +139,8 @@ class ChoicesFilter(CustomFilter):
|
|
126
139
|
|
127
140
|
|
128
141
|
class ColumnFilterWithDefaultValue(_FilterByField):
|
129
|
-
|
130
142
|
def get_script(self):
|
131
|
-
control = _create_control_for_field(
|
132
|
-
self.field,
|
133
|
-
**self._field_fabric_params
|
134
|
-
)
|
143
|
+
control = _create_control_for_field(self.field, **self._field_fabric_params)
|
135
144
|
control._put_config_value('filterName', self._uid)
|
136
145
|
control._put_config_value('tooltip', self._tooltip or control.label)
|
137
146
|
control.name = self._uid
|
@@ -142,8 +151,7 @@ class ColumnFilterWithDefaultValue(_FilterByField):
|
|
142
151
|
return [control.render()]
|
143
152
|
|
144
153
|
|
145
|
-
def reconfigure_grid_by_access(grid, can_add=False, can_edit=False,
|
146
|
-
can_delete=False, can_view=True):
|
154
|
+
def reconfigure_grid_by_access(grid, can_add=False, can_edit=False, can_delete=False, can_view=True):
|
147
155
|
"""Перенастраивает грид в зависимости от прав доступа.
|
148
156
|
|
149
157
|
:param grid: Перенастраиваемый грид.
|
@@ -172,8 +180,7 @@ def reconfigure_grid_by_access(grid, can_add=False, can_edit=False,
|
|
172
180
|
grid.url_delete = None
|
173
181
|
|
174
182
|
|
175
|
-
def reconfigure_object_tree_by_access(grid, can_add=False, can_edit=False,
|
176
|
-
can_delete=False, can_view=True):
|
183
|
+
def reconfigure_object_tree_by_access(grid, can_add=False, can_edit=False, can_delete=False, can_view=True):
|
177
184
|
"""Перенастраивает древовидный грид в зависимости от прав доступа.
|
178
185
|
|
179
186
|
:param grid: Перенастраиваемый грид.
|
@@ -212,16 +219,14 @@ class FilterByField(_FilterByField):
|
|
212
219
|
if parser:
|
213
220
|
self.parsers_map = list(self.parsers_map)
|
214
221
|
self.parsers_map.append(parser)
|
215
|
-
|
222
|
+
|
223
|
+
super().__init__(*args, **kwargs)
|
216
224
|
|
217
225
|
def create_control(self):
|
218
226
|
if self._control_creator is not None:
|
219
227
|
return self._control_creator()
|
220
228
|
|
221
|
-
return _create_control_for_field(
|
222
|
-
self.field,
|
223
|
-
**self._field_fabric_params
|
224
|
-
)
|
229
|
+
return _create_control_for_field(self.field, **self._field_fabric_params)
|
225
230
|
|
226
231
|
def get_control(self):
|
227
232
|
control = self.create_control()
|
@@ -243,75 +248,84 @@ class DatetimeFilterCreator:
|
|
243
248
|
Поддерживает значения по умолчанию.
|
244
249
|
"""
|
245
250
|
|
246
|
-
def __init__(
|
247
|
-
|
251
|
+
def __init__(
|
252
|
+
self,
|
253
|
+
model: Type[Model],
|
254
|
+
field_name: str,
|
255
|
+
get_from: Callable[[], None | date] = lambda: None,
|
256
|
+
get_to: Callable[[], None | date] = lambda: None,
|
257
|
+
min_value: Callable[[], None | date] = lambda: None,
|
258
|
+
max_value: Callable[[], None | date] = lambda: None,
|
259
|
+
) -> None:
|
248
260
|
"""Фильтр по интервалу для datetime поля.
|
249
261
|
|
250
|
-
:
|
251
|
-
|
252
|
-
|
253
|
-
|
262
|
+
Args:
|
263
|
+
model: Модель для фильтра.
|
264
|
+
field_name: Имя поля модели.
|
265
|
+
get_from: Дата по умолчанию для фильтра "С".
|
266
|
+
get_to: Дата по умолчанию для фильтра "По".
|
267
|
+
min_value: Минимально возможная дата.
|
268
|
+
max_value: Максимально возможная дата.
|
254
269
|
|
255
270
|
Значения по умолчанию передаются в качестве callable, чтобы они
|
256
271
|
вычислялись во время создания js. То есть, если в фильтре должна быть
|
257
272
|
текущая дата, а пак с колонками был создан вчера, пользователь увидит
|
258
273
|
в фильтре сегодняшнюю дату, а не вчерашнюю.
|
259
274
|
"""
|
275
|
+
|
260
276
|
self.model = model
|
261
277
|
self.field_name = field_name
|
262
278
|
|
263
279
|
assert callable(get_from)
|
264
280
|
assert callable(get_to)
|
281
|
+
assert callable(min_value)
|
282
|
+
assert callable(max_value)
|
283
|
+
|
265
284
|
self.defaults = {
|
266
285
|
'from': get_from,
|
267
286
|
'to': get_to,
|
287
|
+
'min': min_value if min_value() else lambda: date(1, 1, 1),
|
288
|
+
'max': max_value,
|
268
289
|
}
|
269
290
|
|
270
291
|
@cached_property
|
271
|
-
def filter(self):
|
292
|
+
def filter(self) -> FilterGroup:
|
272
293
|
"""Фильтр для колонки.
|
273
294
|
|
274
|
-
:
|
275
|
-
|
295
|
+
Returns:
|
296
|
+
Группа колоночных фильтров для грида.
|
276
297
|
"""
|
298
|
+
|
277
299
|
observer = ioc.get('observer')
|
278
300
|
|
279
|
-
return (
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
),
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
),
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
hour=23, minute=59, second=59, microsecond=999999
|
302
|
-
)
|
303
|
-
)
|
304
|
-
}),
|
305
|
-
tooltip='По',
|
306
|
-
)
|
301
|
+
return FilterByField(
|
302
|
+
self.model,
|
303
|
+
model_register=observer,
|
304
|
+
control_creator=lambda: ext.ExtDateField(
|
305
|
+
value=self.defaults['from'](),
|
306
|
+
min_value=self.defaults['min'](),
|
307
|
+
max_value=self.defaults['max'](),
|
308
|
+
),
|
309
|
+
field_name=self.field_name,
|
310
|
+
lookup=lambda dt: Q(**{f'{self.field_name}__gte': datetime.combine(dt, time.min)}),
|
311
|
+
tooltip='С',
|
312
|
+
) & FilterByField(
|
313
|
+
self.model,
|
314
|
+
model_register=observer,
|
315
|
+
control_creator=lambda: ext.ExtDateField(
|
316
|
+
value=self.defaults['to'](),
|
317
|
+
min_value=self.defaults['min'](),
|
318
|
+
max_value=self.defaults['max'](),
|
319
|
+
),
|
320
|
+
field_name=self.field_name,
|
321
|
+
lookup=lambda dt: Q(**{f'{self.field_name}__lte': datetime.combine(dt, time.max)}),
|
322
|
+
tooltip='По',
|
307
323
|
)
|
308
324
|
|
309
325
|
@property
|
310
|
-
def base_params(self):
|
311
|
-
"""Базовые параметры для store грида.
|
326
|
+
def base_params(self) -> dict[str, str]:
|
327
|
+
"""Базовые параметры для store грида."""
|
312
328
|
|
313
|
-
:rtype: dict
|
314
|
-
"""
|
315
329
|
result = {}
|
316
330
|
|
317
331
|
value = self.defaults['from']()
|
@@ -321,6 +335,7 @@ class DatetimeFilterCreator:
|
|
321
335
|
value = self.defaults['to']()
|
322
336
|
if value is not None:
|
323
337
|
result[self.filter._items[1]._uid] = str(value)
|
338
|
+
|
324
339
|
return result
|
325
340
|
|
326
341
|
|
@@ -335,7 +350,7 @@ def switch_window_in_read_only_mode(window):
|
|
335
350
|
assert isinstance(window, BaseEditWindow), type(window)
|
336
351
|
|
337
352
|
if window.title.endswith(': Редактирование'):
|
338
|
-
window.title = window.title[
|
353
|
+
window.title = window.title[: -len('Редактирование')] + 'Просмотр'
|
339
354
|
|
340
355
|
window.buttons.remove(window.save_btn)
|
341
356
|
window.cancel_btn.text = 'Закрыть'
|
@@ -358,16 +373,9 @@ def local_template(file_name):
|
|
358
373
|
root_package_name = frame.f_globals['__name__'].rsplit('.', 2)[0]
|
359
374
|
module = __import__(root_package_name)
|
360
375
|
|
361
|
-
TEMPLATE_DIRS = set(
|
362
|
-
path
|
363
|
-
for config in settings.TEMPLATES
|
364
|
-
for path in config.get('DIRS', ())
|
365
|
-
)
|
376
|
+
TEMPLATE_DIRS = set(path for config in settings.TEMPLATES for path in config.get('DIRS', ()))
|
366
377
|
|
367
|
-
assert any(
|
368
|
-
os.path.dirname(path) in TEMPLATE_DIRS
|
369
|
-
for path in module.__path__
|
370
|
-
), (
|
378
|
+
assert any(os.path.dirname(path) in TEMPLATE_DIRS for path in module.__path__), (
|
371
379
|
'{} package path must be in TEMPLATES config.'.format(module.__path__),
|
372
380
|
TEMPLATE_DIRS,
|
373
381
|
)
|
@@ -377,14 +385,13 @@ def local_template(file_name):
|
|
377
385
|
|
378
386
|
for path in TEMPLATE_DIRS:
|
379
387
|
if module_path.startswith(path):
|
380
|
-
module_path = module_path[len(path) + 1:]
|
388
|
+
module_path = module_path[len(path) + 1 :]
|
381
389
|
break
|
382
390
|
|
383
391
|
return os.path.join(module_path, file_name)
|
384
392
|
|
385
393
|
|
386
394
|
class FilterByTextField(FilterByField):
|
387
|
-
|
388
395
|
"""Фильтр для поля TextField, с ExtStringField в качестве контрола.
|
389
396
|
|
390
397
|
Возвращает контрол однострочного текстового поля
|
@@ -396,10 +403,7 @@ class FilterByTextField(FilterByField):
|
|
396
403
|
# Работаем только с TextField
|
397
404
|
assert isinstance(self.field, TextField)
|
398
405
|
|
399
|
-
return ext.ExtStringField(
|
400
|
-
max_length=self.field.max_length,
|
401
|
-
**self._field_fabric_params
|
402
|
-
)
|
406
|
+
return ext.ExtStringField(max_length=self.field.max_length, **self._field_fabric_params)
|
403
407
|
|
404
408
|
|
405
409
|
def append_template_globals(comp, template):
|
@@ -417,8 +421,7 @@ def append_template_globals(comp, template):
|
|
417
421
|
:type template: str or unicode
|
418
422
|
"""
|
419
423
|
if not isinstance(comp, BaseExtComponent):
|
420
|
-
raise ApplicationLogicException(
|
421
|
-
'Component has no attribute template_globals')
|
424
|
+
raise ApplicationLogicException('Component has no attribute template_globals')
|
422
425
|
if isinstance(comp.template_globals, str):
|
423
426
|
# если template_globals - пустая строка, просто заменяем ее
|
424
427
|
if len(comp.template_globals) == 0:
|
educommon/utils/xml/__init__.py
CHANGED
@@ -62,9 +62,7 @@ def parse_xml(xml):
|
|
62
62
|
|
63
63
|
|
64
64
|
def get_text(elements):
|
65
|
-
"""Возвращает текст первого элемента найденного
|
66
|
-
с помощью make_xpath_query
|
67
|
-
"""
|
65
|
+
"""Возвращает текст первого элемента найденного с помощью make_xpath_query."""
|
68
66
|
return elements[0].text if elements else ''
|
69
67
|
|
70
68
|
|
@@ -74,9 +72,6 @@ def make_xpath_query(*tags):
|
|
74
72
|
:param tags: Имена тэгов XML-документа в порядке иерархии (без учета
|
75
73
|
пространств имен).
|
76
74
|
"""
|
77
|
-
result = '/' + ''.join(
|
78
|
-
"/*[local-name()='{}']".format(tag)
|
79
|
-
for tag in tags
|
80
|
-
)
|
75
|
+
result = '/' + ''.join("/*[local-name()='{}']".format(tag) for tag in tags)
|
81
76
|
|
82
77
|
return result
|