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.
- wbcore/cache/decorators.py +5 -3
- wbcore/cache/registry.py +14 -7
- wbcore/configs/__init__.py +1 -0
- wbcore/configs/configs.py +5 -0
- wbcore/configs/decorators.py +1 -1
- wbcore/configurations/configurations/apps.py +3 -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/i18nl10n.py +2 -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 +3 -3
- wbcore/configurations/configurations/wbcore.py +1 -1
- wbcore/content_type/serializers.py +13 -5
- wbcore/content_type/utils.py +3 -3
- wbcore/content_type/viewsets.py +2 -2
- wbcore/contrib/agenda/filters/calendar_item.py +5 -4
- wbcore/contrib/agenda/locale/de/LC_MESSAGES/django.po +145 -52
- wbcore/contrib/agenda/locale/de/LC_MESSAGES/django.po.translated +236 -0
- wbcore/contrib/agenda/locale/en/LC_MESSAGES/django.po +200 -0
- wbcore/contrib/agenda/locale/fr/LC_MESSAGES/django.po +201 -0
- wbcore/contrib/agenda/viewsets/calendar_items.py +7 -7
- wbcore/contrib/agenda/viewsets/menu/calendar_items.py +0 -6
- wbcore/contrib/ai/exceptions.py +5 -5
- wbcore/contrib/ai/llm/config.py +76 -27
- wbcore/contrib/ai/llm/mixins.py +5 -8
- wbcore/contrib/ai/llm/utils.py +50 -26
- wbcore/contrib/authentication/admin.py +2 -2
- wbcore/contrib/authentication/factories/__init__.py +8 -1
- wbcore/contrib/authentication/factories/users.py +19 -0
- wbcore/contrib/authentication/filters.py +1 -2
- wbcore/contrib/authentication/locale/de/LC_MESSAGES/django.po +209 -187
- wbcore/contrib/authentication/locale/de/LC_MESSAGES/django.po.translated +634 -0
- wbcore/contrib/authentication/locale/en/LC_MESSAGES/django.po +590 -0
- wbcore/contrib/authentication/locale/fr/LC_MESSAGES/django.po +592 -0
- 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/urls.py +0 -4
- wbcore/contrib/authentication/viewsets/endpoints/user_activities.py +2 -11
- wbcore/contrib/authentication/viewsets/endpoints/users.py +0 -3
- 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 +30 -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/currency/viewsets/endpoints/currency_fx_rates.py +0 -9
- wbcore/contrib/dataloader/tests/test/dataloaders/protocols.py +1 -2
- wbcore/contrib/dataloader/utils.py +2 -2
- wbcore/contrib/directory/factories/__init__.py +1 -1
- wbcore/contrib/directory/factories/entries.py +2 -1
- wbcore/contrib/directory/filters/entries.py +9 -0
- wbcore/contrib/directory/locale/de/LC_MESSAGES/django.po +728 -714
- wbcore/contrib/directory/locale/de/LC_MESSAGES/django.po.translated +1779 -0
- wbcore/contrib/directory/locale/en/LC_MESSAGES/django.po +1652 -0
- wbcore/contrib/directory/locale/fr/LC_MESSAGES/django.po +1654 -0
- wbcore/contrib/directory/migrations/0011_person_description_person_i18n.py +24 -0
- wbcore/contrib/directory/migrations/0012_alter_person_managers.py +20 -0
- wbcore/contrib/directory/migrations/0013_alter_clientmanagerrelationship_options.py +17 -0
- wbcore/contrib/directory/models/contacts.py +2 -2
- wbcore/contrib/directory/models/entries.py +31 -5
- wbcore/contrib/directory/models/relationships.py +31 -35
- wbcore/contrib/directory/permissions.py +6 -0
- wbcore/contrib/directory/serializers/companies.py +16 -8
- wbcore/contrib/directory/serializers/contacts.py +8 -8
- wbcore/contrib/directory/serializers/entries.py +26 -15
- wbcore/contrib/directory/serializers/entry_representations.py +4 -2
- wbcore/contrib/directory/serializers/persons.py +12 -10
- 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/contacts.py +1 -14
- wbcore/contrib/directory/viewsets/display/entries.py +68 -38
- wbcore/contrib/directory/viewsets/display/relationships.py +26 -50
- wbcore/contrib/directory/viewsets/endpoints/relationships.py +1 -26
- wbcore/contrib/directory/viewsets/entries.py +8 -6
- 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/documents/locale/de/LC_MESSAGES/django.po +103 -94
- wbcore/contrib/documents/locale/de/LC_MESSAGES/django.po.translated +285 -0
- wbcore/contrib/documents/locale/en/LC_MESSAGES/django.po +271 -0
- wbcore/contrib/documents/locale/fr/LC_MESSAGES/django.po +270 -0
- wbcore/contrib/documents/tests/test_models.py +32 -28
- wbcore/contrib/documents/viewsets/endpoints/shareable_links.py +2 -21
- wbcore/contrib/dynamic_preferences/types.py +108 -0
- wbcore/contrib/dynamic_preferences/viewsets.py +27 -0
- wbcore/contrib/example_app/filters/event.py +3 -1
- wbcore/contrib/example_app/filters/match.py +1 -1
- wbcore/contrib/example_app/models.py +91 -22
- wbcore/contrib/example_app/serializers/person_team.py +4 -4
- wbcore/contrib/example_app/templates/example_app/embedded_view.html +19 -0
- wbcore/contrib/example_app/tests/e2e/test_teams.py +1 -1
- wbcore/contrib/example_app/tests/test_models/test_match.py +17 -7
- wbcore/contrib/example_app/urls.py +2 -0
- wbcore/contrib/example_app/views.py +7 -0
- wbcore/contrib/example_app/viewsets/displays/team.py +23 -4
- wbcore/contrib/example_app/viewsets/menu/menus.py +1 -1
- wbcore/contrib/example_app/viewsets/menus.py +1 -1
- wbcore/contrib/geography/tests/conftest.py +14 -0
- wbcore/contrib/geography/tests/test_models.py +23 -8
- wbcore/contrib/geography/tests/test_viewsets.py +96 -2
- wbcore/contrib/guardian/tests/test_model_mixins.py +3 -4
- wbcore/contrib/guardian/tests/test_tasks.py +9 -9
- wbcore/contrib/guardian/tests/test_viewsets.py +2 -2
- wbcore/contrib/guardian/viewsets/configs/__init__.py +1 -1
- wbcore/contrib/guardian/viewsets/configs/buttons.py +9 -0
- wbcore/contrib/guardian/viewsets/configs/endpoints.py +7 -0
- wbcore/contrib/guardian/viewsets/viewsets.py +2 -0
- wbcore/contrib/i18n/__init__.py +2 -0
- wbcore/contrib/i18n/buttons.py +33 -0
- wbcore/contrib/i18n/serializers/__init__.py +0 -0
- wbcore/contrib/i18n/serializers/fields.py +20 -0
- wbcore/contrib/i18n/serializers/mixins.py +13 -0
- wbcore/contrib/i18n/tests/conftest.py +11 -0
- wbcore/contrib/i18n/tests/test_viewsets.py +67 -0
- wbcore/contrib/i18n/translation.py +140 -0
- wbcore/contrib/i18n/viewsets.py +36 -0
- 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/admin.py +1 -0
- wbcore/contrib/io/backends/mail.py +3 -2
- wbcore/contrib/io/backends/utils.py +14 -17
- wbcore/contrib/io/exceptions.py +8 -0
- wbcore/contrib/io/factories.py +1 -1
- wbcore/contrib/io/import_export/backends/mail.py +1 -0
- wbcore/contrib/io/import_export/backends/sftp.py +29 -20
- wbcore/contrib/io/import_export/backends/stream.py +2 -2
- wbcore/contrib/io/import_export/parsers/__init__.py +0 -0
- wbcore/contrib/io/import_export/parsers/base_csv.py +36 -0
- wbcore/contrib/io/import_export/parsers/resources.py +50 -0
- wbcore/contrib/io/imports.py +33 -25
- wbcore/contrib/io/locale/de/LC_MESSAGES/django.po +114 -22
- wbcore/contrib/io/locale/de/LC_MESSAGES/django.po.translated +103 -0
- wbcore/contrib/io/locale/en/LC_MESSAGES/django.po +138 -0
- wbcore/contrib/io/locale/fr/LC_MESSAGES/django.po +138 -0
- wbcore/contrib/io/migrations/0008_importsource_resource_kwargs.py +18 -0
- wbcore/contrib/io/models.py +65 -45
- wbcore/contrib/io/resources.py +0 -6
- wbcore/contrib/io/serializers.py +2 -2
- wbcore/contrib/io/signals.py +4 -0
- wbcore/contrib/io/tests/test_backends.py +19 -13
- wbcore/contrib/io/tests/test_exports.py +1 -1
- wbcore/contrib/io/tests/test_imports.py +1 -1
- wbcore/contrib/io/tests/test_models.py +47 -14
- wbcore/contrib/io/tests/test_viewsets.py +271 -0
- wbcore/contrib/io/viewset_mixins.py +41 -54
- wbcore/contrib/notifications/admin.py +1 -0
- wbcore/contrib/notifications/apps.py +2 -1
- wbcore/contrib/notifications/backends/abstract_backend.py +2 -4
- wbcore/contrib/notifications/backends/firebase/backends.py +5 -2
- wbcore/contrib/notifications/dispatch.py +18 -7
- wbcore/contrib/notifications/factories/notification_types.py +1 -0
- wbcore/contrib/notifications/locale/de/LC_MESSAGES/django.po +25 -19
- wbcore/contrib/notifications/locale/de/LC_MESSAGES/django.po.translated +63 -0
- wbcore/contrib/notifications/locale/en/LC_MESSAGES/django.po +61 -0
- wbcore/contrib/notifications/locale/fr/LC_MESSAGES/django.po +62 -0
- wbcore/contrib/notifications/migrations/0008_notificationtype_is_lock.py +18 -0
- wbcore/contrib/notifications/migrations/0009_alter_notificationtypesetting_options_and_more.py +32 -0
- wbcore/contrib/notifications/models/notification_types.py +67 -24
- wbcore/contrib/notifications/serializers/notification_types.py +16 -1
- wbcore/contrib/notifications/tests/test_models/test_tokens.py +8 -0
- wbcore/contrib/notifications/tests/test_serializers/test_notification_types.py +5 -0
- wbcore/contrib/notifications/tests/test_viewsets/test_notification_types.py +3 -5
- wbcore/contrib/notifications/utils.py +3 -2
- wbcore/contrib/notifications/viewsets/configs/notification_types.py +28 -6
- wbcore/contrib/notifications/viewsets/menus.py +1 -1
- wbcore/contrib/notifications/viewsets/notification_types.py +12 -2
- wbcore/contrib/pandas/fields.py +38 -10
- wbcore/contrib/pandas/filters.py +4 -1
- wbcore/contrib/pandas/filterset.py +8 -7
- wbcore/contrib/pandas/tests/test_fields/test_number_fields.py +2 -7
- wbcore/contrib/pandas/utils.py +1 -1
- wbcore/contrib/pandas/views.py +14 -13
- wbcore/contrib/tags/models/tags.py +4 -1
- wbcore/contrib/workflow/factories/display.py +2 -2
- wbcore/contrib/workflow/factories/transition.py +16 -15
- wbcore/contrib/workflow/locale/de/LC_MESSAGES/django.po +457 -566
- wbcore/contrib/workflow/locale/de/LC_MESSAGES/django.po.translated +1326 -0
- wbcore/contrib/workflow/locale/en/LC_MESSAGES/django.po +1102 -0
- wbcore/contrib/workflow/locale/fr/LC_MESSAGES/django.po +1114 -0
- wbcore/contrib/workflow/models/data.py +7 -4
- wbcore/contrib/workflow/models/process.py +2 -2
- wbcore/contrib/workflow/models/step.py +57 -15
- wbcore/contrib/workflow/serializers/data.py +8 -8
- wbcore/contrib/workflow/serializers/process.py +3 -2
- wbcore/contrib/workflow/tests/conftest.py +224 -0
- wbcore/contrib/workflow/tests/test_dispatch.py +82 -77
- wbcore/contrib/workflow/tests/test_displays.py +10 -88
- wbcore/contrib/workflow/tests/test_filters.py +57 -40
- wbcore/contrib/workflow/tests/test_models/step/test_decision_step.py +71 -68
- wbcore/contrib/workflow/tests/test_models/step/test_email_step.py +78 -38
- wbcore/contrib/workflow/tests/test_models/step/test_finish_step.py +152 -90
- wbcore/contrib/workflow/tests/test_models/step/test_join_step.py +100 -110
- wbcore/contrib/workflow/tests/test_models/step/test_step.py +168 -33
- wbcore/contrib/workflow/tests/test_models/test_condition.py +1 -1
- wbcore/contrib/workflow/tests/test_models/test_workflow.py +3 -3
- wbcore/contrib/workflow/tests/test_serializers.py +172 -150
- wbcore/contrib/workflow/tests/test_viewsets.py +264 -323
- wbcore/contrib/workflow/tests/test_workflow_assignees.py +215 -205
- wbcore/contrib/workflow/viewsets/process.py +4 -1
- wbcore/contrib/workflow/workflows/assignees.py +12 -7
- wbcore/dynamic_preferences_registry.py +102 -0
- wbcore/enums.py +2 -51
- wbcore/filters/fields/choices.py +4 -6
- wbcore/filters/fields/content_type.py +15 -4
- wbcore/filters/fields/datetime.py +50 -25
- wbcore/filters/fields/models.py +18 -9
- wbcore/filters/fields/numbers.py +9 -8
- wbcore/filters/filterset.py +27 -6
- wbcore/filters/mixins.py +41 -42
- wbcore/forms.py +6 -6
- wbcore/fsm/markdown_extensions.py +1 -1
- wbcore/fsm/mixins.py +20 -6
- wbcore/locale/de/LC_MESSAGES/django.po +982 -397
- wbcore/locale/de/LC_MESSAGES/django.po.translated +1580 -0
- wbcore/locale/en/LC_MESSAGES/django.po +1234 -0
- wbcore/locale/fr/LC_MESSAGES/django.po +1235 -0
- wbcore/markdown/models.py +8 -5
- wbcore/markdown/views.py +1 -1
- wbcore/menus/menus.py +2 -2
- wbcore/metadata/configs/buttons/bases.py +10 -7
- wbcore/metadata/configs/buttons/buttons.py +2 -1
- wbcore/metadata/configs/buttons/enums.py +50 -0
- wbcore/metadata/configs/buttons/view_config.py +13 -46
- wbcore/metadata/configs/display/display.py +2 -2
- wbcore/metadata/configs/display/formatting.py +6 -9
- wbcore/metadata/configs/display/instance_display/display.py +5 -2
- wbcore/metadata/configs/display/instance_display/pages.py +1 -1
- wbcore/metadata/configs/display/instance_display/shortcuts.py +1 -1
- wbcore/metadata/configs/display/list_display.py +54 -40
- wbcore/metadata/configs/display/models.py +6 -0
- wbcore/metadata/configs/display/view_config.py +11 -9
- wbcore/metadata/configs/endpoints.py +11 -4
- wbcore/metadata/configs/fields.py +6 -1
- wbcore/metadata/configs/filter_fields.py +12 -13
- wbcore/metadata/configs/identifiers.py +3 -1
- wbcore/metadata/tests/test_buttons.py +13 -16
- wbcore/models/fields.py +2 -2
- wbcore/pagination.py +1 -2
- wbcore/permissions/permissions.py +2 -2
- wbcore/permissions/utils.py +2 -2
- wbcore/release_notes/display.py +2 -8
- wbcore/release_notes/serializers.py +2 -9
- wbcore/release_notes/viewsets.py +8 -2
- wbcore/reversion/viewsets/titles.py +4 -3
- wbcore/serializers/__init__.py +2 -0
- wbcore/serializers/fields/__init__.py +2 -1
- wbcore/serializers/fields/boolean.py +1 -1
- wbcore/serializers/fields/choice.py +28 -4
- wbcore/serializers/fields/datetime.py +45 -36
- wbcore/serializers/fields/fields.py +1 -1
- wbcore/serializers/fields/fsm.py +1 -1
- wbcore/serializers/fields/list.py +2 -5
- wbcore/serializers/fields/mixins.py +24 -11
- wbcore/serializers/fields/number.py +6 -23
- wbcore/serializers/fields/other.py +2 -10
- wbcore/serializers/fields/related.py +4 -6
- wbcore/serializers/fields/text.py +1 -1
- wbcore/serializers/fields/types.py +2 -0
- wbcore/serializers/serializers.py +12 -3
- wbcore/signals/__init__.py +1 -0
- wbcore/signals/clone.py +4 -0
- wbcore/signals/models.py +2 -6
- 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 +52 -102
- 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 +4 -7
- wbcore/tests/test_configs.py +2 -5
- wbcore/tests/test_enums.py +2 -1
- wbcore/tests/test_fields/test_choice_fields.py +9 -1
- wbcore/tests/test_fields/test_number_fields.py +7 -15
- wbcore/tests/test_fields/test_other_fields.py +1 -2
- wbcore/tests/test_filters/test_mixins.py +35 -35
- wbcore/tests/test_list_display.py +0 -2
- 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/tests/test_utils/test_primary.py +1 -1
- wbcore/urls.py +4 -0
- wbcore/utils/date.py +18 -2
- wbcore/utils/figures.py +2 -2
- wbcore/utils/models.py +21 -4
- wbcore/utils/reportlab.py +7 -0
- wbcore/utils/rrules.py +3 -1
- wbcore/utils/string_loader.py +1 -1
- wbcore/utils/strings.py +3 -3
- wbcore/utils/views.py +8 -3
- wbcore/viewsets/mixins.py +9 -4
- {wbcore-1.46.0.dist-info → wbcore-1.58.2.dist-info}/METADATA +9 -5
- {wbcore-1.46.0.dist-info → wbcore-1.58.2.dist-info}/RECORD +317 -271
- wbcore/contrib/geography/tests/test_serializers.py +0 -7
- wbcore/contrib/geography/tests/tests.py +0 -13
- wbcore/contrib/io/tests/tests.py +0 -19
- wbcore/contrib/workflow/tests/tests.py +0 -25
- {wbcore-1.46.0.dist-info → wbcore-1.58.2.dist-info}/WHEEL +0 -0
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)
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import pytest
|
|
2
|
-
from faker import Faker
|
|
3
2
|
|
|
4
3
|
from wbcore.cache.decorators import cache_table
|
|
5
4
|
from wbcore.cache.registry import periodic_cache_registry
|
|
6
5
|
|
|
7
|
-
fake = Faker()
|
|
8
|
-
|
|
9
6
|
|
|
10
7
|
@pytest.mark.parametrize(
|
|
11
8
|
"timeout, key_prefix, periodic_caching_view_kwargs, periodic_caching_get_parameters",
|
|
12
|
-
[(
|
|
9
|
+
[(2, "Foo", [{"a": "a"}], [{"b": "b"}])],
|
|
13
10
|
)
|
|
14
11
|
def test_cache_table(timeout, key_prefix, periodic_caching_view_kwargs, periodic_caching_get_parameters):
|
|
15
12
|
@cache_table(
|
|
@@ -22,9 +19,9 @@ def test_cache_table(timeout, key_prefix, periodic_caching_view_kwargs, periodic
|
|
|
22
19
|
class CacheTable:
|
|
23
20
|
pass
|
|
24
21
|
|
|
25
|
-
assert
|
|
26
|
-
assert
|
|
27
|
-
assert
|
|
22
|
+
assert CacheTable.CACHE_ENABLED is True
|
|
23
|
+
assert CacheTable.CACHE_TIMEOUT == timeout
|
|
24
|
+
assert CacheTable.CACHE_KEY_PREFIX == key_prefix
|
|
28
25
|
cache_entry = periodic_cache_registry.classes[0]
|
|
29
26
|
assert cache_entry.view_class == CacheTable
|
|
30
27
|
assert cache_entry.view_kwargs == periodic_caching_view_kwargs
|
wbcore/tests/test_configs.py
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from django.conf import settings
|
|
3
|
-
from faker import Faker
|
|
4
3
|
|
|
5
4
|
from wbcore.configs.decorators import register_config
|
|
6
5
|
from wbcore.configs.registry import ConfigRegistry
|
|
7
6
|
from wbcore.configs.views import ConfigAPIView
|
|
8
7
|
|
|
9
|
-
fake = Faker()
|
|
10
|
-
|
|
11
8
|
|
|
12
9
|
def test_registry(config_registry: ConfigRegistry):
|
|
13
10
|
configs = config_registry.get_config_dict()
|
|
@@ -40,7 +37,7 @@ def test_menu_calendar_config(config_registry: ConfigRegistry):
|
|
|
40
37
|
assert menu_calendar
|
|
41
38
|
|
|
42
39
|
|
|
43
|
-
@pytest.mark.parametrize("text, version", [(
|
|
40
|
+
@pytest.mark.parametrize("text, version", [("Foo bar", "Foo")])
|
|
44
41
|
def test_beta_button_config(config_registry: ConfigRegistry, text, version):
|
|
45
42
|
settings.BETA_BUTTON_VERSION = version
|
|
46
43
|
settings.BETA_BUTTON_TEXT = text
|
|
@@ -55,7 +52,7 @@ def test_config_registry_decorator():
|
|
|
55
52
|
|
|
56
53
|
assert not hasattr(some_callable, "_is_config")
|
|
57
54
|
some_callable = register_config(some_callable)
|
|
58
|
-
assert
|
|
55
|
+
assert some_callable._is_config
|
|
59
56
|
|
|
60
57
|
|
|
61
58
|
def test_config_view(rf):
|
wbcore/tests/test_enums.py
CHANGED
|
@@ -33,7 +33,7 @@ class TestChoiceField:
|
|
|
33
33
|
assert self.field.field_type == WBCoreType.SELECT.value
|
|
34
34
|
|
|
35
35
|
def test_representation(self):
|
|
36
|
-
choices = [{"label": choice[1], "value": choice[0]} for choice in self.CHOICES]
|
|
36
|
+
choices = [{"label": choice[1], "value": choice[0], "group": None} for choice in self.CHOICES]
|
|
37
37
|
assert self.field.get_representation(None, "field_name") == (
|
|
38
38
|
"field_name",
|
|
39
39
|
{
|
|
@@ -47,3 +47,11 @@ class TestChoiceField:
|
|
|
47
47
|
"depends_on": [],
|
|
48
48
|
},
|
|
49
49
|
)
|
|
50
|
+
|
|
51
|
+
def test_representation_with_group(self):
|
|
52
|
+
field = ChoiceField(choices=self.CHOICES, group_key_mapping={"choice1": "group1"})
|
|
53
|
+
rep = field.get_representation(None, "field_name")[1]
|
|
54
|
+
assert rep["choices"] == [
|
|
55
|
+
{"label": "Choice 1", "value": "choice1", "group": "group1"},
|
|
56
|
+
{"label": "Choice 2", "value": "choice2", "group": None},
|
|
57
|
+
]
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
from decimal import Decimal
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
-
from faker import Faker
|
|
5
4
|
from rest_framework.exceptions import ValidationError
|
|
6
5
|
|
|
7
6
|
from wbcore.serializers import DecimalField, FloatField, IntegerField
|
|
8
7
|
from wbcore.serializers.fields import YearField
|
|
9
8
|
from wbcore.serializers.fields.types import WBCoreType
|
|
10
9
|
|
|
11
|
-
fake = Faker()
|
|
12
|
-
|
|
13
10
|
|
|
14
11
|
class TestIntegerField:
|
|
15
12
|
def setup_method(self):
|
|
@@ -44,9 +41,8 @@ class TestIntegerField:
|
|
|
44
41
|
"max_digits": 34,
|
|
45
42
|
"decorators": [],
|
|
46
43
|
"depends_on": [],
|
|
47
|
-
"decimal_mark": ".",
|
|
48
|
-
"delimiter": ",",
|
|
49
44
|
"signed": True,
|
|
45
|
+
"disable_formatting": False,
|
|
50
46
|
},
|
|
51
47
|
)
|
|
52
48
|
|
|
@@ -87,9 +83,8 @@ class TestDecimalField:
|
|
|
87
83
|
"decorators": [],
|
|
88
84
|
"depends_on": [],
|
|
89
85
|
"display_mode": "decimal",
|
|
90
|
-
"decimal_mark": ".",
|
|
91
|
-
"delimiter": ",",
|
|
92
86
|
"signed": True,
|
|
87
|
+
"disable_formatting": False,
|
|
93
88
|
},
|
|
94
89
|
)
|
|
95
90
|
|
|
@@ -109,9 +104,8 @@ class TestDecimalField:
|
|
|
109
104
|
"decorators": [],
|
|
110
105
|
"depends_on": [],
|
|
111
106
|
"display_mode": "decimal",
|
|
112
|
-
"decimal_mark": ".",
|
|
113
|
-
"delimiter": ",",
|
|
114
107
|
"signed": True,
|
|
108
|
+
"disable_formatting": False,
|
|
115
109
|
},
|
|
116
110
|
)
|
|
117
111
|
|
|
@@ -149,9 +143,8 @@ class TestFloatField:
|
|
|
149
143
|
"decorators": [],
|
|
150
144
|
"depends_on": [],
|
|
151
145
|
"display_mode": "decimal",
|
|
152
|
-
"decimal_mark": ".",
|
|
153
|
-
"delimiter": ",",
|
|
154
146
|
"signed": True,
|
|
147
|
+
"disable_formatting": False,
|
|
155
148
|
},
|
|
156
149
|
)
|
|
157
150
|
|
|
@@ -163,12 +156,11 @@ class TestYearField:
|
|
|
163
156
|
"label",
|
|
164
157
|
),
|
|
165
158
|
[
|
|
166
|
-
(
|
|
159
|
+
("Foo", "Bar"),
|
|
167
160
|
],
|
|
168
161
|
)
|
|
169
162
|
def test_year_field_values(self, key, label):
|
|
170
|
-
field = YearField(label=label, precision=2
|
|
163
|
+
field = YearField(label=label, precision=2)
|
|
171
164
|
representation = field.get_representation(None, key)[1]
|
|
172
165
|
assert representation["precision"] == 0
|
|
173
|
-
assert representation["
|
|
174
|
-
assert representation["delimiter"] == ""
|
|
166
|
+
assert representation["disable_formatting"]
|
|
@@ -14,47 +14,47 @@ class TestWBCoreFilterMixin:
|
|
|
14
14
|
def view(self):
|
|
15
15
|
return ViewSet()
|
|
16
16
|
|
|
17
|
-
@pytest.mark.parametrize("field_name", [
|
|
17
|
+
@pytest.mark.parametrize("field_name", ["Foo"])
|
|
18
18
|
def test_key_column_name(self, field_name):
|
|
19
19
|
filter_field = CharFilter(column_field_name=field_name, field_name=field_name)
|
|
20
20
|
assert filter_field.key == field_name
|
|
21
21
|
|
|
22
|
-
@pytest.mark.parametrize("field_name", [
|
|
22
|
+
@pytest.mark.parametrize("field_name", ["Foo"])
|
|
23
23
|
def test_key_field_name(self, field_name):
|
|
24
24
|
filter_field = CharFilter(field_name=field_name)
|
|
25
25
|
assert filter_field.key == field_name
|
|
26
26
|
|
|
27
|
-
@pytest.mark.parametrize("label", [
|
|
27
|
+
@pytest.mark.parametrize("label", ["Foo"])
|
|
28
28
|
def test_get_label_with_label(self, label):
|
|
29
29
|
filter_field = CharFilter(label=label)
|
|
30
30
|
assert filter_field.get_label() == label
|
|
31
31
|
|
|
32
|
-
@pytest.mark.parametrize("field_name1, field_name2", [(
|
|
32
|
+
@pytest.mark.parametrize("field_name1, field_name2", [("Foo", "Bar")])
|
|
33
33
|
def test_get_label_without_label(self, field_name1, field_name2):
|
|
34
34
|
field_name = field_name1 + "_" + field_name2
|
|
35
35
|
filter_field = CharFilter(field_name=field_name)
|
|
36
36
|
assert filter_field.get_label() == field_name1.title() + " " + field_name2.title()
|
|
37
37
|
|
|
38
|
-
@pytest.mark.parametrize("
|
|
39
|
-
def
|
|
40
|
-
filter_field = CharFilter(
|
|
41
|
-
assert filter_field.
|
|
38
|
+
@pytest.mark.parametrize("initial", ["Foo"])
|
|
39
|
+
def test__get_initial_with_callable(self, rf, initial, view):
|
|
40
|
+
filter_field = CharFilter(initial=lambda f, r, v: initial)
|
|
41
|
+
assert filter_field._get_initial(rf, view) == initial
|
|
42
42
|
|
|
43
|
-
@pytest.mark.parametrize("
|
|
44
|
-
def
|
|
43
|
+
@pytest.mark.parametrize("initial", ["Foo"])
|
|
44
|
+
def test__get_initial_with_callable_str(self, rf, initial, view):
|
|
45
45
|
class CustomFilterField(CharFilter):
|
|
46
|
-
def
|
|
47
|
-
return
|
|
46
|
+
def custom_initial(self, *args):
|
|
47
|
+
return initial
|
|
48
48
|
|
|
49
|
-
filter_field = CustomFilterField(
|
|
50
|
-
assert filter_field.
|
|
49
|
+
filter_field = CustomFilterField(initial="custom_initial")
|
|
50
|
+
assert filter_field._get_initial(rf, view) == initial
|
|
51
51
|
|
|
52
|
-
@pytest.mark.parametrize("
|
|
53
|
-
def
|
|
54
|
-
filter_field = CharFilter(
|
|
55
|
-
assert filter_field.
|
|
52
|
+
@pytest.mark.parametrize("initial", ["Foo"])
|
|
53
|
+
def test__get_initial_with_initial(self, rf, initial, view):
|
|
54
|
+
filter_field = CharFilter(initial=initial)
|
|
55
|
+
assert filter_field._get_initial(rf, view) == initial
|
|
56
56
|
|
|
57
|
-
@pytest.mark.parametrize("name", [
|
|
57
|
+
@pytest.mark.parametrize("name", ["Foo"])
|
|
58
58
|
def test_get_representation(self, name, rf, view):
|
|
59
59
|
filter_field = CharFilter(field_name=name)
|
|
60
60
|
request = rf.get("")
|
|
@@ -62,48 +62,48 @@ class TestWBCoreFilterMixin:
|
|
|
62
62
|
for key in ["label", "key", "label_format"]:
|
|
63
63
|
assert key in rep
|
|
64
64
|
|
|
65
|
-
@pytest.mark.parametrize("name, initial", [(
|
|
65
|
+
@pytest.mark.parametrize("name, initial", [("Foo", "Bar")])
|
|
66
66
|
def test_get_representation_request_initial(self, name, initial, rf, view):
|
|
67
67
|
filter_field = CharFilter(field_name=name)
|
|
68
68
|
rf.GET = {name: initial}
|
|
69
69
|
_, le = filter_field.get_representation(rf, name, view)
|
|
70
70
|
assert le["input_properties"]["initial"] == initial
|
|
71
71
|
|
|
72
|
-
@pytest.mark.parametrize("name,
|
|
73
|
-
def
|
|
74
|
-
filter_field = CharFilter(field_name=name,
|
|
72
|
+
@pytest.mark.parametrize("name, initial", [("Foo", "Bar")])
|
|
73
|
+
def test_get_representation_field_initial(self, name, initial, rf, view):
|
|
74
|
+
filter_field = CharFilter(field_name=name, initial=initial)
|
|
75
75
|
request = rf.get("")
|
|
76
76
|
_, le = filter_field.get_representation(request, name, view)
|
|
77
|
-
assert le["input_properties"]["
|
|
77
|
+
assert le["input_properties"]["initial"] == initial
|
|
78
78
|
|
|
79
|
-
@pytest.mark.parametrize("name,
|
|
80
|
-
def test_get_help_text(self, name,
|
|
79
|
+
@pytest.mark.parametrize("name, initial, help_text", [("Foo", "Bar", "Sesquipedalophobie")])
|
|
80
|
+
def test_get_help_text(self, name, initial, help_text, rf, view):
|
|
81
81
|
request = rf.get("")
|
|
82
82
|
|
|
83
83
|
# assert that no field nor help text nor label returns an empty help_text
|
|
84
|
-
filter_field = CharFilter(field_name=name,
|
|
84
|
+
filter_field = CharFilter(field_name=name, initial=initial, help_text=None)
|
|
85
85
|
assert filter_field.get_representation(request, name, view)[0]["help_text"] is None
|
|
86
86
|
|
|
87
87
|
# assert that no field nor help text but a label returns an "Filter by {{label}}"
|
|
88
|
-
filter_field = CharFilter(field_name=name,
|
|
88
|
+
filter_field = CharFilter(field_name=name, initial=initial, help_text=None, label=help_text)
|
|
89
89
|
assert filter_field.get_representation(request, name, view)[0]["help_text"] == f"Filter by {help_text}"
|
|
90
90
|
|
|
91
|
-
filter_field = CharFilter(field_name=name,
|
|
91
|
+
filter_field = CharFilter(field_name=name, initial=initial, help_text=help_text)
|
|
92
92
|
assert filter_field.get_representation(request, name, view)[0]["help_text"] == help_text
|
|
93
93
|
|
|
94
94
|
@patch("wbcore.filters.mixins.get_model_field")
|
|
95
|
-
@pytest.mark.parametrize("name,
|
|
96
|
-
def test_get_help_text_from_model_help_text(self, mock_fct, name,
|
|
95
|
+
@pytest.mark.parametrize("name, initial, text", [("Foo", "Bar", "Sesquipedalophobie")])
|
|
96
|
+
def test_get_help_text_from_model_help_text(self, mock_fct, name, initial, text, rf, view):
|
|
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
|
-
filter_field = CharFilter(field_name=name,
|
|
107
|
-
filter_field.parent =
|
|
106
|
+
filter_field = CharFilter(field_name=name, initial=initial, help_text=None)
|
|
107
|
+
filter_field.parent = DummyParent()
|
|
108
108
|
request = rf.get("")
|
|
109
109
|
assert filter_field.get_representation(request, name, view)[0]["help_text"] == text
|
|
@@ -10,7 +10,6 @@ def test_tree_group_open_level_list_display():
|
|
|
10
10
|
fields=[],
|
|
11
11
|
tree=True,
|
|
12
12
|
tree_group_field="field",
|
|
13
|
-
tree_group_label="label",
|
|
14
13
|
tree_group_open_level=3,
|
|
15
14
|
tree_group_level_options=[TreeGroupLevelOption(list_endpoint="endpoint")],
|
|
16
15
|
)
|
|
@@ -24,7 +23,6 @@ def test_not_tree_group_open_level_list_display():
|
|
|
24
23
|
fields=[],
|
|
25
24
|
tree=False,
|
|
26
25
|
tree_group_field="field",
|
|
27
|
-
tree_group_label="label",
|
|
28
26
|
tree_group_open_level=3,
|
|
29
27
|
)
|
|
30
28
|
)
|
|
@@ -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:
|
|
@@ -36,7 +36,7 @@ class TestPrimaryMixin:
|
|
|
36
36
|
instance_other
|
|
37
37
|
} # Test the queryset are all instance related to pivot "other"
|
|
38
38
|
|
|
39
|
-
@pytest.mark.parametrize("field",
|
|
39
|
+
@pytest.mark.parametrize("field", ("Foo Bar"))
|
|
40
40
|
def test_saving_primary(self, field):
|
|
41
41
|
initial_instance = TestPrimaryModel.objects.create(primary=False, pivot="main", field=field)
|
|
42
42
|
assert (
|
wbcore/urls.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from django.urls import include, path
|
|
2
|
+
from dynamic_preferences.api.viewsets import GlobalPreferencesViewSet
|
|
2
3
|
|
|
3
4
|
from wbcore.cache.views import clear_cache
|
|
5
|
+
from wbcore.contrib.dynamic_preferences.viewsets import UserPreferencesViewSet
|
|
4
6
|
from wbcore.shares.views import ShareAPIView
|
|
5
7
|
|
|
6
8
|
from .configs.views import ConfigAPIView
|
|
@@ -35,6 +37,8 @@ router.register(
|
|
|
35
37
|
FrontendUserConfigurationModelViewSet,
|
|
36
38
|
basename="frontenduserconfiguration",
|
|
37
39
|
)
|
|
40
|
+
router.register(r"global_preferences", GlobalPreferencesViewSet, basename="global_preferences")
|
|
41
|
+
router.register(r"user_preferences", UserPreferencesViewSet, basename="user_preferences")
|
|
38
42
|
|
|
39
43
|
router.register(r"version", VersionModelViewSet, basename="version")
|
|
40
44
|
router.register(r"revision", RevisionModelViewSet, basename="revision")
|
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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from contextlib import suppress
|
|
2
|
+
from typing import Self
|
|
2
3
|
|
|
3
4
|
from django.core.exceptions import FieldDoesNotExist, FieldError
|
|
4
5
|
from django.db import models
|
|
@@ -12,6 +13,7 @@ from wbcore.contrib.color.enums import WBColor
|
|
|
12
13
|
from wbcore.contrib.color.fields import ColorField
|
|
13
14
|
from wbcore.contrib.icons import WBIcon
|
|
14
15
|
from wbcore.contrib.icons.models import IconField
|
|
16
|
+
from wbcore.signals import post_clone
|
|
15
17
|
from wbcore.utils.enum import ChoiceEnum
|
|
16
18
|
|
|
17
19
|
|
|
@@ -105,7 +107,7 @@ class Status(ChoiceEnum):
|
|
|
105
107
|
@classmethod
|
|
106
108
|
def get_color_map(cls):
|
|
107
109
|
colors = [WBColor.YELLOW_LIGHT.value, WBColor.RED_LIGHT.value, WBColor.GREEN_LIGHT.value]
|
|
108
|
-
return [choice for choice in zip(cls, colors)]
|
|
110
|
+
return [choice for choice in zip(cls, colors, strict=False)]
|
|
109
111
|
|
|
110
112
|
|
|
111
113
|
class PrimaryMixin(models.Model):
|
|
@@ -162,9 +164,8 @@ class PrimaryMixin(models.Model):
|
|
|
162
164
|
next_primary = qs.first()
|
|
163
165
|
next_primary.primary = True
|
|
164
166
|
next_primary.save()
|
|
165
|
-
self.primary = False
|
|
166
|
-
self.save()
|
|
167
167
|
if no_deletion:
|
|
168
|
+
self.primary = False
|
|
168
169
|
for field_name in self.PRIMARY_ATTR_FIELDS:
|
|
169
170
|
with suppress(FieldError, FieldDoesNotExist):
|
|
170
171
|
if self._meta.get_field(field_name).null: # ensure the field is nullable
|
|
@@ -272,5 +273,21 @@ class ResolvableModelMixin(models.Model):
|
|
|
272
273
|
resolved = models.BooleanField(default=False, verbose_name="Resolved")
|
|
273
274
|
|
|
274
275
|
def delete(self, *args, **kwargs):
|
|
275
|
-
|
|
276
|
+
if self.resolved:
|
|
277
|
+
raise ValueError("Resolved Entities cannot be deleted.")
|
|
276
278
|
super().delete(*args, **kwargs)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class CloneMixin(models.Model):
|
|
282
|
+
# Clone mixin
|
|
283
|
+
|
|
284
|
+
def _clone(self, **kwargs) -> Self:
|
|
285
|
+
raise NotImplementedError()
|
|
286
|
+
|
|
287
|
+
def clone(self, **kwargs) -> Self:
|
|
288
|
+
clone = self._clone(**kwargs)
|
|
289
|
+
post_clone.send(sender=self.__class__, instance=self, clone=clone, **kwargs)
|
|
290
|
+
return clone
|
|
291
|
+
|
|
292
|
+
class Meta:
|
|
293
|
+
abstract = True
|
|
@@ -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
|
@@ -21,6 +21,8 @@ def convert_rrulestr_to_dict(
|
|
|
21
21
|
rule_dict["wkst"] = getattr(rrule, wkst)
|
|
22
22
|
if byday := rule_dict.pop("byday", None):
|
|
23
23
|
rule_dict["byweekday"] = [getattr(rrule, day.strip()) for day in byday.split(",")]
|
|
24
|
+
if bymonthday := rule_dict.pop("bymonthday", None):
|
|
25
|
+
rule_dict["bymonthday"] = [int(v) for v in bymonthday.split(",")]
|
|
24
26
|
if dtstart:
|
|
25
27
|
rule_dict["dtstart"] = dtstart
|
|
26
28
|
if count:
|
|
@@ -46,7 +48,7 @@ def humanize_rrule(rrule_str: rrule) -> str:
|
|
|
46
48
|
Returns:
|
|
47
49
|
A humanized version of the rrule
|
|
48
50
|
"""
|
|
49
|
-
if
|
|
51
|
+
if rrule_str._freq is None:
|
|
50
52
|
raise ValueError("We do no support humanization of rrule without frequency yet")
|
|
51
53
|
text = "Every "
|
|
52
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))
|