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
@@ -0,0 +1,138 @@
1
+ # SOME DESCRIPTIVE TITLE.
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: PACKAGE VERSION\n"
9
+ "Report-Msgid-Bugs-To: \n"
10
+ "POT-Creation-Date: 2025-05-30 12:10+0200\n"
11
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
+ "Language-Team: LANGUAGE <LL@li.org>\n"
14
+ "Language: \n"
15
+ "MIME-Version: 1.0\n"
16
+ "Content-Type: text/plain; charset=UTF-8\n"
17
+ "Content-Transfer-Encoding: 8bit\n"
18
+
19
+ #: contrib/io/models.py:95
20
+ msgid "Parser-Handler"
21
+ msgstr ""
22
+
23
+ #: contrib/io/models.py:96
24
+ msgid "Parsers-Handlers"
25
+ msgstr ""
26
+
27
+ #: contrib/io/models.py:133
28
+ msgid "Content object Provider Identifier relationship"
29
+ msgstr ""
30
+
31
+ #: contrib/io/models.py:134
32
+ msgid "Content object Provider Identifier relationships"
33
+ msgstr ""
34
+
35
+ #: contrib/io/models.py:161 contrib/io/models.py:187
36
+ msgid "Provider"
37
+ msgstr ""
38
+
39
+ #: contrib/io/models.py:162
40
+ msgid "Providers"
41
+ msgstr ""
42
+
43
+ #: contrib/io/models.py:206 contrib/io/models.py:250
44
+ msgid "Data Backend"
45
+ msgstr ""
46
+
47
+ #: contrib/io/models.py:207
48
+ msgid "Data Backends"
49
+ msgstr ""
50
+
51
+ #: contrib/io/models.py:257
52
+ msgid "Crontab Schedule"
53
+ msgstr ""
54
+
55
+ #: contrib/io/models.py:258
56
+ msgid ""
57
+ "Crontab Schedule to run the task on. Set only one schedule type, leave the "
58
+ "others null."
59
+ msgstr ""
60
+
61
+ #: contrib/io/models.py:299
62
+ msgid "Source"
63
+ msgstr ""
64
+
65
+ #: contrib/io/models.py:300
66
+ msgid "Sources"
67
+ msgstr ""
68
+
69
+ #: contrib/io/models.py:688
70
+ msgid "Format of file to be exported"
71
+ msgstr ""
72
+
73
+ #: contrib/io/models.py:691
74
+ msgid "Export job Content Type"
75
+ msgstr ""
76
+
77
+ #: contrib/io/models.py:694
78
+ msgid "Resource path to use when exporting"
79
+ msgstr ""
80
+
81
+ #: contrib/io/models.py:697
82
+ msgid "SQL query to be executed"
83
+ msgstr ""
84
+
85
+ #: contrib/io/models.py:699
86
+ msgid "SQL query parameters to be used with the sql query"
87
+ msgstr ""
88
+
89
+ #: contrib/io/models.py:703
90
+ msgid "Export Source"
91
+ msgstr ""
92
+
93
+ #: contrib/io/models.py:704
94
+ msgid "Export Sources"
95
+ msgstr ""
96
+
97
+ #: contrib/io/models.py:761
98
+ msgid "Your export file is available"
99
+ msgstr ""
100
+
101
+ #: contrib/io/models.py:762
102
+ msgid ""
103
+ "<p>The export job you requested is finished and available for one hour.</p>"
104
+ msgstr ""
105
+
106
+ #: contrib/io/models.py:813
107
+ msgid "Import Source"
108
+ msgstr ""
109
+
110
+ #: contrib/io/models.py:814
111
+ msgid "Import Sources"
112
+ msgstr ""
113
+
114
+ #: contrib/io/models.py:1022
115
+ msgid "Import Credential"
116
+ msgstr ""
117
+
118
+ #: contrib/io/models.py:1023
119
+ msgid "Import Credentials"
120
+ msgstr ""
121
+
122
+ #: contrib/io/viewset_mixins.py:240 contrib/io/viewset_mixins.py:241
123
+ #: contrib/io/viewset_mixins.py:242
124
+ msgid "Import"
125
+ msgstr ""
126
+
127
+ #: contrib/io/viewset_mixins.py:243
128
+ msgid "Please provide a valid CSV file (Coma Separated Value)"
129
+ msgstr ""
130
+
131
+ #: contrib/io/viewset_mixins.py:258 contrib/io/viewset_mixins.py:259
132
+ #: contrib/io/viewset_mixins.py:260
133
+ msgid "Export"
134
+ msgstr ""
135
+
136
+ #: contrib/io/viewset_mixins.py:261
137
+ msgid "Select the export format"
138
+ msgstr ""
@@ -0,0 +1,138 @@
1
+ # SOME DESCRIPTIVE TITLE.
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: PACKAGE VERSION\n"
9
+ "Report-Msgid-Bugs-To: \n"
10
+ "POT-Creation-Date: 2025-05-30 12:10+0200\n"
11
+ "PO-Revision-Date: 2025-05-30 09:40+0000\n"
12
+ "Language-Team: French (https://app.transifex.com/stainly/teams/171242/fr/)\n"
13
+ "MIME-Version: 1.0\n"
14
+ "Content-Type: text/plain; charset=UTF-8\n"
15
+ "Content-Transfer-Encoding: 8bit\n"
16
+ "Language: fr\n"
17
+ "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
18
+
19
+ #: contrib/io/models.py:95
20
+ msgid "Parser-Handler"
21
+ msgstr ""
22
+
23
+ #: contrib/io/models.py:96
24
+ msgid "Parsers-Handlers"
25
+ msgstr ""
26
+
27
+ #: contrib/io/models.py:133
28
+ msgid "Content object Provider Identifier relationship"
29
+ msgstr ""
30
+
31
+ #: contrib/io/models.py:134
32
+ msgid "Content object Provider Identifier relationships"
33
+ msgstr ""
34
+
35
+ #: contrib/io/models.py:161 contrib/io/models.py:187
36
+ msgid "Provider"
37
+ msgstr ""
38
+
39
+ #: contrib/io/models.py:162
40
+ msgid "Providers"
41
+ msgstr ""
42
+
43
+ #: contrib/io/models.py:206 contrib/io/models.py:250
44
+ msgid "Data Backend"
45
+ msgstr ""
46
+
47
+ #: contrib/io/models.py:207
48
+ msgid "Data Backends"
49
+ msgstr ""
50
+
51
+ #: contrib/io/models.py:257
52
+ msgid "Crontab Schedule"
53
+ msgstr ""
54
+
55
+ #: contrib/io/models.py:258
56
+ msgid ""
57
+ "Crontab Schedule to run the task on. Set only one schedule type, leave the "
58
+ "others null."
59
+ msgstr ""
60
+
61
+ #: contrib/io/models.py:299
62
+ msgid "Source"
63
+ msgstr ""
64
+
65
+ #: contrib/io/models.py:300
66
+ msgid "Sources"
67
+ msgstr ""
68
+
69
+ #: contrib/io/models.py:688
70
+ msgid "Format of file to be exported"
71
+ msgstr ""
72
+
73
+ #: contrib/io/models.py:691
74
+ msgid "Export job Content Type"
75
+ msgstr ""
76
+
77
+ #: contrib/io/models.py:694
78
+ msgid "Resource path to use when exporting"
79
+ msgstr ""
80
+
81
+ #: contrib/io/models.py:697
82
+ msgid "SQL query to be executed"
83
+ msgstr ""
84
+
85
+ #: contrib/io/models.py:699
86
+ msgid "SQL query parameters to be used with the sql query"
87
+ msgstr ""
88
+
89
+ #: contrib/io/models.py:703
90
+ msgid "Export Source"
91
+ msgstr ""
92
+
93
+ #: contrib/io/models.py:704
94
+ msgid "Export Sources"
95
+ msgstr ""
96
+
97
+ #: contrib/io/models.py:761
98
+ msgid "Your export file is available"
99
+ msgstr ""
100
+
101
+ #: contrib/io/models.py:762
102
+ msgid ""
103
+ "<p>The export job you requested is finished and available for one hour.</p>"
104
+ msgstr ""
105
+
106
+ #: contrib/io/models.py:813
107
+ msgid "Import Source"
108
+ msgstr ""
109
+
110
+ #: contrib/io/models.py:814
111
+ msgid "Import Sources"
112
+ msgstr ""
113
+
114
+ #: contrib/io/models.py:1022
115
+ msgid "Import Credential"
116
+ msgstr ""
117
+
118
+ #: contrib/io/models.py:1023
119
+ msgid "Import Credentials"
120
+ msgstr ""
121
+
122
+ #: contrib/io/viewset_mixins.py:240 contrib/io/viewset_mixins.py:241
123
+ #: contrib/io/viewset_mixins.py:242
124
+ msgid "Import"
125
+ msgstr ""
126
+
127
+ #: contrib/io/viewset_mixins.py:243
128
+ msgid "Please provide a valid CSV file (Coma Separated Value)"
129
+ msgstr ""
130
+
131
+ #: contrib/io/viewset_mixins.py:258 contrib/io/viewset_mixins.py:259
132
+ #: contrib/io/viewset_mixins.py:260
133
+ msgid "Export"
134
+ msgstr ""
135
+
136
+ #: contrib/io/viewset_mixins.py:261
137
+ msgid "Select the export format"
138
+ msgstr ""
@@ -0,0 +1,18 @@
1
+ # Generated by Django 5.0.12 on 2025-02-18 09:04
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('io', '0007_alter_exportsource_query_params'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='importsource',
15
+ name='resource_kwargs',
16
+ field=models.JSONField(blank=True, default=dict),
17
+ ),
18
+ ]
@@ -1,4 +1,5 @@
1
1
  import importlib
2
+ import io
2
3
  import json
3
4
  import logging
4
5
  import os
@@ -40,7 +41,7 @@ from wbcore.utils.importlib import import_from_dotted_path
40
41
  from wbcore.utils.models import ComplexToStringMixin
41
42
 
42
43
  from .enums import ExportFormat, get_django_import_export_format
43
- from .exceptions import ImportError
44
+ from .signals import post_import
44
45
 
45
46
  logger = logging.getLogger("io")
46
47
 
@@ -60,6 +61,10 @@ class ParserHandler(models.Model):
60
61
  def __str__(self) -> str:
61
62
  return f"{self.parser}::{self.handler}"
62
63
 
64
+ @cached_property
65
+ def model(self):
66
+ return apps.get_model(self.handler)
67
+
63
68
  def parse(self, import_source: "ImportSource") -> Dict[str, Any]:
64
69
  """
65
70
  call the parser on the provided data
@@ -80,30 +85,29 @@ class ParserHandler(models.Model):
80
85
  parsed_data: The parsed_data
81
86
  """
82
87
  if parsed_data:
83
- model: Any = apps.get_model(self.handler)
84
- if handler_class := getattr(model, "import_export_handler_class", None):
88
+ if handler_class := getattr(self.model, "import_export_handler_class", None):
85
89
  handler = handler_class(import_source)
86
90
  handler.process(parsed_data, **kwargs)
87
91
 
88
92
  class Meta:
89
93
  unique_together = ("parser", "handler")
90
- verbose_name = "Parser-Handler"
91
- verbose_name_plural = "Parsers-Handlers"
94
+ verbose_name = _("Parser-Handler")
95
+ verbose_name_plural = _("Parsers-Handlers")
92
96
 
93
97
  @classmethod
94
- def get_representation_value_key(self) -> str:
98
+ def get_representation_value_key(cls) -> str:
95
99
  return "id"
96
100
 
97
101
  @classmethod
98
- def get_representation_label_key(self) -> str:
102
+ def get_representation_label_key(cls) -> str:
99
103
  return "{{parser}}::{{handler}}"
100
104
 
101
105
  @classmethod
102
- def get_representation_endpoint(self) -> str:
106
+ def get_representation_endpoint(cls) -> str:
103
107
  return "wbcore:io:parserhandlerrepresentation-list"
104
108
 
105
109
 
106
- class ImportedObjectProviderRelationship(ComplexToStringMixin, models.Model):
110
+ class ImportedObjectProviderRelationship(ComplexToStringMixin):
107
111
  """
108
112
  A model that represent the relationship/link between the imported object and a provider.
109
113
 
@@ -125,8 +129,8 @@ class ImportedObjectProviderRelationship(ComplexToStringMixin, models.Model):
125
129
  return f"{self.provider.title} ({self.provider_identifier})"
126
130
 
127
131
  class Meta:
128
- verbose_name = "Content object Provider Identifier relationship"
129
- verbose_name_plural = "Content object Provider Identifier relationships"
132
+ verbose_name = _("Content object Provider Identifier relationship")
133
+ verbose_name_plural = _("Content object Provider Identifier relationships")
130
134
  unique_together = [
131
135
  ("content_type", "object_id", "provider"),
132
136
  ("content_type", "provider_identifier", "provider"),
@@ -153,8 +157,8 @@ class Provider(models.Model):
153
157
  return f"{self.title} ({self.key})"
154
158
 
155
159
  class Meta:
156
- verbose_name = "Provider"
157
- verbose_name_plural = "Providers"
160
+ verbose_name = _("Provider")
161
+ verbose_name_plural = _("Providers")
158
162
 
159
163
 
160
164
  class DataBackend(models.Model):
@@ -198,8 +202,8 @@ class DataBackend(models.Model):
198
202
  return rel.content_object
199
203
 
200
204
  class Meta:
201
- verbose_name = "Data Backend"
202
- verbose_name_plural = "Data Backends"
205
+ verbose_name = _("Data Backend")
206
+ verbose_name_plural = _("Data Backends")
203
207
 
204
208
  @cached_property
205
209
  def backend_class(self) -> Any:
@@ -214,15 +218,15 @@ class DataBackend(models.Model):
214
218
  return self.title
215
219
 
216
220
  @classmethod
217
- def get_representation_value_key(self) -> str:
221
+ def get_representation_value_key(cls) -> str:
218
222
  return "id"
219
223
 
220
224
  @classmethod
221
- def get_representation_label_key(self) -> str:
225
+ def get_representation_label_key(cls) -> str:
222
226
  return "{{title}}"
223
227
 
224
228
  @classmethod
225
- def get_representation_endpoint(self) -> str:
229
+ def get_representation_endpoint(cls) -> str:
226
230
  return "wbcore:io:databackendrepresentation-list"
227
231
 
228
232
 
@@ -291,8 +295,8 @@ class Source(models.Model):
291
295
  return self.title if self.title else f"{self.uuid}"
292
296
 
293
297
  class Meta:
294
- verbose_name = "Source"
295
- verbose_name_plural = "Sources"
298
+ verbose_name = _("Source")
299
+ verbose_name_plural = _("Sources")
296
300
 
297
301
  @property
298
302
  def current_valid_credential(self) -> "ImportCredential":
@@ -518,7 +522,7 @@ class Source(models.Model):
518
522
  return res
519
523
 
520
524
  @classmethod
521
- def load_sources_from_settings(self, settings: list[tuple[list[tuple[str, str]], str, dict[str, Any]]]):
525
+ def load_sources_from_settings(cls, settings: list[tuple[list[tuple[str, str]], str, dict[str, Any]]]):
522
526
  """
523
527
  Utility classmethod to parser sources from the settings.
524
528
 
@@ -584,15 +588,15 @@ class Source(models.Model):
584
588
  source.save()
585
589
 
586
590
  @classmethod
587
- def get_representation_value_key(self) -> str:
591
+ def get_representation_value_key(cls) -> str:
588
592
  return "id"
589
593
 
590
594
  @classmethod
591
- def get_representation_label_key(self) -> str:
595
+ def get_representation_label_key(cls) -> str:
592
596
  return "{{title}} ({{id}})"
593
597
 
594
598
  @classmethod
595
- def get_representation_endpoint(self) -> str:
599
+ def get_representation_endpoint(cls) -> str:
596
600
  return "wbcore:io:sourcerepresentation-list"
597
601
 
598
602
 
@@ -635,15 +639,9 @@ def process_import_sources_as_task(import_source_ids: list[int]):
635
639
  """
636
640
  Call the `import_source` as a celery task
637
641
  """
638
- errors = []
639
642
  for import_source_id in import_source_ids:
640
643
  import_source = ImportSource.objects.get(id=import_source_id)
641
- try:
642
- import_source.import_data(force_reimport=True)
643
- except ImportError:
644
- errors.append(import_source_id)
645
- if len(errors) > 0:
646
- raise ImportError(f"Error while processing import source {errors}")
644
+ import_source.import_data(force_reimport=True)
647
645
 
648
646
 
649
647
  class ImportExportSource(models.Model):
@@ -669,6 +667,7 @@ class ImportExportSource(models.Model):
669
667
  "authentication.User", null=True, blank=True, verbose_name="Creator", on_delete=models.SET_NULL
670
668
  )
671
669
  data = models.JSONField(default=dict, null=True, blank=True)
670
+ resource_kwargs = models.JSONField(default=dict, blank=True, null=False)
672
671
 
673
672
  class Meta:
674
673
  abstract = True
@@ -688,8 +687,6 @@ class ExportSource(ImportExportSource):
688
687
  verbose_name=_("Resource path to use when exporting"), max_length=255, default="", blank=True, null=True
689
688
  )
690
689
 
691
- resource_kwargs = models.JSONField(default=dict, blank=True, null=False)
692
-
693
690
  query_str = models.TextField(verbose_name=_("SQL query to be executed"), blank=True, null=False)
694
691
  query_params = PickledObjectField( # we have to use picklefield because the sql parameters are given as python object. However, no django model should be stored in this field, which should be the case as these objects are given through their IDs
695
692
  blank=True, verbose_name=_("SQL query parameters to be used with the sql query"), default=list
@@ -716,6 +713,9 @@ class ExportSource(ImportExportSource):
716
713
  )
717
714
  ]
718
715
 
716
+ def __str__(self) -> str:
717
+ return str(self.id)
718
+
719
719
  @property
720
720
  def file_format(self):
721
721
  return get_django_import_export_format(self.format)()
@@ -793,6 +793,7 @@ class ExportSource(ImportExportSource):
793
793
  self.log = f"{ex_type}: {ex_value}\n"
794
794
  self.log += traceback.format_exc()
795
795
  self.save()
796
+ logger.error(f"Export source {self.id} error: {e}")
796
797
 
797
798
 
798
799
  class ImportSource(ImportExportSource):
@@ -806,8 +807,8 @@ class ImportSource(ImportExportSource):
806
807
  errors_log = models.TextField(null=True, blank=True)
807
808
 
808
809
  class Meta:
809
- verbose_name = "Import Source"
810
- verbose_name_plural = "Import Sources"
810
+ verbose_name = _("Import Source")
811
+ verbose_name_plural = _("Import Sources")
811
812
  notification_types = [
812
813
  create_notification_type(
813
814
  code="io.import_done",
@@ -884,29 +885,48 @@ class ImportSource(ImportExportSource):
884
885
  try:
885
886
  data = self._parse_data()
886
887
  self._process_data(data, debug=debug, **kwargs)
888
+ post_import.send(sender=self.parser_handler.model, import_source=self)
889
+
887
890
  except Exception as e:
888
891
  ex_type, ex_value, _ = sys.exc_info()
889
892
  self.status = self.Status.ERROR.name
890
893
  self.log = f"{ex_type}: {ex_value}\n"
891
894
  self.log += traceback.format_exc()
892
895
  self.save()
893
- if not debug:
894
- raise ImportError(
895
- f"Could not import file {self.file.name} for backend {self.source.data_backend.title} and parser/handler {self.parser_handler}"
896
- )
897
- else:
896
+ if debug:
898
897
  raise e
898
+ elif not self.creator: # if a creator is set in this import source, they will receive a proper notification with feedback. No need then to pollute the logger
899
+ logger.error(f"Could not import file {self.file.name} (parser/handler: {self.parser_handler})")
900
+ self.notify()
901
+
902
+ def notify(self):
903
+ if self.creator:
904
+ body = f"""<p><strong>File Name Processed:</strong> {self.file.name}</p>
905
+ <p><strong>Number of Rows:</strong> {self.progress_index}</p>
906
+ <p><strong>Status:</strong> {self.Status[self.status].label}</p>"""
907
+ if self.status == self.Status.ERROR:
908
+ body += "<p>While processing the import, we encountered an unrecoverable error. Please contact a system administrator.</p>"
909
+ elif self.status == self.Status.WARNING and self.errors_log:
910
+ body += f"""<p><strong>Warning:</strong> Some rows were ignored during import.</p>
911
+ <p><strong>Ignored Rows:</strong></p>
912
+ <ul>{"".join(['<li>' + line + '</li>' for line in io.StringIO(self.errors_log)])}</ul>"""
913
+ send_notification(
914
+ code="io.import_done",
915
+ title=f"Your import finished with status {self.Status[self.status].label}",
916
+ body=body,
917
+ user=self.creator,
918
+ )
899
919
 
900
920
  @classmethod
901
- def get_representation_value_key(self) -> str:
921
+ def get_representation_value_key(cls) -> str:
902
922
  return "id"
903
923
 
904
924
  @classmethod
905
- def get_representation_label_key(self) -> str:
925
+ def get_representation_label_key(cls) -> str:
906
926
  return "{{file}}"
907
927
 
908
928
  @classmethod
909
- def get_representation_endpoint(self) -> str:
929
+ def get_representation_endpoint(cls) -> str:
910
930
  return "wbcore:io:importsourcerepresentation-list"
911
931
 
912
932
 
@@ -996,8 +1016,8 @@ class ImportCredential(models.Model):
996
1016
  super().save(*args, **kwargs)
997
1017
 
998
1018
  class Meta:
999
- verbose_name = "Import Credential"
1000
- verbose_name_plural = "Import Credentials"
1019
+ verbose_name = _("Import Credential")
1020
+ verbose_name_plural = _("Import Credentials")
1001
1021
 
1002
1022
  def __str__(self) -> str:
1003
1023
  dates_repr = ""
@@ -171,12 +171,6 @@ class ViewResource(ExportResourceMixin, resources.Resource):
171
171
  columns_map = {"id": "id"}
172
172
  # Get default fields from the list display
173
173
  if list_display := view.display_config_class(view, view.request, instance=None).get_list_display():
174
- if list_display.tree_group_field:
175
- columns_map[list_display.tree_group_field] = (
176
- list_display.tree_group_label
177
- if list_display.tree_group_label
178
- else list_display.tree_group_field.title()
179
- )
180
174
  for key, label in list_display.flatten_fields:
181
175
  columns_map[key] = label
182
176
  return columns_map
@@ -88,8 +88,8 @@ class ImportSourceModelSerializer(ModelSerializer):
88
88
  parser_lookup = dict(id=parser_handler)
89
89
  try:
90
90
  data["parser_handler"] = ParserHandler.objects.get(**parser_lookup)
91
- except (ParserHandler.DoesNotExist, ValueError):
92
- raise ValidationError({"parser_handler": "Invalid parser handler"})
91
+ except (ParserHandler.DoesNotExist, ValueError) as e:
92
+ raise ValidationError({"parser_handler": "Invalid parser handler"}) from e
93
93
  return data
94
94
 
95
95
  def create(self, validated_data):
@@ -0,0 +1,4 @@
1
+ from django.db.models.signals import ModelSignal
2
+
3
+ # Signal fired after a import source was imported
4
+ post_import = ModelSignal(use_caching=False)
@@ -6,8 +6,6 @@ from anymail.signals import AnymailInboundEvent, EventType, inbound
6
6
  from django.utils import timezone
7
7
  from faker import Faker
8
8
 
9
- from ..import_export.backends.mail import DataBackend as MailDataBackend
10
- from ..import_export.backends.sftp import DataBackend as SFTPDataBackend
11
9
  from ..models import Source
12
10
  from .test_models import get_byte_stream
13
11
 
@@ -43,6 +41,8 @@ VFS = {
43
41
  @pytest.mark.django_db
44
42
  class TestBackend:
45
43
  def test_mail_backend(self):
44
+ from wbcore.contrib.io.import_export.backends.mail import DataBackend as MailDataBackend
45
+
46
46
  b1 = get_byte_stream()
47
47
  b2 = get_byte_stream()
48
48
  filename1 = "file_valid_12385.json"
@@ -69,15 +69,20 @@ class TestBackend:
69
69
  assert (res[0][1]).getvalue() == b1.getvalue()
70
70
 
71
71
  @pytest.mark.parametrize(
72
- "source__import_parameters,data_backend__backend_class_path,from_email",
72
+ "source__import_parameters,from_email",
73
73
  [
74
- ({"whitelisted_emails": ["test@test.ch"]}, "wbcore.contrib.io.backends.mail", "test@test.ch"),
75
- ({"whitelisted_emails": ["test@test.ch"]}, "wbcore.contrib.io.backends.mail", "spam@test.ch"),
76
- ({"whitelisted_emails": ["test@test.ch"]}, "wbcore.contrib.io.backends.unvalid", "test@test.ch"),
74
+ ({"whitelisted_emails": ["test@test.ch"]}, "test@test.ch"),
75
+ ({"whitelisted_emails": ["test@test.ch"]}, "spam@ test.ch"),
77
76
  ],
78
77
  )
79
- @patch.object(Source, "generate_import_sources")
80
- def test_inbound_mail(self, mock_process_source, source, data_backend, from_email):
78
+ @patch.object(Source, "trigger_workflow")
79
+ def test_inbound_mail(self, mock_process_source, source, from_email):
80
+ from wbcore.contrib.io.models import DataBackend
81
+
82
+ data_backend = DataBackend.objects.get(title="Default Mail")
83
+
84
+ assert data_backend.backend_class_path == "wbcore.contrib.io.import_export.backends.mail"
85
+
81
86
  source.data_backend = data_backend
82
87
  source.save()
83
88
  message = AnymailInboundMessage.construct(
@@ -95,23 +100,24 @@ class TestBackend:
95
100
  message=message,
96
101
  )
97
102
  inbound.send(sender="TEST", event=event, esp_name="test")
98
- if (
99
- source.data_backend.backend_class_path == "wbcore.contrib.io.backends.mail"
100
- and source.import_parameters.get("whitelisted_emails", [None])[0] == from_email
101
- ):
103
+ if source.import_parameters.get("whitelisted_emails", [None])[0] == from_email:
102
104
  assert mock_process_source.call_count == 1
103
105
  else:
104
106
  assert mock_process_source.call_count == 0
105
107
 
106
108
  def test_sftp_backend_without_credential(self):
109
+ from wbcore.contrib.io.import_export.backends.sftp import DataBackend as SFTPDataBackend
110
+
107
111
  with pytest.raises(ValueError):
108
112
  SFTPDataBackend()
109
113
 
110
114
  def test_sftp_backend(self, sftpserver, import_credential_factory):
115
+ from wbcore.contrib.io.import_export.backends.sftp import DataBackend as SFTPDataBackend
116
+
111
117
  with sftpserver.serve_content(VFS):
112
118
  import_credential = import_credential_factory.create(
113
119
  username="user",
114
- password="password",
120
+ password="password", # noqa
115
121
  additional_resources={
116
122
  "host": sftpserver.host,
117
123
  "port": sftpserver.port,