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
wbcore/enums.py CHANGED
@@ -12,56 +12,6 @@ class RequestType(Enum):
12
12
  HEAD = "head"
13
13
 
14
14
 
15
- class Button(Enum):
16
- # Buttons
17
- REFRESH = "refresh"
18
- NEW = "new"
19
- DELETE = "delete"
20
-
21
- # Buttons and Create Buttons
22
- SAVE = "save"
23
- SAVE_AND_CLOSE = "saveandclose"
24
- SAVE_AND_NEW = "saveandnew"
25
-
26
- # Create Buttons
27
- RESET = "reset"
28
-
29
- # Custom Buttons
30
- DROPDOWN = "dropdown"
31
- HYPERLINK = "hyperlink"
32
- WIDGET = "widget"
33
- ACTION = "action"
34
-
35
- @classmethod
36
- def buttons(cls):
37
- return [
38
- cls.REFRESH.value,
39
- cls.NEW.value,
40
- cls.DELETE.value,
41
- cls.SAVE.value,
42
- cls.SAVE_AND_CLOSE.value,
43
- cls.SAVE_AND_NEW.value,
44
- ]
45
-
46
- @classmethod
47
- def create_buttons(cls):
48
- return [
49
- cls.SAVE.value,
50
- cls.SAVE_AND_CLOSE.value,
51
- cls.SAVE_AND_NEW.value,
52
- cls.RESET.value,
53
- ]
54
-
55
- @classmethod
56
- def custom_buttons(cls):
57
- return [
58
- cls.DROPDOWN.value,
59
- cls.HYPERLINK.value,
60
- cls.WIDGET.value,
61
- cls.ACTION.value,
62
- ]
63
-
64
-
65
15
  class WidgetType(Enum):
66
16
  LIST = "list"
67
17
  INSTANCE = "instance"
@@ -89,7 +39,8 @@ class Unit(Enum):
89
39
  return (float(_value), self.value)
90
40
 
91
41
  def unit(self, _value: Union[float, str, int]):
92
- assert isinstance(_value, (float, str, int)), "_value needs to be one of str, float or int"
42
+ if not isinstance(_value, (float, str, int)):
43
+ raise AssertionError("_value needs to be one of str, float or int")
93
44
 
94
45
  return f"{float(_value)}{self.value}"
95
46
 
@@ -47,16 +47,14 @@ class MultipleChoiceFilter(WBCoreChoiceFilterMixin, django_filters.MultipleChoic
47
47
  field_class = WBCoreMultipleChoiceField
48
48
  filter_type = "select"
49
49
 
50
- def _validate_default_with_request(self, default, request, name):
51
- if request_default := request.GET.get(name):
52
- return request_default.split(",")
53
- return default
50
+ def _validate_initial_with_request(self, initial, request, name):
51
+ if request_initial := request.GET.get(name):
52
+ return request_initial.split(",")
53
+ return initial
54
54
 
55
55
  def get_representation(self, request, name, view):
56
56
  representation, lookup_expr = super().get_representation(request, name, view)
57
57
  lookup_expr["input_properties"]["multiple"] = True
58
- if (default := lookup_expr["input_properties"].get("default")) and not isinstance(default, list):
59
- lookup_expr["input_properties"]["default"] = [default]
60
58
  if (initial := lookup_expr["input_properties"].get("initial")) and not isinstance(initial, list):
61
59
  lookup_expr["input_properties"]["initial"] = [initial]
62
60
  return representation, lookup_expr
@@ -1,3 +1,4 @@
1
+ import json
1
2
  import operator
2
3
  from functools import reduce
3
4
 
@@ -14,6 +15,12 @@ class MultipleChoiceContentTypeFilter(WBCoreFilterMixin, django_filters.Filter):
14
15
  field_class = ContentTypeMultiValueField
15
16
  filter_type = "text"
16
17
 
18
+ def _validate_initial_with_request(self, initial, request, name):
19
+ initial = super()._validate_initial_with_request(initial, request, name)
20
+ if initial:
21
+ initial = json.dumps(initial)
22
+ return initial
23
+
17
24
  def __init__(self, object_id_label="object_id", content_type_label="content_type", **kwargs):
18
25
  self.object_id_label = object_id_label
19
26
  self.content_type_label = content_type_label
@@ -22,13 +29,17 @@ class MultipleChoiceContentTypeFilter(WBCoreFilterMixin, django_filters.Filter):
22
29
  def filter(self, qs, value):
23
30
  if value in EMPTY_VALUES:
24
31
  return qs
25
- if self.distinct:
26
- qs = qs.distinct()
27
32
  conditions = [
28
33
  (
29
34
  Q(**{self.content_type_label: ContentType.objects.get_for_model(val)})
30
35
  & Q(**{self.object_id_label: val.id})
31
36
  )
32
- for val in value
37
+ for val in filter(None, value)
33
38
  ]
34
- return qs.filter(reduce(operator.or_, conditions))
39
+ if conditions:
40
+ qs = qs.filter(reduce(operator.or_, conditions))
41
+ else:
42
+ qs = qs.none()
43
+ if self.distinct:
44
+ qs = qs.distinct()
45
+ return qs
@@ -1,6 +1,9 @@
1
1
  from contextlib import suppress
2
2
 
3
3
  import django_filters
4
+ from django.contrib.postgres.fields import RangeField
5
+ from django_filters.constants import EMPTY_VALUES
6
+ from django_filters.utils import get_model_field
4
7
 
5
8
  from wbcore.filters.mixins import WBCoreFilterMixin
6
9
  from wbcore.forms import DateRangeField, DateTimeRangeField
@@ -28,10 +31,10 @@ class ShortcutAndPerformanceMixin(WBCoreFilterMixin):
28
31
 
29
32
  def get_representation(self, request, name, view):
30
33
  representation, lookup_expr = super().get_representation(request, name, view)
31
- representation["performance_mode"] = self.performance_mode
34
+ lookup_expr["input_properties"]["performance_mode"] = self.performance_mode
32
35
 
33
36
  if self.shortcuts:
34
- representation["shortcuts"] = self.shortcuts
37
+ lookup_expr["input_properties"]["shortcuts"] = self.shortcuts
35
38
 
36
39
  return representation, lookup_expr
37
40
 
@@ -39,54 +42,76 @@ class ShortcutAndPerformanceMixin(WBCoreFilterMixin):
39
42
  class DateRangeFilter(ShortcutAndPerformanceMixin, django_filters.Filter):
40
43
  field_class = DateRangeField
41
44
  filter_type = "daterange"
42
- default_format = "%Y-%m-%d"
45
+ initial_format = "%Y-%m-%d"
43
46
 
44
47
  def __init__(self, *args, **kwargs):
45
48
  kwargs.setdefault("lookup_expr", "overlap")
46
49
  super().__init__(*args, **kwargs)
47
50
 
48
- def _get_default(self, *args):
49
- default = super()._get_default(*args)
50
- if default is not None:
51
+ @property
52
+ def is_range(self) -> bool:
53
+ if hasattr(self, "model"):
54
+ field = get_model_field(self.model, self.field_name)
55
+ return issubclass(field.__class__, RangeField)
56
+ return False
57
+
58
+ def _get_initial(self, *args):
59
+ initial = super()._get_initial(*args)
60
+ if initial is not None:
51
61
  lower = upper = None
52
- if isinstance(default, tuple):
53
- lower, upper = default
62
+ if isinstance(initial, tuple):
63
+ lower, upper = initial
54
64
 
55
- # if the default is a tuple of components, we need to convert them to string
65
+ # if the initial is a tuple of components, we need to convert them to string
56
66
  if isinstance(lower, Component) and isinstance(upper, Component):
57
67
  return f"{lower},{upper}"
58
68
 
59
- elif hasattr(default, "lower") and hasattr(default, "upper"):
60
- lower, upper = default.lower, default.upper
61
- default = f'{lower.strftime(self.default_format) if lower else ""},{upper.strftime(self.default_format) if upper else ""}'
62
- return default
69
+ elif hasattr(initial, "lower") and hasattr(initial, "upper"):
70
+ lower, upper = initial.lower, initial.upper
71
+ initial = f'{lower.strftime(self.initial_format) if lower else ""},{upper.strftime(self.initial_format) if upper else ""}'
72
+ return initial
63
73
 
64
74
  def get_representation(self, request, name, view):
65
75
  representation, lookup_expr = super().get_representation(request, name, view)
66
76
  representation["lookup_expr"] = {"exact": self.field_name}
67
77
  with suppress(KeyError): # TODO frontend needs to support both exact and overlaps lookup
68
- default = representation["default"].pop(self.lookup_expr)
69
- representation["default"]["exact"] = default
78
+ initial = representation["initial"].pop(self.lookup_expr)
79
+ lookup_expr["input_properties"]["initial"]["exact"] = initial
70
80
 
71
81
  return representation, lookup_expr
72
82
 
73
- @classmethod
74
- def base_date_range_filter_method(cls, queryset, field_name, value):
83
+ def filter(self, qs, value):
84
+ if value in EMPTY_VALUES:
85
+ return qs
75
86
  if value:
87
+ lower, upper = value.lower, value.upper
76
88
  filters = {}
77
- if value.lower:
78
- filters[f"{field_name}__gte"] = value.lower
79
- if value.upper:
80
- filters[f"{field_name}__lte"] = value.upper
81
- return queryset.filter(**filters)
82
- return queryset
89
+ is_field_range = self.is_range
90
+ if lower:
91
+ if is_field_range:
92
+ filters[f"{self.field_name}__startswith__gte"] = lower
93
+ else:
94
+ filters[f"{self.field_name}__gte"] = lower
95
+
96
+ if upper:
97
+ if is_field_range:
98
+ filters[f"{self.field_name}__endswith__lte"] = upper
99
+ else:
100
+ filters[f"{self.field_name}__lte"] = upper
101
+
102
+ if self.exclude:
103
+ qs = qs.exclude(**filters)
104
+ else:
105
+ qs = qs.filter(**filters)
106
+ return qs
83
107
 
84
108
 
85
109
  class FinancialPerformanceDateRangeFilter(DateRangeFilter):
86
110
  def __init__(self, *args, **kwargs):
87
- super().__init__(performance_mode=True, shortcuts=financial_performance_shortcuts, *args, **kwargs)
111
+ super().__init__(*args, performance_mode=True, shortcuts=financial_performance_shortcuts, **kwargs)
88
112
 
89
113
 
90
114
  class DateTimeRangeFilter(DateRangeFilter):
91
115
  field_class = DateTimeRangeField
92
- default_format = "%Y-%m-%dT%H:%M:%S%z"
116
+ initial_format = "%Y-%m-%dT%H:%M:%S%z"
117
+ filter_type = "datetimerange"
@@ -14,6 +14,11 @@ from wbcore.filters.mixins import WBCoreFilterMixin
14
14
  class ModelChoiceFilterMixin(WBCoreFilterMixin):
15
15
  MULTIPLE: bool = False
16
16
 
17
+ def _validate_initial_with_request(self, initial, request, name):
18
+ if request_default := request.GET.get(name):
19
+ return request_default.split(",")
20
+ return initial
21
+
17
22
  @classmethod
18
23
  def get_parsed_values(cls, queryset, value_ids: int | str | Iterable[int]):
19
24
  if isinstance(value_ids, str):
@@ -28,8 +33,8 @@ class ModelChoiceFilterMixin(WBCoreFilterMixin):
28
33
  "value": value_id,
29
34
  "label": str(queryset.get(id=value_id)),
30
35
  }
31
- except ObjectDoesNotExist:
32
- raise ParseError("Filter value invalid")
36
+ except ObjectDoesNotExist as e:
37
+ raise ParseError("Filter value invalid") from e
33
38
 
34
39
  def get_representation(self, request, name, view):
35
40
  representation, lookup_expr = super().get_representation(request, name, view)
@@ -45,19 +50,21 @@ class ModelChoiceFilterMixin(WBCoreFilterMixin):
45
50
  url = reverse(self.endpoint, request=request)
46
51
  if self.filter_params:
47
52
  if callable(self.filter_params):
48
- url += f"?{urlencode(self.filter_params(request, view))}"
53
+ filter_params = self.filter_params(request, view)
49
54
  else:
50
- url += f"?{urlencode(self.filter_params)}"
55
+ filter_params = self.filter_params
56
+ # we need to convert any list into comma seperated string
57
+ for key, value in filter_params.items():
58
+ if isinstance(value, list):
59
+ filter_params[key] = ",".join(map(lambda x: str(x), value))
60
+
61
+ url += f"?{urlencode(filter_params, doseq=True)}"
51
62
  lookup_expr["input_properties"]["endpoint"] = {
52
63
  "url": url,
53
64
  "value_key": self.value_key,
54
65
  "label_key": label_key,
55
66
  }
56
67
 
57
- if default_ids := lookup_expr["input_properties"].get("default", None):
58
- values = self.get_parsed_values(queryset, default_ids)
59
- lookup_expr["input_properties"]["default"] = list(values) if self.MULTIPLE else next(values)
60
-
61
68
  if initial_ids := lookup_expr["input_properties"].get("initial", None):
62
69
  values = self.get_parsed_values(queryset, initial_ids)
63
70
  lookup_expr["input_properties"]["initial"] = list(values) if self.MULTIPLE else next(values)
@@ -86,12 +93,14 @@ class ModelMultipleChoiceFilter(ModelChoiceFilterMixin, django_filters.ModelMult
86
93
  self.filter_params = kwargs.pop("filter_params", None)
87
94
  self.value_key = kwargs.pop("value_key", None)
88
95
  self.label_key = kwargs.pop("label_key", None)
89
-
90
96
  # TODO: This is monkeypatched. Make sure that the CSVWidget is set here and only here!
91
97
  if "widget" not in kwargs:
92
98
  kwargs["widget"] = django_filters.widgets.CSVWidget
93
99
  super().__init__(*args, **kwargs)
94
100
 
101
+ # django filter sets it to True by default. In our case, the fitlering will happen on primary keys, so we do not expect any duplicate. Furthermore, for table without explicit unique constraint, using "distinct" leads to unexpected results (i.e. row with same value are dropped)
102
+ self.distinct = False
103
+
95
104
 
96
105
  class ModelChoiceFilter(ModelChoiceFilterMixin, django_filters.ModelChoiceFilter):
97
106
  class SimpleModelChoiceField(ModelChoiceField):
@@ -1,23 +1,25 @@
1
1
  import django_filters
2
2
 
3
3
  from wbcore.filters.mixins import WBCoreFilterMixin
4
+ from wbcore.serializers import WBCoreType
4
5
 
5
6
 
6
7
  class NumberFilter(WBCoreFilterMixin, django_filters.NumberFilter):
7
8
  filter_type = "number"
8
9
 
9
- def __init__(self, precision: int = 0, percent: bool = False, delimiter=",", decimal_mark=".", *args, **kwargs):
10
+ def __init__(self, precision: int = 0, percent: bool = False, disable_formatting: bool = False, *args, **kwargs):
10
11
  self.precision = precision
11
12
  self.percent = percent
12
- self.delimiter = delimiter
13
- self.decimal_mark = decimal_mark
13
+ self.disable_formatting = disable_formatting
14
14
  super().__init__(*args, **kwargs)
15
15
 
16
16
  def get_representation(self, request, name, view):
17
17
  representation, lookup_expr = super().get_representation(request, name, view)
18
- representation["precision"] = self.precision
19
- representation["delimiter"] = self.delimiter
20
- representation["decimal_mark"] = self.decimal_mark
18
+ lookup_expr["input_properties"]["precision"] = self.precision
19
+ if self.percent: # TODO: Discuss with Christoph if this is necessary like this
20
+ lookup_expr["input_properties"]["type"] = WBCoreType.PERCENT.value
21
+ lookup_expr["input_properties"]["precision"] = max(self.precision - 2, 0)
22
+ lookup_expr["input_properties"]["disable_formatting"] = self.disable_formatting
21
23
  return representation, lookup_expr
22
24
 
23
25
  def filter(self, qs, value):
@@ -29,8 +31,7 @@ class NumberFilter(WBCoreFilterMixin, django_filters.NumberFilter):
29
31
  class YearFilter(NumberFilter):
30
32
  def __init__(self, *args, **kwargs):
31
33
  super().__init__(*args, **kwargs)
32
- self.delimiter = ""
33
- self.decimal_mark = "."
34
+ self.disable_formatting = True
34
35
 
35
36
 
36
37
  class RangeSelectFilter(NumberFilter):
@@ -1,15 +1,19 @@
1
1
  import logging
2
+ from collections import OrderedDict
2
3
  from contextlib import suppress
4
+ from copy import copy
3
5
 
4
6
  from django.contrib.postgres.fields import DateRangeField, DateTimeRangeField
5
7
  from django.core.exceptions import FieldError
6
8
  from django.db import models
9
+ from django.db.models import GeneratedField
7
10
  from django.db.models.fields.related import ManyToManyRel, ManyToOneRel, OneToOneRel
8
11
  from django_filters.filterset import FilterSetMetaclass, remote_queryset, settings
9
12
  from django_filters.rest_framework import FilterSet as DjangoFilterSet
10
13
 
11
14
  from wbcore.filters import fields
12
15
  from wbcore.filters.fields.multiple_lookups import MultipleLookupFilter
16
+ from wbcore.models.fields import YearField
13
17
  from wbcore.signals.filters import add_filters
14
18
 
15
19
  from .utils import check_required_filters
@@ -26,8 +30,8 @@ def _is_number(field):
26
30
 
27
31
 
28
32
  class CustomFilterSetMetaClass(FilterSetMetaclass):
29
- def __new__(cls, name, bases, attrs):
30
- new_class = super().__new__(cls, name, bases, attrs)
33
+ def __new__(cls, *args, **kwargs):
34
+ new_class = super().__new__(cls, *args, **kwargs)
31
35
  if _meta := getattr(new_class, "Meta", None):
32
36
  for parent_field_name, child_fields in getattr(_meta, "flatten_fields", dict()).items():
33
37
  if remote_field := getattr(_meta.model._meta.get_field(parent_field_name), "remote_field", None):
@@ -63,6 +67,7 @@ class CustomFilterSetMetaClass(FilterSetMetaclass):
63
67
 
64
68
 
65
69
  class FilterSet(DjangoFilterSet, metaclass=CustomFilterSetMetaClass):
70
+ DEFAULT_EXCLUDE_FILTER_LOOKUP: str = "exclude"
66
71
  FILTER_DEFAULTS = {
67
72
  models.BooleanField: {"filter_class": fields.BooleanFilter},
68
73
  models.NullBooleanField: {"filter_class": fields.BooleanFilter},
@@ -83,6 +88,7 @@ class FilterSet(DjangoFilterSet, metaclass=CustomFilterSetMetaClass):
83
88
  models.DecimalField: {"filter_class": fields.NumberFilter},
84
89
  DateTimeRangeField: {"filter_class": fields.DateTimeRangeFilter},
85
90
  DateRangeField: {"filter_class": fields.DateRangeFilter},
91
+ YearField: {"filter_class": fields.YearFilter},
86
92
  # models.DurationField: {"filter_class": DurationFilter},
87
93
  # models.SmallIntegerField: {"filter_class": NumberFilter},
88
94
  # models.AutoField: {"filter_class": NumberFilter},
@@ -165,11 +171,13 @@ class FilterSet(DjangoFilterSet, metaclass=CustomFilterSetMetaClass):
165
171
  for _, res in remote_filters:
166
172
  if res:
167
173
  for remote_filter_key, remote_filter in res.items():
168
- setattr(remote_filter, "column_field_name", remote_filter_key)
174
+ remote_filter.column_field_name = remote_filter_key
169
175
  self.filters[remote_filter_key] = remote_filter
170
176
 
171
177
  @classmethod
172
178
  def filter_for_lookup(cls, field, lookup_type):
179
+ if isinstance(field, GeneratedField):
180
+ return cls.filter_for_lookup(field.output_field, lookup_type)
173
181
  if lookup_type == "exact" and getattr(field, "choices", None):
174
182
  filter_class, params = fields.ChoiceFilter, {"choices": field.choices}
175
183
  else:
@@ -191,12 +199,12 @@ class FilterSet(DjangoFilterSet, metaclass=CustomFilterSetMetaClass):
191
199
 
192
200
  @classmethod
193
201
  def get_filters(cls):
194
- filters = super().get_filters()
202
+ filters = dict(super().get_filters())
195
203
  remote_filters = add_filters.send(sender=cls.filter_class_for_remote_filter())
196
204
  for _, res in remote_filters:
197
205
  if res:
198
206
  for remote_filter_key, remote_filter in res.items():
199
- setattr(remote_filter, "column_field_name", remote_filter_key)
207
+ remote_filter.column_field_name = remote_filter_key
200
208
  filters[remote_filter_key] = remote_filter
201
209
 
202
210
  for field, help_text in getattr(cls, "help_texts", {}).items():
@@ -206,7 +214,20 @@ class FilterSet(DjangoFilterSet, metaclass=CustomFilterSetMetaClass):
206
214
  for field, values in cls.get_dependency_map():
207
215
  for value in values:
208
216
  filters[field].depends_on.append({"field": value, "options": {}})
209
- return filters
217
+
218
+ excluding_fields = {}
219
+ for name, field in filters.items():
220
+ # if allow_exclude is true, we add a copy of the field with the parameter exclude=True
221
+ # (to use `exclude` queryset method instead of `filter`) and add this with the suffix __{cls.DEFAULT_EXCLUDE_FILTER_LOOKUP}
222
+ if field.allow_exclude:
223
+ excluding_field = copy(field)
224
+ excluding_field.exclude = True
225
+ excluding_field.excluded_filter = True
226
+ excluding_field.hidden = True
227
+ excluding_field.required = False
228
+ excluding_fields[f"{name}__{cls.DEFAULT_EXCLUDE_FILTER_LOOKUP}"] = excluding_field
229
+ filters.update(excluding_fields)
230
+ return OrderedDict(filters)
210
231
 
211
232
  def extract_required_field_labels(self):
212
233
  return [label for label, filter in self.base_filters.items() if getattr(filter, "required", False)]
wbcore/filters/mixins.py CHANGED
@@ -1,5 +1,7 @@
1
+ import warnings
1
2
  from contextlib import suppress
2
3
 
4
+ from django.core.exceptions import ValidationError
3
5
  from django_filters.utils import get_model_field
4
6
 
5
7
  from .lookups import get_lookup_icon, get_lookup_label
@@ -7,18 +9,31 @@ from .lookups import get_lookup_icon, get_lookup_label
7
9
 
8
10
  class WBCoreFilterMixin:
9
11
  def __init__(self, *args, **kwargs):
10
- self.default = kwargs.pop("default", None)
11
- self.initial = kwargs.pop("initial", None)
12
- self.required = kwargs.pop("required", False)
12
+ default = kwargs.pop("default", None)
13
+ if default is not None:
14
+ self.initial = default
15
+ self.required = True
16
+ warnings.warn(
17
+ "The use of default will be deprecated in favour of the equivalent 'initial' and 'required=True'",
18
+ DeprecationWarning,
19
+ stacklevel=2,
20
+ )
21
+ else:
22
+ self.initial = kwargs.pop("initial", None)
23
+ self.required = kwargs.pop("required", False)
13
24
  self.clearable = kwargs.pop("clearable", True) # TODO: Take away
14
25
  self.hidden = kwargs.pop("hidden", False)
15
26
  self.column_field_name = kwargs.pop("column_field_name", None)
16
27
  self.help_text = kwargs.pop("help_text", None)
17
- self.allow_empty_default = kwargs.pop("allow_empty_default", False)
28
+ self.allow_empty_initial = kwargs.pop("allow_empty_initial", False)
18
29
  self.label_format = kwargs.pop(
19
30
  "label_format",
20
31
  getattr(self, "default_label_format", "{{field_label}} {{operation_icon}} {{value_label}}"),
21
32
  )
33
+ self.allow_exclude = kwargs.pop(
34
+ "allow_exclude", kwargs.get("method") is None
35
+ ) # if False, we will not automatically add a similar filter "opposite" filter
36
+ self.excluded_filter = kwargs.pop("excluded_filter", False)
22
37
  self.lookup_icon = kwargs.pop("lookup_icon", None)
23
38
  self.lookup_label = kwargs.pop("lookup_label", None)
24
39
  self.depends_on = kwargs.pop("depends_on", [])
@@ -34,25 +49,28 @@ class WBCoreFilterMixin:
34
49
  else:
35
50
  return self.label
36
51
 
37
- def _get_default(self, request, view):
38
- # We consider the case where default is a boolean with value False.
39
- if callable(self.default):
40
- default = self.default(self, request, view)
52
+ def _get_initial(self, request, view):
53
+ # We consider the case where initial is a boolean with value False.
54
+ if callable(self.initial):
55
+ initial = self.initial(self, request, view)
41
56
  elif (
42
- isinstance(self.default, str)
43
- and (callable_default := getattr(self, self.default, None))
44
- and (callable(callable_default))
57
+ isinstance(self.initial, str)
58
+ and (callable_initial := getattr(self, self.initial, None))
59
+ and (callable(callable_initial))
45
60
  ):
46
- default = callable_default(self, request, view)
61
+ initial = callable_initial(self, request, view)
47
62
  else:
48
- default = self.default
63
+ initial = self.initial
49
64
 
50
- return default
65
+ return initial
51
66
 
52
- def _validate_default_with_request(self, default, request, name):
67
+ def _validate_initial_with_request(self, initial, request, name):
53
68
  if request_default := request.GET.get(name):
54
- return request_default
55
- return default
69
+ try:
70
+ return self.field.to_python(request_default)
71
+ except ValidationError:
72
+ return None
73
+ return initial
56
74
 
57
75
  def get_help_text(self) -> str:
58
76
  if self.help_text:
@@ -65,9 +83,6 @@ class WBCoreFilterMixin:
65
83
  return "Filter by " + self.label
66
84
 
67
85
  def get_representation(self, request, name, view):
68
- if self.hidden:
69
- return {}
70
-
71
86
  representation = {
72
87
  "key": self.key,
73
88
  "label_format": self.label_format,
@@ -79,34 +94,18 @@ class WBCoreFilterMixin:
79
94
  "icon": get_lookup_icon(self.lookup_expr) if self.lookup_icon is None else self.lookup_icon,
80
95
  "key": name,
81
96
  "hidden": self.hidden,
97
+ "allow_exclude": self.allow_exclude,
82
98
  "input_properties": {
83
99
  "type": self.filter_type,
84
100
  },
85
101
  }
86
- default = self._get_default(request, view)
87
- if default is not None or self.allow_empty_default:
88
- lookup_expr["input_properties"]["default"] = default
89
-
90
- initial = None
91
- if callable(self.initial):
92
- initial = self.initial(self, request, view)
93
- elif (
94
- isinstance(self.initial, str)
95
- and (callable_initial := getattr(self, self.initial, None))
96
- and (callable(callable_initial))
97
- ):
98
- initial = callable_initial(self, request, view)
99
- else:
100
- initial = self.initial
101
-
102
- if _initial := self._validate_default_with_request(initial, request, name):
103
- initial = _initial
102
+ initial = self._get_initial(request, view)
103
+ if (overridden_initial := self._validate_initial_with_request(initial, request, name)) is not None:
104
+ initial = overridden_initial
104
105
 
105
- if initial is not None:
106
+ if initial is not None or self.allow_empty_initial:
106
107
  lookup_expr["input_properties"]["initial"] = initial
107
108
 
108
- if self.required:
109
- lookup_expr["input_properties"]["required"] = True
110
- # assert representation["default"] != {}, "If a filter is required, it needs at least one default value"
109
+ lookup_expr["input_properties"]["required"] = self.required
111
110
  representation["depends_on"] = self.depends_on
112
111
  return representation, lookup_expr
wbcore/forms.py CHANGED
@@ -3,7 +3,7 @@ from urllib.parse import unquote
3
3
 
4
4
  from django import forms
5
5
  from django.contrib.contenttypes.models import ContentType
6
- from django.core.exceptions import ValidationError
6
+ from django.core.exceptions import ObjectDoesNotExist, ValidationError
7
7
  from django.forms import modelformset_factory
8
8
  from django.forms.models import BaseModelFormSet
9
9
  from psycopg.types.range import DateRange, TimestamptzRange
@@ -39,10 +39,10 @@ def nonrelated_inlineformset_factory(
39
39
  """
40
40
  FormSet factory that sets an explicit queryset on new classes.
41
41
  """
42
- FormSet = modelformset_factory(model, formset=formset, **kwargs)
43
- FormSet.real_queryset = queryset
44
- FormSet.save_new_instance = save_new_instance
45
- return FormSet
42
+ form = modelformset_factory(model, formset=formset, **kwargs)
43
+ form.real_queryset = queryset
44
+ form.save_new_instance = save_new_instance
45
+ return form
46
46
 
47
47
 
48
48
  class ContentTypeMultiValueField(forms.fields.MultiValueField):
@@ -68,7 +68,7 @@ class ContentTypeMultiValueField(forms.fields.MultiValueField):
68
68
  try:
69
69
  content_type = ContentType.objects.get_for_id(content_type_id)
70
70
  return content_type.get_object_for_this_type(id=object_id)
71
- except (ContentType.DoesNotExist, content_type.model_class().DoesNotExist):
71
+ except ObjectDoesNotExist:
72
72
  return None
73
73
 
74
74
 
@@ -27,5 +27,5 @@ class FSMPreprocessor(Preprocessor):
27
27
 
28
28
 
29
29
  class FSMExtension(Extension):
30
- def extendMarkdown(self, md, md_globals):
30
+ def extendMarkdown(self, md, md_globals): # noqa
31
31
  md.preprocessors.register(FSMPreprocessor(), "wbcore-fsm", 100)