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.
- wbcore/cache/decorators.py +3 -3
- wbcore/cache/registry.py +3 -2
- wbcore/configs/decorators.py +1 -1
- wbcore/configurations/configurations/apps.py +2 -2
- wbcore/configurations/configurations/authentication.py +1 -1
- wbcore/configurations/configurations/base.py +1 -1
- wbcore/configurations/configurations/cache.py +1 -1
- wbcore/configurations/configurations/maintenance.py +1 -1
- wbcore/configurations/configurations/media.py +1 -1
- wbcore/configurations/configurations/middleware.py +1 -1
- wbcore/configurations/configurations/rest_framework.py +1 -1
- wbcore/configurations/configurations/static.py +1 -1
- wbcore/configurations/configurations/wbcore.py +1 -1
- wbcore/content_type/serializers.py +1 -1
- wbcore/content_type/utils.py +3 -3
- wbcore/contrib/agenda/viewsets/calendar_items.py +7 -7
- wbcore/contrib/ai/llm/config.py +1 -1
- wbcore/contrib/authentication/admin.py +2 -2
- wbcore/contrib/authentication/filters.py +0 -1
- wbcore/contrib/authentication/models/users.py +3 -3
- wbcore/contrib/authentication/models/users_activities.py +1 -1
- wbcore/contrib/authentication/serializers/users.py +2 -2
- wbcore/contrib/authentication/tests/test_tokens.py +3 -3
- wbcore/contrib/authentication/tests/test_users.py +0 -1
- wbcore/contrib/authentication/viewsets/user_activities.py +2 -1
- wbcore/contrib/authentication/viewsets/users.py +6 -4
- wbcore/contrib/color/models.py +2 -1
- wbcore/contrib/currency/factories.py +1 -1
- wbcore/contrib/currency/import_export/backends/fixerio/currency_fx_rates.py +3 -1
- wbcore/contrib/currency/models.py +28 -8
- wbcore/contrib/currency/serializers.py +5 -1
- wbcore/contrib/currency/tests/test_serializers.py +7 -3
- wbcore/contrib/currency/tests/test_viewsets.py +1 -1
- wbcore/contrib/currency/viewsets/currency.py +2 -2
- wbcore/contrib/dataloader/utils.py +2 -2
- wbcore/contrib/directory/factories/__init__.py +1 -1
- wbcore/contrib/directory/factories/entries.py +1 -1
- wbcore/contrib/directory/models/contacts.py +2 -2
- wbcore/contrib/directory/models/entries.py +18 -4
- wbcore/contrib/directory/models/relationships.py +25 -30
- wbcore/contrib/directory/permissions.py +6 -0
- wbcore/contrib/directory/serializers/companies.py +15 -8
- wbcore/contrib/directory/serializers/contacts.py +8 -8
- wbcore/contrib/directory/serializers/entries.py +24 -15
- wbcore/contrib/directory/serializers/entry_representations.py +4 -2
- wbcore/contrib/directory/serializers/persons.py +8 -9
- wbcore/contrib/directory/serializers/relationships.py +2 -2
- wbcore/contrib/directory/tests/conftest.py +2 -0
- wbcore/contrib/directory/tests/disable_signals.py +11 -1
- wbcore/contrib/directory/tests/signals.py +2 -2
- wbcore/contrib/directory/tests/test_models.py +88 -66
- wbcore/contrib/directory/tests/test_serializers.py +1 -1
- wbcore/contrib/directory/tests/test_viewsets.py +8 -8
- wbcore/contrib/directory/viewsets/buttons/__init__.py +1 -1
- wbcore/contrib/directory/viewsets/buttons/relationships.py +32 -0
- wbcore/contrib/directory/viewsets/contacts.py +6 -6
- wbcore/contrib/directory/viewsets/display/__init__.py +1 -1
- wbcore/contrib/directory/viewsets/display/entries.py +51 -36
- wbcore/contrib/directory/viewsets/display/relationships.py +22 -22
- wbcore/contrib/directory/viewsets/entries.py +4 -5
- wbcore/contrib/directory/viewsets/previews/entries.py +3 -3
- wbcore/contrib/directory/viewsets/relationships.py +16 -2
- wbcore/contrib/directory/viewsets/titles/relationships.py +2 -3
- wbcore/contrib/documents/filters.py +0 -2
- wbcore/contrib/example_app/models.py +4 -4
- wbcore/contrib/example_app/serializers/person_team.py +4 -4
- wbcore/contrib/example_app/tests/e2e/test_teams.py +1 -1
- wbcore/contrib/geography/tests/test_viewsets.py +1 -1
- wbcore/contrib/guardian/tests/test_model_mixins.py +3 -3
- wbcore/contrib/guardian/tests/test_tasks.py +9 -9
- wbcore/contrib/guardian/tests/test_viewsets.py +2 -2
- wbcore/contrib/icons/backends/default.py +1 -0
- wbcore/contrib/icons/backends/material.py +1 -0
- wbcore/contrib/icons/icons.py +5 -8
- wbcore/contrib/io/exceptions.py +8 -0
- wbcore/contrib/io/import_export/backends/stream.py +2 -2
- wbcore/contrib/io/imports.py +10 -5
- wbcore/contrib/io/models.py +17 -14
- wbcore/contrib/io/serializers.py +2 -2
- wbcore/contrib/io/tests/test_backends.py +1 -1
- wbcore/contrib/io/tests/test_imports.py +1 -1
- wbcore/contrib/io/viewset_mixins.py +4 -4
- wbcore/contrib/notifications/dispatch.py +18 -7
- wbcore/contrib/pandas/filterset.py +8 -7
- wbcore/contrib/pandas/views.py +7 -5
- wbcore/contrib/tags/models/tags.py +4 -1
- wbcore/contrib/workflow/factories/display.py +2 -2
- wbcore/contrib/workflow/models/data.py +7 -4
- wbcore/contrib/workflow/models/process.py +2 -2
- wbcore/contrib/workflow/serializers/data.py +8 -8
- wbcore/contrib/workflow/tests/test_models/test_condition.py +1 -1
- wbcore/contrib/workflow/workflows/assignees.py +4 -4
- wbcore/dynamic_preferences_registry.py +23 -9
- wbcore/enums.py +2 -1
- wbcore/filters/fields/content_type.py +5 -4
- wbcore/filters/fields/datetime.py +34 -9
- wbcore/filters/fields/models.py +2 -2
- wbcore/filters/filterset.py +22 -6
- wbcore/filters/mixins.py +6 -2
- wbcore/forms.py +6 -6
- wbcore/fsm/markdown_extensions.py +1 -1
- wbcore/fsm/mixins.py +7 -4
- wbcore/markdown/models.py +8 -5
- wbcore/metadata/configs/buttons/bases.py +6 -6
- wbcore/metadata/configs/buttons/buttons.py +2 -1
- wbcore/metadata/configs/buttons/view_config.py +5 -3
- wbcore/metadata/configs/display/display.py +2 -2
- wbcore/metadata/configs/display/formatting.py +6 -7
- wbcore/metadata/configs/display/list_display.py +6 -7
- wbcore/metadata/configs/display/models.py +6 -0
- wbcore/metadata/configs/fields.py +6 -1
- wbcore/metadata/configs/filter_fields.py +12 -11
- wbcore/models/fields.py +2 -2
- wbcore/permissions/permissions.py +2 -2
- wbcore/permissions/utils.py +2 -2
- wbcore/reversion/viewsets/titles.py +4 -3
- wbcore/serializers/__init__.py +1 -0
- wbcore/serializers/fields/__init__.py +1 -0
- wbcore/serializers/fields/datetime.py +35 -6
- wbcore/serializers/fields/fields.py +1 -1
- wbcore/serializers/fields/fsm.py +1 -1
- wbcore/serializers/fields/list.py +1 -1
- wbcore/serializers/fields/mixins.py +13 -5
- wbcore/serializers/fields/related.py +4 -6
- wbcore/serializers/fields/text.py +1 -1
- wbcore/serializers/fields/types.py +1 -0
- wbcore/serializers/serializers.py +6 -2
- wbcore/tasks.py +2 -2
- wbcore/templates/wbcore/email_base_template.html +3 -3
- wbcore/test/e2e_helpers_methods/e2e_checks.py +10 -4
- wbcore/test/e2e_helpers_methods/e2e_helper_methods.py +4 -2
- wbcore/test/mixins.py +1 -1
- wbcore/test/tests.py +6 -9
- wbcore/test/utils.py +3 -4
- wbcore/tests/e2e/test_e2e.py +2 -2
- wbcore/tests/test_cache/test_decorators.py +3 -3
- wbcore/tests/test_configs.py +1 -1
- wbcore/tests/test_fields/test_number_fields.py +1 -1
- wbcore/tests/test_filters/test_mixins.py +3 -3
- wbcore/tests/test_models/test_mixins.py +1 -1
- wbcore/tests/test_utils/test_date.py +1 -1
- wbcore/tests/test_utils/test_date_builder.py +25 -1
- wbcore/utils/date.py +18 -2
- wbcore/utils/figures.py +2 -2
- wbcore/utils/models.py +3 -2
- wbcore/utils/reportlab.py +7 -0
- wbcore/utils/rrules.py +1 -1
- wbcore/utils/string_loader.py +1 -1
- wbcore/utils/strings.py +2 -2
- wbcore/viewsets/mixins.py +6 -4
- {wbcore-1.54.10.dist-info → wbcore-1.58.2.dist-info}/METADATA +2 -1
- {wbcore-1.54.10.dist-info → wbcore-1.58.2.dist-info}/RECORD +153 -151
- {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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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:
|
wbcore/tests/e2e/test_e2e.py
CHANGED
|
@@ -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
|
|
23
|
-
assert
|
|
24
|
-
assert
|
|
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
|
wbcore/tests/test_configs.py
CHANGED
|
@@ -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"]
|
|
166
|
+
assert representation["disable_formatting"]
|
|
@@ -97,13 +97,13 @@ class TestWBCoreFilterMixin:
|
|
|
97
97
|
class Field:
|
|
98
98
|
help_text = text
|
|
99
99
|
|
|
100
|
-
class
|
|
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 =
|
|
107
|
+
filter_field.parent = DummyParent()
|
|
108
108
|
request = rf.get("")
|
|
109
109
|
assert filter_field.get_representation(request, name, view)[0]["help_text"] == text
|
|
@@ -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=
|
|
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=
|
|
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
|
|
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
|
-
|
|
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
|
|
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]
|
wbcore/utils/string_loader.py
CHANGED
|
@@ -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 |
|
|
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
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
doc =
|
|
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.
|
|
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.*
|