educommon 3.12.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 +149 -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.2.dist-info/METADATA +57 -0
- educommon-3.13.2.dist-info/RECORD +354 -0
- {educommon-3.12.0.dist-info → educommon-3.13.2.dist-info}/WHEEL +1 -1
- educommon/utils/patches.py +0 -27
- educommon/version.conf +0 -11
- educommon-3.12.0.dist-info/METADATA +0 -47
- educommon-3.12.0.dist-info/RECORD +0 -357
- educommon-3.12.0.dist-info/dependency_links.txt +0 -1
- {educommon-3.12.0.dist-info → educommon-3.13.2.dist-info}/top_level.txt +0 -0
@@ -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
|
@@ -29,8 +29,8 @@ from educommon.django.storages.atcfs.api import (
|
|
29
29
|
|
30
30
|
|
31
31
|
def dictfetchall(cursor):
|
32
|
-
"""
|
33
|
-
|
32
|
+
"""Вспомогательная функция.
|
33
|
+
|
34
34
|
cursor.fetchall возвращает данные в виде списка списков:
|
35
35
|
(('43', 'text 1'), ('44', 'text 2'), ('45', 'text 3'))
|
36
36
|
Эта функция преобразует в вид:
|
@@ -40,13 +40,13 @@ def dictfetchall(cursor):
|
|
40
40
|
"""
|
41
41
|
desc = cursor.description
|
42
42
|
columns = [col[0] for col in desc]
|
43
|
+
|
43
44
|
return [dict(list(zip(columns, row))) for row in cursor.fetchall()]
|
44
45
|
|
45
46
|
|
46
47
|
class Command(BaseCommand):
|
47
|
-
"""
|
48
|
-
|
49
|
-
в которых есть поля FileField.
|
48
|
+
"""Команда обходит все зарегистрированные модели, в которых есть поля FileField.
|
49
|
+
|
50
50
|
Если для поля установлен AtcfsStorage, или он установлен глобально,
|
51
51
|
то файл переносится на сервер ATCFS.
|
52
52
|
"""
|
@@ -61,12 +61,13 @@ class Command(BaseCommand):
|
|
61
61
|
)
|
62
62
|
|
63
63
|
def __init__(self):
|
64
|
-
super(
|
64
|
+
super().__init__()
|
65
|
+
|
65
66
|
self.api = AtcfsApi()
|
66
67
|
|
67
68
|
def _get_fields(self, model):
|
68
|
-
"""
|
69
|
-
|
69
|
+
"""Выбираем в модели все переменные, являющиеся FileField-полями.
|
70
|
+
|
70
71
|
:param model: класс модели
|
71
72
|
:return: список названий полей FileField
|
72
73
|
"""
|
@@ -78,20 +79,18 @@ class Command(BaseCommand):
|
|
78
79
|
continue
|
79
80
|
if isinstance(mem, FileDescriptor):
|
80
81
|
fields.append(nam)
|
82
|
+
|
81
83
|
return fields
|
82
84
|
|
83
85
|
def _get_models(self):
|
84
|
-
"""
|
85
|
-
|
86
|
+
"""Берем все модели, в которых есть FileField.
|
87
|
+
|
86
88
|
:return: список классов моделей
|
87
89
|
"""
|
88
90
|
models = {}
|
89
91
|
|
90
|
-
is_model
|
91
|
-
inspect.isclass(x) and
|
92
|
-
isinstance(x, ModelBase) and
|
93
|
-
not x._meta.abstract
|
94
|
-
)
|
92
|
+
def is_model(x):
|
93
|
+
return inspect.isclass(x) and isinstance(x, ModelBase) and not x._meta.abstract
|
95
94
|
|
96
95
|
apps = settings.INSTALLED_APPS
|
97
96
|
for app in apps:
|
@@ -108,18 +107,18 @@ class Command(BaseCommand):
|
|
108
107
|
return models
|
109
108
|
|
110
109
|
def _delete_all_files(self, models):
|
111
|
-
"""
|
112
|
-
|
113
|
-
В работе команды не учавствует.
|
110
|
+
"""Сервисный метод.
|
111
|
+
|
112
|
+
Используется для технических нужд. В работе команды не учавствует.
|
114
113
|
"""
|
115
114
|
for model, fields in models.items():
|
116
|
-
kwargs = dict(list(zip(fields, ['']*len(fields))))
|
115
|
+
kwargs = dict(list(zip(fields, [''] * len(fields))))
|
117
116
|
cnt = model.objects.all().update(**kwargs)
|
118
117
|
print('{0}: {1}'.format(model, cnt))
|
119
118
|
|
120
119
|
def _send_file(self, file_name):
|
121
|
-
"""
|
122
|
-
|
120
|
+
"""Непосредственная отправка файла на ATCFS.
|
121
|
+
|
123
122
|
:param file_name: название файла
|
124
123
|
:return: идентификатор файла в ATCFS
|
125
124
|
"""
|
@@ -127,37 +126,31 @@ class Command(BaseCommand):
|
|
127
126
|
file_path = os.path.join(settings.MEDIA_ROOT, file_name)
|
128
127
|
try:
|
129
128
|
with open(file_path, 'r') as fd:
|
130
|
-
ident = self.api.upload_file(
|
131
|
-
os.path.basename(file_name), fd.read()
|
132
|
-
)
|
129
|
+
ident = self.api.upload_file(os.path.basename(file_name), fd.read())
|
133
130
|
except IOError:
|
134
131
|
pass
|
132
|
+
|
135
133
|
return ident
|
136
134
|
|
137
135
|
def _get_objs(self, model, fields):
|
138
|
-
"""
|
139
|
-
|
136
|
+
"""Запрашиваем из базы напрямую объекты по модели.
|
137
|
+
|
140
138
|
:param model: класс модели
|
141
139
|
:param fields: список полей
|
142
140
|
:return: список словарей в которых id и значения полей
|
143
141
|
"""
|
144
142
|
cursor = connection.cursor()
|
145
143
|
select_fields = ', '.join(fields)
|
146
|
-
where_fields = ' OR '.join(
|
147
|
-
|
148
|
-
)
|
149
|
-
sql = 'SELECT id, {0} from {1} WHERE {2};'.format(
|
150
|
-
select_fields,
|
151
|
-
model._meta.db_table,
|
152
|
-
where_fields
|
153
|
-
)
|
144
|
+
where_fields = ' OR '.join(["COALESCE({0}, '') <> ''".format(field) for field in fields])
|
145
|
+
sql = 'SELECT id, {0} from {1} WHERE {2};'.format(select_fields, model._meta.db_table, where_fields)
|
154
146
|
cursor.execute(sql)
|
155
147
|
objs = dictfetchall(cursor)
|
148
|
+
|
156
149
|
return objs
|
157
150
|
|
158
151
|
def _update_objs(self, model, objs):
|
159
|
-
"""
|
160
|
-
|
152
|
+
"""Изменяем значения полей в базе.
|
153
|
+
|
161
154
|
:param model: класс модели
|
162
155
|
:param objs: словарь списков, где ключ - id объекта,
|
163
156
|
а значение - список тюплов (название, значение)
|
@@ -165,28 +158,20 @@ class Command(BaseCommand):
|
|
165
158
|
cursor = connection.cursor()
|
166
159
|
sql = ''
|
167
160
|
for obj_id, obj_fields in objs.items():
|
168
|
-
set_fields = ', '.join(
|
169
|
-
|
170
|
-
)
|
171
|
-
sql += 'UPDATE {0} SET {1} WHERE id = {2};'.format(
|
172
|
-
model._meta.db_table,
|
173
|
-
set_fields,
|
174
|
-
obj_id
|
175
|
-
)
|
161
|
+
set_fields = ', '.join(["{0[0]} = '{0[1]}'".format(field) for field in obj_fields])
|
162
|
+
sql += 'UPDATE {0} SET {1} WHERE id = {2};'.format(model._meta.db_table, set_fields, obj_id)
|
176
163
|
if sql:
|
177
164
|
cursor.execute(sql)
|
178
165
|
commit_unless_managed()
|
179
166
|
|
180
167
|
def _migrate_all_files(self, models):
|
181
|
-
"""
|
182
|
-
|
168
|
+
"""Проходимся по всем моделям, всем объектам, отсылаем файлы на ATCFS.
|
169
|
+
|
183
170
|
:param models: модели, в которых есть FileField
|
184
171
|
"""
|
185
172
|
total = len(models)
|
186
173
|
for i, (model, fields) in enumerate(models.items(), start=1):
|
187
|
-
print(self.style.SQL_KEYWORD(
|
188
|
-
'{0} ({1}/{2})'.format(model, i, total)
|
189
|
-
))
|
174
|
+
print(self.style.SQL_KEYWORD('{0} ({1}/{2})'.format(model, i, total)))
|
190
175
|
objs = self._get_objs(model, fields)
|
191
176
|
updated_objs = {}
|
192
177
|
for obj in objs:
|
@@ -196,12 +181,14 @@ class Command(BaseCommand):
|
|
196
181
|
if field_value:
|
197
182
|
ident = self._send_file(field_value)
|
198
183
|
updated_fields.append((field_name, ident))
|
199
|
-
print(
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
184
|
+
print(
|
185
|
+
'{0},{1},{2},{3}'.format(
|
186
|
+
obj['id'],
|
187
|
+
field_name.decode('UTF-8'),
|
188
|
+
field_value.decode('UTF-8'),
|
189
|
+
ident.decode('UTF-8'),
|
190
|
+
)
|
191
|
+
)
|
205
192
|
updated_objs[obj['id']] = updated_fields
|
206
193
|
self._update_objs(model, updated_objs)
|
207
194
|
|
@@ -1,7 +1,5 @@
|
|
1
|
-
"""
|
2
|
-
|
3
|
-
для работы с AtcfsStorage.
|
4
|
-
"""
|
1
|
+
"""Внедряем в джанговский дефолтный FieldFile необходимый функционал для работы с AtcfsStorage."""
|
2
|
+
|
5
3
|
import re
|
6
4
|
|
7
5
|
from django.core.files.storage import (
|
@@ -21,8 +19,8 @@ DEFAULT_FILE_STORAGE = get_storage_class()
|
|
21
19
|
|
22
20
|
|
23
21
|
def is_atcfs_storage(storage):
|
24
|
-
"""
|
25
|
-
|
22
|
+
"""Функция определяет является ли переданный storage AtcfsStorage.
|
23
|
+
|
26
24
|
:param storage: объект Storage
|
27
25
|
:return: True/False
|
28
26
|
"""
|
@@ -30,11 +28,12 @@ def is_atcfs_storage(storage):
|
|
30
28
|
# Второй случай когда в сетингсах установлен DEFAULT_FILE_STORAGE,
|
31
29
|
# и он не переопределен через параметр storage в филде.
|
32
30
|
if (
|
33
|
-
isinstance(storage, AtcfsStorage)
|
34
|
-
isinstance(storage, DefaultStorage)
|
35
|
-
|
31
|
+
isinstance(storage, AtcfsStorage)
|
32
|
+
or isinstance(storage, DefaultStorage)
|
33
|
+
and DEFAULT_FILE_STORAGE == AtcfsStorage
|
36
34
|
):
|
37
35
|
return True
|
36
|
+
|
38
37
|
return False
|
39
38
|
|
40
39
|
|
@@ -42,13 +41,13 @@ def is_atcfs_storage(storage):
|
|
42
41
|
# Необходимо установить field_name,
|
43
42
|
# в котором будет храниться реальное название файла.
|
44
43
|
|
45
|
-
uuid_re = re.compile(
|
46
|
-
r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
|
47
|
-
)
|
44
|
+
uuid_re = re.compile(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
|
48
45
|
|
49
46
|
old_field_file__init__ = files.FieldFile.__init__
|
50
47
|
|
48
|
+
|
51
49
|
def new_field_file__init__(self, instance, field, name):
|
50
|
+
"""Инициализирует FieldFile с дополнительной логикой для AtcfsStorage."""
|
52
51
|
old_field_file__init__(self, instance, field, name)
|
53
52
|
if is_atcfs_storage(self.storage):
|
54
53
|
if self.name and uuid_re.match(self.name):
|
@@ -56,6 +55,7 @@ def new_field_file__init__(self, instance, field, name):
|
|
56
55
|
else:
|
57
56
|
self.file_name = ''
|
58
57
|
|
58
|
+
|
59
59
|
files.FieldFile.__init__ = new_field_file__init__
|
60
60
|
|
61
61
|
|
@@ -63,12 +63,15 @@ files.FieldFile.__init__ = new_field_file__init__
|
|
63
63
|
|
64
64
|
old_field_file__str__ = files.FieldFile.__str__
|
65
65
|
|
66
|
+
|
66
67
|
def new_field_file__str__(self):
|
68
|
+
"""Возвращает строковое представление файла."""
|
67
69
|
if is_atcfs_storage(self.storage):
|
68
70
|
return self.file_name or ''
|
69
71
|
else:
|
70
72
|
return old_field_file__str__(self)
|
71
73
|
|
74
|
+
|
72
75
|
files.FieldFile.__str__ = new_field_file__str__
|
73
76
|
|
74
77
|
|
@@ -76,7 +79,9 @@ files.FieldFile.__str__ = new_field_file__str__
|
|
76
79
|
|
77
80
|
old_file_field_get_prep_value = files.FileField.get_prep_value
|
78
81
|
|
82
|
+
|
79
83
|
def new_file_field_get_prep_value(self, value):
|
84
|
+
"""Подготавливает значение FileField для сохранения в БД."""
|
80
85
|
if is_atcfs_storage(self.storage):
|
81
86
|
if value is None:
|
82
87
|
return None
|
@@ -84,4 +89,5 @@ def new_file_field_get_prep_value(self, value):
|
|
84
89
|
else:
|
85
90
|
return old_file_field_get_prep_value(self, value)
|
86
91
|
|
92
|
+
|
87
93
|
files.FileField.get_prep_value = new_file_field_get_prep_value
|
@@ -24,20 +24,18 @@ from educommon.django.storages.atcfs.exceptions import (
|
|
24
24
|
|
25
25
|
|
26
26
|
# Сообщение, выдаваемое в интерфейс при сохранении если сервер недоступен.
|
27
|
-
ATCFS_UNAVAILABLE_MSG =
|
27
|
+
ATCFS_UNAVAILABLE_MSG = """
|
28
28
|
Извините, в настоящий момент внешнее файловое хранилище недоступно,
|
29
29
|
сохранение приложенного файла невозможно. Пожалуйста, повторите действие позже
|
30
30
|
или удалите приложенный файл перед сохранением.
|
31
|
-
|
31
|
+
"""
|
32
32
|
|
33
33
|
# Ссылка на файл и имя, когда недоступен сервер.
|
34
34
|
UNAVAILABLE_FILE_NAME = 'Файл недоступен (сбой в работе файлового хранилища)'
|
35
35
|
|
36
36
|
|
37
37
|
class AtcfsStorage(Storage):
|
38
|
-
"""
|
39
|
-
ATCFS Storage
|
40
|
-
"""
|
38
|
+
"""ATCFS Storage."""
|
41
39
|
|
42
40
|
def __init__(self):
|
43
41
|
self.api = AtcfsApi()
|
@@ -55,6 +53,7 @@ class AtcfsStorage(Storage):
|
|
55
53
|
except AtcfsUnavailable:
|
56
54
|
# Выдаем сообщение непосредственно в интерфейс.
|
57
55
|
raise ApplicationLogicException(ATCFS_UNAVAILABLE_MSG)
|
56
|
+
|
58
57
|
return ident
|
59
58
|
|
60
59
|
def delete(self, ident):
|
@@ -72,6 +71,7 @@ class AtcfsStorage(Storage):
|
|
72
71
|
file_url = self.api.get_file_url(ident)
|
73
72
|
except AtcfsUnavailable:
|
74
73
|
file_url = reverse('atcfs_unavailable')
|
74
|
+
|
75
75
|
return file_url
|
76
76
|
|
77
77
|
def size(self, ident):
|
@@ -80,6 +80,7 @@ class AtcfsStorage(Storage):
|
|
80
80
|
file_size = file_info['size']
|
81
81
|
except AtcfsUnavailable:
|
82
82
|
file_size = 0
|
83
|
+
|
83
84
|
return file_size
|
84
85
|
|
85
86
|
def name(self, ident):
|
@@ -88,12 +89,11 @@ class AtcfsStorage(Storage):
|
|
88
89
|
file_name = file_info['name']
|
89
90
|
except AtcfsUnavailable:
|
90
91
|
file_name = UNAVAILABLE_FILE_NAME
|
92
|
+
|
91
93
|
return file_name
|
92
94
|
|
93
95
|
def path(self, ident):
|
94
|
-
"""
|
95
|
-
Загружаем файл, сохраняем его во временной папке, отдаем путь.
|
96
|
-
"""
|
96
|
+
"""Загружаем файл, сохраняем его во временной папке, отдаем путь."""
|
97
97
|
try:
|
98
98
|
file_name, file_content = self.api.download_file(ident)
|
99
99
|
except AtcfsUnavailable:
|
@@ -103,34 +103,25 @@ class AtcfsStorage(Storage):
|
|
103
103
|
file_path = os.path.join(dir_path, file_name)
|
104
104
|
with open(file_path, 'w') as fd:
|
105
105
|
fd.write(file_content)
|
106
|
+
|
106
107
|
return file_path
|
107
108
|
|
108
109
|
def exists(self, name):
|
109
|
-
"""
|
110
|
-
Заглушка
|
111
|
-
"""
|
110
|
+
"""Заглушка."""
|
112
111
|
return False
|
113
112
|
|
114
113
|
def listdir(self, path):
|
115
|
-
"""
|
116
|
-
Заглушка
|
117
|
-
"""
|
114
|
+
"""Заглушка."""
|
118
115
|
return [], []
|
119
116
|
|
120
117
|
def accessed_time(self, name):
|
121
|
-
"""
|
122
|
-
Заглушка
|
123
|
-
"""
|
118
|
+
"""Заглушка."""
|
124
119
|
return datetime.datetime(1, 1, 1)
|
125
120
|
|
126
121
|
def created_time(self, name):
|
127
|
-
"""
|
128
|
-
Заглушка
|
129
|
-
"""
|
122
|
+
"""Заглушка."""
|
130
123
|
return datetime.datetime(1, 1, 1)
|
131
124
|
|
132
125
|
def modified_time(self, name):
|
133
|
-
"""
|
134
|
-
Заглушка
|
135
|
-
"""
|
126
|
+
"""Заглушка."""
|
136
127
|
return datetime.datetime(1, 1, 1)
|