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,10 +1,12 @@
1
1
  import re
2
2
 
3
+ from django.urls import NoReverseMatch, resolve
3
4
  from rest_framework.reverse import reverse
4
5
 
5
6
  from wbcore.enums import WidgetType
6
7
  from wbcore.utils.urls import get_parse_endpoint, get_urlencode_endpoint
7
8
 
9
+ from ...utils.deprecations import deprecate_warning
8
10
  from .base import WBCoreViewConfig
9
11
 
10
12
 
@@ -16,6 +18,11 @@ class EndpointViewConfig(WBCoreViewConfig):
16
18
  DELETE_PK_FIELD = "id"
17
19
  UPDATE_PK_FIELD = "id"
18
20
 
21
+ def __init__(self, *args, **kwargs):
22
+ super().__init__(*args, **kwargs)
23
+ if hasattr(self, "get_list_endpoint"):
24
+ deprecate_warning("get_list_endpoint might be deprecated in a future version of wbcore.")
25
+
19
26
  def get_endpoint(self, **kwargs):
20
27
  model = self.view.get_model()
21
28
  basename_method_name = (
@@ -57,11 +64,11 @@ class EndpointViewConfig(WBCoreViewConfig):
57
64
  return endpoint
58
65
  return None
59
66
 
60
- def get_list_endpoint(self, **kwargs):
61
- return self.get_endpoint(is_list=True)
62
-
63
67
  def _get_list_endpoint(self):
64
- return self.get_list_endpoint()
68
+ try:
69
+ return reverse(resolve(self.request.path).view_name, kwargs=self.view.kwargs, request=self.request)
70
+ except NoReverseMatch:
71
+ return None
65
72
 
66
73
  def get_delete_endpoint(self, **kwargs):
67
74
  return self.get_endpoint()
@@ -10,8 +10,13 @@ class FieldsViewConfig(WBCoreViewConfig):
10
10
  def get_metadata(self) -> dict:
11
11
  fields = defaultdict(dict)
12
12
  if (serializer_class := getattr(self.view, "get_serializer", None)) and (serializer := serializer_class()):
13
+ related_key_fields = []
13
14
  for field_name, field in serializer.fields.items():
14
15
  field_key, field_representation = field.get_representation(self.request, field_name)
16
+ # we need to get the representation of the related field last so that the key update properly (priority to the related field values)
17
+ if "related_key" in field_representation:
18
+ related_key_fields.append((field_key, field_representation))
19
+ fields[field_key].update(field_representation)
20
+ for field_key, field_representation in related_key_fields:
15
21
  fields[field_key].update(field_representation)
16
-
17
22
  return fields
@@ -25,18 +25,17 @@ class FilterFieldsViewConfig(WBCoreViewConfig):
25
25
  hidden_fields.extend(getattr(filterset_class_meta, "hidden_fields", []))
26
26
  filters.update(getattr(filterset_class_meta, "df_fields", {}))
27
27
  for name, field in filters.items():
28
- field.parent = filterset
29
- if (
30
- not field.hidden
31
- and name not in hidden_fields
32
- and (res := field.get_representation(self.request, name, self.view))
33
- ):
34
- representation, lookup_expr = res
35
- if field.key in filter_fields:
36
- filter_fields[field.key]["lookup_expr"].append(lookup_expr)
37
- else:
38
- filter_fields[field.key] = representation
39
- filter_fields[field.key]["lookup_expr"] = [lookup_expr]
40
- filter_fields[field.key]["label"] = field.label
28
+ if not field.excluded_filter:
29
+ field.parent = filterset
30
+ if res := field.get_representation(self.request, name, self.view):
31
+ representation, lookup_expr = res
32
+ if name in hidden_fields:
33
+ lookup_expr["hidden"] = True
34
+ if field.key in filter_fields:
35
+ filter_fields[field.key]["lookup_expr"].append(lookup_expr)
36
+ else:
37
+ filter_fields[field.key] = representation
38
+ filter_fields[field.key]["lookup_expr"] = [lookup_expr]
39
+ filter_fields[field.key]["label"] = field.label
41
40
 
42
41
  return filter_fields
@@ -7,7 +7,9 @@ class IdentifierViewConfig(WBCoreViewConfig):
7
7
 
8
8
  # TODO: This does not yet work on the frontend, but should
9
9
  def get_metadata(self) -> str | None:
10
- if identifier := getattr(self.view, "IDENTIFIER", None):
10
+ if (get_identifier := getattr(self.view, "get_identifier", None)) and callable(get_identifier):
11
+ return self.view.get_identifier(self.request)
12
+ elif identifier := getattr(self.view, "IDENTIFIER", None):
11
13
  return identifier
12
14
 
13
15
  content_type = self.view.get_content_type() # type: ignore
@@ -1,7 +1,6 @@
1
1
  import random
2
2
 
3
3
  import pytest
4
- from faker import Faker
5
4
 
6
5
  from wbcore import serializers as wb_serializers
7
6
  from wbcore.contrib.icons import WBIcon
@@ -16,17 +15,15 @@ from wbcore.metadata.configs.buttons.enums import ButtonDefaultColor, HyperlinkT
16
15
  from wbcore.metadata.configs.display.instance_display import create_simple_display
17
16
  from wbcore.metadata.utils import prefix_key
18
17
 
19
- fake = Faker()
20
-
21
18
 
22
19
  class TestDropDownButton:
23
20
  @pytest.fixture()
24
21
  def button(self):
25
22
  return DropDownButton(
26
23
  icon=random.choice(list(WBIcon)).icon,
27
- title=fake.sentence(),
24
+ title="Foo Bar",
28
25
  color=random.choice(list(ButtonDefaultColor)).value,
29
- buttons=[WidgetButton(key=fake.word(), label=fake.sentence())],
26
+ buttons=[WidgetButton(key="Foo", label="Foo Bar")],
30
27
  )
31
28
 
32
29
  def test_serialize(self, rf, button):
@@ -38,7 +35,7 @@ class TestDropDownButton:
38
35
  assert serialized_btn["buttons"] == [button.buttons[0].serialize(rf)]
39
36
  assert serialized_btn["type"] == DropDownButton.button_type.value
40
37
 
41
- @pytest.mark.parametrize("key_prefix", [fake.word()])
38
+ @pytest.mark.parametrize("key_prefix", ["Foo"])
42
39
  def test_serialize_with_prefix(self, rf, button, key_prefix):
43
40
  serialized_btn = button.serialize(rf, key_prefix=key_prefix)
44
41
  nested_button = button.buttons[0]
@@ -62,18 +59,18 @@ class TestWidgetButton:
62
59
  def key_button(self):
63
60
  return self.button_class(
64
61
  icon=random.choice(list(WBIcon)).icon,
65
- title=fake.sentence(),
62
+ title="Foo Bar",
66
63
  color=random.choice(list(ButtonDefaultColor)).value,
67
- key=fake.word(),
64
+ key="Foo",
68
65
  )
69
66
 
70
67
  @pytest.fixture()
71
68
  def endpoint_button(self):
72
69
  return self.button_class(
73
70
  icon=random.choice(list(WBIcon)).icon,
74
- title=fake.sentence(),
71
+ title="Foo Bar",
75
72
  color=random.choice(list(ButtonDefaultColor)).value,
76
- endpoint=fake.url(),
73
+ endpoint="www.foo.com",
77
74
  )
78
75
 
79
76
  def test_serialize(self, rf, key_button):
@@ -85,7 +82,7 @@ class TestWidgetButton:
85
82
  assert serialized_btn["key"] == key_button.key
86
83
  assert serialized_btn["type"] == self.button_class.button_type.value
87
84
 
88
- @pytest.mark.parametrize("key_prefix", [fake.word()])
85
+ @pytest.mark.parametrize("key_prefix", ["Foo"])
89
86
  def test_serialize_with_prefix(self, rf, key_button, key_prefix):
90
87
  serialized_btn = key_button.serialize(rf, key_prefix=key_prefix)
91
88
 
@@ -152,15 +149,15 @@ class TestActionButtonButton(TestHyperlinkButtonutton):
152
149
 
153
150
  return ActionButton(
154
151
  icon=random.choice(list(WBIcon)).icon,
155
- title=fake.sentence(),
152
+ title="Foo Bar",
156
153
  color=random.choice(list(ButtonDefaultColor)).value,
157
- key=fake.word(),
154
+ key="Foo",
158
155
  method=random.choice(list(RequestType)),
159
- action_label=fake.word(),
160
- description_fields=fake.sentence(),
156
+ action_label="Foo",
157
+ description_fields="Foo Bar",
161
158
  instance_display=create_simple_display([["field"]]),
162
159
  serializer=BaseSerializer,
163
- identifiers=(fake.word(),),
160
+ identifiers=("Foo",),
164
161
  )
165
162
 
166
163
  def test_serialize(self, rf, key_button):
wbcore/models/fields.py CHANGED
@@ -5,10 +5,10 @@ from django.db.models import DecimalField, Field, FloatField, PositiveIntegerFie
5
5
  class AbstractDynamicField(Field):
6
6
  dependencies = []
7
7
 
8
- def __init__(self, *args, dependencies=list(), **kwargs):
8
+ def __init__(self, *args, dependencies: list | None = None, **kwargs):
9
9
  blank = kwargs.pop("blank", True)
10
10
  null = kwargs.pop("null", True)
11
- self.dependencies = dependencies
11
+ self.dependencies = dependencies if dependencies else []
12
12
  super().__init__(*args, blank=blank, null=null, **kwargs)
13
13
 
14
14
 
wbcore/pagination.py CHANGED
@@ -61,5 +61,4 @@ class CursorPagination(EndlessPaginationMixin, InitialPaginationMixin, paginatio
61
61
  return super()._get_position_from_instance(instance, new_ordering)
62
62
 
63
63
 
64
- class LimitOffsetPagination(EndlessPaginationMixin, InitialPaginationMixin, pagination.LimitOffsetPagination):
65
- ...
64
+ class LimitOffsetPagination(EndlessPaginationMixin, InitialPaginationMixin, pagination.LimitOffsetPagination): ...
@@ -1,5 +1,5 @@
1
1
  from rest_framework import permissions
2
- from rest_framework.permissions import BasePermission
2
+ from rest_framework.permissions import IsAuthenticated
3
3
 
4
4
  from wbcore.enums import WidgetType
5
5
 
@@ -40,7 +40,7 @@ class RestAPIModelPermissions(permissions.DjangoModelPermissions):
40
40
  return request.user.has_perms(perms)
41
41
 
42
42
 
43
- class IsInternalUser(BasePermission):
43
+ class IsInternalUser(IsAuthenticated):
44
44
  def has_permission(self, request, view) -> bool:
45
45
  return is_internal_user(request.user, True)
46
46
 
@@ -17,10 +17,10 @@ def perm_to_permission(perm: str) -> Permission:
17
17
  """
18
18
  try:
19
19
  app_label, codename = perm.split(".", 1)
20
- except IndexError:
20
+ except IndexError as e:
21
21
  raise AttributeError(
22
22
  "The format of identifier string permission (perm) is wrong. " "It should be in 'app_label.codename'."
23
- )
23
+ ) from e
24
24
  else:
25
25
  permission = Permission.objects.get(content_type__app_label=app_label, codename=codename)
26
26
  return permission
@@ -3,6 +3,7 @@ from typing import Optional
3
3
  from django.utils.translation import gettext as _
4
4
 
5
5
  from wbcore.contrib.icons import WBIcon
6
+ from wbcore.enums import Unit
6
7
  from wbcore.metadata.configs import display as dp
7
8
  from wbcore.metadata.configs.display.instance_display.shortcuts import (
8
9
  Display,
@@ -16,19 +17,12 @@ class ReleaseNoteDisplayConfig(DisplayViewConfig):
16
17
  def get_list_display(self) -> Optional[dp.ListDisplay]:
17
18
  return dp.ListDisplay(
18
19
  fields=[
20
+ dp.Field(key="user_read_icon", label=" ", width=Unit.PIXEL(40)),
19
21
  dp.Field(key="module", label=_("Module")),
20
22
  dp.Field(key="version", label=_("Version")),
21
23
  dp.Field(key="release_date", label=_("Release Date")),
22
24
  dp.Field(key="summary", label=_("Summary")),
23
25
  ],
24
- formatting=[
25
- dp.Formatting(
26
- column="user_read",
27
- formatting_rules=[
28
- dp.FormattingRule(icon=WBIcon.VIEW.icon, condition=("==", True)),
29
- ],
30
- ),
31
- ],
32
26
  legends=[
33
27
  dp.Legend(
34
28
  key="read_unread",
@@ -5,15 +5,8 @@ from .models import ReleaseNote
5
5
 
6
6
  class ReleaseNoteModelSerializer(serializers.ModelSerializer):
7
7
  user_read = serializers.BooleanField()
8
+ user_read_icon = serializers.IconSelectField()
8
9
 
9
10
  class Meta:
10
11
  model = ReleaseNote
11
- fields = (
12
- "id",
13
- "version",
14
- "release_date",
15
- "module",
16
- "summary",
17
- "notes",
18
- "user_read",
19
- )
12
+ fields = ("id", "version", "release_date", "module", "summary", "notes", "user_read", "user_read_icon")
@@ -1,9 +1,10 @@
1
- from django.db.models import Exists, OuterRef, Q
1
+ from django.db.models import Case, Exists, OuterRef, Q, Value, When
2
2
  from rest_framework.decorators import action
3
3
  from rest_framework.response import Response
4
4
 
5
5
  from wbcore import viewsets
6
6
 
7
+ from ..contrib.icons import WBIcon
7
8
  from .buttons import ReleaseNotesButtonConfig
8
9
  from .display import ReleaseNoteDisplayConfig
9
10
  from .filters import ReleaseNoteFilterSet
@@ -26,7 +27,12 @@ class ReleaseNoteReadOnlyModelViewSet(viewsets.ReadOnlyModelViewSet):
26
27
  return (
27
28
  super()
28
29
  .get_queryset()
29
- .annotate(user_read=Exists(ReleaseNote.objects.filter(id=OuterRef("id"), read_by=self.request.user)))
30
+ .annotate(
31
+ user_read=Exists(ReleaseNote.objects.filter(id=OuterRef("id"), read_by=self.request.user)),
32
+ user_read_icon=Case(
33
+ When(user_read=True, then=Value(WBIcon.VIEW.icon)), default=Value(WBIcon.IGNORE.icon)
34
+ ),
35
+ )
30
36
  )
31
37
 
32
38
  def retrieve(self, request, *args, **kwargs):
@@ -1,4 +1,7 @@
1
+ from contextlib import suppress
2
+
1
3
  from django.contrib.contenttypes.models import ContentType
4
+ from django.core.exceptions import ObjectDoesNotExist
2
5
  from django.utils.translation import gettext as _
3
6
 
4
7
  from wbcore.metadata.configs.titles import TitleViewConfig
@@ -9,10 +12,8 @@ class VersionTitleConfig(TitleViewConfig):
9
12
  if (content_type_id := self.view.request.GET.get("content_type", None)) and (
10
13
  object_id := self.view.request.GET.get("object_id", None)
11
14
  ):
12
- try:
15
+ with suppress(ObjectDoesNotExist):
13
16
  content_type = ContentType.objects.get(id=content_type_id)
14
17
  obj = content_type.model_class().objects.get(id=object_id)
15
18
  return _("Versions For {obj}").format(obj=str(obj))
16
- except Exception:
17
- pass
18
19
  return _("Versions")
@@ -1,5 +1,6 @@
1
1
  from .fields import (
2
2
  AdditionalResourcesField,
3
+ IconSelectField,
3
4
  BooleanField,
4
5
  CharField,
5
6
  ChoiceField,
@@ -7,6 +8,7 @@ from .fields import (
7
8
  ColorPickerField,
8
9
  DateField,
9
10
  DateRangeField,
11
+ TimeRange,
10
12
  DateTimeField,
11
13
  DateTimeRangeField,
12
14
  DecimalField,
@@ -1,12 +1,13 @@
1
1
  from wbcore.contrib.icons.serializers import IconSelectField
2
2
 
3
3
  from .boolean import BooleanField
4
- from .choice import ChoiceField, MultipleChoiceField
4
+ from .choice import ChoiceField, MultipleChoiceField, LanguageChoiceField
5
5
  from .datetime import (
6
6
  DateField,
7
7
  DateRangeField,
8
8
  DateTimeField,
9
9
  DateTimeRangeField,
10
+ TimeRange,
10
11
  DurationField,
11
12
  TimeField,
12
13
  TimeZoneField,
@@ -17,7 +17,7 @@ class BooleanField(WBCoreSerializerFieldMixin, serializers.BooleanField):
17
17
  color: str | Iterable[str] | None = None,
18
18
  active_background_color: str | Iterable[str] | None = None,
19
19
  active_color: str | Iterable[str] | None = None,
20
- **kwargs
20
+ **kwargs,
21
21
  ):
22
22
  self.labels = labels
23
23
  self.values = values
@@ -5,22 +5,42 @@ from .mixins import WBCoreSerializerFieldMixin
5
5
  from .types import WBCoreType
6
6
 
7
7
 
8
- class ChoiceField(WBCoreSerializerFieldMixin, serializers.ChoiceField):
8
+ class ChoiceMixin:
9
+ choices: dict[str, str]
10
+
11
+ def __init__(self, *args, group_key_mapping: dict[str, str] | None = None, **kwargs):
12
+ """
13
+
14
+ Args:
15
+ group_key_mapping (dict[str, str] | None, optional): An optional mapping that provides
16
+ a group for every choice value to group the choices in the drop down by. Defaults to None.
17
+ """
18
+ self.group_key_mapping = group_key_mapping or dict()
19
+ super().__init__(*args, **kwargs)
20
+
21
+ def _get_choices_representation(self):
22
+ return [
23
+ {"group": self.group_key_mapping.get(value), "value": value, "label": label}
24
+ for value, label in self.choices.items()
25
+ ]
26
+
27
+
28
+ class ChoiceField(WBCoreSerializerFieldMixin, ChoiceMixin, serializers.ChoiceField):
9
29
  field_type = WBCoreType.SELECT.value
10
30
 
11
31
  def get_representation(self, request, field_name) -> tuple[str, dict]:
12
32
  key, representation = super().get_representation(request, field_name)
13
- representation["choices"] = [{"value": k, "label": v} for k, v in self.choices.items()]
33
+ representation["choices"] = self._get_choices_representation()
14
34
  return key, representation
15
35
 
16
36
 
17
- class MultipleChoiceField(ListFieldMixin, WBCoreSerializerFieldMixin, serializers.MultipleChoiceField):
37
+ class MultipleChoiceField(ListFieldMixin, WBCoreSerializerFieldMixin, ChoiceMixin, serializers.MultipleChoiceField):
18
38
  field_type = WBCoreType.SELECT.value
19
39
 
20
40
  def get_representation(self, request, field_name) -> tuple[str, dict]:
21
41
  key, representation = super().get_representation(request, field_name)
22
42
  representation["multiple"] = True
23
- representation["choices"] = [{"value": k, "label": v} for k, v in self.choices.items()]
43
+ representation["choices"] = self._get_choices_representation()
24
44
  return key, representation
25
45
 
26
46
  def to_internal_value(self, data):
@@ -34,3 +54,7 @@ class MultipleChoiceField(ListFieldMixin, WBCoreSerializerFieldMixin, serializer
34
54
  if isinstance(data, set):
35
55
  data = list(data)
36
56
  return data
57
+
58
+
59
+ class LanguageChoiceField(ChoiceField):
60
+ field_type = WBCoreType.LANGUAGE.value
@@ -1,8 +1,9 @@
1
- from datetime import timedelta
1
+ from datetime import date, datetime, timedelta
2
2
 
3
3
  import pytz
4
- from psycopg.types.range import DateRange, TimestamptzRange
4
+ from psycopg.types.range import DateRange, TimestampRange, TimestamptzRange
5
5
  from rest_framework import serializers
6
+ from rest_framework.settings import api_settings
6
7
  from timezone_field.choices import standard, with_gmt_offset
7
8
  from timezone_field.rest_framework import TimeZoneSerializerField
8
9
 
@@ -49,13 +50,9 @@ class DateRangeField(RangeMixin, ShortcutMixin, serializers.DateField):
49
50
  self.outward_bounds_transform = outward_bounds_transform # Allow to specify another bound representation than the default and used for db (]. If specified, will switch the range around to and from the serializer
50
51
  self.inward_bounds_transform = inward_bounds_transform
51
52
  if self.outward_bounds_transform not in ["[]", "[)", "()", "(]"]:
52
- raise ValueError(
53
- f"Outward bound transform {self.outward_bounds_transform} is not a valid choice"
54
- )
53
+ raise ValueError(f"Outward bound transform {self.outward_bounds_transform} is not a valid choice")
55
54
  if self.inward_bounds_transform not in ["[]", "[)", "()", "(]"]:
56
- raise ValueError(
57
- f"Inward bound transform {self.inward_bounds_transform} is not a valid choice"
58
- )
55
+ raise ValueError(f"Inward bound transform {self.inward_bounds_transform} is not a valid choice")
59
56
 
60
57
  super().__init__(*args, **kwargs)
61
58
 
@@ -70,9 +67,7 @@ class DateRangeField(RangeMixin, ShortcutMixin, serializers.DateField):
70
67
  Returns:
71
68
  The shifted bound
72
69
  """
73
- bound_transform = (
74
- self.inward_bounds_transform if inward else self.outward_bounds_transform
75
- )
70
+ bound_transform = self.inward_bounds_transform if inward else self.outward_bounds_transform
76
71
  if lower and bound_transform[0] == "(":
77
72
  lower = lower + timedelta(days=1) if inward else lower - timedelta(days=1)
78
73
  if upper and bound_transform[1] == "]":
@@ -84,54 +79,70 @@ class DateTimeRangeField(RangeMixin, ShortcutMixin, serializers.DateTimeField):
84
79
  field_type = WBCoreType.DATETIMERANGE.value
85
80
  internal_field = TimestamptzRange
86
81
 
87
- def __init__(
88
- self, *args, lower_time_choices=None, upper_time_choices=None, **kwargs
89
- ):
82
+ def __init__(self, *args, lower_time_choices=None, upper_time_choices=None, **kwargs):
90
83
  self.lower_time_choices = lower_time_choices
91
84
  if (
92
85
  self.lower_time_choices
93
86
  and not isinstance(self.lower_time_choices, list)
94
87
  and not callable(self.lower_time_choices)
95
88
  ):
96
- raise ValueError(
97
- "lower_time_choices can only be a static list or a callable."
98
- )
89
+ raise ValueError("lower_time_choices can only be a static list or a callable.")
99
90
  self.upper_time_choices = upper_time_choices
100
91
  if (
101
92
  self.upper_time_choices
102
93
  and not isinstance(self.upper_time_choices, list)
103
94
  and not callable(self.upper_time_choices)
104
95
  ):
105
- raise ValueError(
106
- "upper_time_choices can only be a static list or a callable."
107
- )
96
+ raise ValueError("upper_time_choices can only be a static list or a callable.")
108
97
  super().__init__(*args, **kwargs)
109
98
 
110
99
  def get_representation(self, request, field_name) -> tuple[str, dict]:
111
100
  key, representation = super().get_representation(request, field_name)
112
101
  if self.lower_time_choices is not None:
113
102
  if callable(self.lower_time_choices):
114
- representation["lower_time_choices"] = self.lower_time_choices(
115
- self, request
116
- )
103
+ representation["lower_time_choices"] = self.lower_time_choices(self, request)
117
104
  else:
118
105
  representation["lower_time_choices"] = self.lower_time_choices
119
106
 
120
107
  if self.upper_time_choices is not None:
121
108
  if callable(self.upper_time_choices):
122
- representation["upper_time_choices"] = self.upper_time_choices(
123
- self, request
124
- )
109
+ representation["upper_time_choices"] = self.upper_time_choices(self, request)
125
110
  else:
126
111
  representation["upper_time_choices"] = self.upper_time_choices
127
- if timezone := getattr(self, "timezone", None):
128
- representation["timezone"] = str(timezone)
129
112
  return key, representation
130
113
 
131
114
 
132
- class DurationField(
133
- NumberFieldMixin, WBCoreSerializerFieldMixin, serializers.DurationField
134
- ):
115
+ class TimeRange(RangeMixin, ShortcutMixin, serializers.TimeField):
116
+ field_type = WBCoreType.TIMERANGE.value
117
+ internal_field = TimestampRange
118
+
119
+ def __init__(self, *args, timerange_fields: tuple[str, str] | None = None, **kwargs):
120
+ self.timerange_fields = timerange_fields
121
+ super().__init__(*args, **kwargs)
122
+ self.default_date_repr = date.min.strftime(getattr(self, "format", api_settings.DATE_FORMAT))
123
+ if self.timerange_fields:
124
+ self.source = "*"
125
+
126
+ def _transform_range(self, lower, upper, **kwargs):
127
+ if isinstance(lower, datetime):
128
+ lower = lower.time()
129
+ if isinstance(upper, datetime):
130
+ upper = upper.time()
131
+ return lower, upper
132
+
133
+ def get_attribute(self, instance):
134
+ if self.timerange_fields:
135
+ return [getattr(instance, self.timerange_fields[0]), getattr(instance, self.timerange_fields[1])]
136
+ return super().get_attribute(instance)
137
+
138
+ def to_internal_value(self, data):
139
+ ts_range = super().to_internal_value(data)
140
+ if self.timerange_fields:
141
+ return dict(zip(self.timerange_fields, (ts_range.lower, ts_range.upper), strict=False))
142
+ return ts_range
143
+
144
+
145
+ class DurationField(NumberFieldMixin, WBCoreSerializerFieldMixin, serializers.DurationField):
135
146
  field_type = WBCoreType.DURATION.value
136
147
 
137
148
  def __init__(self, *args, **kwargs):
@@ -144,7 +155,7 @@ class TimeZoneField(WBCoreSerializerFieldMixin, TimeZoneSerializerField):
144
155
 
145
156
  def __init__(self, choices=None, choices_display=None, *args, **kwargs):
146
157
  if choices:
147
- values, displays = zip(*choices)
158
+ values, displays = zip(*choices, strict=False)
148
159
  else:
149
160
  values = pytz.common_timezones
150
161
  displays = None
@@ -154,11 +165,9 @@ class TimeZoneField(WBCoreSerializerFieldMixin, TimeZoneSerializerField):
154
165
  elif choices_display == "STANDARD":
155
166
  choices = standard(values)
156
167
  elif choices_display is None:
157
- choices = zip(values, displays) if displays else standard(values)
168
+ choices = zip(values, displays, strict=False) if displays else standard(values)
158
169
  else:
159
- raise ValueError(
160
- f"Unrecognized value for kwarg 'choices_display' of '{choices_display}'"
161
- )
170
+ raise ValueError(f"Unrecognized value for kwarg 'choices_display' of '{choices_display}'")
162
171
 
163
172
  self.choices = choices
164
173
  super().__init__(*args, **kwargs)
@@ -91,7 +91,7 @@ class DynamicButtonField(WBCoreSerializerFieldMixin, serializers.ReadOnlyField):
91
91
  )
92
92
  for prefix, btns in dynamic_buttons:
93
93
  for btn in btns:
94
- setattr(btn, "prefix_key", prefix)
94
+ btn.prefix_key = prefix
95
95
  buttons.append(btn.serialize(request))
96
96
  if (view := self.parent.context.get("view", None)) and not (getattr(view, "action", "list") == "list"):
97
97
  for _, button_func in getmembers(self.parent.__class__, _is_instance_dynamic_button):
@@ -8,7 +8,7 @@ class FSMStatusField(CharField):
8
8
  def __init__(self, *args, **kwargs):
9
9
  self.choices = kwargs.pop("choices")
10
10
  read_only = kwargs.pop("read_only", True)
11
- super().__init__(read_only=read_only, *args, **kwargs)
11
+ super().__init__(*args, read_only=read_only, **kwargs)
12
12
 
13
13
  def get_representation(self, request, field_name) -> tuple[str, dict]:
14
14
  key, representation = super().get_representation(request, field_name)
@@ -35,9 +35,6 @@ class ListFieldMixin:
35
35
  return super().run_validation(data)
36
36
 
37
37
 
38
- list
39
-
40
-
41
38
  class ListField(ListFieldMixin, WBCoreSerializerFieldMixin, serializers.ListField):
42
39
  field_type = WBCoreType.LIST.value
43
40
 
@@ -101,7 +98,7 @@ class SparklineField(WBCoreSerializerFieldMixin, serializers.ListField):
101
98
  return key, representation
102
99
 
103
100
  def to_representation(self, obj):
104
- representation = {}
101
+ representation = [[]] # if row is [] or null, we default to an empty list of list
105
102
  if (x_data := getattr(obj, self.x_data_label, None)) and (y_data := getattr(obj, self.y_data_label, None)):
106
- representation = zip(x_data, y_data)
103
+ representation = zip(x_data, y_data, strict=False)
107
104
  return representation