wbcore 1.46.0__py2.py3-none-any.whl → 1.58.2__py2.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 (321) hide show
  1. wbcore/cache/decorators.py +5 -3
  2. wbcore/cache/registry.py +14 -7
  3. wbcore/configs/__init__.py +1 -0
  4. wbcore/configs/configs.py +5 -0
  5. wbcore/configs/decorators.py +1 -1
  6. wbcore/configurations/configurations/apps.py +3 -2
  7. wbcore/configurations/configurations/authentication.py +1 -1
  8. wbcore/configurations/configurations/base.py +1 -1
  9. wbcore/configurations/configurations/cache.py +1 -1
  10. wbcore/configurations/configurations/i18nl10n.py +2 -1
  11. wbcore/configurations/configurations/maintenance.py +1 -1
  12. wbcore/configurations/configurations/media.py +1 -1
  13. wbcore/configurations/configurations/middleware.py +1 -1
  14. wbcore/configurations/configurations/rest_framework.py +1 -1
  15. wbcore/configurations/configurations/static.py +3 -3
  16. wbcore/configurations/configurations/wbcore.py +1 -1
  17. wbcore/content_type/serializers.py +13 -5
  18. wbcore/content_type/utils.py +3 -3
  19. wbcore/content_type/viewsets.py +2 -2
  20. wbcore/contrib/agenda/filters/calendar_item.py +5 -4
  21. wbcore/contrib/agenda/locale/de/LC_MESSAGES/django.po +145 -52
  22. wbcore/contrib/agenda/locale/de/LC_MESSAGES/django.po.translated +236 -0
  23. wbcore/contrib/agenda/locale/en/LC_MESSAGES/django.po +200 -0
  24. wbcore/contrib/agenda/locale/fr/LC_MESSAGES/django.po +201 -0
  25. wbcore/contrib/agenda/viewsets/calendar_items.py +7 -7
  26. wbcore/contrib/agenda/viewsets/menu/calendar_items.py +0 -6
  27. wbcore/contrib/ai/exceptions.py +5 -5
  28. wbcore/contrib/ai/llm/config.py +76 -27
  29. wbcore/contrib/ai/llm/mixins.py +5 -8
  30. wbcore/contrib/ai/llm/utils.py +50 -26
  31. wbcore/contrib/authentication/admin.py +2 -2
  32. wbcore/contrib/authentication/factories/__init__.py +8 -1
  33. wbcore/contrib/authentication/factories/users.py +19 -0
  34. wbcore/contrib/authentication/filters.py +1 -2
  35. wbcore/contrib/authentication/locale/de/LC_MESSAGES/django.po +209 -187
  36. wbcore/contrib/authentication/locale/de/LC_MESSAGES/django.po.translated +634 -0
  37. wbcore/contrib/authentication/locale/en/LC_MESSAGES/django.po +590 -0
  38. wbcore/contrib/authentication/locale/fr/LC_MESSAGES/django.po +592 -0
  39. wbcore/contrib/authentication/models/users.py +3 -3
  40. wbcore/contrib/authentication/models/users_activities.py +1 -1
  41. wbcore/contrib/authentication/serializers/users.py +2 -2
  42. wbcore/contrib/authentication/tests/test_tokens.py +3 -3
  43. wbcore/contrib/authentication/tests/test_users.py +0 -1
  44. wbcore/contrib/authentication/urls.py +0 -4
  45. wbcore/contrib/authentication/viewsets/endpoints/user_activities.py +2 -11
  46. wbcore/contrib/authentication/viewsets/endpoints/users.py +0 -3
  47. wbcore/contrib/authentication/viewsets/user_activities.py +2 -1
  48. wbcore/contrib/authentication/viewsets/users.py +6 -4
  49. wbcore/contrib/color/models.py +2 -1
  50. wbcore/contrib/currency/factories.py +1 -1
  51. wbcore/contrib/currency/import_export/backends/fixerio/currency_fx_rates.py +3 -1
  52. wbcore/contrib/currency/models.py +30 -8
  53. wbcore/contrib/currency/serializers.py +5 -1
  54. wbcore/contrib/currency/tests/test_serializers.py +7 -3
  55. wbcore/contrib/currency/tests/test_viewsets.py +1 -1
  56. wbcore/contrib/currency/viewsets/currency.py +2 -2
  57. wbcore/contrib/currency/viewsets/endpoints/currency_fx_rates.py +0 -9
  58. wbcore/contrib/dataloader/tests/test/dataloaders/protocols.py +1 -2
  59. wbcore/contrib/dataloader/utils.py +2 -2
  60. wbcore/contrib/directory/factories/__init__.py +1 -1
  61. wbcore/contrib/directory/factories/entries.py +2 -1
  62. wbcore/contrib/directory/filters/entries.py +9 -0
  63. wbcore/contrib/directory/locale/de/LC_MESSAGES/django.po +728 -714
  64. wbcore/contrib/directory/locale/de/LC_MESSAGES/django.po.translated +1779 -0
  65. wbcore/contrib/directory/locale/en/LC_MESSAGES/django.po +1652 -0
  66. wbcore/contrib/directory/locale/fr/LC_MESSAGES/django.po +1654 -0
  67. wbcore/contrib/directory/migrations/0011_person_description_person_i18n.py +24 -0
  68. wbcore/contrib/directory/migrations/0012_alter_person_managers.py +20 -0
  69. wbcore/contrib/directory/migrations/0013_alter_clientmanagerrelationship_options.py +17 -0
  70. wbcore/contrib/directory/models/contacts.py +2 -2
  71. wbcore/contrib/directory/models/entries.py +31 -5
  72. wbcore/contrib/directory/models/relationships.py +31 -35
  73. wbcore/contrib/directory/permissions.py +6 -0
  74. wbcore/contrib/directory/serializers/companies.py +16 -8
  75. wbcore/contrib/directory/serializers/contacts.py +8 -8
  76. wbcore/contrib/directory/serializers/entries.py +26 -15
  77. wbcore/contrib/directory/serializers/entry_representations.py +4 -2
  78. wbcore/contrib/directory/serializers/persons.py +12 -10
  79. wbcore/contrib/directory/serializers/relationships.py +2 -2
  80. wbcore/contrib/directory/tests/conftest.py +2 -0
  81. wbcore/contrib/directory/tests/disable_signals.py +11 -1
  82. wbcore/contrib/directory/tests/signals.py +2 -2
  83. wbcore/contrib/directory/tests/test_models.py +88 -66
  84. wbcore/contrib/directory/tests/test_serializers.py +1 -1
  85. wbcore/contrib/directory/tests/test_viewsets.py +8 -8
  86. wbcore/contrib/directory/viewsets/buttons/__init__.py +1 -1
  87. wbcore/contrib/directory/viewsets/buttons/relationships.py +32 -0
  88. wbcore/contrib/directory/viewsets/contacts.py +6 -6
  89. wbcore/contrib/directory/viewsets/display/__init__.py +1 -1
  90. wbcore/contrib/directory/viewsets/display/contacts.py +1 -14
  91. wbcore/contrib/directory/viewsets/display/entries.py +68 -38
  92. wbcore/contrib/directory/viewsets/display/relationships.py +26 -50
  93. wbcore/contrib/directory/viewsets/endpoints/relationships.py +1 -26
  94. wbcore/contrib/directory/viewsets/entries.py +8 -6
  95. wbcore/contrib/directory/viewsets/previews/entries.py +3 -3
  96. wbcore/contrib/directory/viewsets/relationships.py +16 -2
  97. wbcore/contrib/directory/viewsets/titles/relationships.py +2 -3
  98. wbcore/contrib/documents/filters.py +0 -2
  99. wbcore/contrib/documents/locale/de/LC_MESSAGES/django.po +103 -94
  100. wbcore/contrib/documents/locale/de/LC_MESSAGES/django.po.translated +285 -0
  101. wbcore/contrib/documents/locale/en/LC_MESSAGES/django.po +271 -0
  102. wbcore/contrib/documents/locale/fr/LC_MESSAGES/django.po +270 -0
  103. wbcore/contrib/documents/tests/test_models.py +32 -28
  104. wbcore/contrib/documents/viewsets/endpoints/shareable_links.py +2 -21
  105. wbcore/contrib/dynamic_preferences/types.py +108 -0
  106. wbcore/contrib/dynamic_preferences/viewsets.py +27 -0
  107. wbcore/contrib/example_app/filters/event.py +3 -1
  108. wbcore/contrib/example_app/filters/match.py +1 -1
  109. wbcore/contrib/example_app/models.py +91 -22
  110. wbcore/contrib/example_app/serializers/person_team.py +4 -4
  111. wbcore/contrib/example_app/templates/example_app/embedded_view.html +19 -0
  112. wbcore/contrib/example_app/tests/e2e/test_teams.py +1 -1
  113. wbcore/contrib/example_app/tests/test_models/test_match.py +17 -7
  114. wbcore/contrib/example_app/urls.py +2 -0
  115. wbcore/contrib/example_app/views.py +7 -0
  116. wbcore/contrib/example_app/viewsets/displays/team.py +23 -4
  117. wbcore/contrib/example_app/viewsets/menu/menus.py +1 -1
  118. wbcore/contrib/example_app/viewsets/menus.py +1 -1
  119. wbcore/contrib/geography/tests/conftest.py +14 -0
  120. wbcore/contrib/geography/tests/test_models.py +23 -8
  121. wbcore/contrib/geography/tests/test_viewsets.py +96 -2
  122. wbcore/contrib/guardian/tests/test_model_mixins.py +3 -4
  123. wbcore/contrib/guardian/tests/test_tasks.py +9 -9
  124. wbcore/contrib/guardian/tests/test_viewsets.py +2 -2
  125. wbcore/contrib/guardian/viewsets/configs/__init__.py +1 -1
  126. wbcore/contrib/guardian/viewsets/configs/buttons.py +9 -0
  127. wbcore/contrib/guardian/viewsets/configs/endpoints.py +7 -0
  128. wbcore/contrib/guardian/viewsets/viewsets.py +2 -0
  129. wbcore/contrib/i18n/__init__.py +2 -0
  130. wbcore/contrib/i18n/buttons.py +33 -0
  131. wbcore/contrib/i18n/serializers/__init__.py +0 -0
  132. wbcore/contrib/i18n/serializers/fields.py +20 -0
  133. wbcore/contrib/i18n/serializers/mixins.py +13 -0
  134. wbcore/contrib/i18n/tests/conftest.py +11 -0
  135. wbcore/contrib/i18n/tests/test_viewsets.py +67 -0
  136. wbcore/contrib/i18n/translation.py +140 -0
  137. wbcore/contrib/i18n/viewsets.py +36 -0
  138. wbcore/contrib/icons/backends/default.py +1 -0
  139. wbcore/contrib/icons/backends/material.py +1 -0
  140. wbcore/contrib/icons/icons.py +5 -8
  141. wbcore/contrib/io/admin.py +1 -0
  142. wbcore/contrib/io/backends/mail.py +3 -2
  143. wbcore/contrib/io/backends/utils.py +14 -17
  144. wbcore/contrib/io/exceptions.py +8 -0
  145. wbcore/contrib/io/factories.py +1 -1
  146. wbcore/contrib/io/import_export/backends/mail.py +1 -0
  147. wbcore/contrib/io/import_export/backends/sftp.py +29 -20
  148. wbcore/contrib/io/import_export/backends/stream.py +2 -2
  149. wbcore/contrib/io/import_export/parsers/__init__.py +0 -0
  150. wbcore/contrib/io/import_export/parsers/base_csv.py +36 -0
  151. wbcore/contrib/io/import_export/parsers/resources.py +50 -0
  152. wbcore/contrib/io/imports.py +33 -25
  153. wbcore/contrib/io/locale/de/LC_MESSAGES/django.po +114 -22
  154. wbcore/contrib/io/locale/de/LC_MESSAGES/django.po.translated +103 -0
  155. wbcore/contrib/io/locale/en/LC_MESSAGES/django.po +138 -0
  156. wbcore/contrib/io/locale/fr/LC_MESSAGES/django.po +138 -0
  157. wbcore/contrib/io/migrations/0008_importsource_resource_kwargs.py +18 -0
  158. wbcore/contrib/io/models.py +65 -45
  159. wbcore/contrib/io/resources.py +0 -6
  160. wbcore/contrib/io/serializers.py +2 -2
  161. wbcore/contrib/io/signals.py +4 -0
  162. wbcore/contrib/io/tests/test_backends.py +19 -13
  163. wbcore/contrib/io/tests/test_exports.py +1 -1
  164. wbcore/contrib/io/tests/test_imports.py +1 -1
  165. wbcore/contrib/io/tests/test_models.py +47 -14
  166. wbcore/contrib/io/tests/test_viewsets.py +271 -0
  167. wbcore/contrib/io/viewset_mixins.py +41 -54
  168. wbcore/contrib/notifications/admin.py +1 -0
  169. wbcore/contrib/notifications/apps.py +2 -1
  170. wbcore/contrib/notifications/backends/abstract_backend.py +2 -4
  171. wbcore/contrib/notifications/backends/firebase/backends.py +5 -2
  172. wbcore/contrib/notifications/dispatch.py +18 -7
  173. wbcore/contrib/notifications/factories/notification_types.py +1 -0
  174. wbcore/contrib/notifications/locale/de/LC_MESSAGES/django.po +25 -19
  175. wbcore/contrib/notifications/locale/de/LC_MESSAGES/django.po.translated +63 -0
  176. wbcore/contrib/notifications/locale/en/LC_MESSAGES/django.po +61 -0
  177. wbcore/contrib/notifications/locale/fr/LC_MESSAGES/django.po +62 -0
  178. wbcore/contrib/notifications/migrations/0008_notificationtype_is_lock.py +18 -0
  179. wbcore/contrib/notifications/migrations/0009_alter_notificationtypesetting_options_and_more.py +32 -0
  180. wbcore/contrib/notifications/models/notification_types.py +67 -24
  181. wbcore/contrib/notifications/serializers/notification_types.py +16 -1
  182. wbcore/contrib/notifications/tests/test_models/test_tokens.py +8 -0
  183. wbcore/contrib/notifications/tests/test_serializers/test_notification_types.py +5 -0
  184. wbcore/contrib/notifications/tests/test_viewsets/test_notification_types.py +3 -5
  185. wbcore/contrib/notifications/utils.py +3 -2
  186. wbcore/contrib/notifications/viewsets/configs/notification_types.py +28 -6
  187. wbcore/contrib/notifications/viewsets/menus.py +1 -1
  188. wbcore/contrib/notifications/viewsets/notification_types.py +12 -2
  189. wbcore/contrib/pandas/fields.py +38 -10
  190. wbcore/contrib/pandas/filters.py +4 -1
  191. wbcore/contrib/pandas/filterset.py +8 -7
  192. wbcore/contrib/pandas/tests/test_fields/test_number_fields.py +2 -7
  193. wbcore/contrib/pandas/utils.py +1 -1
  194. wbcore/contrib/pandas/views.py +14 -13
  195. wbcore/contrib/tags/models/tags.py +4 -1
  196. wbcore/contrib/workflow/factories/display.py +2 -2
  197. wbcore/contrib/workflow/factories/transition.py +16 -15
  198. wbcore/contrib/workflow/locale/de/LC_MESSAGES/django.po +457 -566
  199. wbcore/contrib/workflow/locale/de/LC_MESSAGES/django.po.translated +1326 -0
  200. wbcore/contrib/workflow/locale/en/LC_MESSAGES/django.po +1102 -0
  201. wbcore/contrib/workflow/locale/fr/LC_MESSAGES/django.po +1114 -0
  202. wbcore/contrib/workflow/models/data.py +7 -4
  203. wbcore/contrib/workflow/models/process.py +2 -2
  204. wbcore/contrib/workflow/models/step.py +57 -15
  205. wbcore/contrib/workflow/serializers/data.py +8 -8
  206. wbcore/contrib/workflow/serializers/process.py +3 -2
  207. wbcore/contrib/workflow/tests/conftest.py +224 -0
  208. wbcore/contrib/workflow/tests/test_dispatch.py +82 -77
  209. wbcore/contrib/workflow/tests/test_displays.py +10 -88
  210. wbcore/contrib/workflow/tests/test_filters.py +57 -40
  211. wbcore/contrib/workflow/tests/test_models/step/test_decision_step.py +71 -68
  212. wbcore/contrib/workflow/tests/test_models/step/test_email_step.py +78 -38
  213. wbcore/contrib/workflow/tests/test_models/step/test_finish_step.py +152 -90
  214. wbcore/contrib/workflow/tests/test_models/step/test_join_step.py +100 -110
  215. wbcore/contrib/workflow/tests/test_models/step/test_step.py +168 -33
  216. wbcore/contrib/workflow/tests/test_models/test_condition.py +1 -1
  217. wbcore/contrib/workflow/tests/test_models/test_workflow.py +3 -3
  218. wbcore/contrib/workflow/tests/test_serializers.py +172 -150
  219. wbcore/contrib/workflow/tests/test_viewsets.py +264 -323
  220. wbcore/contrib/workflow/tests/test_workflow_assignees.py +215 -205
  221. wbcore/contrib/workflow/viewsets/process.py +4 -1
  222. wbcore/contrib/workflow/workflows/assignees.py +12 -7
  223. wbcore/dynamic_preferences_registry.py +102 -0
  224. wbcore/enums.py +2 -51
  225. wbcore/filters/fields/choices.py +4 -6
  226. wbcore/filters/fields/content_type.py +15 -4
  227. wbcore/filters/fields/datetime.py +50 -25
  228. wbcore/filters/fields/models.py +18 -9
  229. wbcore/filters/fields/numbers.py +9 -8
  230. wbcore/filters/filterset.py +27 -6
  231. wbcore/filters/mixins.py +41 -42
  232. wbcore/forms.py +6 -6
  233. wbcore/fsm/markdown_extensions.py +1 -1
  234. wbcore/fsm/mixins.py +20 -6
  235. wbcore/locale/de/LC_MESSAGES/django.po +982 -397
  236. wbcore/locale/de/LC_MESSAGES/django.po.translated +1580 -0
  237. wbcore/locale/en/LC_MESSAGES/django.po +1234 -0
  238. wbcore/locale/fr/LC_MESSAGES/django.po +1235 -0
  239. wbcore/markdown/models.py +8 -5
  240. wbcore/markdown/views.py +1 -1
  241. wbcore/menus/menus.py +2 -2
  242. wbcore/metadata/configs/buttons/bases.py +10 -7
  243. wbcore/metadata/configs/buttons/buttons.py +2 -1
  244. wbcore/metadata/configs/buttons/enums.py +50 -0
  245. wbcore/metadata/configs/buttons/view_config.py +13 -46
  246. wbcore/metadata/configs/display/display.py +2 -2
  247. wbcore/metadata/configs/display/formatting.py +6 -9
  248. wbcore/metadata/configs/display/instance_display/display.py +5 -2
  249. wbcore/metadata/configs/display/instance_display/pages.py +1 -1
  250. wbcore/metadata/configs/display/instance_display/shortcuts.py +1 -1
  251. wbcore/metadata/configs/display/list_display.py +54 -40
  252. wbcore/metadata/configs/display/models.py +6 -0
  253. wbcore/metadata/configs/display/view_config.py +11 -9
  254. wbcore/metadata/configs/endpoints.py +11 -4
  255. wbcore/metadata/configs/fields.py +6 -1
  256. wbcore/metadata/configs/filter_fields.py +12 -13
  257. wbcore/metadata/configs/identifiers.py +3 -1
  258. wbcore/metadata/tests/test_buttons.py +13 -16
  259. wbcore/models/fields.py +2 -2
  260. wbcore/pagination.py +1 -2
  261. wbcore/permissions/permissions.py +2 -2
  262. wbcore/permissions/utils.py +2 -2
  263. wbcore/release_notes/display.py +2 -8
  264. wbcore/release_notes/serializers.py +2 -9
  265. wbcore/release_notes/viewsets.py +8 -2
  266. wbcore/reversion/viewsets/titles.py +4 -3
  267. wbcore/serializers/__init__.py +2 -0
  268. wbcore/serializers/fields/__init__.py +2 -1
  269. wbcore/serializers/fields/boolean.py +1 -1
  270. wbcore/serializers/fields/choice.py +28 -4
  271. wbcore/serializers/fields/datetime.py +45 -36
  272. wbcore/serializers/fields/fields.py +1 -1
  273. wbcore/serializers/fields/fsm.py +1 -1
  274. wbcore/serializers/fields/list.py +2 -5
  275. wbcore/serializers/fields/mixins.py +24 -11
  276. wbcore/serializers/fields/number.py +6 -23
  277. wbcore/serializers/fields/other.py +2 -10
  278. wbcore/serializers/fields/related.py +4 -6
  279. wbcore/serializers/fields/text.py +1 -1
  280. wbcore/serializers/fields/types.py +2 -0
  281. wbcore/serializers/serializers.py +12 -3
  282. wbcore/signals/__init__.py +1 -0
  283. wbcore/signals/clone.py +4 -0
  284. wbcore/signals/models.py +2 -6
  285. wbcore/tasks.py +2 -2
  286. wbcore/templates/wbcore/email_base_template.html +3 -3
  287. wbcore/test/e2e_helpers_methods/e2e_checks.py +10 -4
  288. wbcore/test/e2e_helpers_methods/e2e_helper_methods.py +4 -2
  289. wbcore/test/mixins.py +52 -102
  290. wbcore/test/tests.py +6 -9
  291. wbcore/test/utils.py +3 -4
  292. wbcore/tests/e2e/test_e2e.py +2 -2
  293. wbcore/tests/test_cache/test_decorators.py +4 -7
  294. wbcore/tests/test_configs.py +2 -5
  295. wbcore/tests/test_enums.py +2 -1
  296. wbcore/tests/test_fields/test_choice_fields.py +9 -1
  297. wbcore/tests/test_fields/test_number_fields.py +7 -15
  298. wbcore/tests/test_fields/test_other_fields.py +1 -2
  299. wbcore/tests/test_filters/test_mixins.py +35 -35
  300. wbcore/tests/test_list_display.py +0 -2
  301. wbcore/tests/test_models/test_mixins.py +1 -1
  302. wbcore/tests/test_utils/test_date.py +1 -1
  303. wbcore/tests/test_utils/test_date_builder.py +25 -1
  304. wbcore/tests/test_utils/test_primary.py +1 -1
  305. wbcore/urls.py +4 -0
  306. wbcore/utils/date.py +18 -2
  307. wbcore/utils/figures.py +2 -2
  308. wbcore/utils/models.py +21 -4
  309. wbcore/utils/reportlab.py +7 -0
  310. wbcore/utils/rrules.py +3 -1
  311. wbcore/utils/string_loader.py +1 -1
  312. wbcore/utils/strings.py +3 -3
  313. wbcore/utils/views.py +8 -3
  314. wbcore/viewsets/mixins.py +9 -4
  315. {wbcore-1.46.0.dist-info → wbcore-1.58.2.dist-info}/METADATA +9 -5
  316. {wbcore-1.46.0.dist-info → wbcore-1.58.2.dist-info}/RECORD +317 -271
  317. wbcore/contrib/geography/tests/test_serializers.py +0 -7
  318. wbcore/contrib/geography/tests/tests.py +0 -13
  319. wbcore/contrib/io/tests/tests.py +0 -19
  320. wbcore/contrib/workflow/tests/tests.py +0 -25
  321. {wbcore-1.46.0.dist-info → wbcore-1.58.2.dist-info}/WHEEL +0 -0
@@ -28,11 +28,12 @@ def handle_inbound(sender, event, esp_name, **kwargs):
28
28
  conditions |= Q(import_parameters__inbound_address__contains=t.addr_spec)
29
29
 
30
30
  sources = Source.objects.filter(
31
- conditions & Q(data_backend__backend_class_path="wbcore.contrib.io.backends.mail") & Q(is_active=True)
31
+ conditions
32
+ & Q(data_backend__backend_class_path="wbcore.contrib.io.import_export.backends.mail")
33
+ & Q(is_active=True)
32
34
  )
33
35
  if s := re.search(r"\[([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\]", subject):
34
36
  sources = sources.filter(uuid=s.group(1))
35
-
36
37
  for source in sources:
37
38
  if is_sender_allowed(from_email, source.import_parameters.get("whitelisted_emails", []), admin_emails):
38
39
  source.trigger_workflow(message=message)
@@ -1,5 +1,3 @@
1
- from contextlib import suppress
2
-
3
1
  from ..models import DataBackend, Provider
4
2
 
5
3
 
@@ -21,22 +19,21 @@ def register(
21
19
  raise ValueError("At least one name must be passed to register.")
22
20
 
23
21
  def _decorator(backend_class):
24
- with suppress(Exception):
25
- provider = None
26
- if provider_key:
27
- provider, created = Provider.objects.get_or_create(
28
- key=provider_key, defaults={"title": provider_key.capitalize()}
29
- )
30
- DataBackend.objects.update_or_create(
31
- backend_class_path=backend_class.__module__,
32
- backend_class_name=backend_class.__name__,
33
- defaults={
34
- "title": f"{backend_title} ({provider.title})" if provider else backend_title,
35
- "save_data_in_import_source": save_data_in_import_source,
36
- "passive_only": passive_only,
37
- "provider": provider,
38
- },
22
+ provider = None
23
+ if provider_key:
24
+ provider, created = Provider.objects.get_or_create(
25
+ key=provider_key, defaults={"title": provider_key.capitalize()}
39
26
  )
27
+ DataBackend.objects.update_or_create(
28
+ backend_class_path=backend_class.__module__,
29
+ backend_class_name=backend_class.__name__,
30
+ defaults={
31
+ "title": f"{backend_title} ({provider.title})" if provider else backend_title,
32
+ "save_data_in_import_source": save_data_in_import_source,
33
+ "passive_only": passive_only,
34
+ "provider": provider,
35
+ },
36
+ )
40
37
  return backend_class
41
38
 
42
39
  return _decorator
@@ -8,6 +8,14 @@ class DeserializationError(Exception):
8
8
  pass
9
9
 
10
10
 
11
+ class SkipImportError(Exception):
12
+ """
13
+ Exception excepted when a deserialized skip happens during the process object phase in the Handler.
14
+
15
+ This exception won't stop the importing process and won't trigger a warning
16
+ """
17
+
18
+
11
19
  class ImportError(Exception):
12
20
  """
13
21
  Exception returns when something wrong happens during the processing of the import source and means that not all data were succesfully imported
@@ -176,7 +176,7 @@ class SourceFactory(factory.django.DjangoModelFactory):
176
176
 
177
177
  class ParserHandlerFactory(factory.django.DjangoModelFactory):
178
178
  parser = factory.Sequence(lambda n: f"parser_{n}")
179
- handler = factory.Sequence(lambda n: f"handler.Handler-{n}")
179
+ handler = "io.ImportModel"
180
180
  allow_file_type = None
181
181
 
182
182
  class Meta:
@@ -10,6 +10,7 @@ from wbcore.contrib.io.backends.abstract import AbstractDataBackend
10
10
  from wbcore.contrib.io.backends.utils import register
11
11
 
12
12
 
13
+ #### IMPORTANT: If this class path change, it needs to be adapted also on the io.backends.handle_inbound function
13
14
  @register("Default Mail", save_data_in_import_source=True, passive_only=True)
14
15
  class DataBackend(AbstractDataBackend):
15
16
  def get_files(
@@ -1,4 +1,5 @@
1
1
  import io
2
+ import logging
2
3
  import re
3
4
  from datetime import datetime
4
5
  from io import BytesIO
@@ -6,10 +7,13 @@ from typing import Generator, Optional
6
7
 
7
8
  import fabric
8
9
  from django.conf import settings
10
+ from paramiko.ssh_exception import SSHException
9
11
  from wbcore.contrib.io.backends.abstract import AbstractDataBackend
10
12
  from wbcore.contrib.io.backends.utils import register
11
13
  from wbcore.contrib.io.models import ImportCredential
12
14
 
15
+ logger = logging.getLogger("io")
16
+
13
17
 
14
18
  @register("SFTP", save_data_in_import_source=True, passive_only=True)
15
19
  class DataBackend(AbstractDataBackend):
@@ -25,6 +29,7 @@ class DataBackend(AbstractDataBackend):
25
29
  self.password = import_credential.password
26
30
  self.host = import_credential.additional_resources.get("host", "")
27
31
  self.port = import_credential.additional_resources.get("port", "")
32
+ super().__init__(**kwargs)
28
33
 
29
34
  def get_files(
30
35
  self,
@@ -38,23 +43,27 @@ class DataBackend(AbstractDataBackend):
38
43
  with fabric.Connection(
39
44
  host=self.host, port=self.port, user=self.username, connect_kwargs={"password": self.password}
40
45
  ) as conn:
41
- sftp = conn.sftp()
42
-
43
- # Change working directory
44
- sftp.chdir(sftp_folder)
45
-
46
- # Filter all the files we need
47
- file_names = sftp.listdir()
48
- if file_name_regex:
49
- # Compile the regex for later filtering the files
50
- file_names = filter(lambda x: re.match(file_name_regex, x), file_names)
51
-
52
- for file_name in file_names:
53
- # Create a Buffer where we write the file to
54
- sftp_file = io.BytesIO()
55
- sftp.getfo(file_name, sftp_file)
56
- yield file_name, sftp_file
57
-
58
- if cleanup_files:
59
- # Delete the file from the server
60
- sftp.remove(file_name)
46
+ try:
47
+ sftp = conn.sftp()
48
+ # Change working directory
49
+ sftp.chdir(sftp_folder)
50
+
51
+ # Filter all the files we need
52
+ file_names = sftp.listdir()
53
+ if file_name_regex:
54
+ # Compile the regex for later filtering the files
55
+ file_names = filter(lambda x: re.match(file_name_regex, x), file_names)
56
+
57
+ for file_name in file_names:
58
+ # Create a Buffer where we write the file to
59
+ sftp_file = io.BytesIO()
60
+ sftp.getfo(file_name, sftp_file)
61
+ yield file_name, sftp_file
62
+
63
+ if cleanup_files:
64
+ # Delete the file from the server
65
+ sftp.remove(file_name)
66
+ except SSHException as e:
67
+ logger.warning(
68
+ f"While fetching file from data backend {self.data_backend}, we encountered a SSH Exception: {e}"
69
+ )
@@ -35,7 +35,7 @@ class DataBackend(AbstractDataBackend):
35
35
  self.url = url
36
36
 
37
37
  @classmethod
38
- def _check_content_type(self, output: BytesIO, filename: str) -> bool:
38
+ def _check_content_type(cls, output: BytesIO, filename: str) -> bool:
39
39
  """
40
40
  Check if given bytes stream matches the corresponding filename extension
41
41
 
@@ -80,7 +80,7 @@ class DataBackend(AbstractDataBackend):
80
80
  """
81
81
  with suppress(requests.ConnectionError):
82
82
  headers = kwargs.get("headers", {})
83
- r = requests.get(self.url, headers=headers)
83
+ r = requests.get(self.url, headers=headers, timeout=10)
84
84
  if r.ok and (content := r.content):
85
85
  content_file = BytesIO()
86
86
  content_file.write(content)
File without changes
@@ -0,0 +1,36 @@
1
+ import csv
2
+ import re
3
+ from _csv import Error
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+
9
+ def parse(import_source):
10
+ # try to guess the delimiter
11
+ try:
12
+ with import_source.file.open(mode="r") as file:
13
+ sep = csv.Sniffer().sniff(file.readline()).delimiter
14
+ except Error:
15
+ sep = ","
16
+
17
+ df = pd.read_csv(import_source.file, sep=sep)
18
+ df = df.loc[:, ~df.columns.str.contains("^Unnamed")] # remove columns without names
19
+ # in case we have import kwargs, we set them as column values
20
+ columns_mapping = import_source.resource_kwargs.get("columns_mapping", {})
21
+ extra_columns_values = import_source.resource_kwargs.get("extra_columns_values", {})
22
+ for k, v in extra_columns_values.items():
23
+ df[k] = v
24
+ df = df.rename(columns=columns_mapping)
25
+ df = df.replace([np.inf, -np.inf, np.nan], None)
26
+ rows = []
27
+ for row in df.convert_dtypes().to_dict(orient="records"):
28
+ for k, v in row.items():
29
+ if isinstance(v, str):
30
+ # we remove any whitespace special characters
31
+ row[k] = re.sub(r"\s+", "", v.strip())
32
+ rows.append(row)
33
+ data = {"data": rows}
34
+ if extra_columns_values:
35
+ data["history"] = extra_columns_values
36
+ return data
@@ -0,0 +1,50 @@
1
+ import magic
2
+ from django.apps import apps
3
+ from django.conf import settings
4
+ from import_export.formats.base_formats import CSV
5
+ from import_export.results import RowResult
6
+ from import_export.signals import post_import
7
+ from wbcore.utils.importlib import import_from_dotted_path
8
+
9
+
10
+ def default_import_parse(import_source):
11
+ resource_path = import_source.resource_kwargs["resource_path"]
12
+ resource_kwargs = import_source.resource_kwargs["resource_kwargs"]
13
+ resource_class = import_from_dotted_path(resource_path)
14
+ resource = resource_class(**resource_kwargs)
15
+ input_format = CSV(encoding="utf-8-sig")
16
+
17
+ file_stream = import_source.file.read()
18
+ if input_format.CONTENT_TYPE == magic.from_buffer(file_stream, mime=True):
19
+ dataset = input_format.create_dataset(file_stream)
20
+
21
+ model = apps.get_model(import_source.parser_handler.handler)
22
+ result = resource.import_data(
23
+ dataset,
24
+ dry_run=False,
25
+ file_name=import_source.file.name,
26
+ user=import_source.creator,
27
+ rollback_on_validation_errors=True,
28
+ raise_errors=settings.DEBUG,
29
+ )
30
+ import_source.file.close()
31
+ post_import.send(sender=None, model=model)
32
+ success_message = """
33
+ {} import finished:
34
+ * new {}
35
+ * updated {}
36
+ * skipped {}
37
+ * failed {}
38
+ * deleted {}
39
+ * invalid {}
40
+ """.format(
41
+ model._meta.verbose_name_plural,
42
+ result.totals[RowResult.IMPORT_TYPE_NEW],
43
+ result.totals[RowResult.IMPORT_TYPE_UPDATE],
44
+ result.totals[RowResult.IMPORT_TYPE_SKIP],
45
+ result.totals[RowResult.IMPORT_TYPE_ERROR],
46
+ result.totals[RowResult.IMPORT_TYPE_DELETE],
47
+ result.totals[RowResult.IMPORT_TYPE_INVALID],
48
+ )
49
+ import_source.log = success_message
50
+ import_source.save()
@@ -1,16 +1,18 @@
1
1
  import enum
2
2
  from collections.abc import Iterable
3
+ from contextlib import suppress
3
4
  from decimal import Decimal
4
5
  from typing import Any, Dict, List, Optional, Type
5
6
 
6
7
  import numpy as np
7
8
  from django.apps import apps
8
9
  from django.contrib.contenttypes.models import ContentType
10
+ from django.core.exceptions import FieldDoesNotExist
9
11
  from django.db import models
10
12
  from django.db.models import Model
11
13
  from tqdm import tqdm
12
14
 
13
- from .exceptions import DeserializationError, ImportError
15
+ from .exceptions import DeserializationError, ImportError, SkipImportError
14
16
  from .models import ImportedObjectProviderRelationship, ImportSource
15
17
  from .utils import nest_row
16
18
 
@@ -28,6 +30,7 @@ class ImportExportHandler:
28
30
  def __init__(self, import_source: ImportSource, **kwargs):
29
31
  self.import_source: ImportSource = import_source
30
32
  self.model: Type[Model] = apps.get_model(self.MODEL_APP_LABEL)
33
+ self.processed_ids: list[int] = []
31
34
 
32
35
  def _inject_internal_id_from_data(self, data: dict[str, Any]) -> tuple[int, ContentType] | None:
33
36
  if provider_id := data.pop("provider_id", None):
@@ -43,7 +46,7 @@ class ImportExportHandler:
43
46
  else:
44
47
  return provider_id, content_type
45
48
 
46
- def _model_dict_diff(self, model: Any, data: Dict[str, Any]) -> Dict[str, Any]:
49
+ def _model_dict_diff(self, model: Any, data: Dict[str, Any]) -> Dict[str, Any]: # noqa: C901
47
50
  """
48
51
  Given a model and its dictionary representation, compare them and find out the fields that are different
49
52
  Args:
@@ -67,22 +70,23 @@ class ImportExportHandler:
67
70
  if res_list:
68
71
  change_data[k] = res_list
69
72
  else:
70
- field_obj = model._meta.get_field(k)
71
- if v in [np.nan, np.inf, -np.inf]:
72
- v = None
73
- if v is not None:
74
- if isinstance(field_obj, models.DecimalField):
75
- v = round(Decimal(v), field_obj.decimal_places)
76
- if isinstance(field_obj, models.FloatField):
77
- v = float(v)
78
- if isinstance(field_obj, models.IntegerField):
79
- v = int(v)
80
- if isinstance(field_obj, models.CharField):
81
- v = str(v)
82
- if isinstance(field, models.Model) and isinstance(v, models.Model):
83
- if v.pk != field.pk:
84
- change_data[k] = v
85
- # v = field.__class__.objects.get(id=v.pk) # not sure why this was there. We comment it out and monitor
73
+ with suppress(FieldDoesNotExist):
74
+ field_obj = model._meta.get_field(k)
75
+ if v in [np.nan, np.inf, -np.inf]:
76
+ v = None
77
+ if v is not None:
78
+ if isinstance(field_obj, models.DecimalField):
79
+ v = round(Decimal(v), field_obj.decimal_places)
80
+ if isinstance(field_obj, models.FloatField):
81
+ v = float(v)
82
+ if isinstance(field_obj, models.IntegerField):
83
+ v = int(v)
84
+ if isinstance(field_obj, models.CharField):
85
+ v = str(v)
86
+ if isinstance(field, models.Model) and isinstance(v, models.Model):
87
+ if v.pk != field.pk:
88
+ change_data[k] = v
89
+ # v = field.__class__.objects.get(id=v.pk) # not sure why this was there. We comment it out and monitor
86
90
  if k not in change_data and field != v:
87
91
  change_data[k] = v
88
92
  return change_data
@@ -164,7 +168,7 @@ class ImportExportHandler:
164
168
  error_msg = f"\nError {e} while saving data {change_data} for object id {_object.pk}"
165
169
  self.import_source.log += error_msg
166
170
  if not getattr(self, "allow_update_save_failure", False):
167
- raise ImportError(error_msg)
171
+ raise ImportError(error_msg) from e
168
172
  return True
169
173
  return False
170
174
 
@@ -225,6 +229,7 @@ class ImportExportHandler:
225
229
  history: Optional[models.QuerySet] = None,
226
230
  read_only=False,
227
231
  include_update_fields=None,
232
+ raise_exception: bool = True,
228
233
  **kwargs,
229
234
  ):
230
235
  data = nest_row(data)
@@ -251,8 +256,9 @@ class ImportExportHandler:
251
256
  _object = self._create_instance(data, **kwargs)
252
257
  self._post_processing_created_object(_object)
253
258
  import_state = ImportState.CREATED
254
- if not _object:
255
- raise DeserializationError(f"Object couldn't be parsed: {data}")
259
+ if not _object and raise_exception:
260
+ data_repr = " ".join([f'{k}="{v}"' for k, v in data.items()])
261
+ raise DeserializationError(f"{self.model._meta.verbose_name} data couldn't be parsed ({data_repr})")
256
262
  if inject_internal_id_res:
257
263
  ImportedObjectProviderRelationship.objects.get_or_create(
258
264
  object_id=_object.pk,
@@ -290,12 +296,14 @@ class ImportExportHandler:
290
296
  unmodified_objs.append(_object)
291
297
  if (
292
298
  len(self.import_source.log) > self.MAX_ALLOWED_LOG_SIZE
293
- ): # In case we are exceedning the max log size, we reset the log to avoid issue when saving it
299
+ ): # In case we are exceeding the max log size, we reset the log to avoid issue when saving it
294
300
  self.import_source.log = ""
301
+ if object_id := getattr(_object, "id", None):
302
+ self.processed_ids.append(object_id)
303
+ except SkipImportError as e:
304
+ self.import_source.log += f"skipping Row {self.import_source.progress_index}: {str(e)}\n"
295
305
  except DeserializationError as e:
296
- self.import_source.errors_log += (
297
- f"\nRow {self.import_source.progress_index} ignored because error encountered: {str(e)}"
298
- )
306
+ self.import_source.errors_log += f"Warning Row {self.import_source.progress_index}: {str(e)}\n"
299
307
  self.import_source.progress_index += 1
300
308
  if with_post_processing:
301
309
  if history.exists():
@@ -1,52 +1,144 @@
1
- # GERMAN TRANSLATIONS FOR IO
1
+ # SOME DESCRIPTIVE TITLE.
2
2
  # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
3
  # This file is distributed under the same license as the PACKAGE package.
4
4
  # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
5
  #
6
+ # Translators:
7
+ # Kevin Decoster, 2025
8
+ #
6
9
  msgid ""
7
10
  msgstr ""
8
- "Project-Id-Version: \n"
11
+ "Project-Id-Version: PACKAGE VERSION\n"
9
12
  "Report-Msgid-Bugs-To: \n"
10
- "POT-Creation-Date: 2023-04-26 10:51+0200\n"
11
- "PO-Revision-Date: 2023-04-25 17:03+0200\n"
12
- "Last-Translator: \n"
13
- "Language-Team: \n"
14
- "Language: de\n"
13
+ "POT-Creation-Date: 2025-05-30 12:10+0200\n"
14
+ "PO-Revision-Date: 2025-05-30 09:40+0000\n"
15
+ "Last-Translator: Kevin Decoster, 2025\n"
16
+ "Language-Team: German (https://app.transifex.com/stainly/teams/171242/de/)\n"
15
17
  "MIME-Version: 1.0\n"
16
18
  "Content-Type: text/plain; charset=UTF-8\n"
17
19
  "Content-Transfer-Encoding: 8bit\n"
20
+ "Language: de\n"
18
21
  "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19
- "X-Generator: Poedit 3.2.2\n"
20
22
 
21
- #: wbcore/contrib/io/mixins.py:100 wbcore/contrib/io/mixins.py:101
22
- #: wbcore/contrib/io/mixins.py:102
23
- msgid "Import"
24
- msgstr "Importieren"
23
+ #: contrib/io/models.py:95
24
+ msgid "Parser-Handler"
25
+ msgstr ""
25
26
 
26
- #: wbcore/contrib/io/mixins.py:127
27
- msgid "Export"
28
- msgstr "Exportieren"
27
+ #: contrib/io/models.py:96
28
+ msgid "Parsers-Handlers"
29
+ msgstr ""
30
+
31
+ #: contrib/io/models.py:133
32
+ msgid "Content object Provider Identifier relationship"
33
+ msgstr ""
29
34
 
30
- #: wbcore/contrib/io/mixins.py:148 wbcore/contrib/io/mixins.py:149
31
- msgid "Templates"
32
- msgstr "Vorlagen"
35
+ #: contrib/io/models.py:134
36
+ msgid "Content object Provider Identifier relationships"
37
+ msgstr ""
33
38
 
34
- #: wbcore/contrib/io/models.py:157
39
+ #: contrib/io/models.py:161 contrib/io/models.py:187
35
40
  msgid "Provider"
36
41
  msgstr "Anbieter"
37
42
 
38
- #: wbcore/contrib/io/models.py:209
43
+ #: contrib/io/models.py:162
44
+ msgid "Providers"
45
+ msgstr ""
46
+
47
+ #: contrib/io/models.py:206 contrib/io/models.py:250
39
48
  msgid "Data Backend"
40
49
  msgstr "Daten-Backend"
41
50
 
42
- #: wbcore/contrib/io/models.py:216
51
+ #: contrib/io/models.py:207
52
+ msgid "Data Backends"
53
+ msgstr ""
54
+
55
+ #: contrib/io/models.py:257
43
56
  msgid "Crontab Schedule"
44
57
  msgstr "Crontab Schedule"
45
58
 
46
- #: wbcore/contrib/io/models.py:217
59
+ #: contrib/io/models.py:258
47
60
  msgid ""
48
61
  "Crontab Schedule to run the task on. Set only one schedule type, leave the "
49
62
  "others null."
50
63
  msgstr ""
51
64
  "Crontab Schedule mit der die Aufgabe ausgeführt werden soll. Setzen Sie nur "
52
65
  "einen Schedule Typen, lassen Sie die anderen leer."
66
+
67
+ #: contrib/io/models.py:299
68
+ msgid "Source"
69
+ msgstr ""
70
+
71
+ #: contrib/io/models.py:300
72
+ msgid "Sources"
73
+ msgstr ""
74
+
75
+ #: contrib/io/models.py:688
76
+ msgid "Format of file to be exported"
77
+ msgstr ""
78
+
79
+ #: contrib/io/models.py:691
80
+ msgid "Export job Content Type"
81
+ msgstr ""
82
+
83
+ #: contrib/io/models.py:694
84
+ msgid "Resource path to use when exporting"
85
+ msgstr ""
86
+
87
+ #: contrib/io/models.py:697
88
+ msgid "SQL query to be executed"
89
+ msgstr ""
90
+
91
+ #: contrib/io/models.py:699
92
+ msgid "SQL query parameters to be used with the sql query"
93
+ msgstr ""
94
+
95
+ #: contrib/io/models.py:703
96
+ msgid "Export Source"
97
+ msgstr ""
98
+
99
+ #: contrib/io/models.py:704
100
+ msgid "Export Sources"
101
+ msgstr ""
102
+
103
+ #: contrib/io/models.py:761
104
+ msgid "Your export file is available"
105
+ msgstr ""
106
+
107
+ #: contrib/io/models.py:762
108
+ msgid ""
109
+ "<p>The export job you requested is finished and available for one hour.</p>"
110
+ msgstr ""
111
+
112
+ #: contrib/io/models.py:813
113
+ msgid "Import Source"
114
+ msgstr ""
115
+
116
+ #: contrib/io/models.py:814
117
+ msgid "Import Sources"
118
+ msgstr ""
119
+
120
+ #: contrib/io/models.py:1022
121
+ msgid "Import Credential"
122
+ msgstr ""
123
+
124
+ #: contrib/io/models.py:1023
125
+ msgid "Import Credentials"
126
+ msgstr ""
127
+
128
+ #: contrib/io/viewset_mixins.py:240 contrib/io/viewset_mixins.py:241
129
+ #: contrib/io/viewset_mixins.py:242
130
+ msgid "Import"
131
+ msgstr "Importieren"
132
+
133
+ #: contrib/io/viewset_mixins.py:243
134
+ msgid "Please provide a valid CSV file (Coma Separated Value)"
135
+ msgstr ""
136
+
137
+ #: contrib/io/viewset_mixins.py:258 contrib/io/viewset_mixins.py:259
138
+ #: contrib/io/viewset_mixins.py:260
139
+ msgid "Export"
140
+ msgstr "Exportieren"
141
+
142
+ #: contrib/io/viewset_mixins.py:261
143
+ msgid "Select the export format"
144
+ msgstr ""
@@ -0,0 +1,103 @@
1
+ # GERMAN TRANSLATIONS FOR IO
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ msgid ""
7
+ msgstr ""
8
+ "Project-Id-Version: \n"
9
+ "Report-Msgid-Bugs-To: \n"
10
+ "POT-Creation-Date: 2025-05-20 09:30+0200\n"
11
+ "PO-Revision-Date: 2023-04-25 17:03+0200\n"
12
+ "Last-Translator: \n"
13
+ "Language-Team: \n"
14
+ "Language: de\n"
15
+ "MIME-Version: 1.0\n"
16
+ "Content-Type: text/plain; charset=UTF-8\n"
17
+ "Content-Transfer-Encoding: 8bit\n"
18
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19
+ "X-Generator: Poedit 3.2.2\n"
20
+
21
+ #: wbcore/contrib/io/models.py:187
22
+ msgid "Provider"
23
+ msgstr "Anbieter"
24
+
25
+ #: wbcore/contrib/io/models.py:250
26
+ msgid "Data Backend"
27
+ msgstr "Daten-Backend"
28
+
29
+ #: wbcore/contrib/io/models.py:257
30
+ msgid "Crontab Schedule"
31
+ msgstr "Crontab Schedule"
32
+
33
+ #: wbcore/contrib/io/models.py:258
34
+ msgid ""
35
+ "Crontab Schedule to run the task on. Set only one schedule type, leave the "
36
+ "others null."
37
+ msgstr ""
38
+ "Crontab Schedule mit der die Aufgabe ausgeführt werden soll. Setzen Sie nur "
39
+ "einen Schedule Typen, lassen Sie die anderen leer."
40
+
41
+ #: wbcore/contrib/io/models.py:688
42
+ msgid "Format of file to be exported"
43
+ msgstr ""
44
+
45
+ #: wbcore/contrib/io/models.py:691
46
+ msgid "Export job Content Type"
47
+ msgstr ""
48
+
49
+ #: wbcore/contrib/io/models.py:694
50
+ msgid "Resource path to use when exporting"
51
+ msgstr ""
52
+
53
+ #: wbcore/contrib/io/models.py:697
54
+ msgid "SQL query to be executed"
55
+ msgstr ""
56
+
57
+ #: wbcore/contrib/io/models.py:699
58
+ msgid "SQL query parameters to be used with the sql query"
59
+ msgstr ""
60
+
61
+ #: wbcore/contrib/io/models.py:703
62
+ #, fuzzy
63
+ #| msgid "Export"
64
+ msgid "Export Source"
65
+ msgstr "Exportieren"
66
+
67
+ #: wbcore/contrib/io/models.py:704
68
+ #, fuzzy
69
+ #| msgid "Export"
70
+ msgid "Export Sources"
71
+ msgstr "Exportieren"
72
+
73
+ #: wbcore/contrib/io/models.py:761
74
+ msgid "Your export file is available"
75
+ msgstr ""
76
+
77
+ #: wbcore/contrib/io/models.py:762
78
+ msgid ""
79
+ "<p>The export job you requested is finished and available for one hour.</p>"
80
+ msgstr ""
81
+
82
+ #: wbcore/contrib/io/viewset_mixins.py:240
83
+ #: wbcore/contrib/io/viewset_mixins.py:241
84
+ #: wbcore/contrib/io/viewset_mixins.py:242
85
+ msgid "Import"
86
+ msgstr "Importieren"
87
+
88
+ #: wbcore/contrib/io/viewset_mixins.py:243
89
+ msgid "Please provide a valid CSV file (Coma Separated Value)"
90
+ msgstr ""
91
+
92
+ #: wbcore/contrib/io/viewset_mixins.py:258
93
+ #: wbcore/contrib/io/viewset_mixins.py:259
94
+ #: wbcore/contrib/io/viewset_mixins.py:260
95
+ msgid "Export"
96
+ msgstr "Exportieren"
97
+
98
+ #: wbcore/contrib/io/viewset_mixins.py:261
99
+ msgid "Select the export format"
100
+ msgstr ""
101
+
102
+ #~ msgid "Templates"
103
+ #~ msgstr "Vorlagen"