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,7 +28,10 @@ class Data(WBModel):
28
28
  serializers.DateTimeField(),
29
29
  serializers.BooleanField(),
30
30
  ]
31
- return {data_type: serializer_field for data_type, serializer_field in zip(cls, serializer_fields)}
31
+ return {
32
+ data_type: serializer_field
33
+ for data_type, serializer_field in zip(cls, serializer_fields, strict=False)
34
+ }
32
35
 
33
36
  @classmethod
34
37
  def get_cast_mapping(cls) -> dict:
@@ -39,7 +42,7 @@ class Data(WBModel):
39
42
  datetime.strptime,
40
43
  bool,
41
44
  ]
42
- return {data_type: cast_callable for data_type, cast_callable in zip(cls, cast_callables)}
45
+ return {data_type: cast_callable for data_type, cast_callable in zip(cls, cast_callables, strict=False)}
43
46
 
44
47
  workflow = models.ForeignKey(
45
48
  to="workflow.Workflow",
@@ -176,8 +179,8 @@ class Data(WBModel):
176
179
  format_str = "%d.%m.%Y %H:%M:%S"
177
180
  try:
178
181
  casted_object = data_object.strftime(format_str)
179
- except AttributeError:
180
- raise ValueError(gettext("Date(time) type selected but no date(time) object provided!"))
182
+ except AttributeError as e:
183
+ raise ValueError(gettext("Date(time) type selected but no date(time) object provided!")) from e
181
184
 
182
185
  elif data_type == cls.DataType.BOOL:
183
186
  if data_object is True:
@@ -27,7 +27,7 @@ class Process(WBModel):
27
27
  WBColor.GREEN_LIGHT.value,
28
28
  WBColor.RED_LIGHT.value,
29
29
  ]
30
- return [choice for choice in zip(cls, colors)]
30
+ return [choice for choice in zip(cls, colors, strict=False)]
31
31
 
32
32
  id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, verbose_name=_("UUID"))
33
33
  workflow = models.ForeignKey(
@@ -124,7 +124,7 @@ class ProcessStep(WBModel):
124
124
  WBColor.RED_LIGHT.value,
125
125
  WBColor.GREY.value,
126
126
  ]
127
- return [choice for choice in zip(cls, colors)]
127
+ return [choice for choice in zip(cls, colors, strict=False)]
128
128
 
129
129
  id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, verbose_name=_("UUID"))
130
130
  process = models.ForeignKey(
@@ -39,7 +39,10 @@ class Step(WBModel):
39
39
 
40
40
  name = models.CharField(max_length=128, verbose_name=_("Name"))
41
41
  workflow = models.ForeignKey(
42
- to="workflow.Workflow", verbose_name=_("Workflow"), on_delete=models.CASCADE, related_name="associated_steps"
42
+ to="workflow.Workflow",
43
+ verbose_name=_("Workflow"),
44
+ on_delete=models.CASCADE,
45
+ related_name="associated_steps",
43
46
  )
44
47
  code = models.PositiveIntegerField(verbose_name=_("Code"))
45
48
  status = models.CharField(
@@ -62,6 +65,9 @@ class Step(WBModel):
62
65
  help_text=_("Define which permission is needed to be able to view this step being executed."),
63
66
  )
64
67
 
68
+ outgoing_transitions: models.QuerySet[Transition]
69
+ incoming_transitions: models.QuerySet[Transition]
70
+
65
71
  def get_outgoing_transitions(self) -> models.QuerySet[Transition]:
66
72
  return self.outgoing_transitions.all()
67
73
 
@@ -139,15 +145,24 @@ class Step(WBModel):
139
145
  if len(valid_transitions := self.get_all_valid_outgoing_transitions(process_step)) == 1:
140
146
  Step.start_next_step(process_step, valid_transitions[0])
141
147
  elif not valid_transitions:
142
- self.set_failed(process_step, gettext("No valid outgoing transitions found for this step!"))
148
+ self.set_failed(
149
+ process_step,
150
+ gettext("No valid outgoing transitions found for this step!"),
151
+ )
143
152
  else:
144
- self.set_failed(process_step, gettext("More than one possible outgoing transition found for this step!"))
153
+ self.set_failed(
154
+ process_step,
155
+ gettext("More than one possible outgoing transition found for this step!"),
156
+ )
145
157
 
146
158
  def run(self, process_step: ProcessStep):
147
159
  self.set_finished(process_step)
148
160
 
149
161
  def finish(self, process_step: ProcessStep):
150
- if process_step.state in [ProcessStep.StepState.FINISHED, ProcessStep.StepState.CANCELED]:
162
+ if process_step.state in [
163
+ ProcessStep.StepState.FINISHED,
164
+ ProcessStep.StepState.CANCELED,
165
+ ]:
151
166
  # Set finished field
152
167
  process_step.finished = datetime.now()
153
168
  process_step.save()
@@ -281,7 +296,10 @@ class UserStep(Step):
281
296
  else:
282
297
  self.set_failed(process_step, gettext("No assignees selected!"))
283
298
  else:
284
- self.set_failed(process_step, gettext("No valid outgoing transitions found for this step!"))
299
+ self.set_failed(
300
+ process_step,
301
+ gettext("No valid outgoing transitions found for this step!"),
302
+ )
285
303
 
286
304
  def set_failed(self, process_step: ProcessStep, error_message: str):
287
305
  process_step.state = ProcessStep.StepState.FAILED
@@ -314,7 +332,10 @@ class UserStep(Step):
314
332
  title=title,
315
333
  body=body,
316
334
  user=user,
317
- endpoint=reverse(f"{process_step.get_endpoint_basename()}-detail", args=[process_step.id]),
335
+ endpoint=reverse(
336
+ f"{process_step.get_endpoint_basename()}-detail",
337
+ args=[process_step.id],
338
+ ),
318
339
  )
319
340
 
320
341
  @classmethod
@@ -365,7 +386,10 @@ class DecisionStep(Step):
365
386
  if transition := self.get_first_valid_transition(process_step):
366
387
  Step.start_next_step(process_step, transition)
367
388
  else:
368
- self.set_failed(process_step, gettext("No valid outgoing transition found for this step!"))
389
+ self.set_failed(
390
+ process_step,
391
+ gettext("No valid outgoing transition found for this step!"),
392
+ )
369
393
 
370
394
  @classmethod
371
395
  def get_endpoint_basename(cls):
@@ -390,7 +414,10 @@ class SplitStep(Step):
390
414
  next_step: Step = transition.to_step
391
415
  activate_step.delay(next_step.pk, process_step.process.pk)
392
416
  else:
393
- self.set_failed(process_step, gettext("No valid outgoing transitions found for this step!"))
417
+ self.set_failed(
418
+ process_step,
419
+ gettext("No valid outgoing transitions found for this step!"),
420
+ )
394
421
 
395
422
  @classmethod
396
423
  def get_endpoint_basename(cls):
@@ -407,7 +434,8 @@ class SplitStep(Step):
407
434
 
408
435
  class JoinStep(Step):
409
436
  """A background step that joins multiple steps into one. Waits until all incoming transitions are done by default.
410
- If wait_for_all is False the first incoming transition cancels all other active incoming transitions."""
437
+ If wait_for_all is False the first incoming transition cancels all other active incoming transitions.
438
+ """
411
439
 
412
440
  wait_for_all = models.BooleanField(
413
441
  default=True,
@@ -466,7 +494,6 @@ class JoinStep(Step):
466
494
  )
467
495
  ):
468
496
  self.cancel_if_leading_to_self(unfinished_process_step.step, unfinished_process_step)
469
-
470
497
  self.execute_single_next_step(process_step)
471
498
 
472
499
  @classmethod
@@ -528,10 +555,16 @@ class EmailStep(Step):
528
555
  verbose_name=pgettext_lazy("Email context", "To"),
529
556
  )
530
557
  cc = models.ManyToManyField(
531
- "directory.EmailContact", related_name="workflow_steps_cc", verbose_name=_("CC"), blank=True
558
+ "directory.EmailContact",
559
+ related_name="workflow_steps_cc",
560
+ verbose_name=_("CC"),
561
+ blank=True,
532
562
  )
533
563
  bcc = models.ManyToManyField(
534
- "directory.EmailContact", related_name="workflow_steps_bcc", verbose_name=_("BCC"), blank=True
564
+ "directory.EmailContact",
565
+ related_name="workflow_steps_bcc",
566
+ verbose_name=_("BCC"),
567
+ blank=True,
535
568
  )
536
569
 
537
570
  def run(self, process_step: ProcessStep):
@@ -584,12 +617,16 @@ class FinishStep(Step):
584
617
  # Finish if there are no running process steps for this process
585
618
  if (
586
619
  ProcessStep.objects.filter(
587
- process=process_step.process, state__in=[ProcessStep.StepState.WAITING, ProcessStep.StepState.ACTIVE]
620
+ process=process_step.process,
621
+ state__in=[ProcessStep.StepState.WAITING, ProcessStep.StepState.ACTIVE],
588
622
  )
589
623
  .exclude(id=process_step.pk)
590
624
  .exists()
591
625
  ):
592
- self.set_failed(process_step, gettext("There are process steps still running for this workflow!"))
626
+ self.set_failed(
627
+ process_step,
628
+ gettext("There are process steps still running for this workflow!"),
629
+ )
593
630
  else:
594
631
  self.set_finished(process_step)
595
632
 
@@ -726,7 +763,12 @@ def process_can_finish(process_id: UUID):
726
763
  & Q(
727
764
  Q(process_steps__isnull=True)
728
765
  | Q(
729
- Q(process_steps__state__in=[ProcessStep.StepState.ACTIVE, ProcessStep.StepState.WAITING])
766
+ Q(
767
+ process_steps__state__in=[
768
+ ProcessStep.StepState.ACTIVE,
769
+ ProcessStep.StepState.WAITING,
770
+ ]
771
+ )
730
772
  & Q(process_steps__process=process)
731
773
  )
732
774
  )
@@ -43,7 +43,7 @@ class DataModelSerializer(wb_serializers.ModelSerializer):
43
43
  if data_type:
44
44
  try:
45
45
  Data.cast_value_to_datatype(data_type, default)
46
- except ValueError:
46
+ except ValueError as e:
47
47
  if data_type == Data.DataType.DATE:
48
48
  raise ValidationError(
49
49
  {
@@ -51,7 +51,7 @@ class DataModelSerializer(wb_serializers.ModelSerializer):
51
51
  "Invalid default value for this data type. Please use a date formatted to 'day.month.year'."
52
52
  )
53
53
  }
54
- )
54
+ ) from None
55
55
  elif data_type == Data.DataType.DATETIME:
56
56
  raise ValidationError(
57
57
  {
@@ -59,8 +59,8 @@ class DataModelSerializer(wb_serializers.ModelSerializer):
59
59
  "Invalid default value for this data type. Please use a datetime formatted to 'day.month.year hour:minute:second' in the 24h format."
60
60
  )
61
61
  }
62
- )
63
- raise ValidationError({"default": _("Invalid default value for this data type.")})
62
+ ) from None
63
+ raise ValidationError({"default": _("Invalid default value for this data type.")}) from e
64
64
 
65
65
  return data
66
66
 
@@ -99,7 +99,7 @@ class DataValueModelSerializer(wb_serializers.ModelSerializer):
99
99
  if data_obj and value:
100
100
  try:
101
101
  Data.cast_value_to_datatype(data_obj.data_type, value)
102
- except ValueError:
102
+ except ValueError as e:
103
103
  if data_obj.data_type == Data.DataType.DATE:
104
104
  raise ValidationError(
105
105
  {
@@ -107,7 +107,7 @@ class DataValueModelSerializer(wb_serializers.ModelSerializer):
107
107
  "Invalid value for this data type. Please use a date formatted to 'day.month.year'."
108
108
  )
109
109
  }
110
- )
110
+ ) from None
111
111
  elif data_obj.data_type == Data.DataType.DATETIME:
112
112
  raise ValidationError(
113
113
  {
@@ -115,8 +115,8 @@ class DataValueModelSerializer(wb_serializers.ModelSerializer):
115
115
  "Invalid value for this data type. Please use a datetime formatted to 'day.month.year hour:minute:second' in the 24h format."
116
116
  )
117
117
  }
118
- )
119
- raise ValidationError({"value": _("Invalid value for this data type.")})
118
+ ) from None
119
+ raise ValidationError({"value": _("Invalid value for this data type.")}) from e
120
120
 
121
121
  return data
122
122
 
@@ -35,7 +35,9 @@ class ProcessModelSerializer(wb_serializers.ModelSerializer):
35
35
  return {}
36
36
  return {
37
37
  "process_steps": reverse(
38
- "wbcore:workflow:processstep-process-list", args=[instance.pk], request=self.context["request"]
38
+ "wbcore:workflow:processstep-process-list",
39
+ args=[instance.pk],
40
+ request=self.context["request"],
39
41
  )
40
42
  }
41
43
 
@@ -154,7 +156,6 @@ class AssignedProcessStepSerializer(wb_serializers.ModelSerializer):
154
156
 
155
157
  if not self.context.get("request"):
156
158
  return ""
157
-
158
159
  instance_object = obj.process.instance if obj.process.instance else obj
159
160
  return reverse(
160
161
  f"{instance_object.get_endpoint_basename()}-detail",
@@ -1,6 +1,8 @@
1
1
  import pytest
2
2
  from django.apps import apps
3
3
  from django.db.backends.base.base import BaseDatabaseWrapper
4
+ from pytest_mock import MockerFixture
5
+ from rest_framework.test import APIRequestFactory, APIClient
4
6
  from django.db.models.signals import pre_migrate
5
7
  from pytest_factoryboy import register
6
8
  from wbcore.contrib.geography.tests.signals import app_pre_migration
@@ -23,6 +25,24 @@ from wbcore.contrib.workflow.factories import (
23
25
  UserStepFactory,
24
26
  WorkflowFactory,
25
27
  )
28
+ from wbcore.contrib.authentication.factories import PermissionFactory
29
+
30
+ from wbcore.contrib.workflow.models import (
31
+ ProcessStep,
32
+ Step,
33
+ )
34
+
35
+ from wbcore.contrib.workflow.serializers import (
36
+ AssignedProcessStepSerializer,
37
+ ConditionModelSerializer,
38
+ DataModelSerializer,
39
+ DecisionStepModelSerializer,
40
+ ProcessModelSerializer,
41
+ ProcessStepModelSerializer,
42
+ TransitionModelSerializer,
43
+ WorkflowModelSerializer,
44
+ )
45
+
26
46
  from wbcore.tests.conftest import *
27
47
 
28
48
  register(WorkflowFactory)
@@ -44,6 +64,210 @@ register(DataValueFactory)
44
64
  register(StartStepFactory)
45
65
 
46
66
 
67
+ # ============================== General Fixtures ==============================
68
+
69
+
70
+ @pytest.fixture
71
+ def api_client(super_user):
72
+ client = APIClient()
73
+ client.force_authenticate(user=super_user)
74
+ return client
75
+
76
+
77
+ @pytest.fixture
78
+ def request_factory():
79
+ return APIRequestFactory()
80
+
81
+
82
+ @pytest.fixture
83
+ def super_user(django_db_setup, django_db_blocker):
84
+ return UserFactory(is_superuser=True)
85
+
86
+
87
+ @pytest.fixture
88
+ def user(django_db_setup, django_db_blocker):
89
+ with django_db_blocker.unblock():
90
+ return UserFactory(is_superuser=False)
91
+
92
+
93
+ @pytest.fixture()
94
+ def user_step():
95
+ return UserStepFactory(step_type=Step.StepType.USERSTEP)
96
+
97
+
98
+ @pytest.fixture
99
+ def permission(django_db_setup, django_db_blocker):
100
+ with django_db_blocker.unblock():
101
+ return PermissionFactory()
102
+
103
+
104
+ # ============================== Condition Fixtures ==============================
105
+
106
+
107
+ @pytest.fixture
108
+ def condition(conditions):
109
+ return conditions[0]
110
+
111
+
112
+ @pytest.fixture
113
+ def condition_build():
114
+ transition = TransitionFactory()
115
+ instance = ConditionFactory.build(transition=transition)
116
+ return ConditionModelSerializer(instance).data
117
+
118
+
119
+ @pytest.fixture
120
+ def conditions():
121
+ return ConditionFactory.create_batch(3)
122
+
123
+
124
+ # ============================== Data Fixtures ==============================
125
+
126
+
127
+ @pytest.fixture
128
+ def data():
129
+ return DataFactory.create_batch(3)
130
+
131
+
132
+ @pytest.fixture
133
+ def data_build(workflow):
134
+ instance = DataFactory.build(workflow=workflow)
135
+ return DataModelSerializer(instance).data
136
+
137
+
138
+ @pytest.fixture
139
+ def singular_data(data):
140
+ return data[0]
141
+
142
+
143
+ # ============================== Decision Step Fixtures ==============================
144
+
145
+
146
+ @pytest.fixture
147
+ def decision_step(decision_steps):
148
+ return decision_steps[0]
149
+
150
+
151
+ @pytest.fixture
152
+ def decision_step_build(workflow):
153
+ instance = DecisionStepFactory.build(workflow=workflow)
154
+ return DecisionStepModelSerializer(instance).data
155
+
156
+
157
+ @pytest.fixture
158
+ def decision_steps():
159
+ return DecisionStepFactory.create_batch(3)
160
+
161
+
162
+ # ============================== Process Fixtures ==============================
163
+
164
+
165
+ @pytest.fixture
166
+ def process(processes):
167
+ return processes[0]
168
+
169
+
170
+ @pytest.fixture
171
+ def process_build():
172
+ instance = ProcessFactory.build()
173
+ return ProcessModelSerializer(instance).data
174
+
175
+
176
+ @pytest.fixture
177
+ def processes():
178
+ return ProcessFactory.create_batch(3)
179
+
180
+
181
+ # ============================== Process Step Fixtures ==============================
182
+
183
+
184
+ @pytest.fixture
185
+ def process_step(process_steps):
186
+ return process_steps[0]
187
+
188
+
189
+ @pytest.fixture
190
+ def process_steps():
191
+ return ProcessStepFactory.create_batch(3)
192
+
193
+
194
+ @pytest.fixture
195
+ def process_steps_build():
196
+ instance = ProcessStepFactory.build()
197
+ return ProcessStepModelSerializer(instance).data
198
+
199
+
200
+ @pytest.fixture
201
+ def mocked_process_step(mocker: MockerFixture):
202
+ return mocker.MagicMock(spec=ProcessStep)
203
+
204
+
205
+ # ============================== Assigned Process Step Fixtures ==============================
206
+
207
+
208
+ @pytest.fixture
209
+ def assigned_process_step(assigned_process_steps):
210
+ return assigned_process_steps[0]
211
+
212
+
213
+ @pytest.fixture
214
+ def assigned_process_steps(super_user, user_step):
215
+ return ProcessStepFactory.create_batch(
216
+ 3,
217
+ step=user_step,
218
+ state=ProcessStep.StepState.ACTIVE,
219
+ assignee=super_user,
220
+ )
221
+
222
+
223
+ @pytest.fixture
224
+ def assigned_process_steps_build(super_user, user_step):
225
+ instance = ProcessStepFactory.build(
226
+ step=user_step, state=ProcessStep.StepState.ACTIVE, assignee=super_user
227
+ )
228
+ return AssignedProcessStepSerializer(instance).data
229
+
230
+
231
+ # ============================== Transition Fixtures ==============================
232
+
233
+
234
+ @pytest.fixture
235
+ def transition(transitions):
236
+ return transitions[0]
237
+
238
+
239
+ @pytest.fixture
240
+ def transition_build():
241
+ to_step = FinishStepFactory()
242
+ from_step = StartStepFactory(workflow=to_step.workflow)
243
+ instance = TransitionFactory.build(to_step=to_step, from_step=from_step)
244
+ return TransitionModelSerializer(instance).data
245
+
246
+
247
+ @pytest.fixture
248
+ def transitions():
249
+ return TransitionFactory.create_batch(3)
250
+
251
+
252
+ # ============================== Workflow Fixtures ==============================
253
+
254
+
255
+ @pytest.fixture
256
+ def workflow(workflows):
257
+ return workflows[0]
258
+
259
+
260
+ @pytest.fixture
261
+ def workflow_build():
262
+ instance = WorkflowFactory.build()
263
+ return WorkflowModelSerializer(instance).data
264
+
265
+
266
+ @pytest.fixture
267
+ def workflows():
268
+ return WorkflowFactory.create_batch(3)
269
+
270
+
47
271
  @pytest.fixture(autouse=True, scope="session")
48
272
  def django_test_environment(django_test_environment):
49
273
  from django.apps import apps