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
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Валидаторы для простых (текстовых, числовых и т.п.) полей модели Django."""
|
2
|
+
|
2
3
|
import re
|
3
4
|
from datetime import (
|
4
5
|
datetime,
|
@@ -50,12 +51,13 @@ class SNILSValidator(RegexValidator):
|
|
50
51
|
содержит значение, несоответствующее формату СНИЛС, либо контрольное
|
51
52
|
число некорректно.
|
52
53
|
"""
|
54
|
+
|
53
55
|
regex = _snils_re.pattern
|
54
56
|
message = 'СНИЛС должен быть в формате ###-###-### ##'
|
55
57
|
flags = _snils_re.flags
|
56
58
|
|
57
59
|
def __call__(self, value):
|
58
|
-
super(
|
60
|
+
super().__call__(value)
|
59
61
|
snils_checksum_validator(value)
|
60
62
|
|
61
63
|
|
@@ -71,8 +73,7 @@ def snils_checksum_validator(value):
|
|
71
73
|
if summa > 101:
|
72
74
|
summa %= 101
|
73
75
|
|
74
|
-
if
|
75
|
-
summa in (100, 101) and checksum != 0):
|
76
|
+
if summa < 100 and summa != checksum or summa in (100, 101) and checksum != 0:
|
76
77
|
raise ValidationError('не пройдена проверка контрольного числа')
|
77
78
|
|
78
79
|
|
@@ -81,6 +82,7 @@ regex_snils_validator = SNILSValidator()
|
|
81
82
|
|
82
83
|
# для совместимости со старым валидатором-функцией
|
83
84
|
def snils_validator(value):
|
85
|
+
"""Проверка корректности СНИЛС с помощью регулярного выражения и контрольной суммы."""
|
84
86
|
value = str(value)
|
85
87
|
|
86
88
|
regex_snils_validator(value)
|
@@ -95,6 +97,7 @@ is_snils_valid = partial(validate_value, validator=snils_validator)
|
|
95
97
|
|
96
98
|
|
97
99
|
def _check_inn_checksum(coefficients, numbers, checksum):
|
100
|
+
"""Проверка контрольного числа ИНН с использованием весовых коэффициентов."""
|
98
101
|
summa = sum(c * n for c, n in zip(coefficients, numbers))
|
99
102
|
if summa % 11 % 10 != checksum:
|
100
103
|
raise ValidationError('Не пройдена проверка контрольного числа')
|
@@ -114,9 +117,7 @@ def inn10_validator(value):
|
|
114
117
|
if len(value) != 10 or not value.isdigit():
|
115
118
|
raise ValidationError('ИНН должен быть 10-ти значным числом')
|
116
119
|
|
117
|
-
_check_inn_checksum((2, 4, 10, 3, 5, 9, 4, 6, 8),
|
118
|
-
(int(ch) for ch in value[:-1]),
|
119
|
-
int(value[-1]))
|
120
|
+
_check_inn_checksum((2, 4, 10, 3, 5, 9, 4, 6, 8), (int(ch) for ch in value[:-1]), int(value[-1]))
|
120
121
|
|
121
122
|
|
122
123
|
def inn12_validator(value):
|
@@ -133,13 +134,9 @@ def inn12_validator(value):
|
|
133
134
|
if len(value) != 12 or not value.isdigit():
|
134
135
|
raise ValidationError('ИНН должен быть 12-ти значным числом')
|
135
136
|
|
136
|
-
_check_inn_checksum((7, 2, 4, 10, 3, 5, 9, 4, 6, 8),
|
137
|
-
(int(ch) for ch in value[:-2]),
|
138
|
-
int(value[-2]))
|
137
|
+
_check_inn_checksum((7, 2, 4, 10, 3, 5, 9, 4, 6, 8), (int(ch) for ch in value[:-2]), int(value[-2]))
|
139
138
|
|
140
|
-
_check_inn_checksum((3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8),
|
141
|
-
(int(ch) for ch in value[:-1]),
|
142
|
-
int(value[-1]))
|
139
|
+
_check_inn_checksum((3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8), (int(ch) for ch in value[:-1]), int(value[-1]))
|
143
140
|
|
144
141
|
|
145
142
|
def inn_validator(value):
|
@@ -206,19 +203,13 @@ def checksum_pr_50_1_024(value):
|
|
206
203
|
Если в случае повторного расчета остаток от деления вновь сохраняется
|
207
204
|
равным 10, то значение контрольного числа проставляется равным 0.
|
208
205
|
"""
|
209
|
-
summa = sum(
|
210
|
-
int(digit) * weight
|
211
|
-
for digit, weight in zip(value, cycle(list(range(1, 11))))
|
212
|
-
)
|
206
|
+
summa = sum(int(digit) * weight for digit, weight in zip(value, cycle(list(range(1, 11)))))
|
213
207
|
|
214
208
|
remainder = summa % 11
|
215
209
|
if remainder < 10:
|
216
210
|
result = remainder
|
217
211
|
else:
|
218
|
-
summa = sum(
|
219
|
-
int(digit) * weight
|
220
|
-
for digit, weight in zip(value, cycle(list(range(3, 13))))
|
221
|
-
)
|
212
|
+
summa = sum(int(digit) * weight for digit, weight in zip(value, cycle(list(range(3, 13)))))
|
222
213
|
remainder = summa % 11
|
223
214
|
if remainder < 10:
|
224
215
|
result = remainder
|
@@ -249,9 +240,7 @@ def okato_validator(value):
|
|
249
240
|
raise ValidationError('Не пройдена проверка контрольного числа')
|
250
241
|
|
251
242
|
elif value_length not in (2, 5, 8, 11):
|
252
|
-
raise ValidationError(
|
253
|
-
'Код ОКАТО должен состоять из 2, 5, 8 или 11 цифр'
|
254
|
-
)
|
243
|
+
raise ValidationError('Код ОКАТО должен состоять из 2, 5, 8 или 11 цифр')
|
255
244
|
|
256
245
|
|
257
246
|
is_okato_valid = partial(validate_value, validator=okato_validator)
|
@@ -269,9 +258,7 @@ def oktmo_validator(value):
|
|
269
258
|
|
270
259
|
value_length = len(value)
|
271
260
|
if value_length not in (2, 5, 8, 11):
|
272
|
-
raise ValidationError(
|
273
|
-
'ОКТМО не может состоять из {} цифр'.format(value_length)
|
274
|
-
)
|
261
|
+
raise ValidationError('ОКТМО не может состоять из {} цифр'.format(value_length))
|
275
262
|
|
276
263
|
|
277
264
|
is_oktmo_valid = partial(validate_value, validator=oktmo_validator)
|
@@ -304,7 +291,7 @@ def _check_ogrn_checksum(value):
|
|
304
291
|
"""
|
305
292
|
value_length = len(value) # количество цифр в ОГРН
|
306
293
|
divisor = value_length - 2 # делитель для вычисления контрольного числа
|
307
|
-
number = int(value[:value_length - 1]) # ОГРН
|
294
|
+
number = int(value[: value_length - 1]) # ОГРН
|
308
295
|
control_number = int(value[-1]) # контрольное число
|
309
296
|
|
310
297
|
if number % divisor % 10 != control_number:
|
@@ -312,16 +299,17 @@ def _check_ogrn_checksum(value):
|
|
312
299
|
|
313
300
|
|
314
301
|
def _ogrn_validator(value, valid_length, title):
|
302
|
+
"""Общая логика для проверки ОГРН и ОГРНИП.
|
303
|
+
|
304
|
+
Проверяет длину и контрольное число.
|
305
|
+
"""
|
315
306
|
value = str(value)
|
316
307
|
|
317
308
|
if not value.isdigit():
|
318
309
|
raise ValidationError('ОГРН должен состоять только из цифр')
|
319
310
|
if len(value) not in valid_length:
|
320
311
|
raise ValidationError(
|
321
|
-
'{} должен состоять из {} цифр!'.format(
|
322
|
-
title,
|
323
|
-
' или '.join(str(l) for l in valid_length)
|
324
|
-
)
|
312
|
+
'{} должен состоять из {} цифр!'.format(title, ' или '.join(str(length) for length in valid_length))
|
325
313
|
)
|
326
314
|
|
327
315
|
_check_ogrn_checksum(value)
|
@@ -373,9 +361,7 @@ def okved_validator(value):
|
|
373
361
|
|
374
362
|
value_length = len(value)
|
375
363
|
if value_length < 2 or 6 < value_length:
|
376
|
-
raise ValidationError(
|
377
|
-
'Код ОКВЭД не может состоять из {} цифр'.format(value_length)
|
378
|
-
)
|
364
|
+
raise ValidationError('Код ОКВЭД не может состоять из {} цифр'.format(value_length))
|
379
365
|
|
380
366
|
|
381
367
|
is_okved_valid = partial(validate_value, validator=okved_validator)
|
@@ -434,26 +420,18 @@ def validate_file_mime_type(file, allowed_mime_types):
|
|
434
420
|
file.seek(0, 0)
|
435
421
|
file_mime_type = magic.from_buffer(file.read(), mime=True)
|
436
422
|
if file_mime_type not in allowed_mime_types:
|
437
|
-
raise ValidationError(
|
438
|
-
'Mime type файла не соотвествует допустимым в системе'
|
439
|
-
)
|
423
|
+
raise ValidationError('Mime type файла не соотвествует допустимым в системе')
|
440
424
|
|
441
425
|
|
442
426
|
@deconstructible
|
443
427
|
class FileMimeTypeValidator:
|
444
428
|
"""Валидатор для FileField модели проверяющий mimetype файла."""
|
445
429
|
|
446
|
-
message =
|
447
|
-
'Mime type файла не допустим для загрузки в системе.'
|
448
|
-
)
|
430
|
+
message = 'Mime type файла не допустим для загрузки в системе.'
|
449
431
|
code = 'invalid_mimetype'
|
450
432
|
|
451
433
|
def __init__(self, allowed_extensions, message=None, code=None):
|
452
|
-
self.allowed_extensions = [
|
453
|
-
allowed_extension.lower()
|
454
|
-
for allowed_extension
|
455
|
-
in allowed_extensions
|
456
|
-
]
|
434
|
+
self.allowed_extensions = [allowed_extension.lower() for allowed_extension in allowed_extensions]
|
457
435
|
|
458
436
|
self.allowed_mime_types = set(
|
459
437
|
get_mime_type_for_extension(extension)
|
@@ -545,7 +523,6 @@ class OptionalFieldValidator(IModelValidator):
|
|
545
523
|
|
546
524
|
|
547
525
|
class RequiredBooleanValidator(OptionalFieldValidator):
|
548
|
-
|
549
526
|
"""Валидатор обязательного поля ``BooleanField``.
|
550
527
|
|
551
528
|
Пример:
|
@@ -554,7 +531,9 @@ class RequiredBooleanValidator(OptionalFieldValidator):
|
|
554
531
|
|
555
532
|
class Model(BaseModel):
|
556
533
|
accept_rules = models.BooleanField(...)
|
557
|
-
validators = [
|
534
|
+
validators = [
|
535
|
+
RequiredBooleanValidator('accept_rules'),
|
536
|
+
]
|
558
537
|
"""
|
559
538
|
|
560
539
|
def __init__(self, field_name, **kwargs):
|
@@ -562,7 +541,7 @@ class RequiredBooleanValidator(OptionalFieldValidator):
|
|
562
541
|
|
563
542
|
:param str field_name: имя валидируемого поля
|
564
543
|
"""
|
565
|
-
super(
|
544
|
+
super().__init__(field_name, **kwargs)
|
566
545
|
self.empty_values = (False,)
|
567
546
|
|
568
547
|
def _need_check(self, instance):
|
@@ -589,9 +568,12 @@ class RequiredFieldValidator(OptionalFieldValidator):
|
|
589
568
|
# должно быть обязательным
|
590
569
|
employee = models.ForeignKey(
|
591
570
|
'employee.Employee',
|
592
|
-
null=True,
|
571
|
+
null=True,
|
572
|
+
blank=True,
|
593
573
|
)
|
594
|
-
validators = [
|
574
|
+
validators = [
|
575
|
+
RequiredFieldValidator('employee'),
|
576
|
+
]
|
595
577
|
"""
|
596
578
|
|
597
579
|
def _need_check(self, instance):
|
@@ -607,7 +589,6 @@ class RequiredFieldValidator(OptionalFieldValidator):
|
|
607
589
|
|
608
590
|
|
609
591
|
class UnchangeableFieldValidator(IModelValidator):
|
610
|
-
|
611
592
|
"""Валидатор неизменяемого поля.
|
612
593
|
|
613
594
|
При редактировании объекта, если изменяется поле ``field_name``,
|
@@ -619,7 +600,9 @@ class UnchangeableFieldValidator(IModelValidator):
|
|
619
600
|
|
620
601
|
class Model(BaseModel):
|
621
602
|
type = models.BooleanField(...)
|
622
|
-
validators = [
|
603
|
+
validators = [
|
604
|
+
UnchangeableFieldValidator('type'),
|
605
|
+
]
|
623
606
|
|
624
607
|
"""
|
625
608
|
|
@@ -642,12 +625,9 @@ class UnchangeableFieldValidator(IModelValidator):
|
|
642
625
|
|
643
626
|
:rtype: str
|
644
627
|
"""
|
645
|
-
verbose_name = instance._meta.get_field(
|
646
|
-
|
647
|
-
|
648
|
-
return 'Поле "{}" не доступно для редактирования.'.format(
|
649
|
-
verbose_name
|
650
|
-
)
|
628
|
+
verbose_name = instance._meta.get_field(self.field_name).verbose_name
|
629
|
+
|
630
|
+
return 'Поле "{}" не доступно для редактирования.'.format(verbose_name)
|
651
631
|
|
652
632
|
def clean(self, instance, errors):
|
653
633
|
"""Проверка редактирования поля ``field_name``.
|
@@ -669,7 +649,6 @@ class UnchangeableFieldValidator(IModelValidator):
|
|
669
649
|
|
670
650
|
|
671
651
|
class DuplicationValidator(IModelValidator):
|
672
|
-
|
673
652
|
"""Валидатор уникальности объекта.
|
674
653
|
|
675
654
|
Используется для объектов, уникальность которых нельзя проверить
|
@@ -734,11 +713,8 @@ class DuplicationValidator(IModelValidator):
|
|
734
713
|
|
735
714
|
:rtype: str
|
736
715
|
"""
|
737
|
-
return (
|
738
|
-
'
|
739
|
-
).format(
|
740
|
-
instance._meta.verbose_name,
|
741
|
-
', '.join(self._get_field_names(instance))
|
716
|
+
return ('В системе уже существует {} с такими же значениями полей {}.').format(
|
717
|
+
instance._meta.verbose_name, ', '.join(self._get_field_names(instance))
|
742
718
|
)
|
743
719
|
|
744
720
|
def _get_base_query(self, instance):
|
@@ -762,9 +738,7 @@ class DuplicationValidator(IModelValidator):
|
|
762
738
|
:type errors: collections.OrderedDict
|
763
739
|
"""
|
764
740
|
original = get_original_object(instance)
|
765
|
-
query = self._get_base_query(instance).filter(
|
766
|
-
self._get_duplication_params(instance)
|
767
|
-
)
|
741
|
+
query = self._get_base_query(instance).filter(self._get_duplication_params(instance))
|
768
742
|
|
769
743
|
if original:
|
770
744
|
query = query.exclude(id=instance.id)
|
@@ -774,12 +748,10 @@ class DuplicationValidator(IModelValidator):
|
|
774
748
|
|
775
749
|
|
776
750
|
class SingleErrorDecimalValidator(DecimalValidator):
|
777
|
-
|
778
751
|
"""Кастомный класс валидации Decimal поля модели."""
|
779
752
|
|
780
753
|
def __init__(self, max_digits, decimal_places):
|
781
|
-
super(
|
782
|
-
max_digits, decimal_places)
|
754
|
+
super().__init__(max_digits, decimal_places)
|
783
755
|
self.max_whole_digits = max_digits - decimal_places
|
784
756
|
|
785
757
|
def __call__(self, value):
|
@@ -808,8 +780,11 @@ class SingleErrorDecimalValidator(DecimalValidator):
|
|
808
780
|
code='max_decimal_places',
|
809
781
|
params={'max': self.decimal_places},
|
810
782
|
)
|
811
|
-
if (
|
812
|
-
|
783
|
+
if (
|
784
|
+
self.max_digits is not None
|
785
|
+
and self.decimal_places is not None
|
786
|
+
and whole_digits > (self.max_digits - self.decimal_places)
|
787
|
+
):
|
813
788
|
raise ValidationError(
|
814
789
|
self.messages['max_whole_digits'],
|
815
790
|
code='max_whole_digits',
|
@@ -821,13 +796,14 @@ class SingleErrorDecimalValidator(DecimalValidator):
|
|
821
796
|
# Валидаторы персональных данных
|
822
797
|
# =============================================================================
|
823
798
|
|
799
|
+
|
824
800
|
class FIOValidator(RegexValidator):
|
825
801
|
regex = r'(^$)|(^[a-zA-Zа-яА-ЯёЁ]([\s-]?[a-zA-Zа-яА-ЯёЁ])*$)'
|
826
802
|
|
827
803
|
|
828
|
-
def date_range_validator(
|
829
|
-
|
830
|
-
|
804
|
+
def date_range_validator(
|
805
|
+
minimum=None, maximum=None, range_message=None, min_message=None, max_message=None, date_format='%d.%m.%Y'
|
806
|
+
):
|
831
807
|
"""Валидатор даты. Возвращает функцию, в которую должен быть помещен один
|
832
808
|
аргумент: проверяемое значение
|
833
809
|
:param minimum: значение, меньше которого value быть не должно;
|
@@ -857,11 +833,9 @@ def date_range_validator(minimum=None, maximum=None,
|
|
857
833
|
:return: функция, в которую передается проверяемое значение и которая ведет
|
858
834
|
себя как обычный валидатор поля Django
|
859
835
|
"""
|
860
|
-
assert minimum is not None or maximum is not None,
|
861
|
-
'Необходимо определелить границы для валидатора дат')
|
836
|
+
assert minimum is not None or maximum is not None, 'Необходимо определелить границы для валидатора дат'
|
862
837
|
|
863
|
-
range_message =
|
864
|
-
'Значение %(val)s не входит в диапазон %(min)s-%(max)s'))
|
838
|
+
range_message = range_message or ('Значение %(val)s не входит в диапазон %(min)s-%(max)s')
|
865
839
|
min_message = min_message or 'Значение %(val)s меньше, чем %(min)s'
|
866
840
|
max_message = max_message or 'Значение %(val)s больше %(max)s'
|
867
841
|
|
@@ -876,8 +850,9 @@ def date_range_validator(minimum=None, maximum=None,
|
|
876
850
|
max_date = maximum or datetime.max.date()
|
877
851
|
date_format = date_format
|
878
852
|
|
879
|
-
return partial(
|
880
|
-
|
853
|
+
return partial(
|
854
|
+
_date_range_validator, min_date=min_date, max_date=max_date, message=message, date_format=date_format
|
855
|
+
)
|
881
856
|
|
882
857
|
|
883
858
|
def _date_range_validator(value, min_date, max_date, message, date_format):
|
@@ -895,12 +870,14 @@ def _date_range_validator(value, min_date, max_date, message, date_format):
|
|
895
870
|
:raises ValidationError
|
896
871
|
"""
|
897
872
|
# должны быть все аргументы
|
898
|
-
assert all(
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
873
|
+
assert all(
|
874
|
+
[
|
875
|
+
min_date is not None,
|
876
|
+
max_date is not None,
|
877
|
+
message is not None,
|
878
|
+
date_format is not None,
|
879
|
+
]
|
880
|
+
), 'Все аргументы должны быть указаны (см. сигнатуру функции)'
|
904
881
|
|
905
882
|
if isinstance(value, datetime):
|
906
883
|
value = value.date()
|
@@ -915,9 +892,7 @@ def _date_range_validator(value, min_date, max_date, message, date_format):
|
|
915
892
|
}
|
916
893
|
|
917
894
|
if not _min <= value <= _max:
|
918
|
-
raise ValidationError(
|
919
|
-
message % message_values
|
920
|
-
)
|
895
|
+
raise ValidationError(message % message_values)
|
921
896
|
|
922
897
|
|
923
898
|
class HouseValidator(RegexValidator):
|
@@ -935,10 +910,8 @@ class HouseValidator(RegexValidator):
|
|
935
910
|
то регулярку нужно будет расширить до
|
936
911
|
'^(?=.{,20}$)([0-9IVXА-ЯЁа-яё"/_,.-]{1,} ?){1,}\b$'
|
937
912
|
"""
|
938
|
-
|
939
|
-
|
940
|
-
re.IGNORECASE | re.UNICODE
|
941
|
-
)
|
913
|
+
|
914
|
+
regex = re.compile(r'^(?=.{,12}$)([0-9а-яё"/_,.-]{1,} ?){1,}\b$', re.IGNORECASE | re.UNICODE)
|
942
915
|
message = 'Неверно указан номер дома'
|
943
916
|
|
944
917
|
|
@@ -946,6 +919,7 @@ regex_house_validator = HouseValidator()
|
|
946
919
|
|
947
920
|
|
948
921
|
def house_validator(value):
|
922
|
+
"""Функция для валидации номера дома."""
|
949
923
|
value = str(value)
|
950
924
|
|
951
925
|
regex_house_validator(value)
|
@@ -956,6 +930,7 @@ is_house_number_valid = partial(validate_value, validator=house_validator)
|
|
956
930
|
|
957
931
|
class BuildingValidator(RegexValidator):
|
958
932
|
"""Валидатор номера корпуса дома."""
|
933
|
+
|
959
934
|
regex = re.compile(r'^[0-9а-яё/_.-]{0,10}$', re.IGNORECASE | re.UNICODE)
|
960
935
|
message = 'Неверно указан корпус дома'
|
961
936
|
|
@@ -978,6 +953,7 @@ regex_doc_type_validator = DocumentTypeValidator()
|
|
978
953
|
|
979
954
|
|
980
955
|
def doc_type_validator(value):
|
956
|
+
"""Функция для валидации строки с типом документа."""
|
981
957
|
value = str(value)
|
982
958
|
|
983
959
|
regex_doc_type_validator(value)
|
@@ -997,14 +973,10 @@ class PassportNumberValidator(RegexValidator):
|
|
997
973
|
|
998
974
|
|
999
975
|
class DocumentSeriesValidator(RegexValidator):
|
1000
|
-
regex = (
|
1001
|
-
r'(^$)|(^[a-zA-Zа-яА-ЯёЁ\d]([\s|\-|\.|\,|\\|\/]?[a-zA-Zа-яА-ЯёЁ\d])*$)'
|
1002
|
-
)
|
976
|
+
regex = r'(^$)|(^[a-zA-Zа-яА-ЯёЁ\d]([\s|\-|\.|\,|\\|\/]?[a-zA-Zа-яА-ЯёЁ\d])*$)'
|
1003
977
|
message = 'Неверно задана серия документа'
|
1004
978
|
|
1005
979
|
|
1006
980
|
class DocumentNumberValidator(RegexValidator):
|
1007
|
-
regex = (
|
1008
|
-
r'(^$)|(^[a-zA-Zа-яА-ЯёЁ\d]([\s|\-|\.|\,|\\|\/]?[a-zA-Zа-яА-ЯёЁ\d])*$)'
|
1009
|
-
)
|
981
|
+
regex = r'(^$)|(^[a-zA-Zа-яА-ЯёЁ\d]([\s|\-|\.|\,|\\|\/]?[a-zA-Zа-яА-ЯёЁ\d])*$)'
|
1010
982
|
message = 'Неверно задан номер документа'
|
@@ -17,32 +17,33 @@ from educommon.django.storages.atcfs.exceptions import (
|
|
17
17
|
|
18
18
|
|
19
19
|
class AtcfsApi:
|
20
|
-
"""
|
21
|
-
Класс для работы с запросами к ATCFS
|
22
|
-
"""
|
20
|
+
"""Класс для работы с запросами к ATCFS."""
|
23
21
|
|
24
22
|
def _build_url(self, *args):
|
25
|
-
"""
|
26
|
-
|
23
|
+
"""Функция составления полного урла.
|
24
|
+
|
27
25
|
:param args: составные части пути
|
28
26
|
:return: фбсолютный урл
|
29
27
|
"""
|
30
28
|
chunks = (settings.URL,) + args
|
31
29
|
url = '/'.join(chunks)
|
30
|
+
|
32
31
|
return url
|
33
32
|
|
34
33
|
def _get_credential_headers(self):
|
35
|
-
"""
|
36
|
-
|
34
|
+
"""Метод генерации данных для аутентификации на сервере ATCFS.
|
35
|
+
|
37
36
|
:return: словарь с необходимыми для атунетификации полями
|
38
37
|
"""
|
39
38
|
request_id = str(uuid.uuid4())
|
40
|
-
sign = '{vis_id}_{vis_user}_{request_id}_{secret_key}'.format(
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
39
|
+
sign = '{vis_id}_{vis_user}_{request_id}_{secret_key}'.format(
|
40
|
+
**{
|
41
|
+
'vis_id': settings.VIS_ID,
|
42
|
+
'vis_user': settings.VIS_USER,
|
43
|
+
'request_id': request_id,
|
44
|
+
'secret_key': settings.SECRET_KEY,
|
45
|
+
}
|
46
|
+
)
|
46
47
|
sign = hashlib.md5(sign).hexdigest()
|
47
48
|
headers = {
|
48
49
|
'AtcFs-VisId': settings.VIS_ID,
|
@@ -50,11 +51,12 @@ class AtcfsApi:
|
|
50
51
|
'AtcFs-RequestId': request_id,
|
51
52
|
'AtcFs-Sign': sign,
|
52
53
|
}
|
54
|
+
|
53
55
|
return headers
|
54
56
|
|
55
57
|
def _send_request(self, method, url, headers=None, params=None, data=None):
|
56
|
-
"""
|
57
|
-
|
58
|
+
"""Отправка запроса.
|
59
|
+
|
58
60
|
:param method: get|post|delete
|
59
61
|
:param url: URL, на который отправляется запрос
|
60
62
|
:param headers: дополнительные заголовки
|
@@ -71,18 +73,18 @@ class AtcfsApi:
|
|
71
73
|
headers=full_headers,
|
72
74
|
params=params,
|
73
75
|
data=data,
|
74
|
-
timeout=(settings.CONNECT_TIMEOUT, None)
|
76
|
+
timeout=(settings.CONNECT_TIMEOUT, None),
|
75
77
|
)
|
76
78
|
except (
|
77
79
|
requests.exceptions.ConnectionError,
|
78
80
|
requests.exceptions.ConnectTimeout,
|
79
|
-
requests.packages.urllib3.exceptions.ConnectTimeoutError
|
81
|
+
requests.packages.urllib3.exceptions.ConnectTimeoutError,
|
80
82
|
):
|
81
83
|
raise AtcfsUnavailable()
|
82
84
|
|
83
85
|
def upload_file(self, name, content):
|
84
|
-
"""
|
85
|
-
|
86
|
+
"""Загрузка файла на сервер ATCFS.
|
87
|
+
|
86
88
|
:param name: название файла
|
87
89
|
:param content: содержимое файла
|
88
90
|
:return: идентификатор файла на сервере ATCFS
|
@@ -91,34 +93,24 @@ class AtcfsApi:
|
|
91
93
|
headers = {'Content-Type': 'application/octet-stream'}
|
92
94
|
params = {'fileName': name}
|
93
95
|
data = content
|
94
|
-
response = self._send_request(
|
95
|
-
method='post',
|
96
|
-
url=url,
|
97
|
-
headers=headers,
|
98
|
-
params=params,
|
99
|
-
data=data
|
100
|
-
)
|
96
|
+
response = self._send_request(method='post', url=url, headers=headers, params=params, data=data)
|
101
97
|
if response.status_code != 201:
|
102
98
|
raise Exception(response.text)
|
103
99
|
ident = response.text
|
100
|
+
|
104
101
|
return ident
|
105
102
|
|
106
103
|
def download_file(self, ident):
|
107
|
-
"""
|
108
|
-
|
104
|
+
"""Загружаем файл с сервера в память.
|
105
|
+
|
109
106
|
:param ident: идентификатор файла
|
110
107
|
:return: тюпл название и содержимое
|
111
108
|
"""
|
112
109
|
url = self._build_url(settings.FILES_PATH, ident)
|
113
|
-
response = self._send_request(
|
114
|
-
method='get',
|
115
|
-
url=url
|
116
|
-
)
|
110
|
+
response = self._send_request(method='get', url=url)
|
117
111
|
if response.status_code != 200:
|
118
112
|
raise Exception(response.text)
|
119
|
-
_, params = cgi.parse_header(
|
120
|
-
response.headers.get('Content-Disposition')
|
121
|
-
)
|
113
|
+
_, params = cgi.parse_header(response.headers.get('Content-Disposition'))
|
122
114
|
file_name = params['filename*']
|
123
115
|
try:
|
124
116
|
file_name = re.findall(r'UTF-8\'\'(.*)', file_name)[0]
|
@@ -126,49 +118,42 @@ class AtcfsApi:
|
|
126
118
|
except IndexError:
|
127
119
|
pass
|
128
120
|
file_content = response.content
|
121
|
+
|
129
122
|
return file_name, file_content
|
130
123
|
|
131
124
|
def delete_file(self, ident):
|
132
|
-
"""
|
133
|
-
|
125
|
+
"""Удаление файла на сервере ATCFS.
|
126
|
+
|
134
127
|
:param ident: идентификатор файла
|
135
128
|
"""
|
136
129
|
url = self._build_url(settings.FILES_PATH, ident)
|
137
|
-
response = self._send_request(
|
138
|
-
method='delete',
|
139
|
-
url=url
|
140
|
-
)
|
130
|
+
response = self._send_request(method='delete', url=url)
|
141
131
|
if response.status_code != 200:
|
142
132
|
raise Exception(response.text)
|
143
133
|
|
144
134
|
def get_file_url(self, ident):
|
145
|
-
"""
|
146
|
-
|
135
|
+
"""Получить прямую ссылку на файл.
|
136
|
+
|
147
137
|
:param ident: идентификатор файла
|
148
138
|
:return: url
|
149
139
|
"""
|
150
140
|
url = self._build_url(settings.TMP_FILE_LINK_PATH, ident)
|
151
|
-
response = self._send_request(
|
152
|
-
method='get',
|
153
|
-
url=url
|
154
|
-
)
|
141
|
+
response = self._send_request(method='get', url=url)
|
155
142
|
if response.status_code != 200:
|
156
143
|
raise Exception(response.text)
|
157
144
|
tmp_ident = response.text
|
158
145
|
file_url = self._build_url(settings.TMP_FILES_PATH, tmp_ident)
|
146
|
+
|
159
147
|
return file_url
|
160
148
|
|
161
149
|
def get_file_info(self, ident):
|
162
|
-
"""
|
163
|
-
|
150
|
+
"""Получить информацию о файле.
|
151
|
+
|
164
152
|
:param ident: идентификатор файла
|
165
153
|
:return: словарь с названием файла и его размером
|
166
154
|
"""
|
167
155
|
url = self._build_url(settings.FILE_INFO_PATH, ident)
|
168
|
-
response = self._send_request(
|
169
|
-
method='get',
|
170
|
-
url=url
|
171
|
-
)
|
156
|
+
response = self._send_request(method='get', url=url)
|
172
157
|
if response.status_code != 200:
|
173
158
|
raise Exception(response.text)
|
174
159
|
file_json = response.json()
|
@@ -176,4 +161,5 @@ class AtcfsApi:
|
|
176
161
|
'name': file_json['fileName'],
|
177
162
|
'size': file_json['size'],
|
178
163
|
}
|
164
|
+
|
179
165
|
return file_info
|