wbcore 1.54.10__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 (153) hide show
  1. wbcore/cache/decorators.py +3 -3
  2. wbcore/cache/registry.py +3 -2
  3. wbcore/configs/decorators.py +1 -1
  4. wbcore/configurations/configurations/apps.py +2 -2
  5. wbcore/configurations/configurations/authentication.py +1 -1
  6. wbcore/configurations/configurations/base.py +1 -1
  7. wbcore/configurations/configurations/cache.py +1 -1
  8. wbcore/configurations/configurations/maintenance.py +1 -1
  9. wbcore/configurations/configurations/media.py +1 -1
  10. wbcore/configurations/configurations/middleware.py +1 -1
  11. wbcore/configurations/configurations/rest_framework.py +1 -1
  12. wbcore/configurations/configurations/static.py +1 -1
  13. wbcore/configurations/configurations/wbcore.py +1 -1
  14. wbcore/content_type/serializers.py +1 -1
  15. wbcore/content_type/utils.py +3 -3
  16. wbcore/contrib/agenda/viewsets/calendar_items.py +7 -7
  17. wbcore/contrib/ai/llm/config.py +1 -1
  18. wbcore/contrib/authentication/admin.py +2 -2
  19. wbcore/contrib/authentication/filters.py +0 -1
  20. wbcore/contrib/authentication/models/users.py +3 -3
  21. wbcore/contrib/authentication/models/users_activities.py +1 -1
  22. wbcore/contrib/authentication/serializers/users.py +2 -2
  23. wbcore/contrib/authentication/tests/test_tokens.py +3 -3
  24. wbcore/contrib/authentication/tests/test_users.py +0 -1
  25. wbcore/contrib/authentication/viewsets/user_activities.py +2 -1
  26. wbcore/contrib/authentication/viewsets/users.py +6 -4
  27. wbcore/contrib/color/models.py +2 -1
  28. wbcore/contrib/currency/factories.py +1 -1
  29. wbcore/contrib/currency/import_export/backends/fixerio/currency_fx_rates.py +3 -1
  30. wbcore/contrib/currency/models.py +28 -8
  31. wbcore/contrib/currency/serializers.py +5 -1
  32. wbcore/contrib/currency/tests/test_serializers.py +7 -3
  33. wbcore/contrib/currency/tests/test_viewsets.py +1 -1
  34. wbcore/contrib/currency/viewsets/currency.py +2 -2
  35. wbcore/contrib/dataloader/utils.py +2 -2
  36. wbcore/contrib/directory/factories/__init__.py +1 -1
  37. wbcore/contrib/directory/factories/entries.py +1 -1
  38. wbcore/contrib/directory/models/contacts.py +2 -2
  39. wbcore/contrib/directory/models/entries.py +18 -4
  40. wbcore/contrib/directory/models/relationships.py +25 -30
  41. wbcore/contrib/directory/permissions.py +6 -0
  42. wbcore/contrib/directory/serializers/companies.py +15 -8
  43. wbcore/contrib/directory/serializers/contacts.py +8 -8
  44. wbcore/contrib/directory/serializers/entries.py +24 -15
  45. wbcore/contrib/directory/serializers/entry_representations.py +4 -2
  46. wbcore/contrib/directory/serializers/persons.py +8 -9
  47. wbcore/contrib/directory/serializers/relationships.py +2 -2
  48. wbcore/contrib/directory/tests/conftest.py +2 -0
  49. wbcore/contrib/directory/tests/disable_signals.py +11 -1
  50. wbcore/contrib/directory/tests/signals.py +2 -2
  51. wbcore/contrib/directory/tests/test_models.py +88 -66
  52. wbcore/contrib/directory/tests/test_serializers.py +1 -1
  53. wbcore/contrib/directory/tests/test_viewsets.py +8 -8
  54. wbcore/contrib/directory/viewsets/buttons/__init__.py +1 -1
  55. wbcore/contrib/directory/viewsets/buttons/relationships.py +32 -0
  56. wbcore/contrib/directory/viewsets/contacts.py +6 -6
  57. wbcore/contrib/directory/viewsets/display/__init__.py +1 -1
  58. wbcore/contrib/directory/viewsets/display/entries.py +51 -36
  59. wbcore/contrib/directory/viewsets/display/relationships.py +22 -22
  60. wbcore/contrib/directory/viewsets/entries.py +4 -5
  61. wbcore/contrib/directory/viewsets/previews/entries.py +3 -3
  62. wbcore/contrib/directory/viewsets/relationships.py +16 -2
  63. wbcore/contrib/directory/viewsets/titles/relationships.py +2 -3
  64. wbcore/contrib/documents/filters.py +0 -2
  65. wbcore/contrib/example_app/models.py +4 -4
  66. wbcore/contrib/example_app/serializers/person_team.py +4 -4
  67. wbcore/contrib/example_app/tests/e2e/test_teams.py +1 -1
  68. wbcore/contrib/geography/tests/test_viewsets.py +1 -1
  69. wbcore/contrib/guardian/tests/test_model_mixins.py +3 -3
  70. wbcore/contrib/guardian/tests/test_tasks.py +9 -9
  71. wbcore/contrib/guardian/tests/test_viewsets.py +2 -2
  72. wbcore/contrib/icons/backends/default.py +1 -0
  73. wbcore/contrib/icons/backends/material.py +1 -0
  74. wbcore/contrib/icons/icons.py +5 -8
  75. wbcore/contrib/io/exceptions.py +8 -0
  76. wbcore/contrib/io/import_export/backends/stream.py +2 -2
  77. wbcore/contrib/io/imports.py +10 -5
  78. wbcore/contrib/io/models.py +17 -14
  79. wbcore/contrib/io/serializers.py +2 -2
  80. wbcore/contrib/io/tests/test_backends.py +1 -1
  81. wbcore/contrib/io/tests/test_imports.py +1 -1
  82. wbcore/contrib/io/viewset_mixins.py +4 -4
  83. wbcore/contrib/notifications/dispatch.py +18 -7
  84. wbcore/contrib/pandas/filterset.py +8 -7
  85. wbcore/contrib/pandas/views.py +7 -5
  86. wbcore/contrib/tags/models/tags.py +4 -1
  87. wbcore/contrib/workflow/factories/display.py +2 -2
  88. wbcore/contrib/workflow/models/data.py +7 -4
  89. wbcore/contrib/workflow/models/process.py +2 -2
  90. wbcore/contrib/workflow/serializers/data.py +8 -8
  91. wbcore/contrib/workflow/tests/test_models/test_condition.py +1 -1
  92. wbcore/contrib/workflow/workflows/assignees.py +4 -4
  93. wbcore/dynamic_preferences_registry.py +23 -9
  94. wbcore/enums.py +2 -1
  95. wbcore/filters/fields/content_type.py +5 -4
  96. wbcore/filters/fields/datetime.py +34 -9
  97. wbcore/filters/fields/models.py +2 -2
  98. wbcore/filters/filterset.py +22 -6
  99. wbcore/filters/mixins.py +6 -2
  100. wbcore/forms.py +6 -6
  101. wbcore/fsm/markdown_extensions.py +1 -1
  102. wbcore/fsm/mixins.py +7 -4
  103. wbcore/markdown/models.py +8 -5
  104. wbcore/metadata/configs/buttons/bases.py +6 -6
  105. wbcore/metadata/configs/buttons/buttons.py +2 -1
  106. wbcore/metadata/configs/buttons/view_config.py +5 -3
  107. wbcore/metadata/configs/display/display.py +2 -2
  108. wbcore/metadata/configs/display/formatting.py +6 -7
  109. wbcore/metadata/configs/display/list_display.py +6 -7
  110. wbcore/metadata/configs/display/models.py +6 -0
  111. wbcore/metadata/configs/fields.py +6 -1
  112. wbcore/metadata/configs/filter_fields.py +12 -11
  113. wbcore/models/fields.py +2 -2
  114. wbcore/permissions/permissions.py +2 -2
  115. wbcore/permissions/utils.py +2 -2
  116. wbcore/reversion/viewsets/titles.py +4 -3
  117. wbcore/serializers/__init__.py +1 -0
  118. wbcore/serializers/fields/__init__.py +1 -0
  119. wbcore/serializers/fields/datetime.py +35 -6
  120. wbcore/serializers/fields/fields.py +1 -1
  121. wbcore/serializers/fields/fsm.py +1 -1
  122. wbcore/serializers/fields/list.py +1 -1
  123. wbcore/serializers/fields/mixins.py +13 -5
  124. wbcore/serializers/fields/related.py +4 -6
  125. wbcore/serializers/fields/text.py +1 -1
  126. wbcore/serializers/fields/types.py +1 -0
  127. wbcore/serializers/serializers.py +6 -2
  128. wbcore/tasks.py +2 -2
  129. wbcore/templates/wbcore/email_base_template.html +3 -3
  130. wbcore/test/e2e_helpers_methods/e2e_checks.py +10 -4
  131. wbcore/test/e2e_helpers_methods/e2e_helper_methods.py +4 -2
  132. wbcore/test/mixins.py +1 -1
  133. wbcore/test/tests.py +6 -9
  134. wbcore/test/utils.py +3 -4
  135. wbcore/tests/e2e/test_e2e.py +2 -2
  136. wbcore/tests/test_cache/test_decorators.py +3 -3
  137. wbcore/tests/test_configs.py +1 -1
  138. wbcore/tests/test_fields/test_number_fields.py +1 -1
  139. wbcore/tests/test_filters/test_mixins.py +3 -3
  140. wbcore/tests/test_models/test_mixins.py +1 -1
  141. wbcore/tests/test_utils/test_date.py +1 -1
  142. wbcore/tests/test_utils/test_date_builder.py +25 -1
  143. wbcore/utils/date.py +18 -2
  144. wbcore/utils/figures.py +2 -2
  145. wbcore/utils/models.py +3 -2
  146. wbcore/utils/reportlab.py +7 -0
  147. wbcore/utils/rrules.py +1 -1
  148. wbcore/utils/string_loader.py +1 -1
  149. wbcore/utils/strings.py +2 -2
  150. wbcore/viewsets/mixins.py +6 -4
  151. {wbcore-1.54.10.dist-info → wbcore-1.58.2.dist-info}/METADATA +2 -1
  152. {wbcore-1.54.10.dist-info → wbcore-1.58.2.dist-info}/RECORD +153 -151
  153. {wbcore-1.54.10.dist-info → wbcore-1.58.2.dist-info}/WHEEL +0 -0
@@ -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
wbcore/test/tests.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import pytest
2
2
  from django.conf import settings
3
3
  from django.db import models
4
- from django.urls import get_resolver
5
4
 
6
5
  from wbcore import serializers, viewsets
7
6
  from wbcore.pandas.views import PandasAPIViewSet
@@ -24,8 +23,6 @@ This is useful mainly when running tests, or running locally without Celery work
24
23
  """
25
24
  settings.CELERY_TASK_ALWAYS_EAGER = True
26
25
 
27
- get_resolver().url_patterns
28
-
29
26
 
30
27
  def modules_condition(module):
31
28
  return not module.startswith(("wbcore", "django", "rest_framework", "dynamic_preferences", "eventtools")) or (
@@ -122,11 +119,11 @@ class GenerateTest:
122
119
  def _test_chartviewsets(_self, cvs):
123
120
  self.test_chartviewsets(cvs)
124
121
 
125
- setattr(test_class, "test_models", _test_models)
126
- setattr(test_class, "test_serializers", _test_serializers)
127
- setattr(test_class, "test_representationviewsets", _test_representationviewsets)
128
- setattr(test_class, "test_modelviewsets", _test_modelviewsets)
129
- setattr(test_class, "test_pandasviews", _test_pandasviews)
130
- setattr(test_class, "test_chartviewsets", _test_chartviewsets)
122
+ test_class.test_models = _test_models
123
+ test_class.test_serializers = _test_serializers
124
+ test_class.test_representationviewsets = _test_representationviewsets
125
+ test_class.test_modelviewsets = _test_modelviewsets
126
+ test_class.test_pandasviews = _test_pandasviews
127
+ test_class.test_chartviewsets = _test_chartviewsets
131
128
 
132
129
  return test_class
wbcore/test/utils.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import datetime
2
2
  import json
3
+ from contextlib import suppress
3
4
  from functools import partial
4
5
  from typing import Any, Dict
5
6
 
@@ -44,7 +45,7 @@ def get_model_factory(model):
44
45
  return mfs[0]
45
46
 
46
47
 
47
- def get_data_from_factory(instance, viewset, delete=False, update=False, superuser=None, factory=None):
48
+ def get_data_from_factory(instance, viewset, delete=False, update=False, superuser=None, factory=None): # noqa: C901
48
49
  """
49
50
  Our goal here is to get the serializer dynamically based on the viewset, use this serializer to generate data for the post and update test.
50
51
  """
@@ -113,14 +114,12 @@ def get_data_from_factory(instance, viewset, delete=False, update=False, superus
113
114
  and _field.many_to_one
114
115
  and (_related_fields := _field.related_fields)
115
116
  ):
116
- try:
117
+ with suppress(Exception):
117
118
  lh_field, rh_field = _related_fields[0]
118
119
  if isinstance(lh_field, models.fields.AutoField) or isinstance(
119
120
  rh_field, models.fields.AutoField
120
121
  ):
121
122
  data[key] = get_model_factory(dict_fields_models[key].related_model).id
122
- except Exception:
123
- pass
124
123
  if update or delete:
125
124
  _kwargs = {"user": superuser, "obj_factory": obj_factory}
126
125
  if delete:
@@ -11,14 +11,14 @@ USER_PASSWORD = "User_Password"
11
11
  @pytest.mark.django_db
12
12
  class TestE2ELogin:
13
13
  def test_login_success(self, live_server, selenium):
14
- user: User = SuperUserFactory(plaintext_password=USER_PASSWORD)
14
+ user: User = SuperUserFactory(plaintext_password=USER_PASSWORD) # noqa
15
15
  selenium.get(live_server.url)
16
16
  assert not find_element(selenium, "//div[contains(@class, 'sidebar')]")
17
17
  login(selenium, user.email, USER_PASSWORD)
18
18
  assert find_element(selenium, "//div[contains(@class, 'sidebar')]")
19
19
 
20
20
  def test_login_failure(self, live_server, selenium):
21
- user: User = SuperUserFactory(plaintext_password=USER_PASSWORD)
21
+ user: User = SuperUserFactory(plaintext_password=USER_PASSWORD) # noqa
22
22
  selenium.get(live_server.url)
23
23
  assert not find_element(selenium, "//p[@type='error']")
24
24
  login(selenium, user.email + "Wrong Name", USER_PASSWORD)
@@ -19,9 +19,9 @@ def test_cache_table(timeout, key_prefix, periodic_caching_view_kwargs, periodic
19
19
  class CacheTable:
20
20
  pass
21
21
 
22
- assert getattr(CacheTable, "CACHE_ENABLED") is True
23
- assert getattr(CacheTable, "CACHE_TIMEOUT") == timeout
24
- assert getattr(CacheTable, "CACHE_KEY_PREFIX") == key_prefix
22
+ assert CacheTable.CACHE_ENABLED is True
23
+ assert CacheTable.CACHE_TIMEOUT == timeout
24
+ assert CacheTable.CACHE_KEY_PREFIX == key_prefix
25
25
  cache_entry = periodic_cache_registry.classes[0]
26
26
  assert cache_entry.view_class == CacheTable
27
27
  assert cache_entry.view_kwargs == periodic_caching_view_kwargs
@@ -52,7 +52,7 @@ def test_config_registry_decorator():
52
52
 
53
53
  assert not hasattr(some_callable, "_is_config")
54
54
  some_callable = register_config(some_callable)
55
- assert getattr(some_callable, "_is_config")
55
+ assert some_callable._is_config
56
56
 
57
57
 
58
58
  def test_config_view(rf):
@@ -163,4 +163,4 @@ class TestYearField:
163
163
  field = YearField(label=label, precision=2)
164
164
  representation = field.get_representation(None, key)[1]
165
165
  assert representation["precision"] == 0
166
- assert representation["disable_formatting"] == True
166
+ assert representation["disable_formatting"]
@@ -97,13 +97,13 @@ class TestWBCoreFilterMixin:
97
97
  class Field:
98
98
  help_text = text
99
99
 
100
- class dummy_parent:
101
- class _meta:
100
+ class DummyParent:
101
+ class _meta: # noqa
102
102
  model = "tmp"
103
103
 
104
104
  mock_fct.return_value = Field
105
105
 
106
106
  filter_field = CharFilter(field_name=name, initial=initial, help_text=None)
107
- filter_field.parent = dummy_parent()
107
+ filter_field.parent = DummyParent()
108
108
  request = rf.get("")
109
109
  assert filter_field.get_representation(request, name, view)[0]["help_text"] == text
@@ -3,7 +3,7 @@ from django.db import models
3
3
  from wbcore.models.orderable import OrderableModel
4
4
 
5
5
 
6
- class ForeignKeyClass(models.Model):
6
+ class ForeignKeyClass(models.Model): # noqa
7
7
  @classmethod
8
8
  def get_endpoint_basename(cls) -> str:
9
9
  return "wbcore:foreignkeyclass"
@@ -4,7 +4,7 @@ import pytest
4
4
  from rest_framework.test import APIRequestFactory
5
5
 
6
6
  from wbcore.utils.date import get_date_interval_from_request, shortcut
7
- from wbcore.utils.date_builder import *
7
+ from wbcore.utils.date_builder import Day, Now, WeekStart
8
8
 
9
9
 
10
10
  class TestGetDateInterval:
@@ -3,7 +3,31 @@ from operator import add, sub
3
3
 
4
4
  import pytest
5
5
 
6
- from wbcore.utils.date_builder import *
6
+ from wbcore.utils.date_builder import (
7
+ BusinessDay,
8
+ Day,
9
+ Hour,
10
+ HourEnd,
11
+ HourStart,
12
+ Minute,
13
+ MinuteEnd,
14
+ MinuteStart,
15
+ Month,
16
+ MonthEnd,
17
+ MonthStart,
18
+ Quarter,
19
+ QuarterEnd,
20
+ QuarterStart,
21
+ Second,
22
+ SecondEnd,
23
+ SecondStart,
24
+ Week,
25
+ WeekEnd,
26
+ WeekStart,
27
+ Year,
28
+ YearEnd,
29
+ YearStart,
30
+ )
7
31
 
8
32
 
9
33
  class TestDateBuilder:
wbcore/utils/date.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from collections import defaultdict
2
- from datetime import date, datetime, time, timedelta
2
+ from datetime import date, datetime, time, timedelta, timezone
3
+ from zoneinfo import ZoneInfo, available_timezones
3
4
 
4
5
  from dateutil import rrule
5
6
  from django.utils.dateparse import parse_date
@@ -153,8 +154,11 @@ def get_date_interval_from_request(
153
154
 
154
155
 
155
156
  def get_number_of_hours_between_dates(
156
- d1, d2, skip_weekends=True, list_public_holidays=False, hours_range=range(0, 23), granularity=12
157
+ d1, d2, skip_weekends=True, list_public_holidays=False, hours_range=None, granularity=12
157
158
  ):
159
+ if hours_range is None:
160
+ hours_range = range(0, 23)
161
+
158
162
  def convert_days_from_hours(hours, granularity, hours_per_day):
159
163
  return int(hours / granularity) * granularity / hours_per_day
160
164
 
@@ -219,3 +223,15 @@ def get_next_day_timedelta(now: datetime | None = None) -> int:
219
223
  if not now:
220
224
  now = datetime.now()
221
225
  return (datetime.combine(now.date() + timedelta(days=1), time(0, 0, 0)) - now).seconds
226
+
227
+
228
+ def get_timezone_choices() -> list[tuple[str, str]]:
229
+ now_utc = datetime.now(timezone.utc)
230
+ tz_tuples = [] # a list of (timezone_name, timezone_name (UTC offset))
231
+ for tz_name in sorted(available_timezones()):
232
+ tz = ZoneInfo(tz_name)
233
+ now_in_tz = now_utc.astimezone(tz)
234
+ offset_str = now_in_tz.strftime("UTC%z") # gives UTC+HHMM
235
+ offset_str = offset_str[:-2] + ":" + offset_str[-2:]
236
+ tz_tuples.append((tz_name, f"{tz_name} ({offset_str})"))
237
+ return tz_tuples
wbcore/utils/figures.py CHANGED
@@ -183,7 +183,7 @@ def get_horizontal_barplot(
183
183
  df,
184
184
  x_label="weighting",
185
185
  y_label="aggregated_title",
186
- colors=["#B4DAFF"],
186
+ colors: tuple[str, ...] = ("#B4DAFF",),
187
187
  colors_label=None,
188
188
  drop_null_x_value: bool = True,
189
189
  ):
@@ -204,7 +204,7 @@ def get_horizontal_barplot(
204
204
  if colors_label:
205
205
  colors = df[colors_label]
206
206
  opacity = 1
207
- for i, label in enumerate(x_label):
207
+ for label in x_label:
208
208
  data.append(
209
209
  go.Bar(
210
210
  name=label,
wbcore/utils/models.py CHANGED
@@ -107,7 +107,7 @@ class Status(ChoiceEnum):
107
107
  @classmethod
108
108
  def get_color_map(cls):
109
109
  colors = [WBColor.YELLOW_LIGHT.value, WBColor.RED_LIGHT.value, WBColor.GREEN_LIGHT.value]
110
- return [choice for choice in zip(cls, colors)]
110
+ return [choice for choice in zip(cls, colors, strict=False)]
111
111
 
112
112
 
113
113
  class PrimaryMixin(models.Model):
@@ -273,7 +273,8 @@ class ResolvableModelMixin(models.Model):
273
273
  resolved = models.BooleanField(default=False, verbose_name="Resolved")
274
274
 
275
275
  def delete(self, *args, **kwargs):
276
- assert not self.resolved, "Resolved Entities cannot be deleted."
276
+ if self.resolved:
277
+ raise ValueError("Resolved Entities cannot be deleted.")
277
278
  super().delete(*args, **kwargs)
278
279
 
279
280
 
@@ -0,0 +1,7 @@
1
+ from reportlab.platypus import Paragraph as BaseParagraph
2
+
3
+
4
+ class FormattedParagraph(BaseParagraph):
5
+ def __init__(self, text, *args, **kwargs):
6
+ text = text.replace("<br>", "<br/>") # convert the HTML line break into a compatible XML line break tag
7
+ super().__init__(text, *args, **kwargs)
wbcore/utils/rrules.py CHANGED
@@ -48,7 +48,7 @@ def humanize_rrule(rrule_str: rrule) -> str:
48
48
  Returns:
49
49
  A humanized version of the rrule
50
50
  """
51
- if not rrule_str._freq:
51
+ if rrule_str._freq is None:
52
52
  raise ValueError("We do no support humanization of rrule without frequency yet")
53
53
  text = "Every "
54
54
  freq = rrule.FREQNAMES[rrule_str._freq]
@@ -21,7 +21,7 @@ class StringSourceLoader(importlib.abc.SourceLoader):
21
21
 
22
22
  def exec_module(self, module):
23
23
  code = self.source_to_code(self.data, self.get_filename(module.__name__))
24
- exec(code, module.__dict__)
24
+ exec(code, module.__dict__) # noqa: S102
25
25
 
26
26
  def get_spec(self):
27
27
  return importlib.machinery.ModuleSpec(self.MOD_NAME, self, origin=self.get_filename(self.MOD_NAME))
wbcore/utils/strings.py CHANGED
@@ -15,7 +15,7 @@ def enumerated_string_join(array):
15
15
  return ""
16
16
 
17
17
 
18
- def format_number(number, decimal: int = 2, **kwargs) -> float | None:
18
+ def format_number(number, decimal: int = 2, **kwargs) -> float | str:
19
19
  """
20
20
  utility function used to serialize an aggregate to a json compatible value
21
21
  Args:
@@ -28,7 +28,7 @@ def format_number(number, decimal: int = 2, **kwargs) -> float | None:
28
28
  try:
29
29
  return float(round(number, decimal))
30
30
  except TypeError:
31
- return None
31
+ return ""
32
32
 
33
33
 
34
34
  class ReferenceIDMixin:
wbcore/viewsets/mixins.py CHANGED
@@ -66,7 +66,9 @@ class WBCoreOrderingFilter(OrderingFilter):
66
66
 
67
67
  return queryset
68
68
 
69
- def get_valid_fields(self, queryset, view, context={}):
69
+ def get_valid_fields(self, queryset, view, context: dict | None = None):
70
+ if context is None:
71
+ context = {}
70
72
  valid_fields = view.get_ordering_fields()
71
73
  if valid_fields is None:
72
74
  # Default to allowing filtering on serializer fields
@@ -90,9 +92,9 @@ class FilterMixin:
90
92
 
91
93
  class DocumentationMixin:
92
94
  def _get_documentation_url(self, detail):
93
- INSTANCE_DOCUMENTATION = getattr(self, "INSTANCE_DOCUMENTATION", None)
94
- LIST_DOCUMENTATION = getattr(self, "LIST_DOCUMENTATION", None)
95
- doc = INSTANCE_DOCUMENTATION if detail else LIST_DOCUMENTATION
95
+ instance_documentation = getattr(self, "INSTANCE_DOCUMENTATION", None)
96
+ list_documentation = getattr(self, "LIST_DOCUMENTATION", None)
97
+ doc = instance_documentation if detail else list_documentation
96
98
 
97
99
  if doc and finders.find(doc):
98
100
  return static(doc)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbcore
3
- Version: 1.54.10
3
+ Version: 1.58.2
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  Requires-Dist: boto3==1.35.*
6
6
  Requires-Dist: celery[redis]==5.*
@@ -35,6 +35,7 @@ Requires-Dist: fabric==3.2.*
35
35
  Requires-Dist: faker==25.*
36
36
  Requires-Dist: firebase-admin==6.*
37
37
  Requires-Dist: graphviz==0.*
38
+ Requires-Dist: humanize==4.*
38
39
  Requires-Dist: ics==0.*
39
40
  Requires-Dist: igraph==0.*
40
41
  Requires-Dist: inscriptis==1.*