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
@@ -30,10 +30,12 @@ _SPECIAL_KEYS = (
30
30
 
31
31
 
32
32
  def _fold(tree, path='', group=None):
33
- """
33
+ """Свертка словаря-дерева.
34
+
34
35
  Свертка словаря-дерева в список пар (путь, значение, группа|None)
35
- ("группа", это uuid, для группировки значений одного поддерева)
36
+ ("группа", это uuid, для группировки значений одного поддерева).
36
37
  """
38
+
37
39
  if not isinstance(tree, dict):
38
40
  return [(path, tree, group)]
39
41
 
@@ -41,20 +43,21 @@ def _fold(tree, path='', group=None):
41
43
  if tree.get(SUBTREE_CAN_BE_EMPTY, False):
42
44
  group = uuid.uuid4()
43
45
 
44
- path = ('%s/%%s' % path) if path else '%s'
46
+ path = f'{path}/{{0}}' if path else '{0}'
47
+
45
48
  res = []
46
49
  for key, val in tree.items():
47
50
  if key in _SPECIAL_KEYS:
48
51
  continue
49
- key = path % key
52
+
53
+ key = path.format(key)
50
54
  res.extend(_fold(val, key, group))
55
+
51
56
  return res
52
57
 
53
58
 
54
59
  def _unfold(lst):
55
- """
56
- Развертка списка пар (путь, значение) в словарь-дерево
57
- """
60
+ """Развертка списка пар (путь, значение) в словарь-дерево."""
58
61
  res = {}
59
62
 
60
63
  def set_val(res, path, val):
@@ -79,9 +82,7 @@ MATCH_IF_CONTAINS = 3
79
82
 
80
83
 
81
84
  def _is_match(value, pattern, match_type=MATCH_IF_EXACT):
82
- """
83
- Проверка на совпадение значения с образцом
84
- """
85
+ """Проверка на совпадение значения с образцом."""
85
86
  pattern = pattern.upper()
86
87
  value = value.upper()
87
88
 
@@ -89,29 +90,25 @@ def _is_match(value, pattern, match_type=MATCH_IF_EXACT):
89
90
  return value.find(pattern) >= 0
90
91
  elif match_type == MATCH_IF_STARTS_WITH:
91
92
  return value.startswith(pattern)
93
+
92
94
  return value == pattern
93
95
 
94
96
 
95
97
  class CellValueError(Exception):
96
- """
97
- Исключение для неверного значения ячейки
98
- """
98
+ """Исключение для неверного значения ячейки."""
99
+
99
100
  pass
100
101
 
101
102
 
102
103
  class EmptyObligatoryCellError(CellValueError):
103
- """
104
- Исключение для неверного значения ячейки
105
- """
104
+ """Исключение для неверного значения ячейки."""
105
+
106
106
  def __init__(self, message=None):
107
- super(EmptyObligatoryCellError, self).__init__(
108
- message or 'Ячейка не может быть пустой!')
107
+ super().__init__(message or 'Ячейка не может быть пустой!')
109
108
 
110
109
 
111
110
  class BaseCell:
112
- """
113
- Прототип парсера ячейки
114
- """
111
+ """Прототип парсера ячейки."""
115
112
 
116
113
  _default = _NO_DEFAULT
117
114
 
@@ -125,15 +122,11 @@ class BaseCell:
125
122
  return (value, False)
126
123
 
127
124
  def _parse(self, value):
128
- """
129
- Разбор значения
130
- """
125
+ """Разбор значения."""
131
126
  raise NotImplementedError('Прототип не используется напрямую!')
132
127
 
133
128
  def from_cell(self, sheet, row, col):
134
- """
135
- Получение значения из ячейки в виде (значение, is_default)
136
- """
129
+ """Получение значения из ячейки в виде (значение, is_default)."""
137
130
  try:
138
131
  value = sheet.cell(row, col).value
139
132
  except IndexError:
@@ -144,7 +137,7 @@ class BaseCell:
144
137
  if isinstance(value, str):
145
138
  value = value.strip()
146
139
 
147
- if value is None or value == "":
140
+ if value is None or value == '':
148
141
  if self.is_obligatory():
149
142
  raise EmptyObligatoryCellError()
150
143
  return self.default()
@@ -156,19 +149,15 @@ class BaseCell:
156
149
 
157
150
 
158
151
  class StringCell(BaseCell):
159
- """
160
- Строковая ячейка
161
- """
152
+ """Строковая ячейка."""
162
153
 
163
- MSG_RECORD_TRUNCATED = (
164
- 'Превышена максимально допустимая длина записи. Запись сокращена'
165
- )
154
+ MSG_RECORD_TRUNCATED = 'Превышена максимально допустимая длина записи. Запись сокращена'
166
155
 
167
156
  DEFAULT_RE_MESSAGE = 'Неправильный формат записи'
168
157
 
169
158
  def __init__(
170
- self, default=_NO_DEFAULT, max_length=None, regex=None,
171
- validator=None, error_message=None, verbose=True):
159
+ self, default=_NO_DEFAULT, max_length=None, regex=None, validator=None, error_message=None, verbose=True
160
+ ):
172
161
  """Строковая ячейка.
173
162
 
174
163
  :param int max_length: Максимальная длина строки.
@@ -179,7 +168,7 @@ class StringCell(BaseCell):
179
168
  """
180
169
  assert max_length is None or isinstance(max_length, int)
181
170
 
182
- super(StringCell, self).__init__(default)
171
+ super().__init__(default)
183
172
 
184
173
  self._max_length = max_length
185
174
  self._verbose = verbose
@@ -218,9 +207,7 @@ class StringCell(BaseCell):
218
207
  except ValidationError as err:
219
208
  if self._default is None:
220
209
  if self._verbose:
221
- self.set_mgs(
222
- ', '.join(err.messages)
223
- )
210
+ self.set_mgs(', '.join(err.messages))
224
211
  value = ''
225
212
 
226
213
  elif self._default is _NO_DEFAULT:
@@ -230,13 +217,13 @@ class StringCell(BaseCell):
230
217
 
231
218
  def _truncate(self, value):
232
219
  """Обрезает строку до указанной в self._max_length длины."""
233
- new_value = value[:self._max_length]
220
+ new_value = value[: self._max_length]
234
221
  if self._verbose and new_value != value:
235
222
  self.set_mgs(self.MSG_RECORD_TRUNCATED)
223
+
236
224
  return new_value
237
225
 
238
226
  def _parse(self, value):
239
-
240
227
  self.message = None
241
228
 
242
229
  if value.endswith('.0'):
@@ -255,12 +242,9 @@ class StringCell(BaseCell):
255
242
 
256
243
 
257
244
  class MaybeStringCell(StringCell):
258
- """
259
- Строка или None
260
- """
261
- def __init__(
262
- self, default=None, max_length=None, regex=None,
263
- validator=None, error_message=None, verbose=True):
245
+ """Строка или None."""
246
+
247
+ def __init__(self, default=None, max_length=None, regex=None, validator=None, error_message=None, verbose=True):
264
248
  """Строка или None.
265
249
 
266
250
  :param int max_length: Максимальная длина строки.
@@ -269,33 +253,36 @@ class MaybeStringCell(StringCell):
269
253
  :param error_message: Сообщение в случае провала проверки.
270
254
  :param bool verbose: Записывать ли сообщения в лог.
271
255
  """
272
- super(MaybeStringCell, self).__init__(
273
- default=None, max_length=max_length, regex=regex,
274
- validator=validator, error_message=error_message, verbose=verbose)
256
+ super().__init__(
257
+ default=None,
258
+ max_length=max_length,
259
+ regex=regex,
260
+ validator=validator,
261
+ error_message=error_message,
262
+ verbose=verbose,
263
+ )
275
264
 
276
265
 
277
266
  class RawCell(BaseCell):
267
+ """Сырая строковая ячейка "как есть".
268
+
269
+ В том числе с добавками Excel в зависимости от типа ячейки.
278
270
  """
279
- Сырая строковая ячейка "как есть",
280
- в том числе с добавками Excel в зависимости
281
- от типа ячейки
282
- """
271
+
283
272
  def _parse(self, value):
284
273
  return self.result(str(value))
285
274
 
286
275
 
287
276
  class MaybeRawCell(RawCell):
288
- """
289
- Сырая строковая ячейка "как есть" или None
290
- """
277
+ """Сырая строковая ячейка "как есть" или None."""
278
+
291
279
  def __init__(self):
292
- super(MaybeRawCell, self).__init__(default=None)
280
+ super().__init__(default=None)
293
281
 
294
282
 
295
283
  class IntCell(BaseCell):
296
- """
297
- Integer ячейка
298
- """
284
+ """Integer ячейка."""
285
+
299
286
  def _parse(self, value):
300
287
  try:
301
288
  result = int(value)
@@ -303,73 +290,62 @@ class IntCell(BaseCell):
303
290
  try:
304
291
  result = int(float(value))
305
292
  except (TypeError, ValueError):
306
- raise CellValueError(
307
- 'Число имеет неверный формат! "%s"' % value)
293
+ raise CellValueError(f'Число имеет неверный формат! "{value}"')
308
294
  return self.result(result)
309
295
 
310
296
 
311
297
  class MaybeIntCell(IntCell):
312
- """
313
- Int или None
314
- """
298
+ """Int или None."""
299
+
315
300
  def __init__(self):
316
- super(MaybeIntCell, self).__init__(default=None)
301
+ super().__init__(default=None)
317
302
 
318
303
 
319
304
  class MaybeNonNegativeIntCell(MaybeIntCell):
305
+ """Не отрицательное целое число или None.
306
+
307
+ Не отбрасывает дробную часть (если она больше 0) и райзит ошибку.
320
308
  """
321
- Не отрицательное целое число или None
322
- Не отбрасывает дробную часть (если она больше 0) и райзит ошибку
323
- """
309
+
324
310
  def _parse(self, value):
325
- result, _ = super(MaybeIntCell, self)._parse(value)
311
+ result, _ = super()._parse(value)
312
+
326
313
  if result < 0:
327
- raise CellValueError(
328
- 'Число должно быть не меньше нуля! "%s"' % value)
314
+ raise CellValueError(f'Число должно быть не меньше нуля! "{value}"')
329
315
  if float(value) != float(int(float(value))):
330
- raise CellValueError(
331
- 'Число должно быть целым! "%s"' % value)
316
+ raise CellValueError(f'Число должно быть целым! "{value}"')
332
317
  return self.result(result)
333
318
 
334
319
 
335
320
  class BooleanCell(BaseCell):
336
- """
337
- Bool-ячейка, получает True при совпадении с паттерном
338
- """
339
- def __init__(self, default=_NO_DEFAULT,
340
- pattern=None, match_type=MATCH_IF_EXACT):
341
- """
342
- Если паттерн опущен (или None), то результат будет True при любой
343
- значащей строке в ячейке.
344
- """
321
+ """Bool-ячейка, получает True при совпадении с паттерном."""
322
+
323
+ def __init__(self, default=_NO_DEFAULT, pattern=None, match_type=MATCH_IF_EXACT):
324
+ """Если паттерн опущен (или None), то результат будет True при любой значащей строке в ячейке."""
325
+ super().__init__(default=default)
345
326
 
346
- super(BooleanCell, self).__init__(default=default)
347
327
  self.pattern = pattern
348
328
  self.match_type = match_type
349
329
 
350
330
  def _parse(self, value):
351
331
  if self.pattern is None:
352
- return self.result(
353
- bool(value.strip()))
332
+ return self.result(bool(value.strip()))
354
333
  return self.result(_is_match(value, self.pattern, self.match_type))
355
334
 
356
335
 
357
336
  class MaybeTrueCell(BooleanCell):
358
- """
359
- Bool-ячейка, которая получает значение True при успешном совападении
360
- паттерна
361
- """
337
+ """Bool-ячейка, которая получает значение True при успешном совпадении паттерна."""
338
+
362
339
  def __init__(self, default=None, pattern=None, match_type=MATCH_IF_EXACT):
340
+ """Если паттерн опущен (или None), то результат будет True при любой значащей строке в ячейке.
341
+
342
+ Параметр default игнорируется (всегда равен False).
363
343
  """
364
- Если паттерн опущен (или None), то результат будет True при любой
365
- значащей строке в ячейке.
366
- Параметр default игнорируется (всегда равет False)
367
- """
368
- super(MaybeTrueCell, self).__init__(
369
- default=False, pattern=pattern, match_type=match_type)
344
+ super().__init__(default=False, pattern=pattern, match_type=match_type)
370
345
 
371
346
  def _parse(self, value):
372
- result, _ = super(MaybeTrueCell, self)._parse(value)
347
+ result, _ = super()._parse(value)
348
+
373
349
  if result:
374
350
  return self.result(True)
375
351
  else:
@@ -377,21 +353,18 @@ class MaybeTrueCell(BooleanCell):
377
353
 
378
354
 
379
355
  class MaybeFalseCell(BooleanCell):
380
- """
381
- Bool-ячейка, которая получает значение False при успешном совападении
382
- паттерна
383
- """
356
+ """Bool-ячейка, которая получает значение False при успешном совпадении паттерна."""
357
+
384
358
  def __init__(self, default=None, pattern=None, match_type=MATCH_IF_EXACT):
359
+ """Если паттерн опущен (или None), то результат будет False при любой значащей строке в ячейке.
360
+
361
+ Параметр default игнорируется (всегда равен True).
385
362
  """
386
- Если паттерн опущен (или None), то результат будет False при любой
387
- значащей строке в ячейке.
388
- Параметр default игнорируется (всегда равет True)
389
- """
390
- super(MaybeFalseCell, self).__init__(
391
- default=True, pattern=pattern, match_type=match_type)
363
+ super().__init__(default=True, pattern=pattern, match_type=match_type)
392
364
 
393
365
  def _parse(self, value):
394
- result, _ = super(MaybeFalseCell, self)._parse(value)
366
+ result, _ = super()._parse(value)
367
+
395
368
  if result:
396
369
  return self.result(False)
397
370
  else:
@@ -399,16 +372,14 @@ class MaybeFalseCell(BooleanCell):
399
372
 
400
373
 
401
374
  class EnumCell(BaseCell):
402
- """
403
- Ячейка, которая может принимать одно из заданных значений
404
- """
405
- def __init__(self, default=_NO_DEFAULT, choices=None,
406
- match_type=MATCH_IF_EXACT, blank_values=None):
407
- """
408
- choices - список вариантов вида (pattern, value)
409
- """
375
+ """Ячейка, которая может принимать одно из заданных значений."""
376
+
377
+ def __init__(self, default=_NO_DEFAULT, choices=None, match_type=MATCH_IF_EXACT, blank_values=None):
378
+ """choices - список вариантов вида (pattern, value)."""
410
379
  assert len(choices) >= 2, 'Должно быть указано хотя бы 2 варианта!'
411
- super(EnumCell, self).__init__(default=default)
380
+
381
+ super().__init__(default=default)
382
+
412
383
  self.choices = choices
413
384
  self.match_type = match_type
414
385
  self.blank_values = blank_values
@@ -442,8 +413,7 @@ class DateCell(BaseCell):
442
413
  result = None
443
414
  try:
444
415
  result = datetime(*xlrd.xldate_as_tuple(float(value), 0))
445
- except (XLDateNegative, XLDateAmbiguous, XLDateTooLarge,
446
- XLDateBadDatemode):
416
+ except (XLDateNegative, XLDateAmbiguous, XLDateTooLarge, XLDateBadDatemode):
447
417
  raise CellValueError(WRONG_DATE_FORMAT_MSG)
448
418
  except ValueError:
449
419
  value = str(value).strip()[:10]
@@ -464,22 +434,16 @@ class DateCell(BaseCell):
464
434
 
465
435
 
466
436
  class MaybeDateCell(DateCell):
467
- """
468
- Date или None
469
- """
437
+ """Date или None."""
438
+
470
439
  def __init__(self):
471
- super(MaybeDateCell, self).__init__(default=None)
440
+ super().__init__(default=None)
472
441
 
473
442
 
474
443
  class DynamicStartRow:
475
- """
476
- Автоматический поиск начала таблицы по ее заголовкам ее столбцов
477
- """
444
+ """Автоматический поиск начала таблицы по ее заголовкам ее столбцов."""
478
445
 
479
- def __init__(self, table_headers,
480
- order_sensetive=True,
481
- case_sensitive=False,
482
- search_area=[(0, 0), (50, 50)]):
446
+ def __init__(self, table_headers, order_sensetive=True, case_sensitive=False, search_area=[(0, 0), (50, 50)]):
483
447
  """
484
448
  :param list table_headers: Список заголовков: ['Фамилия' 'Имя']
485
449
  :param bool order_sensetive: Учитывать порядок списка table_headers
@@ -501,7 +465,6 @@ class DynamicStartRow:
501
465
  self.table_headers = [s.upper() for s in table_headers]
502
466
 
503
467
  def find_pos(self, sheet):
504
-
505
468
  max_row = min(self.max_row, sheet.nrows)
506
469
  max_col = min(self.max_col, sheet.ncols)
507
470
 
@@ -517,7 +480,6 @@ class DynamicStartRow:
517
480
  matched.setdefault(val, col)
518
481
 
519
482
  if len(matched) == len(self.table_headers):
520
-
521
483
  cols_nums = [matched[h] for h in self.table_headers]
522
484
  if self.order_sensetive and sorted(cols_nums) != cols_nums:
523
485
  continue
@@ -525,7 +487,6 @@ class DynamicStartRow:
525
487
  return row, min(cols_nums)
526
488
 
527
489
  def find_row(self, sheet):
528
-
529
490
  pos = self.find_pos(sheet)
530
491
  if pos:
531
492
  row, col = pos
@@ -533,12 +494,9 @@ class DynamicStartRow:
533
494
 
534
495
 
535
496
  class SimpleColumnMatcher:
536
- """
537
- Класс сопоставления столбцов таблицы
538
- """
497
+ """Класс сопоставления столбцов таблицы."""
539
498
 
540
- def __init__(self, column_name, unique_check=False,
541
- strip=True, case_sensitive=False, replacer=None):
499
+ def __init__(self, column_name, unique_check=False, strip=True, case_sensitive=False, replacer=None):
542
500
  """
543
501
  :param str column_name: Название столбца
544
502
  :param bool unique_check: Нужно ли проверять на то, чтобы данный
@@ -559,7 +517,7 @@ class SimpleColumnMatcher:
559
517
  self.replacer = replacer
560
518
 
561
519
  def get_column_name(self):
562
- """Возвращает название столбца"""
520
+ """Возвращает название столбца."""
563
521
  return self.column_name
564
522
 
565
523
  @property
@@ -567,7 +525,7 @@ class SimpleColumnMatcher:
567
525
  return self._unique_check
568
526
 
569
527
  def _prepare_cell_value(self, cell_value):
570
- """Подготовка значения с учетом флагов настроек"""
528
+ """Подготовка значения с учетом флагов настроек."""
571
529
  cell_value = str(cell_value)
572
530
  if self.strip:
573
531
  cell_value = cell_value.strip()
@@ -581,26 +539,21 @@ class SimpleColumnMatcher:
581
539
  return cell_value
582
540
 
583
541
  def match(self, cell_value):
584
- """
585
- Проверка: отвечает ли значение в cell_value
586
- требуемому названию столбца
587
- """
542
+ """Проверка: отвечает ли значение в cell_value требуемому названию столбца."""
588
543
 
589
544
  column_name = self.column_name
590
545
  if not self.case_sensitive:
591
546
  column_name = column_name.upper()
592
547
 
593
548
  cell_value = self._prepare_cell_value(cell_value)
549
+
594
550
  return cell_value == column_name
595
551
 
596
552
 
597
553
  class RegexColumnMatcher(SimpleColumnMatcher):
598
- """
599
- Сопоставление столбцов по регулярному выражению
600
- """
554
+ """Сопоставление столбцов по регулярному выражению."""
601
555
 
602
556
  def __init__(self, column_name, regex, **kwargs):
603
-
604
557
  if isinstance(regex, str):
605
558
  flags = re.UNICODE
606
559
  if not kwargs.get('case_sensitive'):
@@ -608,10 +561,12 @@ class RegexColumnMatcher(SimpleColumnMatcher):
608
561
 
609
562
  regex = re.compile(regex, flags)
610
563
  self.regex = regex
611
- super(RegexColumnMatcher, self).__init__(column_name, **kwargs)
564
+
565
+ super().__init__(column_name, **kwargs)
612
566
 
613
567
  def match(self, cell_value):
614
568
  cell_value = self._prepare_cell_value(cell_value)
569
+
615
570
  return bool(self.regex.search(cell_value))
616
571
 
617
572
 
@@ -619,10 +574,10 @@ class RegexColumnMatcher(SimpleColumnMatcher):
619
574
  # XLSLoader
620
575
  # =============================================================================
621
576
 
577
+
622
578
  class XLSLoader:
623
- """
624
- Загрузчик xls-файла с разбором
625
- """
579
+ """Загрузчик xls-файла с разбором."""
580
+
626
581
  config = {}
627
582
 
628
583
  """
@@ -665,14 +620,10 @@ class XLSLoader:
665
620
  self._row_errors_log = {}
666
621
  self._log_change_flag = False
667
622
 
668
- self._book = xlrd.open_workbook(
669
- file_contents=memory_mapped_file.read())
623
+ self._book = xlrd.open_workbook(file_contents=memory_mapped_file.read())
670
624
 
671
- if not all((
672
- self._file,
673
- self._book.nsheets > 0
674
- )):
675
- self._log("Не удалось загрузить файл!")
625
+ if not all((self._file, self._book.nsheets > 0)):
626
+ self._log('Не удалось загрузить файл!')
676
627
  self._book = None
677
628
  raise ValueError('See loader log')
678
629
 
@@ -694,30 +645,23 @@ class XLSLoader:
694
645
  HEADER_PARSER,
695
646
  # парсер строки шапки листа по-умолчанию:
696
647
  # накапливает в список непустые ячейки
697
- lambda x: list(filter(bool, x))
648
+ lambda x: list(filter(bool, x)),
698
649
  )
699
650
  self._flat_config[k] = _fold(v)
700
651
 
701
652
  if self._XLSLoader__RESERVED_SHEET in self._flat_config:
702
- raise AssertionError(
703
- 'Название листа "%s" зарезервировано!' %
704
- self._XLSLoader__RESERVED_SHEET
705
- )
653
+ raise AssertionError(f'Название листа "{self._XLSLoader__RESERVED_SHEET}" зарезервировано!')
706
654
 
707
655
  self._loaded_data = {}
708
656
  self._headers = {}
709
657
 
710
658
  def _log_common_error(self, *items):
711
- """
712
- Запись в основной лог ошибок
713
- """
659
+ """Запись в основной лог ошибок."""
714
660
  self._log_change_flag = True
715
661
  self._common_log.extend(items)
716
662
 
717
663
  def _log_row_error(self, xls_pos, *items):
718
- """
719
- Добавление ошибки в строке
720
- """
664
+ """Добавление ошибки в строке."""
721
665
  self._log_change_flag = True
722
666
  self._row_errors_log.setdefault(xls_pos, []).extend(items)
723
667
 
@@ -744,9 +688,7 @@ class XLSLoader:
744
688
  return self._headers
745
689
 
746
690
  def load(self):
747
- """
748
- Загрузка листа по дескриптору колонок строки
749
- """
691
+ """Загрузка листа по дескриптору колонок строки."""
750
692
  if self._book is None:
751
693
  return False
752
694
 
@@ -769,8 +711,7 @@ class XLSLoader:
769
711
  if sheet_name == self._XLSLoader__RESERVED_SHEET:
770
712
  continue
771
713
  for k, v in self._flat_config.items():
772
- if (isinstance(k, int) and (sheet_num + 1) == k) or (
773
- isinstance(k, tuple) and (sheet_num + 1) in k):
714
+ if (isinstance(k, int) and (sheet_num + 1) == k) or (isinstance(k, tuple) and (sheet_num + 1) in k):
774
715
  parsers = v
775
716
  parser_key = k
776
717
  break
@@ -780,22 +721,14 @@ class XLSLoader:
780
721
  else:
781
722
  expected_sheets = (
782
723
  key
783
- for key in map(
784
- lambda x: str(x).capitalize(),
785
- self._flat_config
786
- )
724
+ for key in map(lambda x: str(x).capitalize(), self._flat_config)
787
725
  if key != self.ANY_SHEET
788
726
  )
789
727
 
790
728
  self._log_row_error(
791
729
  (sheet.name, sheet_num + 1, 0),
792
- 'Неверное название листа №%s: %s.\n'
793
- 'Ожидаемые названия листов: %s' %
794
- (
795
- sheet_num + 1,
796
- sheet.name,
797
- ', '.join(expected_sheets)
798
- ),
730
+ f'Неверное название листа №{sheet_num + 1}: {sheet.name}.\n'
731
+ f'Ожидаемые названия листов: {", ".join(expected_sheets)}',
799
732
  )
800
733
  continue
801
734
 
@@ -816,18 +749,13 @@ class XLSLoader:
816
749
  header_data = self._headers.setdefault(parser_key, [])
817
750
  for row in range(0, start_row):
818
751
  # парсеру заголовка передаётся итератор ячеек по строкам
819
- header_data.append(
820
- header_parser((
821
- sheet.cell(row, i).value for i in range(sheet.ncols)
822
- ))
823
- )
752
+ header_data.append(header_parser((sheet.cell(row, i).value for i in range(sheet.ncols))))
824
753
 
825
754
  # разбор шапки листа
826
755
  col_parsers = []
827
756
  errors = []
828
757
 
829
758
  for path, (col_mather, parser), grp in parsers:
830
-
831
759
  if isinstance(col_mather, str):
832
760
  col_mather = SimpleColumnMatcher(col_mather)
833
761
  # в шапке могут быть целочисленные ячейки
@@ -847,39 +775,28 @@ class XLSLoader:
847
775
  break
848
776
 
849
777
  if col_mather.match(cell_value):
850
- matched_cols.append(
851
- (col, str(cell_value).strip())
852
- )
778
+ matched_cols.append((col, str(cell_value).strip()))
853
779
 
854
780
  if not matched_cols:
855
781
  if parser.is_obligatory():
856
782
  errors.append(
857
- 'На листе "%s" (%s) отсутствует столбец "%s", '
858
- 'необходимый для импорта' % (
859
- sheet_name, sheet_num + 1, column_name
860
- )
783
+ f'На листе "{sheet_name}" ({sheet_num + 1}) отсутствует столбец "{column_name}", '
784
+ 'необходимый для импорта'
861
785
  )
862
786
 
863
787
  elif col_mather.unique_check and len(matched_cols) > 1:
788
+ column_names = ', '.join(f'"{x[1]}"' for x in matched_cols)
864
789
  errors.append(
865
- 'На листе "%s" (%s) присутствуют взаимоисключающие '
866
- 'столбцы: %s.' % (
867
- sheet_name, sheet_num + 1,
868
- ', '.join('"%s"' % x[1] for x in matched_cols)
869
- )
790
+ f'На листе "{sheet_name}" ({sheet_num + 1}) присутствуют взаимоисключающие '
791
+ f'столбцы: {column_names}.'
870
792
  )
871
793
  else:
872
794
  col_pos, real_column_name = matched_cols[0]
873
- col_parsers.append(
874
- (col_pos, real_column_name, path, parser, grp)
875
- )
795
+ col_parsers.append((col_pos, real_column_name, path, parser, grp))
876
796
 
877
797
  if errors:
878
798
  for error in errors:
879
- self._log_row_error(
880
- (sheet.name, sheet_num + 1, start_row + 1),
881
- error
882
- )
799
+ self._log_row_error((sheet.name, sheet_num + 1, start_row + 1), error)
883
800
  continue
884
801
 
885
802
  # список результатов связанный с именем листа
@@ -900,19 +817,15 @@ class XLSLoader:
900
817
  filled_cells_counts = {}
901
818
 
902
819
  def inc_counter(grp):
903
- filled_cells_counts[grp] = (
904
- filled_cells_counts.get(grp, 0) + 1
905
- )
820
+ filled_cells_counts[grp] = filled_cells_counts.get(grp, 0) + 1
906
821
 
907
822
  row_errors = {}
908
823
 
909
824
  def add_err(grp, title, err):
910
- row_errors.setdefault(grp, set()).add(
911
- 'Cтолбец "%s": %s' % (title, err)
912
- )
825
+ row_errors.setdefault(grp, set()).add(f'Cтолбец "{title}": {err}')
913
826
 
914
827
  def add_warn(grp, title, msg):
915
- message = 'Cтолбец "%s": %s' % (title, msg)
828
+ message = f'Cтолбец "{title}": {msg}'
916
829
  self._log_row_error(xls_pos, *[message])
917
830
 
918
831
  groups = set()
@@ -978,15 +891,14 @@ class XLSLoader:
978
891
  Преобразует словарь-лог ошибок строк в отсортированный список строк
979
892
  """
980
893
  result = []
981
- for pos, lines in sorted(
982
- log.items(), key=lambda x: (x[0][1], str(x[0][2]))
983
- ):
984
- result.append('Лист "%s" (%s), строка %s:' % pos)
985
- result.extend((' %s' % line) for line in sorted(lines))
894
+ for pos, lines in sorted(log.items(), key=lambda x: (x[0][1], str(x[0][2]))):
895
+ result.append(f'Лист "{pos[0]}" ({pos[1]}), строка {pos[2]}:')
896
+ result.extend(f' {line}' for line in sorted(lines))
986
897
  result.append('')
987
898
 
988
899
  return result
989
900
 
901
+
990
902
  if __name__ == '__main__':
991
903
 
992
904
  def cells_to_pair(cells):
@@ -1002,14 +914,8 @@ if __name__ == '__main__':
1002
914
  'date': ('date', DateCell()),
1003
915
  'int': ('int', IntCell(default=-100)),
1004
916
  },
1005
- 'mb_true': (
1006
- 'mb_true',
1007
- MaybeTrueCell(pattern='y', match_type=MATCH_IF_STARTS_WITH)
1008
- ),
1009
- 'enum': (
1010
- 'enum',
1011
- EnumCell(choices=((1, 'aaa'), (2, 'bbb')), default=3)
1012
- ),
917
+ 'mb_true': ('mb_true', MaybeTrueCell(pattern='y', match_type=MATCH_IF_STARTS_WITH)),
918
+ 'enum': ('enum', EnumCell(choices=((1, 'aaa'), (2, 'bbb')), default=3)),
1013
919
  'opt_strs': {
1014
920
  # SUBTREE_CAN_BE_EMPTY: False,
1015
921
  'date': ('date', DateCell()),
@@ -1018,8 +924,7 @@ if __name__ == '__main__':
1018
924
  'mb_int': ('mb_int', MaybeIntCell()),
1019
925
  },
1020
926
  'pasport': (
1021
- RegexColumnMatcher(
1022
- 'Номер паспорта', '^(№|Номер) паспорта$'),
927
+ RegexColumnMatcher('Номер паспорта', '^(№|Номер) паспорта$'),
1023
928
  StringCell(),
1024
929
  ),
1025
930
  # Авто определение начала таблицы
@@ -1032,17 +937,14 @@ if __name__ == '__main__':
1032
937
  with open('test_file.xls') as xls_file:
1033
938
  ldr = XLSLoader(xls_file, config=config)
1034
939
 
1035
- print("Loaded: %s" % ldr.load())
940
+ print(f'Loaded: {ldr.load()}')
1036
941
 
1037
942
  print('---- header ----')
1038
- print('\n'.join(
1039
- '%s -> %r' % i
1040
- for i in ldr.headers['TEST']
1041
- if i
1042
- ))
943
+ print('\n'.join(f'{col} -> {val!r}' for col, val in ldr.headers['TEST'] if (col, val)))
1043
944
 
1044
945
  print('---- data ----')
1045
946
  import pprint
947
+
1046
948
  pprint.pprint(ldr.data)
1047
949
 
1048
950
  print('---- rows_log ----')