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
@@ -10,8 +10,10 @@ logger = logging.getLogger(__name__)
10
10
 
11
11
 
12
12
  def decorator(position: str, value: str, decorator_type: str = "icon") -> dict:
13
- assert position in ("left", "right"), "Decorator Position can only be right or left"
14
- assert decorator_type in ("icon", "text"), "Decorator Type can only be icon or text"
13
+ if position not in ("left", "right"):
14
+ raise ValueError("Decorator Position can only be right or left")
15
+ if decorator_type not in ("icon", "text"):
16
+ raise ValueError("Decorator Type can only be icon or text")
15
17
  return {"position": position, "value": value, "type": decorator_type}
16
18
 
17
19
 
@@ -31,6 +33,8 @@ class WBCoreSerializerFieldMixin:
31
33
  read_only=False,
32
34
  copyable=None,
33
35
  related_key=None,
36
+ on_unsatisfied_deps="read_only",
37
+ clear_dependent_fields=True,
34
38
  **kwargs,
35
39
  ):
36
40
  if not decorators:
@@ -48,6 +52,8 @@ class WBCoreSerializerFieldMixin:
48
52
  self._callable_read_only = read_only
49
53
  read_only = True
50
54
  self.related_key = related_key
55
+ self.on_unsatisfied_deps = on_unsatisfied_deps
56
+ self.clear_dependent_fields = clear_dependent_fields
51
57
  super().__init__(*args, read_only=read_only, **kwargs)
52
58
 
53
59
  def _evaluate_read_only(self, field_name, parent):
@@ -105,12 +111,8 @@ class WBCoreSerializerFieldMixin:
105
111
 
106
112
  default = getattr(self, "default", None)
107
113
  if default is None or default == empty or default == NOT_PROVIDED:
108
- try:
109
- default = self.parent.Meta.model._meta._forward_fields_map[
110
- field_name
111
- ].default
112
- except Exception: # TODO Add some explicit exception handling
113
- pass
114
+ with suppress(Exception): # TODO Add some explicit exception handling
115
+ default = self.parent.Meta.model._meta._forward_fields_map[field_name].default
114
116
 
115
117
  if default is not None and default != empty and default != NOT_PROVIDED:
116
118
  if callable(default):
@@ -133,6 +135,10 @@ class WBCoreSerializerFieldMixin:
133
135
  representation["math"] = self.math
134
136
  if self.copyable:
135
137
  representation["copyable"] = self.copyable
138
+ if self.on_unsatisfied_deps != "read_only":
139
+ representation["on_unsatisfied_deps"] = self.on_unsatisfied_deps
140
+ if self.clear_dependent_fields is not True:
141
+ representation["clear_dependent_fields"] = self.clear_dependent_fields
136
142
  return field_name, representation
137
143
 
138
144
  def validate_empty_values(self, data):
@@ -173,9 +179,16 @@ class RangeMixin(WBCoreSerializerFieldMixin, Field):
173
179
  return self.internal_field(lower, upper)
174
180
 
175
181
  def to_representation(self, instance):
176
- lower, upper = self._transform_range(
177
- instance.lower, instance.upper, inward=False
178
- )
182
+ if hasattr(instance, "lower"):
183
+ lower = instance.lower
184
+ else:
185
+ lower = instance[0]
186
+
187
+ if hasattr(instance, "upper"):
188
+ upper = instance.upper
189
+ else:
190
+ upper = instance[1]
191
+ lower, upper = self._transform_range(lower, upper, inward=False)
179
192
  with suppress(Exception):
180
193
  lower = super().to_representation(lower)
181
194
  with suppress(Exception):
@@ -14,19 +14,12 @@ class NumberFieldMixin:
14
14
  percent=False,
15
15
  display_mode=DisplayMode.DECIMAL,
16
16
  precision=2,
17
- delimiter=",",
18
- decimal_mark=".",
19
17
  signed: bool = True,
20
18
  **kwargs,
21
19
  ):
22
20
  super().__init__(*args, **kwargs)
23
21
  self.percent = percent
24
- self.delimiter = delimiter
25
- self.decimal_mark = decimal_mark
26
- if self.delimiter == self.decimal_mark:
27
- raise ValueError(
28
- "Decimal mark and delimiter need to be different character"
29
- )
22
+ self.disable_formatting = getattr(self, "disable_formatting", kwargs.pop("disable_formatting", False))
30
23
  self.display_mode = display_mode
31
24
  self.precision = kwargs.get("decimal_places", precision)
32
25
  self.max_digits = kwargs.get("max_digits", 34)
@@ -45,8 +38,7 @@ class NumberFieldMixin:
45
38
  representation["display_mode"] = self.display_mode.value
46
39
  representation["precision"] = self.precision
47
40
  representation["max_digits"] = self.max_digits
48
- representation["delimiter"] = self.delimiter
49
- representation["decimal_mark"] = self.decimal_mark
41
+ representation["disable_formatting"] = self.disable_formatting
50
42
  representation["signed"] = self.signed
51
43
 
52
44
  if self.percent: # TODO: Discuss with Christoph if this is necessary like this
@@ -55,9 +47,7 @@ class NumberFieldMixin:
55
47
  return key, representation
56
48
 
57
49
 
58
- class IntegerField(
59
- NumberFieldMixin, WBCoreSerializerFieldMixin, serializers.IntegerField
60
- ):
50
+ class IntegerField(NumberFieldMixin, WBCoreSerializerFieldMixin, serializers.IntegerField):
61
51
  field_type = WBCoreType.NUMBER.value
62
52
 
63
53
  def __init__(self, *args, **kwargs):
@@ -66,15 +56,10 @@ class IntegerField(
66
56
 
67
57
 
68
58
  class YearField(IntegerField):
69
- def __init__(self, *args, **kwargs):
70
- super().__init__(*args, **kwargs)
71
- self.delimiter = ""
72
- self.decimal_mark = "."
59
+ disable_formatting = True
73
60
 
74
61
 
75
- class DecimalField(
76
- NumberFieldMixin, WBCoreSerializerFieldMixin, serializers.DecimalField
77
- ):
62
+ class DecimalField(NumberFieldMixin, WBCoreSerializerFieldMixin, serializers.DecimalField):
78
63
  field_type = WBCoreType.NUMBER.value
79
64
 
80
65
  # TODO: If this is used, then the validation for max_digits and decimal_fields is not done
@@ -97,9 +82,7 @@ class FloatField(NumberFieldMixin, WBCoreSerializerFieldMixin, serializers.Float
97
82
  field_type = WBCoreType.NUMBER.value
98
83
 
99
84
 
100
- class DecimalRangeField(
101
- RangeMixin, WBCoreSerializerFieldMixin, serializers.DecimalField
102
- ):
85
+ class DecimalRangeField(RangeMixin, WBCoreSerializerFieldMixin, serializers.DecimalField):
103
86
  field_type = WBCoreType.NUMBERRANGE.value
104
87
  internal_field = NumericRange
105
88
 
@@ -7,20 +7,12 @@ from .number import IntegerField
7
7
 
8
8
  class StarRatingField(IntegerField):
9
9
  field_type = "starrating"
10
-
11
- def __init__(self, *args, **kwargs):
12
- super().__init__(*args, **kwargs)
13
- self.delimiter = ""
14
- self.decimal_mark = ""
10
+ disable_formatting = True
15
11
 
16
12
 
17
13
  class EmojiRatingField(IntegerField):
18
14
  field_type = "emojirating"
19
-
20
- def __init__(self, *args, **kwargs):
21
- super().__init__(*args, **kwargs)
22
- self.delimiter = ""
23
- self.decimal_mark = ""
15
+ disable_formatting = True
24
16
 
25
17
 
26
18
  class RangeSelectField(WBCoreSerializerFieldMixin, serializers.FloatField):
@@ -29,7 +29,7 @@ class WBCoreManyRelatedField(ListFieldMixin, WBCoreSerializerFieldMixin, ManyRel
29
29
  self.child_relation.context["view"] = self.view
30
30
  self.child_relation._evaluate_read_only(field_name, parent)
31
31
  if not self.child_relation.read_only and hasattr(self.child_relation, "_queryset"):
32
- setattr(self.child_relation, "queryset", self.child_relation._queryset)
32
+ self.child_relation.queryset = self.child_relation._queryset
33
33
 
34
34
  def get_representation(self, request: Request, field_name: str) -> tuple[str, dict]:
35
35
  key, representation = self.child_relation.get_representation(request, field_name)
@@ -58,7 +58,7 @@ class PrimaryKeyRelatedField(WBCoreSerializerFieldMixin, serializers.PrimaryKeyR
58
58
  )
59
59
 
60
60
  def __init__(self, *args, queryset=None, read_only=False, **kwargs):
61
- self.field_type = kwargs.pop("field_type", WBCoreType.SELECT.value)
61
+ self.field_type = kwargs.pop("field_type", WBCoreType.PRIMARY_KEY.value)
62
62
  if callable(read_only) and queryset is not None:
63
63
  self._queryset = queryset # we unset any given queryset to be compliant with the RelatedField assertion
64
64
  queryset = None
@@ -72,7 +72,7 @@ class PrimaryKeyRelatedField(WBCoreSerializerFieldMixin, serializers.PrimaryKeyR
72
72
  super().bind(field_name, parent)
73
73
  # In case we had to unset the queryset attribute because read_only was a callable, we reinstate it here.
74
74
  if not self.read_only and hasattr(self, "_queryset"):
75
- setattr(self, "queryset", self._queryset)
75
+ self.queryset = self._queryset
76
76
 
77
77
  @classmethod
78
78
  def many_init(cls, *args, **kwargs):
@@ -103,7 +103,7 @@ class PrimaryKeyRelatedField(WBCoreSerializerFieldMixin, serializers.PrimaryKeyR
103
103
  # In case we annotate the representation, we need to ensure that the value is an object
104
104
  if isinstance(value, (list, tuple, set)):
105
105
  return [self.to_representation(d) for d in value]
106
- try:
106
+ with suppress(Exception): # TODO: investigate what exception are we expecting here
107
107
  if isinstance(value, str):
108
108
  try:
109
109
  value = int(value)
@@ -112,8 +112,6 @@ class PrimaryKeyRelatedField(WBCoreSerializerFieldMixin, serializers.PrimaryKeyR
112
112
  if isinstance(value, int):
113
113
  value = PKOnlyObject(value)
114
114
  return super().to_representation(value)
115
- except Exception:
116
- pass
117
115
  return None
118
116
 
119
117
  def get_queryset(self):
@@ -54,7 +54,7 @@ class CodeField(CharField):
54
54
  try:
55
55
  compile(data, "", "exec")
56
56
  except Exception as e:
57
- raise ValidationError(_("Compiling script failed with the exception: {}".format(e)))
57
+ raise ValidationError(_("Compiling script failed with the exception: {}".format(e))) from e
58
58
  return super().to_internal_value(data)
59
59
 
60
60
 
@@ -14,6 +14,7 @@ class WBCoreType(Enum):
14
14
  DATETIMERANGE = "datetimerange"
15
15
  DATE = "date"
16
16
  DATERANGE = "daterange"
17
+ TIMERANGE = "timerange"
17
18
  DURATION = "duration"
18
19
  TIME = "time"
19
20
  PRIMARY_KEY = "primary_key"
@@ -29,6 +30,7 @@ class WBCoreType(Enum):
29
30
  COLOR = "color"
30
31
  URL = "url"
31
32
  SPARKLINE = "sparkline"
33
+ LANGUAGE = "language"
32
34
 
33
35
 
34
36
  class ReturnContentType(Enum):
@@ -23,6 +23,7 @@ from timezone_field import TimeZoneField
23
23
  from wbcore.contrib.color.fields import ColorField
24
24
  from wbcore.contrib.icons.models import IconField
25
25
  from wbcore.contrib.icons.serializers import IconSelectField
26
+ from wbcore.metadata.configs.display.list_display import BaseTreeGroupLevelOption
26
27
  from wbcore.models.fields import YearField
27
28
  from wbcore.serializers import (
28
29
  fields,
@@ -48,8 +49,8 @@ def validate_nested_representation(instance, value):
48
49
 
49
50
 
50
51
  class WBCoreSerializerMetaClass(SerializerMetaclass):
51
- def __new__(cls, name, bases, dct):
52
- _class = super().__new__(cls, name, bases, dct)
52
+ def __new__(cls, *args, **kwargs): # noqa: C901
53
+ _class = super().__new__(cls, *args, **kwargs)
53
54
 
54
55
  if _meta := getattr(_class, "Meta", None):
55
56
  model = _meta.model
@@ -306,7 +307,7 @@ class ModelSerializer(
306
307
  class RepresentationSerializer(WBCoreSerializerFieldMixin, ModelSerializer):
307
308
  field_type = WBCoreType.SELECT.value
308
309
 
309
- def __init__(self, *args, **kwargs):
310
+ def __init__(self, *args, tree_config: BaseTreeGroupLevelOption | None = None, **kwargs):
310
311
  self.ignore_filter = kwargs.pop("ignore_filter", getattr(self, "ignore_filter", None))
311
312
  self.filter_params = kwargs.pop("filter_params", getattr(self, "filter_params", None))
312
313
  self.endpoint = kwargs.pop(
@@ -332,6 +333,8 @@ class RepresentationSerializer(WBCoreSerializerFieldMixin, ModelSerializer):
332
333
  "optional_get_parameters",
333
334
  getattr(self, "optional_get_parameters", None),
334
335
  )
336
+ self.tree_config = tree_config
337
+ self.select_first_choice = kwargs.pop("select_first_choice", getattr(self, "select_first_choice", None))
335
338
  super().__init__(*args, **kwargs)
336
339
 
337
340
  def to_representation(self, value):
@@ -408,9 +411,15 @@ class RepresentationSerializer(WBCoreSerializerFieldMixin, ModelSerializer):
408
411
  },
409
412
  }
410
413
 
414
+ if self.select_first_choice:
415
+ representation["select_first_choice"] = True
416
+
411
417
  if self.help_text:
412
418
  representation["help_text"] = self.help_text
413
419
 
414
420
  if self.optional_get_parameters:
415
421
  representation["endpoint"]["optional_get_parameters"] = self.optional_get_parameters
422
+
423
+ if self.tree_config:
424
+ representation["tree_config"] = dict(self.tree_config)
416
425
  return self.get_related_key(), representation
@@ -4,3 +4,4 @@ from .models import get_dependant_dynamic_fields_instances, pre_collection
4
4
  from .permissions import load_permission_objects
5
5
  from .serializers import add_additional_resource, add_instance_additional_resource, add_dynamic_button
6
6
  from .merge import pre_merge
7
+ from .clone import post_clone
@@ -0,0 +1,4 @@
1
+ from django.db.models.signals import ModelSignal
2
+
3
+ # Signal fired before merged/obsolete object are deleted during a merge phase
4
+ post_clone = ModelSignal(use_caching=False)
wbcore/signals/models.py CHANGED
@@ -1,8 +1,4 @@
1
1
  from django.db.models.signals import ModelSignal
2
2
 
3
- get_dependant_dynamic_fields_instances = (
4
- ModelSignal()
5
- ) # Signal use to gather the dependant fields to be computed before the sender (use for the dynamic field framework). Experimental state
6
- pre_collection = (
7
- ModelSignal()
8
- ) # pre_delete signal is sent after collection. Therefore, protect field will trigger an IntergrityError. This signal can be used to circuvent that.
3
+ get_dependant_dynamic_fields_instances = ModelSignal() # Signal use to gather the dependant fields to be computed before the sender (use for the dynamic field framework). Experimental state
4
+ pre_collection = ModelSignal() # pre_delete signal is sent after collection. Therefore, protect field will trigger an IntergrityError. This signal can be used to circuvent that.
wbcore/tasks.py CHANGED
@@ -45,7 +45,7 @@ def recompute_computed_str(debug: bool = False):
45
45
  When this task is executed, it will loop over all objects that inherit from ComplexToStringMixin and compare their current computed_str value with the expected one.
46
46
  If different, the expected one is saved in place.
47
47
  """
48
- BULK_SIZE = 1000
48
+ bulk_size = 1000
49
49
  for subclass in get_inheriting_subclasses(ComplexToStringMixin):
50
50
  if getattr(subclass, "COMPUTED_STR_RECOMPUTE_PERIODICALLY", True):
51
51
  objs = []
@@ -59,7 +59,7 @@ def recompute_computed_str(debug: bool = False):
59
59
  if new_computed_str != instance.computed_str:
60
60
  instance.computed_str = new_computed_str
61
61
  objs.append(instance)
62
- if len(objs) % BULK_SIZE == 0:
62
+ if len(objs) % bulk_size == 0:
63
63
  subclass.objects.bulk_update(objs, ["computed_str"])
64
64
  objs = []
65
65
  if objs:
@@ -208,7 +208,7 @@
208
208
  width: 100%;
209
209
  }
210
210
  .fixed {
211
- width: 500px;
211
+ width: 750px;
212
212
  }
213
213
  #body-table {
214
214
  width: 100%;
@@ -248,8 +248,8 @@
248
248
  }
249
249
  #body-table .content-row > .body-content {
250
250
  background-color: #fff;
251
- padding-right: 30px;
252
- padding-left: 30px;
251
+ padding-right: 15px;
252
+ padding-left: 15px;
253
253
  }
254
254
  #body-table #spacer-row > .spacer-left,
255
255
  #body-table #spacer-row > .spacer-middle,
@@ -2,7 +2,7 @@ from selenium.common.exceptions import TimeoutException
2
2
  from selenium.webdriver.common.by import By
3
3
  from selenium.webdriver.remote.webdriver import WebDriver
4
4
  from selenium.webdriver.remote.webelement import WebElement
5
- from selenium.webdriver.support import expected_conditions as EC
5
+ from selenium.webdriver.support import expected_conditions
6
6
  from selenium.webdriver.support.color import Color
7
7
  from selenium.webdriver.support.wait import WebDriverWait
8
8
 
@@ -49,7 +49,9 @@ def is_string_not_visible(driver: WebDriver, string: str) -> bool:
49
49
  bool: True if the text is not visible
50
50
  """
51
51
  try:
52
- WebDriverWait(driver, 5).until(EC.invisibility_of_element_located((By.XPATH, f"//*[text()='{string}']")))
52
+ WebDriverWait(driver, 5).until(
53
+ expected_conditions.invisibility_of_element_located((By.XPATH, f"//*[text()='{string}']"))
54
+ )
53
55
  return True
54
56
  except TimeoutException:
55
57
  return False
@@ -82,7 +84,9 @@ def is_tag_not_visible(driver: WebDriver, tag_label: str) -> bool:
82
84
  """
83
85
  try:
84
86
  WebDriverWait(driver, 2.5).until(
85
- EC.invisibility_of_element_located((By.XPATH, f"//*[@class='tag-label' and text()='{tag_label}']"))
87
+ expected_conditions.invisibility_of_element_located(
88
+ (By.XPATH, f"//*[@class='tag-label' and text()='{tag_label}']")
89
+ )
86
90
  )
87
91
  return True
88
92
  except TimeoutException:
@@ -103,7 +107,9 @@ def is_error_visible(driver: WebDriver):
103
107
 
104
108
  if saving_failed_hint and saving_failed_hint.is_displayed():
105
109
  WebDriverWait(driver, 10).until_not(
106
- EC.invisibility_of_element_located((By.XPATH, "//div[contains(@class, 'task-dropper-content')]"))
110
+ expected_conditions.invisibility_of_element_located(
111
+ (By.XPATH, "//div[contains(@class, 'task-dropper-content')]")
112
+ )
107
113
  )
108
114
  return error_element is not None
109
115
 
@@ -4,7 +4,7 @@ from selenium.webdriver.common.action_chains import ActionChains
4
4
  from selenium.webdriver.common.by import By
5
5
  from selenium.webdriver.remote.webdriver import WebDriver
6
6
  from selenium.webdriver.remote.webelement import WebElement
7
- from selenium.webdriver.support import expected_conditions as EC
7
+ from selenium.webdriver.support import expected_conditions
8
8
  from selenium.webdriver.support.wait import WebDriverWait
9
9
  from wbcore import serializers as wb_serializers
10
10
 
@@ -157,7 +157,9 @@ def click_element_by_path(driver: WebDriver, xpath: str, wait_sec=WAIT_TIME_SEC)
157
157
  xpath (str): The xpath leading to the element.
158
158
  """
159
159
  try:
160
- WebDriverWait(driver, wait_sec).until(EC.presence_of_element_located((By.XPATH, xpath))).click()
160
+ WebDriverWait(driver, wait_sec).until(
161
+ expected_conditions.presence_of_element_located((By.XPATH, xpath))
162
+ ).click()
161
163
  except TimeoutException:
162
164
  return []
163
165
 
wbcore/test/mixins.py CHANGED
@@ -173,7 +173,7 @@ class ParentViewset:
173
173
  else get_model_factory(self.model)
174
174
  )
175
175
 
176
- def _get_mixins_data(self, type="GET", dump_data=False, data=None):
176
+ def _get_mixins_data(self, type="GET", dump_data=False, data=None): # noqa: C901
177
177
  api_request = APIRequestFactory()
178
178
  superuser = get_or_create_superuser()
179
179
  kwargs = None
@@ -307,34 +307,27 @@ class TestViewSet(ParentViewset):
307
307
  kwargs["pk"] = obj.pk
308
308
  vs = self.mvs.as_view({"options": "options"})
309
309
  response = vs(request, **kwargs)
310
- if ep.get_endpoint() or ep.get_list_endpoint():
311
- assert response.status_code == status.HTTP_200_OK, (
312
- str(response.status_code) + f" == 200 ({response.data})"
313
- )
314
- assert response.data, str(response.data) + " should not be empty"
315
- if "buttons" in response.data.keys():
316
- if "custom_instance" in response.data.get("buttons").keys():
317
- assert (
318
- list(response.data["buttons"]["custom_instance"])
319
- or len(list(response.data["buttons"]["custom_instance"])) == 0
320
- )
321
- assert response.data.get("fields"), str(response.data.get("fields")) + " should not be None"
322
- assert response.data.get("identifier"), str(response.data.get("identifier")) + " should not be None"
323
- # assert response.data.get("pagination")
324
- # assert response.data.get("pk")
325
- # assert response.data.get("type")
326
- # assert response.data.get("filterset_fields")
327
- # assert response.data.get("search_fields")
328
- # assert response.data.get("ordering_fields")
329
- # assert response.data.get("buttons"), str(response.data.get("buttons")) + " should not be None" # TODO: Refactor - buttons can be none
330
- assert response.data.get("display"), str(response.data.get("display")) + " should not be None"
331
- assert response.data.get("titles"), str(response.data.get("titles")) + " should not be None"
332
- assert response.data.get("endpoints"), str(response.data.get("endpoints")) + " should not be None"
333
- # assert response.data.get("preview")
334
- else:
335
- assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED, (
336
- str(response.status_code) + f" == 405 ({response.data})"
337
- )
310
+ assert response.status_code == status.HTTP_200_OK, str(response.status_code) + f" == 200 ({response.data})"
311
+ assert response.data, str(response.data) + " should not be empty"
312
+ if "buttons" in response.data.keys():
313
+ if "custom_instance" in response.data.get("buttons").keys():
314
+ assert (
315
+ list(response.data["buttons"]["custom_instance"])
316
+ or len(list(response.data["buttons"]["custom_instance"])) == 0
317
+ )
318
+ assert response.data.get("fields"), str(response.data.get("fields")) + " should not be None"
319
+ assert response.data.get("identifier"), str(response.data.get("identifier")) + " should not be None"
320
+ # assert response.data.get("pagination")
321
+ # assert response.data.get("pk")
322
+ # assert response.data.get("type")
323
+ # assert response.data.get("filterset_fields")
324
+ # assert response.data.get("search_fields")
325
+ # assert response.data.get("ordering_fields")
326
+ # assert response.data.get("buttons"), str(response.data.get("buttons")) + " should not be None" # TODO: Refactor - buttons can be none
327
+ assert response.data.get("display"), str(response.data.get("display")) + " should not be None"
328
+ assert response.data.get("titles"), str(response.data.get("titles")) + " should not be None"
329
+ assert response.data.get("endpoints"), str(response.data.get("endpoints")) + " should not be None"
330
+ # assert response.data.get("preview")
338
331
  print(f"- {self.__class__.__name__}:test_option_request", colored("PASSED", "green")) # noqa: T201
339
332
 
340
333
  # ----- LIST ROUTE TEST ----- #
@@ -346,16 +339,8 @@ class TestViewSet(ParentViewset):
346
339
  _, request, kwargs, _ = self._get_mixins_data("GET")
347
340
  vs = self.mvs.as_view({"get": "list"})
348
341
  response = vs(request, **kwargs)
349
- ep = self._get_endpoint_config(request, kwargs)
350
- if ep.get_endpoint() or ep.get_list_endpoint():
351
- assert response.status_code == status.HTTP_200_OK, (
352
- str(response.status_code) + f" == 200 ({response.data})"
353
- )
354
- assert response.data, str(response.data) + " should not be empty"
355
- else:
356
- assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED, (
357
- str(response.status_code) + f" == 405 ({response.data})"
358
- )
342
+ assert response.status_code == status.HTTP_200_OK, str(response.status_code) + f" == 200 ({response.data})"
343
+ assert response.data, str(response.data) + " should not be empty"
359
344
  print(f"- {self.__class__.__name__}:test_get_request", colored("PASSED", "green")) # noqa: T201
360
345
 
361
346
  # Test viewset "get": "list" -> aggregation
@@ -367,24 +352,16 @@ class TestViewSet(ParentViewset):
367
352
 
368
353
  vs = self.mvs.as_view({"get": "list"})
369
354
  response = vs(request, **kwargs)
370
- ep = self._get_endpoint_config(request, kwargs)
371
- if ep.get_endpoint() or ep.get_list_endpoint():
372
- assert response.status_code == status.HTTP_200_OK, (
373
- str(response.status_code) + f" == 200 ({response.data})"
355
+ assert response.status_code == status.HTTP_200_OK, str(response.status_code) + f" == 200 ({response.data})"
356
+ assert response.data, str(response.data) + " should not be empty"
357
+ if not response.data.get("aggregates"):
358
+ print( # noqa: T201
359
+ f"- {self.__class__.__name__}:test_aggregation:" + self.mvs.__name__,
360
+ colored("WARNING - aggregates not found in " + self.mvs.__name__, "yellow"),
374
361
  )
375
- assert response.data, str(response.data) + " should not be empty"
376
- if not response.data.get("aggregates"):
377
- print( # noqa: T201
378
- f"- {self.__class__.__name__}:test_aggregation:" + self.mvs.__name__,
379
- colored("WARNING - aggregates not found in " + self.mvs.__name__, "yellow"),
380
- )
381
- else:
382
- if name_field:
383
- assert response.data.get("aggregates").get(name_field), name_field + "not found in aggregates"
384
362
  else:
385
- assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED, (
386
- str(response.status_code) + f" == 405 ({response.data})"
387
- )
363
+ if name_field:
364
+ assert response.data.get("aggregates").get(name_field), name_field + "not found in aggregates"
388
365
  print(f"- {self.__class__.__name__}:test_aggregation", colored("PASSED", "green")) # noqa: T201
389
366
 
390
367
  # Test viewset "get": "list" with client and endpoint
@@ -398,16 +375,9 @@ class TestViewSet(ParentViewset):
398
375
  if ep.get_endpoint():
399
376
  response = client.get(ep.get_endpoint())
400
377
  else:
401
- response = client.get(ep.get_list_endpoint())
402
- if ep.get_endpoint() or ep.get_list_endpoint():
403
- assert response.status_code == status.HTTP_200_OK, (
404
- str(response.status_code) + f" == 200 ({response.data})"
405
- )
406
- assert response.data, str(response.data) + " should not be empty"
407
- else:
408
- assert response.status_code == status.HTTP_404_NOT_FOUND, (
409
- str(response.status_code) + f" == 404 ({response.data})"
410
- )
378
+ response = client.get(ep._get_list_endpoint())
379
+ assert response.status_code == status.HTTP_200_OK, str(response.status_code) + f" == 200 ({response.data})"
380
+ assert response.data, str(response.data) + " should not be empty"
411
381
  print(f"- {self.__class__.__name__}:test_get_endpoint", colored("PASSED", "green")) # noqa: T201
412
382
 
413
383
  # Test viewset "post": "create"
@@ -513,17 +483,12 @@ class TestViewSet(ParentViewset):
513
483
  obj, request, kwargs, _ = self._get_mixins_data("GET")
514
484
  vs = self.mvs.as_view({"get": "retrieve"})
515
485
  ep = self._get_endpoint_config(request, kwargs, obj)
516
- ep_list = ep.get_list_endpoint()
517
486
  response = vs(request, **kwargs)
518
- if ep_list:
487
+ if ep._get_instance_endpoint():
519
488
  assert response.status_code == status.HTTP_200_OK, (
520
489
  str(response.status_code) + f" == 200 ({response.data})"
521
490
  )
522
491
  assert response.data.get("instance"), str(response.data.get("instance")) + " should not be empty"
523
- else:
524
- assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED, (
525
- str(response.status_code) + f" == 405 ({response.data})"
526
- )
527
492
  print(f"- {self.__class__.__name__}:test_retrieve_request", colored("PASSED", "green")) # noqa: T201
528
493
 
529
494
  # Test "delete": "destroy"
@@ -649,16 +614,8 @@ class TestPandasView(TestViewSet):
649
614
  _, request, kwargs, _ = self._get_mixins_data("GET")
650
615
  vs = self.mvs.as_view({"get": "list"})
651
616
  response = vs(request, **kwargs)
652
- ep = self._get_endpoint_config(request, kwargs)
653
- if ep.get_endpoint() or ep.get_list_endpoint():
654
- assert response.status_code == status.HTTP_200_OK, (
655
- str(response.status_code) + f" == 200 ({response.data})"
656
- )
657
- assert response.data, str(response.data) + " should not be empty"
658
- else:
659
- assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED, (
660
- str(response.status_code) + f" == 405 ({response.data})"
661
- )
617
+ assert response.status_code == status.HTTP_200_OK, str(response.status_code) + f" == 200 ({response.data})"
618
+ assert response.data, str(response.data) + " should not be empty"
662
619
  print(f"- {self.__class__.__name__}:test_get_request", colored("PASSED", "green")) # noqa: T201
663
620
 
664
621
  def execute_test(self):
@@ -681,27 +638,20 @@ class TestChartViewSet(TestViewSet):
681
638
 
682
639
  vs = self.mvs.as_view({"options": "options"})
683
640
  response = vs(request, **kwargs)
684
- if ep.get_endpoint() or ep.get_list_endpoint():
685
- assert response.status_code == status.HTTP_200_OK, (
686
- str(response.status_code) + f" == 200 ({response.data})"
687
- )
688
- assert response.data, str(response.data) + " should not be empty"
689
- if "buttons" in response.data.keys():
690
- if "custom_instance" in response.data.get("buttons").keys():
691
- assert (
692
- list(response.data["buttons"]["custom_instance"])
693
- or len(list(response.data["buttons"]["custom_instance"])) == 0
694
- )
695
- assert response.data.get("identifier"), str(response.data.get("identifier")) + " should not be None"
696
- assert response.data.get("type") == "chart", "type of view should be chart"
697
- assert response.data.get("buttons"), str(response.data.get("buttons")) + " should not be None"
698
- assert response.data.get("display"), str(response.data.get("display")) + " should not be None"
699
- assert response.data.get("titles"), str(response.data.get("titles")) + " should not be None"
700
- assert response.data.get("endpoints"), str(response.data.get("endpoints")) + " should not be None"
701
- else:
702
- assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED, (
703
- str(response.status_code) + f" == 405 ({response.data})"
704
- )
641
+ assert response.status_code == status.HTTP_200_OK, str(response.status_code) + f" == 200 ({response.data})"
642
+ assert response.data, str(response.data) + " should not be empty"
643
+ if "buttons" in response.data.keys():
644
+ if "custom_instance" in response.data.get("buttons").keys():
645
+ assert (
646
+ list(response.data["buttons"]["custom_instance"])
647
+ or len(list(response.data["buttons"]["custom_instance"])) == 0
648
+ )
649
+ assert response.data.get("identifier"), str(response.data.get("identifier")) + " should not be None"
650
+ assert response.data.get("type") == "chart", "type of view should be chart"
651
+ assert response.data.get("buttons"), str(response.data.get("buttons")) + " should not be None"
652
+ assert response.data.get("display"), str(response.data.get("display")) + " should not be None"
653
+ assert response.data.get("titles"), str(response.data.get("titles")) + " should not be None"
654
+ assert response.data.get("endpoints"), str(response.data.get("endpoints")) + " should not be None"
705
655
  print(f"- {self.__class__.__name__}:test_option_request", colored("PASSED", "green")) # noqa: T201
706
656
 
707
657
  def execute_test(self):