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
@@ -1,3 +1,4 @@
1
+ from django.db.models import Case, Value, When
1
2
  from django.db.models.query import F, QuerySet
2
3
  from rest_framework import status
3
4
  from rest_framework.request import Request
@@ -13,7 +14,8 @@ from wbcore.contrib.notifications.serializers import (
13
14
  NotificationTypeSettingModelSerializer,
14
15
  )
15
16
 
16
- from .configs.notification_types import NotificationTypeSettingDisplayConfig
17
+ from ...icons import WBIcon
18
+ from .configs.notification_types import NotificationTypeSettingDisplayConfig, NotificationTypeSettingEndpointConfig
17
19
 
18
20
 
19
21
  class NotificationTypeRepresentationViewSet(viewsets.RepresentationViewSet):
@@ -25,6 +27,7 @@ class NotificationTypeSettingModelViewSet(viewsets.ModelViewSet):
25
27
  queryset = NotificationTypeSetting.objects.all()
26
28
  serializer_class = NotificationTypeSettingModelSerializer
27
29
 
30
+ endpoint_config_class = NotificationTypeSettingEndpointConfig
28
31
  display_config_class = NotificationTypeSettingDisplayConfig
29
32
 
30
33
  search_fields = ["notification_type__title", "notification_type__help_text"]
@@ -37,5 +40,12 @@ class NotificationTypeSettingModelViewSet(viewsets.ModelViewSet):
37
40
 
38
41
  def get_queryset(self) -> QuerySet[NotificationTypeSetting]:
39
42
  return (
40
- super().get_queryset().filter(user=self.request.user).annotate(help_text=F("notification_type__help_text"))
43
+ super()
44
+ .get_queryset()
45
+ .filter(user=self.request.user)
46
+ .annotate(
47
+ help_text=F("notification_type__help_text"),
48
+ locked=F("notification_type__is_lock"),
49
+ locked_icon=Case(When(locked=True, then=Value(WBIcon.LOCK.icon)), default=Value(None)),
50
+ )
41
51
  )
@@ -1,6 +1,8 @@
1
1
  from collections import defaultdict
2
2
  from dataclasses import dataclass
3
- from typing import List
3
+ from typing import List, Literal
4
+
5
+ import pandas as pd
4
6
 
5
7
  from wbcore.serializers.fields.types import DisplayMode
6
8
 
@@ -24,6 +26,9 @@ class _BaseField:
24
26
  base[_attr] = attr
25
27
  return base
26
28
 
29
+ def to_representation(self, value: pd.Series) -> pd.Series:
30
+ return value
31
+
27
32
 
28
33
  @dataclass
29
34
  class PKField(_BaseField):
@@ -34,11 +39,17 @@ class PKField(_BaseField):
34
39
  class CharField(_BaseField):
35
40
  type: str = "text"
36
41
 
42
+ def to_representation(self, value: pd.Series) -> pd.Series:
43
+ return super().to_representation(value).astype("string", errors="ignore")
44
+
37
45
 
38
46
  @dataclass
39
47
  class DateField(_BaseField):
40
48
  type: str = "date"
41
49
 
50
+ def to_representation(self, value: pd.Series) -> pd.Series:
51
+ return pd.to_datetime(super().to_representation(value)).dt.strftime("%Y-%m-%d")
52
+
42
53
 
43
54
  @dataclass
44
55
  class DateRangeField(_BaseField):
@@ -54,6 +65,9 @@ class BooleanField(_BaseField):
54
65
  class TextField(_BaseField):
55
66
  type: str = "texteditor"
56
67
 
68
+ def to_representation(self, value: pd.Series) -> pd.Series:
69
+ return super().to_representation(value).astype("string", errors="ignore")
70
+
57
71
 
58
72
  @dataclass
59
73
  class EmojiRatingField(_BaseField):
@@ -64,43 +78,44 @@ class EmojiRatingField(_BaseField):
64
78
  class FloatField(_BaseField):
65
79
  type: str = "number"
66
80
  precision: int = 2
67
- delimiter: str = ","
68
- decimal_mark: str = "."
69
81
  percent: bool = False
70
82
  display_mode: DisplayMode = None
83
+ disable_formatting: bool = False
71
84
 
72
85
  def to_dict(self):
73
86
  base = super().to_dict()
74
87
  base.update(
75
88
  {
76
89
  "precision": self.precision,
77
- "delimiter": self.delimiter,
78
- "decimal_mark": self.decimal_mark,
79
90
  }
80
91
  )
81
92
  if self.percent:
82
93
  base["type"] = "percent"
83
94
  if self.display_mode:
84
95
  base["display_mode"] = self.display_mode.value
96
+ base["disable_formatting"] = self.disable_formatting
85
97
  return base
86
98
 
99
+ def to_representation(self, value: pd.Series) -> pd.Series:
100
+ return super().to_representation(value).astype("float", errors="ignore")
101
+
87
102
 
88
103
  @dataclass
89
104
  class IntegerField(FloatField):
90
105
  type: str = "number"
91
106
  precision: int = 0
92
107
 
108
+ def to_representation(self, value: pd.Series) -> pd.Series:
109
+ return super().to_representation(value).astype("Int64", errors="ignore")
110
+
93
111
 
94
112
  @dataclass
95
113
  class YearField(IntegerField):
96
114
  precision: int = 0
97
- delimiter: str = ""
98
- decimal_mark: str = "."
115
+ disable_formatting: bool = True
99
116
 
100
117
  def __post_init__(self):
101
118
  self.precision = 0
102
- self.delimiter = ""
103
- self.decimal_mark = "."
104
119
 
105
120
 
106
121
  @dataclass
@@ -116,12 +131,25 @@ class JsonField(_BaseField):
116
131
  @dataclass
117
132
  class SparklineField(ListField):
118
133
  type: str = "sparkline"
134
+ dimension: Literal["single"] | Literal["double"] = (
135
+ "single" # "single" for data [y1, y2, y3... ] or "double" if contain already the X axis as [[x1, y1], [x2, y2], ... ]
136
+ )
119
137
 
120
138
  def to_dict(self):
121
139
  rv = super().to_dict()
122
- rv["sparkline_type"] = "column"
140
+ rv["sparkline_type"] = "bar"
123
141
  return rv
124
142
 
143
+ def _sanitize_row(self, row):
144
+ if not row:
145
+ row = [[]] # if row is [] or null, we default to an empty list of list
146
+ if self.dimension == "single": # This ensure that the returned data format contains a list of tuple of x,y
147
+ row = list(map(lambda o: (o[0], o[1]), enumerate(row)))
148
+ return row
149
+
150
+ def to_representation(self, value: pd.Series) -> pd.Series:
151
+ return super().to_representation(value).apply(lambda x: self._sanitize_row(x))
152
+
125
153
 
126
154
  @dataclass(unsafe_hash=True)
127
155
  class PandasFields:
@@ -44,7 +44,10 @@ class PandasDjangoFilterBackend(DjangoFilterBackend):
44
44
  if isinstance(_filter, wb_filters.NumberFilter):
45
45
  try:
46
46
  conditions.append(
47
- self.lookups_operator[lookup_expr](df[_filter.field_name], float(value))
47
+ self.lookups_operator[lookup_expr](
48
+ df[_filter.field_name],
49
+ float(value) if not _filter.percent else float(value) / 100,
50
+ )
48
51
  )
49
52
  except ValueError:
50
53
  pass
@@ -16,12 +16,13 @@ class PandasFilterSetMixin:
16
16
  queryset = self.filters[name].filter(queryset, value)
17
17
  except FieldError:
18
18
  pass
19
- assert isinstance(queryset, models.QuerySet), (
20
- "Expected '%s.%s' to return a QuerySet, but got a %s instead."
21
- % (
22
- type(self).__name__,
23
- name,
24
- type(queryset).__name__,
19
+ if not isinstance(queryset, models.QuerySet):
20
+ raise AssertionError(
21
+ "Expected '%s.%s' to return a QuerySet, but got a %s instead."
22
+ % (
23
+ type(self).__name__,
24
+ name,
25
+ type(queryset).__name__,
26
+ )
25
27
  )
26
- )
27
28
  return queryset
@@ -1,10 +1,7 @@
1
1
  import pytest
2
- from faker import Faker
3
2
 
4
3
  from wbcore.contrib.pandas.fields import YearField as PandasYearField
5
4
 
6
- fake = Faker()
7
-
8
5
 
9
6
  class TestPandasYearField:
10
7
  @pytest.mark.parametrize(
@@ -13,12 +10,10 @@ class TestPandasYearField:
13
10
  "label",
14
11
  ),
15
12
  [
16
- (fake.word(), fake.word()),
13
+ ("Foo", "Bar"),
17
14
  ],
18
15
  )
19
16
  def test_pandas_year_field_values(self, key, label):
20
- field = PandasYearField(key=key, label=label, precision=2, decimal_mark="*", delimiter=",")
17
+ field = PandasYearField(key=key, label=label, precision=2)
21
18
  representation = field.to_dict()
22
19
  assert representation["precision"] == 0
23
- assert representation["decimal_mark"] == "."
24
- assert representation["delimiter"] == ""
@@ -141,7 +141,7 @@ def override_number_to_integer_without_decorations(df: DataFrame, *conditions, f
141
141
  value="number",
142
142
  precision=0,
143
143
  displayMode="decimal",
144
- delimiter="",
144
+ disable_formatting=True,
145
145
  )
146
146
  ]
147
147
  }
@@ -1,4 +1,5 @@
1
1
  import json
2
+ from contextlib import suppress
2
3
  from functools import cached_property
3
4
 
4
5
  import numpy as np
@@ -39,8 +40,8 @@ class PandasMixin(CacheMixin):
39
40
  @cached_property
40
41
  def df(self) -> pd.DataFrame:
41
42
  if not hasattr(self, "_df"):
42
- setattr(self, "_df", self._get_dataframe())
43
- return getattr(self, "_df")
43
+ self._df = self._get_dataframe()
44
+ return self._df
44
45
 
45
46
  # BASIC DATAFRAME GENERATION FRAMEWORK METHODS
46
47
  def filter_queryset(self, queryset: QuerySet) -> QuerySet:
@@ -58,15 +59,13 @@ class PandasMixin(CacheMixin):
58
59
  pd_fields = self.get_pandas_fields(self.request)
59
60
  # Ensure the returned data satisfy the primitive field type
60
61
  for field in pd_fields.fields:
62
+ if isinstance(field, fields.PKField) and field.key not in df.columns:
63
+ with suppress(
64
+ IndexError
65
+ ): # if df is empty with an empty multi-index, the pandas subrountine fails with an IndexError
66
+ df = df.reset_index(names=field.key)
61
67
  if field.key in df.columns:
62
- if isinstance(field, fields.FloatField):
63
- df[field.key] = df[field.key].astype("float", errors="ignore")
64
- if isinstance(field, fields.CharField) or isinstance(field, fields.TextField):
65
- df[field.key] = df[field.key].astype("string", errors="ignore")
66
- if isinstance(field, fields.IntegerField):
67
- df[field.key] = df[field.key].astype("Int64", errors="ignore")
68
- elif isinstance(field, fields.DateField):
69
- df[field.key] = pd.to_datetime(df[field.key])
68
+ df.loc[:, field.key] = field.to_representation(df[field.key])
70
69
  with pd.option_context("future.no_silent_downcasting", True):
71
70
  return (
72
71
  df.drop(columns=df.columns.difference(pd_fields.to_dict().keys()))
@@ -81,11 +80,13 @@ class PandasMixin(CacheMixin):
81
80
  )
82
81
 
83
82
  def get_queryset(self):
84
- assert hasattr(self, "queryset"), "Either specify a queryset or implement the get_queryset method."
83
+ if not hasattr(self, "queryset"):
84
+ raise AssertionError("Either specify a queryset or implement the get_queryset method.")
85
85
  return self.queryset
86
86
 
87
87
  def get_dataframe(self, request, queryset, **kwargs):
88
- assert self.get_pandas_fields(request), "No pandas_fields specified"
88
+ if not self.get_pandas_fields(request):
89
+ raise AssertionError("No pandas_fields specified")
89
90
  return pd.DataFrame(queryset.values(*self.get_pandas_fields(request).to_dict().keys()))
90
91
 
91
92
  def manipulate_dataframe(self, df):
@@ -111,7 +112,7 @@ class PandasMixin(CacheMixin):
111
112
  df = pd.DataFrame(
112
113
  columns=[field.key for field in self.get_pandas_fields(self.request).fields]
113
114
  ) # if queryset is empty, we make sure the returning df contains all the columns to avoid keyerrors exception
114
- setattr(self, "_df", df)
115
+ self._df = df
115
116
  df = self.filter_dataframe(df, **kwargs)
116
117
  return df
117
118
 
@@ -17,7 +17,7 @@ class ManagedMixin(models.Model):
17
17
  abstract = True
18
18
 
19
19
 
20
- class Tag(ManagedMixin, ComplexToStringMixin, WBModel):
20
+ class Tag(ComplexToStringMixin, ManagedMixin):
21
21
  title = models.CharField(max_length=255)
22
22
 
23
23
  slug = models.CharField(max_length=255, null=True, blank=True)
@@ -35,6 +35,9 @@ class Tag(ManagedMixin, ComplexToStringMixin, WBModel):
35
35
  verbose_name = "Tag"
36
36
  verbose_name_plural = "Tags"
37
37
 
38
+ def __str__(self) -> str:
39
+ return super().__str__()
40
+
38
41
  @classmethod
39
42
  def get_endpoint_basename(cls):
40
43
  return "wbcore:tags:tag"
@@ -12,9 +12,9 @@ fake = Faker()
12
12
  def _generate_grid_areas() -> list[list[str]]:
13
13
  outer_list = []
14
14
  inner_list_length = random.randint(1, 5)
15
- for k in range(random.randint(1, 5)):
15
+ for _ in range(random.randint(1, 5)):
16
16
  inner_list = []
17
- for i in range(inner_list_length):
17
+ for _ in range(inner_list_length):
18
18
  inner_list.append(random.choice(PersonModelSerializer.Meta.fields))
19
19
  outer_list.append(inner_list)
20
20
  return outer_list
@@ -7,20 +7,21 @@ from wbcore.contrib.workflow.models import Transition
7
7
 
8
8
  @factory.django.mute_signals(signals.post_save)
9
9
  class TransitionFactory(factory.django.DjangoModelFactory):
10
- name = factory.Faker("text", max_nb_chars=128)
11
- to_step = factory.SubFactory("wbcore.contrib.workflow.factories.RandomChildStepFactory")
12
- from_step = factory.SubFactory("wbcore.contrib.workflow.factories.RandomChildStepFactory")
13
- icon = factory.Iterator(WBIcon.values)
14
-
15
- @classmethod
16
- def _create(cls, model_class, *args, **kwargs):
17
- # Both steps need to belong to the same workflow
18
- if (from_step := kwargs.get("from_step")) and (to_step := kwargs.get("to_step")):
19
- if kwargs["from_step"].workflow != kwargs["to_step"].workflow:
20
- from_step.workflow = to_step.workflow
21
- from_step.save()
22
- kwargs["from_step"] = from_step
23
- return super()._create(model_class, *args, **kwargs)
24
-
25
10
  class Meta:
26
11
  model = Transition
12
+ exclude = ("_workflow",)
13
+
14
+ _workflow = factory.SubFactory("wbcore.contrib.workflow.factories.WorkflowFactory")
15
+
16
+ from_step = factory.SubFactory(
17
+ "wbcore.contrib.workflow.factories.StartStepFactory",
18
+ workflow=factory.SelfAttribute(".._workflow"),
19
+ )
20
+ to_step = factory.SubFactory(
21
+ "wbcore.contrib.workflow.factories.FinishStepFactory",
22
+ workflow=factory.SelfAttribute(".._workflow"),
23
+ )
24
+
25
+ icon = factory.Iterator(WBIcon.values)
26
+
27
+ name = factory.Sequence(lambda n: f"Transition #{n}")