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.
Files changed (221) hide show
  1. educommon/__init__.py +0 -1
  2. educommon/about/ui/actions.py +16 -30
  3. educommon/about/ui/ui.py +3 -12
  4. educommon/about/utils.py +6 -5
  5. educommon/async_task/__init__.py +0 -1
  6. educommon/async_task/actions.py +18 -13
  7. educommon/async_task/apps.py +4 -0
  8. educommon/async_task/locker.py +2 -5
  9. educommon/async_task/migrations/0001_initial.py +55 -9
  10. educommon/async_task/migrations/0002_task_type_and_status_data.py +94 -89
  11. educommon/async_task/migrations/0003_alter_runningtask_options.py +0 -1
  12. educommon/async_task/models.py +9 -6
  13. educommon/async_task/tasks.py +11 -7
  14. educommon/async_task/ui.py +16 -35
  15. educommon/async_tasks/__init__.py +0 -1
  16. educommon/async_tasks/apps.py +4 -0
  17. educommon/async_tasks/locks.py +11 -21
  18. educommon/async_tasks/migrations/0001_initial.py +68 -8
  19. educommon/async_tasks/migrations/0002_load_initial_data.py +0 -1
  20. educommon/async_tasks/models.py +9 -29
  21. educommon/async_tasks/tasks.py +25 -54
  22. educommon/audit_log/__init__.py +1 -0
  23. educommon/audit_log/actions.py +27 -36
  24. educommon/audit_log/app_meta.py +7 -4
  25. educommon/audit_log/apps.py +44 -29
  26. educommon/audit_log/constants.py +7 -4
  27. educommon/audit_log/error_log/actions.py +1 -3
  28. educommon/audit_log/helpers.py +2 -4
  29. educommon/audit_log/management/commands/reinstall_audit_log.py +11 -7
  30. educommon/audit_log/migrations/0001_initial.py +91 -16
  31. educommon/audit_log/migrations/0002_install_audit_log.py +13 -13
  32. educommon/audit_log/migrations/0003_logproxy.py +1 -3
  33. educommon/audit_log/migrations/0004_reinstall_audit_log.py +1 -4
  34. educommon/audit_log/migrations/0005_postgresql_error.py +4 -2
  35. educommon/audit_log/migrations/0006_auto_20200806_1707.py +3 -4
  36. educommon/audit_log/migrations/0007_create_selective_tables_function.py +8 -5
  37. educommon/audit_log/migrations/0008_table_logged.py +0 -1
  38. educommon/audit_log/migrations/0009_reinstall_audit_log.py +0 -1
  39. educommon/audit_log/models.py +36 -42
  40. educommon/audit_log/permissions.py +11 -9
  41. educommon/audit_log/proxies.py +12 -23
  42. educommon/audit_log/ui.py +18 -15
  43. educommon/audit_log/utils/__init__.py +28 -60
  44. educommon/audit_log/utils/operations.py +16 -2
  45. educommon/auth/__init__.py +0 -3
  46. educommon/auth/rbac/__init__.py +2 -4
  47. educommon/auth/rbac/actions.py +148 -145
  48. educommon/auth/rbac/app_meta.py +9 -6
  49. educommon/auth/rbac/backends/base.py +2 -8
  50. educommon/auth/rbac/backends/caching.py +27 -37
  51. educommon/auth/rbac/backends/simple.py +1 -4
  52. educommon/auth/rbac/checker.py +1 -3
  53. educommon/auth/rbac/management/commands/rbac.py +6 -11
  54. educommon/auth/rbac/manager.py +18 -47
  55. educommon/auth/rbac/migrations/0001_initial.py +73 -12
  56. educommon/auth/rbac/migrations/0002_model_modifier_metaclass_fix.py +7 -6
  57. educommon/auth/rbac/migrations/0003_permission_hidden.py +1 -5
  58. educommon/auth/rbac/migrations/0004_auto_20171024_1245.py +26 -19
  59. educommon/auth/rbac/models.py +63 -68
  60. educommon/auth/rbac/permissions.py +6 -7
  61. educommon/auth/rbac/ui.py +83 -84
  62. educommon/auth/rbac/utils.py +10 -11
  63. educommon/auth/rbac/validators.py +4 -5
  64. educommon/auth/simple_auth/__init__.py +1 -5
  65. educommon/auth/simple_auth/actions.py +79 -92
  66. educommon/auth/simple_auth/app_meta.py +2 -9
  67. educommon/auth/simple_auth/checkers.py +3 -3
  68. educommon/auth/simple_auth/migrations/0001_initial.py +23 -4
  69. educommon/auth/simple_auth/validators.py +0 -1
  70. educommon/contingent/actions.py +7 -7
  71. educommon/contingent/app_meta.py +1 -4
  72. educommon/contingent/base.py +10 -15
  73. educommon/contingent/catalogs.py +424 -540
  74. educommon/contingent/contingent_plugin/actions.py +4 -15
  75. educommon/contingent/contingent_plugin/apps.py +10 -4
  76. educommon/contingent/contingent_plugin/migrations/0001_initial.py +5 -6
  77. educommon/contingent/contingent_plugin/migrations/0002_add_contingent_model_deleted.py +6 -11
  78. educommon/contingent/contingent_plugin/model_views.py +2 -12
  79. educommon/contingent/contingent_plugin/models.py +2 -7
  80. educommon/contingent/contingent_plugin/observer.py +14 -13
  81. educommon/contingent/contingent_plugin/plugin_meta.py +1 -3
  82. educommon/contingent/contingent_plugin/storage.py +8 -7
  83. educommon/contingent/contingent_plugin/utils.py +6 -6
  84. educommon/django/db/fields.py +72 -86
  85. educommon/django/db/migration/__init__.py +3 -7
  86. educommon/django/db/migration/operations.py +29 -51
  87. educommon/django/db/mixins/__init__.py +16 -10
  88. educommon/django/db/mixins/date_interval.py +47 -75
  89. educommon/django/db/mixins/validation.py +26 -26
  90. educommon/django/db/model_view/__init__.py +18 -22
  91. educommon/django/db/models.py +9 -8
  92. educommon/django/db/observer.py +9 -27
  93. educommon/django/db/partitioning/__init__.py +66 -92
  94. educommon/django/db/partitioning/management/commands/apply_partitioning.py +3 -13
  95. educommon/django/db/partitioning/management/commands/clear_table.py +18 -14
  96. educommon/django/db/partitioning/management/commands/split_table.py +18 -13
  97. educommon/django/db/routers.py +6 -15
  98. educommon/django/db/signals.py +149 -2
  99. educommon/django/db/utils.py +14 -19
  100. educommon/django/db/validators/__init__.py +1 -0
  101. educommon/django/db/validators/simple.py +72 -100
  102. educommon/django/storages/atcfs/api.py +39 -53
  103. educommon/django/storages/atcfs/app_meta.py +1 -1
  104. educommon/django/storages/atcfs/management/commands/atcfs_migrate.py +42 -55
  105. educommon/django/storages/atcfs/models.py +0 -3
  106. educommon/django/storages/atcfs/monkey_patching.py +18 -12
  107. educommon/django/storages/atcfs/storage.py +14 -23
  108. educommon/extjs/fields/input_params.py +15 -45
  109. educommon/importer/XLSReader.py +143 -241
  110. educommon/importer/__init__.py +86 -4
  111. educommon/importer/api.py +53 -84
  112. educommon/importer/constants.py +4 -14
  113. educommon/importer/loggers.py +16 -26
  114. educommon/importer/proxy.py +131 -176
  115. educommon/importer/proxy_import.py +11 -12
  116. educommon/importer/report.py +4 -6
  117. educommon/importer/ui.py +32 -26
  118. educommon/importer/validators.py +4 -7
  119. educommon/integration_entities/helpers.py +14 -18
  120. educommon/ioc/__init__.py +3 -6
  121. educommon/logger/loggers.py +10 -14
  122. educommon/m3/__init__.py +20 -38
  123. educommon/m3/extensions/__init__.py +1 -0
  124. educommon/m3/extensions/listeners/__init__.py +22 -38
  125. educommon/m3/extensions/listeners/delete_check/listeners.py +31 -41
  126. educommon/m3/extensions/listeners/delete_check/mixins.py +20 -25
  127. educommon/m3/extensions/listeners/delete_check/signals.py +2 -2
  128. educommon/m3/extensions/listeners/delete_check/ui.py +15 -14
  129. educommon/m3/extensions/listeners/delete_check/utils.py +9 -11
  130. educommon/m3/extensions/ui.py +15 -33
  131. educommon/m3/transaction_context.py +17 -19
  132. educommon/objectpack/actions.py +70 -88
  133. educommon/objectpack/apps.py +5 -0
  134. educommon/objectpack/filters.py +9 -15
  135. educommon/objectpack/ui.py +59 -77
  136. educommon/report/__init__.py +9 -5
  137. educommon/report/actions.py +29 -32
  138. educommon/report/constructor/__init__.py +5 -8
  139. educommon/report/constructor/app_meta.py +1 -3
  140. educommon/report/constructor/apps.py +1 -0
  141. educommon/report/constructor/base.py +33 -80
  142. educommon/report/constructor/builders/excel/_base.py +138 -286
  143. educommon/report/constructor/builders/excel/_header.py +2 -9
  144. educommon/report/constructor/builders/excel/product.py +13 -34
  145. educommon/report/constructor/builders/excel/with_merged_cells.py +18 -14
  146. educommon/report/constructor/config.py +2 -0
  147. educommon/report/constructor/editor/actions.py +101 -215
  148. educommon/report/constructor/editor/ui.py +71 -93
  149. educommon/report/constructor/exceptions.py +6 -12
  150. educommon/report/constructor/migrations/0001_initial.py +36 -44
  151. educommon/report/constructor/migrations/0002_report_filters.py +86 -72
  152. educommon/report/constructor/migrations/0003_reportfilter_exclude.py +5 -5
  153. educommon/report/constructor/migrations/0004_reportfilter_fields.py +22 -18
  154. educommon/report/constructor/migrations/0005_reportcolumn_visible.py +5 -4
  155. educommon/report/constructor/migrations/0006_reportsorting.py +21 -17
  156. educommon/report/constructor/migrations/0007_include_available_units.py +14 -14
  157. educommon/report/constructor/migrations/0008_auto_20170407_1318.py +4 -5
  158. educommon/report/constructor/migrations/0009_auto_20180405_0642.py +1 -4
  159. educommon/report/constructor/migrations/0010_add_aggregate_fields.py +7 -8
  160. educommon/report/constructor/mixins.py +14 -15
  161. educommon/report/constructor/models.py +76 -124
  162. educommon/report/constructor/utils.py +3 -8
  163. educommon/report/constructor/validators.py +1 -3
  164. educommon/report/reporter.py +25 -43
  165. educommon/report/utils.py +14 -40
  166. educommon/rest/actions.py +7 -11
  167. educommon/rest/context.py +6 -16
  168. educommon/rest/controllers.py +10 -10
  169. educommon/rest/mixins.py +29 -27
  170. educommon/secure_media/app_meta.py +9 -9
  171. educommon/utils/__init__.py +3 -2
  172. educommon/utils/caching.py +1 -3
  173. educommon/utils/conversion.py +1 -3
  174. educommon/utils/crypto.py +1 -2
  175. educommon/utils/date.py +13 -26
  176. educommon/utils/db/__init__.py +17 -26
  177. educommon/utils/db/postgresql.py +1 -4
  178. educommon/utils/fonts/__init__.py +3 -4
  179. educommon/utils/licence/__init__.py +5 -16
  180. educommon/utils/misc.py +9 -18
  181. educommon/utils/object_grid.py +55 -62
  182. educommon/utils/phone_number/modelfields.py +1 -3
  183. educommon/utils/phone_number/phone_number.py +5 -8
  184. educommon/utils/phone_number/validators.py +8 -23
  185. educommon/utils/plugins.py +15 -28
  186. educommon/utils/registry.py +2 -1
  187. educommon/utils/seqtools.py +1 -3
  188. educommon/utils/serializer.py +9 -16
  189. educommon/utils/storage.py +3 -2
  190. educommon/utils/system.py +1 -3
  191. educommon/utils/system_app/management/commands/delete_objects.py +17 -34
  192. educommon/utils/ui.py +87 -84
  193. educommon/utils/xml/__init__.py +2 -7
  194. educommon/utils/xml/resolver.py +1 -0
  195. educommon/ws_log/actions.py +31 -76
  196. educommon/ws_log/base.py +6 -20
  197. educommon/ws_log/migrations/0001_initial.py +25 -8
  198. educommon/ws_log/migrations/0002_auto_20160628_1334.py +0 -1
  199. educommon/ws_log/migrations/0003_add_fields_to_smev_logs.py +20 -4
  200. educommon/ws_log/migrations/0004_auto_20160727_1600.py +7 -6
  201. educommon/ws_log/migrations/0005_auto_20161130_1615.py +14 -4
  202. educommon/ws_log/migrations/0006_auto_20170327_1027.py +3 -2
  203. educommon/ws_log/migrations/0007_auto_20180607_1040.py +8 -9
  204. educommon/ws_log/migrations/0008_auto_20180713_1445.py +23 -10
  205. educommon/ws_log/migrations/0009_auto_20201130_1553.py +7 -2
  206. educommon/ws_log/models.py +21 -35
  207. educommon/ws_log/provider.py +2 -1
  208. educommon/ws_log/report.py +8 -13
  209. educommon/ws_log/smev/applications.py +12 -27
  210. educommon/ws_log/smev/exceptions.py +2 -3
  211. educommon/ws_log/ui.py +32 -32
  212. educommon/ws_log/utils.py +1 -3
  213. educommon-3.13.2.dist-info/METADATA +57 -0
  214. educommon-3.13.2.dist-info/RECORD +354 -0
  215. {educommon-3.12.0.dist-info → educommon-3.13.2.dist-info}/WHEEL +1 -1
  216. educommon/utils/patches.py +0 -27
  217. educommon/version.conf +0 -11
  218. educommon-3.12.0.dist-info/METADATA +0 -47
  219. educommon-3.12.0.dist-info/RECORD +0 -357
  220. educommon-3.12.0.dist-info/dependency_links.txt +0 -1
  221. {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
- Метод генерации данных для аутентификации на сервере ATCFS.
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
- 'vis_id': settings.VIS_ID,
42
- 'vis_user': settings.VIS_USER,
43
- 'request_id': request_id,
44
- 'secret_key': settings.SECRET_KEY,
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
- Загрузка файла на сервер ATCFS
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
- Удаление файла на сервере ATCFS
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
@@ -11,7 +11,7 @@ def register_urlpatterns():
11
11
  re_path(
12
12
  r'^atcfs_unavailable/$',
13
13
  TemplateView.as_view(template_name='atcfs_unavailable.html'),
14
- name='atcfs_unavailable'
14
+ name='atcfs_unavailable',
15
15
  ),
16
16
  ]
17
17
 
@@ -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(Command, self).__init__()
64
+ super().__init__()
65
+
65
66
  self.api = AtcfsApi()
66
67
 
67
68
  def _get_fields(self, model):
68
- """
69
- Выбираем в модели все переменные, являющиеся FileField-полями.
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
- Берем все модели, в которых есть FileField
86
+ """Берем все модели, в которых есть FileField.
87
+
86
88
  :return: список классов моделей
87
89
  """
88
90
  models = {}
89
91
 
90
- is_model = lambda x: (
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
- Непосредственная отправка файла на ATCFS.
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
- ['COALESCE({0}, \'\') <> \'\''.format(field) for field in fields]
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
- ['{0[0]} = \'{0[1]}\''.format(field) for field in obj_fields]
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
- Проходимся по всем моделям, всем объектам, отсылаем файлы на ATCFS.
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('{0},{1},{2},{3}'.format(
200
- obj['id'],
201
- field_name.decode('UTF-8'),
202
- field_value.decode('UTF-8'),
203
- ident.decode('UTF-8')
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,4 +1 @@
1
1
  # В данном случае файл используется только как точка вызова патча.
2
- from . import (
3
- monkey_patching,
4
- )
@@ -1,7 +1,5 @@
1
- """
2
- Внедряем в джанговский дефолтный FieldFile необходимый функционал
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
- Функция определяет является ли переданный storage AtcfsStorage.
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) or
34
- isinstance(storage, DefaultStorage) and
35
- DEFAULT_FILE_STORAGE == AtcfsStorage
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)