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
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import re
|
|
2
2
|
|
|
3
|
+
from django.urls import NoReverseMatch, resolve
|
|
3
4
|
from rest_framework.reverse import reverse
|
|
4
5
|
|
|
5
6
|
from wbcore.enums import WidgetType
|
|
6
7
|
from wbcore.utils.urls import get_parse_endpoint, get_urlencode_endpoint
|
|
7
8
|
|
|
9
|
+
from ...utils.deprecations import deprecate_warning
|
|
8
10
|
from .base import WBCoreViewConfig
|
|
9
11
|
|
|
10
12
|
|
|
@@ -16,6 +18,11 @@ class EndpointViewConfig(WBCoreViewConfig):
|
|
|
16
18
|
DELETE_PK_FIELD = "id"
|
|
17
19
|
UPDATE_PK_FIELD = "id"
|
|
18
20
|
|
|
21
|
+
def __init__(self, *args, **kwargs):
|
|
22
|
+
super().__init__(*args, **kwargs)
|
|
23
|
+
if hasattr(self, "get_list_endpoint"):
|
|
24
|
+
deprecate_warning("get_list_endpoint might be deprecated in a future version of wbcore.")
|
|
25
|
+
|
|
19
26
|
def get_endpoint(self, **kwargs):
|
|
20
27
|
model = self.view.get_model()
|
|
21
28
|
basename_method_name = (
|
|
@@ -57,11 +64,11 @@ class EndpointViewConfig(WBCoreViewConfig):
|
|
|
57
64
|
return endpoint
|
|
58
65
|
return None
|
|
59
66
|
|
|
60
|
-
def get_list_endpoint(self, **kwargs):
|
|
61
|
-
return self.get_endpoint(is_list=True)
|
|
62
|
-
|
|
63
67
|
def _get_list_endpoint(self):
|
|
64
|
-
|
|
68
|
+
try:
|
|
69
|
+
return reverse(resolve(self.request.path).view_name, kwargs=self.view.kwargs, request=self.request)
|
|
70
|
+
except NoReverseMatch:
|
|
71
|
+
return None
|
|
65
72
|
|
|
66
73
|
def get_delete_endpoint(self, **kwargs):
|
|
67
74
|
return self.get_endpoint()
|
|
@@ -10,8 +10,13 @@ class FieldsViewConfig(WBCoreViewConfig):
|
|
|
10
10
|
def get_metadata(self) -> dict:
|
|
11
11
|
fields = defaultdict(dict)
|
|
12
12
|
if (serializer_class := getattr(self.view, "get_serializer", None)) and (serializer := serializer_class()):
|
|
13
|
+
related_key_fields = []
|
|
13
14
|
for field_name, field in serializer.fields.items():
|
|
14
15
|
field_key, field_representation = field.get_representation(self.request, field_name)
|
|
16
|
+
# we need to get the representation of the related field last so that the key update properly (priority to the related field values)
|
|
17
|
+
if "related_key" in field_representation:
|
|
18
|
+
related_key_fields.append((field_key, field_representation))
|
|
19
|
+
fields[field_key].update(field_representation)
|
|
20
|
+
for field_key, field_representation in related_key_fields:
|
|
15
21
|
fields[field_key].update(field_representation)
|
|
16
|
-
|
|
17
22
|
return fields
|
|
@@ -25,18 +25,17 @@ class FilterFieldsViewConfig(WBCoreViewConfig):
|
|
|
25
25
|
hidden_fields.extend(getattr(filterset_class_meta, "hidden_fields", []))
|
|
26
26
|
filters.update(getattr(filterset_class_meta, "df_fields", {}))
|
|
27
27
|
for name, field in filters.items():
|
|
28
|
-
field.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
filter_fields[field.key]["label"] = field.label
|
|
28
|
+
if not field.excluded_filter:
|
|
29
|
+
field.parent = filterset
|
|
30
|
+
if res := field.get_representation(self.request, name, self.view):
|
|
31
|
+
representation, lookup_expr = res
|
|
32
|
+
if name in hidden_fields:
|
|
33
|
+
lookup_expr["hidden"] = True
|
|
34
|
+
if field.key in filter_fields:
|
|
35
|
+
filter_fields[field.key]["lookup_expr"].append(lookup_expr)
|
|
36
|
+
else:
|
|
37
|
+
filter_fields[field.key] = representation
|
|
38
|
+
filter_fields[field.key]["lookup_expr"] = [lookup_expr]
|
|
39
|
+
filter_fields[field.key]["label"] = field.label
|
|
41
40
|
|
|
42
41
|
return filter_fields
|
|
@@ -7,7 +7,9 @@ class IdentifierViewConfig(WBCoreViewConfig):
|
|
|
7
7
|
|
|
8
8
|
# TODO: This does not yet work on the frontend, but should
|
|
9
9
|
def get_metadata(self) -> str | None:
|
|
10
|
-
if
|
|
10
|
+
if (get_identifier := getattr(self.view, "get_identifier", None)) and callable(get_identifier):
|
|
11
|
+
return self.view.get_identifier(self.request)
|
|
12
|
+
elif identifier := getattr(self.view, "IDENTIFIER", None):
|
|
11
13
|
return identifier
|
|
12
14
|
|
|
13
15
|
content_type = self.view.get_content_type() # type: ignore
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import random
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
-
from faker import Faker
|
|
5
4
|
|
|
6
5
|
from wbcore import serializers as wb_serializers
|
|
7
6
|
from wbcore.contrib.icons import WBIcon
|
|
@@ -16,17 +15,15 @@ from wbcore.metadata.configs.buttons.enums import ButtonDefaultColor, HyperlinkT
|
|
|
16
15
|
from wbcore.metadata.configs.display.instance_display import create_simple_display
|
|
17
16
|
from wbcore.metadata.utils import prefix_key
|
|
18
17
|
|
|
19
|
-
fake = Faker()
|
|
20
|
-
|
|
21
18
|
|
|
22
19
|
class TestDropDownButton:
|
|
23
20
|
@pytest.fixture()
|
|
24
21
|
def button(self):
|
|
25
22
|
return DropDownButton(
|
|
26
23
|
icon=random.choice(list(WBIcon)).icon,
|
|
27
|
-
title=
|
|
24
|
+
title="Foo Bar",
|
|
28
25
|
color=random.choice(list(ButtonDefaultColor)).value,
|
|
29
|
-
buttons=[WidgetButton(key=
|
|
26
|
+
buttons=[WidgetButton(key="Foo", label="Foo Bar")],
|
|
30
27
|
)
|
|
31
28
|
|
|
32
29
|
def test_serialize(self, rf, button):
|
|
@@ -38,7 +35,7 @@ class TestDropDownButton:
|
|
|
38
35
|
assert serialized_btn["buttons"] == [button.buttons[0].serialize(rf)]
|
|
39
36
|
assert serialized_btn["type"] == DropDownButton.button_type.value
|
|
40
37
|
|
|
41
|
-
@pytest.mark.parametrize("key_prefix", [
|
|
38
|
+
@pytest.mark.parametrize("key_prefix", ["Foo"])
|
|
42
39
|
def test_serialize_with_prefix(self, rf, button, key_prefix):
|
|
43
40
|
serialized_btn = button.serialize(rf, key_prefix=key_prefix)
|
|
44
41
|
nested_button = button.buttons[0]
|
|
@@ -62,18 +59,18 @@ class TestWidgetButton:
|
|
|
62
59
|
def key_button(self):
|
|
63
60
|
return self.button_class(
|
|
64
61
|
icon=random.choice(list(WBIcon)).icon,
|
|
65
|
-
title=
|
|
62
|
+
title="Foo Bar",
|
|
66
63
|
color=random.choice(list(ButtonDefaultColor)).value,
|
|
67
|
-
key=
|
|
64
|
+
key="Foo",
|
|
68
65
|
)
|
|
69
66
|
|
|
70
67
|
@pytest.fixture()
|
|
71
68
|
def endpoint_button(self):
|
|
72
69
|
return self.button_class(
|
|
73
70
|
icon=random.choice(list(WBIcon)).icon,
|
|
74
|
-
title=
|
|
71
|
+
title="Foo Bar",
|
|
75
72
|
color=random.choice(list(ButtonDefaultColor)).value,
|
|
76
|
-
endpoint=
|
|
73
|
+
endpoint="www.foo.com",
|
|
77
74
|
)
|
|
78
75
|
|
|
79
76
|
def test_serialize(self, rf, key_button):
|
|
@@ -85,7 +82,7 @@ class TestWidgetButton:
|
|
|
85
82
|
assert serialized_btn["key"] == key_button.key
|
|
86
83
|
assert serialized_btn["type"] == self.button_class.button_type.value
|
|
87
84
|
|
|
88
|
-
@pytest.mark.parametrize("key_prefix", [
|
|
85
|
+
@pytest.mark.parametrize("key_prefix", ["Foo"])
|
|
89
86
|
def test_serialize_with_prefix(self, rf, key_button, key_prefix):
|
|
90
87
|
serialized_btn = key_button.serialize(rf, key_prefix=key_prefix)
|
|
91
88
|
|
|
@@ -152,15 +149,15 @@ class TestActionButtonButton(TestHyperlinkButtonutton):
|
|
|
152
149
|
|
|
153
150
|
return ActionButton(
|
|
154
151
|
icon=random.choice(list(WBIcon)).icon,
|
|
155
|
-
title=
|
|
152
|
+
title="Foo Bar",
|
|
156
153
|
color=random.choice(list(ButtonDefaultColor)).value,
|
|
157
|
-
key=
|
|
154
|
+
key="Foo",
|
|
158
155
|
method=random.choice(list(RequestType)),
|
|
159
|
-
action_label=
|
|
160
|
-
description_fields=
|
|
156
|
+
action_label="Foo",
|
|
157
|
+
description_fields="Foo Bar",
|
|
161
158
|
instance_display=create_simple_display([["field"]]),
|
|
162
159
|
serializer=BaseSerializer,
|
|
163
|
-
identifiers=(
|
|
160
|
+
identifiers=("Foo",),
|
|
164
161
|
)
|
|
165
162
|
|
|
166
163
|
def test_serialize(self, rf, key_button):
|
wbcore/models/fields.py
CHANGED
|
@@ -5,10 +5,10 @@ from django.db.models import DecimalField, Field, FloatField, PositiveIntegerFie
|
|
|
5
5
|
class AbstractDynamicField(Field):
|
|
6
6
|
dependencies = []
|
|
7
7
|
|
|
8
|
-
def __init__(self, *args, dependencies=
|
|
8
|
+
def __init__(self, *args, dependencies: list | None = None, **kwargs):
|
|
9
9
|
blank = kwargs.pop("blank", True)
|
|
10
10
|
null = kwargs.pop("null", True)
|
|
11
|
-
self.dependencies = dependencies
|
|
11
|
+
self.dependencies = dependencies if dependencies else []
|
|
12
12
|
super().__init__(*args, blank=blank, null=null, **kwargs)
|
|
13
13
|
|
|
14
14
|
|
wbcore/pagination.py
CHANGED
|
@@ -61,5 +61,4 @@ class CursorPagination(EndlessPaginationMixin, InitialPaginationMixin, paginatio
|
|
|
61
61
|
return super()._get_position_from_instance(instance, new_ordering)
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
class LimitOffsetPagination(EndlessPaginationMixin, InitialPaginationMixin, pagination.LimitOffsetPagination):
|
|
65
|
-
...
|
|
64
|
+
class LimitOffsetPagination(EndlessPaginationMixin, InitialPaginationMixin, pagination.LimitOffsetPagination): ...
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from rest_framework import permissions
|
|
2
|
-
from rest_framework.permissions import
|
|
2
|
+
from rest_framework.permissions import IsAuthenticated
|
|
3
3
|
|
|
4
4
|
from wbcore.enums import WidgetType
|
|
5
5
|
|
|
@@ -40,7 +40,7 @@ class RestAPIModelPermissions(permissions.DjangoModelPermissions):
|
|
|
40
40
|
return request.user.has_perms(perms)
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
class IsInternalUser(
|
|
43
|
+
class IsInternalUser(IsAuthenticated):
|
|
44
44
|
def has_permission(self, request, view) -> bool:
|
|
45
45
|
return is_internal_user(request.user, True)
|
|
46
46
|
|
wbcore/permissions/utils.py
CHANGED
|
@@ -17,10 +17,10 @@ def perm_to_permission(perm: str) -> Permission:
|
|
|
17
17
|
"""
|
|
18
18
|
try:
|
|
19
19
|
app_label, codename = perm.split(".", 1)
|
|
20
|
-
except IndexError:
|
|
20
|
+
except IndexError as e:
|
|
21
21
|
raise AttributeError(
|
|
22
22
|
"The format of identifier string permission (perm) is wrong. " "It should be in 'app_label.codename'."
|
|
23
|
-
)
|
|
23
|
+
) from e
|
|
24
24
|
else:
|
|
25
25
|
permission = Permission.objects.get(content_type__app_label=app_label, codename=codename)
|
|
26
26
|
return permission
|
wbcore/release_notes/display.py
CHANGED
|
@@ -3,6 +3,7 @@ from typing import Optional
|
|
|
3
3
|
from django.utils.translation import gettext as _
|
|
4
4
|
|
|
5
5
|
from wbcore.contrib.icons import WBIcon
|
|
6
|
+
from wbcore.enums import Unit
|
|
6
7
|
from wbcore.metadata.configs import display as dp
|
|
7
8
|
from wbcore.metadata.configs.display.instance_display.shortcuts import (
|
|
8
9
|
Display,
|
|
@@ -16,19 +17,12 @@ class ReleaseNoteDisplayConfig(DisplayViewConfig):
|
|
|
16
17
|
def get_list_display(self) -> Optional[dp.ListDisplay]:
|
|
17
18
|
return dp.ListDisplay(
|
|
18
19
|
fields=[
|
|
20
|
+
dp.Field(key="user_read_icon", label=" ", width=Unit.PIXEL(40)),
|
|
19
21
|
dp.Field(key="module", label=_("Module")),
|
|
20
22
|
dp.Field(key="version", label=_("Version")),
|
|
21
23
|
dp.Field(key="release_date", label=_("Release Date")),
|
|
22
24
|
dp.Field(key="summary", label=_("Summary")),
|
|
23
25
|
],
|
|
24
|
-
formatting=[
|
|
25
|
-
dp.Formatting(
|
|
26
|
-
column="user_read",
|
|
27
|
-
formatting_rules=[
|
|
28
|
-
dp.FormattingRule(icon=WBIcon.VIEW.icon, condition=("==", True)),
|
|
29
|
-
],
|
|
30
|
-
),
|
|
31
|
-
],
|
|
32
26
|
legends=[
|
|
33
27
|
dp.Legend(
|
|
34
28
|
key="read_unread",
|
|
@@ -5,15 +5,8 @@ from .models import ReleaseNote
|
|
|
5
5
|
|
|
6
6
|
class ReleaseNoteModelSerializer(serializers.ModelSerializer):
|
|
7
7
|
user_read = serializers.BooleanField()
|
|
8
|
+
user_read_icon = serializers.IconSelectField()
|
|
8
9
|
|
|
9
10
|
class Meta:
|
|
10
11
|
model = ReleaseNote
|
|
11
|
-
fields = (
|
|
12
|
-
"id",
|
|
13
|
-
"version",
|
|
14
|
-
"release_date",
|
|
15
|
-
"module",
|
|
16
|
-
"summary",
|
|
17
|
-
"notes",
|
|
18
|
-
"user_read",
|
|
19
|
-
)
|
|
12
|
+
fields = ("id", "version", "release_date", "module", "summary", "notes", "user_read", "user_read_icon")
|
wbcore/release_notes/viewsets.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
from django.db.models import Exists, OuterRef, Q
|
|
1
|
+
from django.db.models import Case, Exists, OuterRef, Q, Value, When
|
|
2
2
|
from rest_framework.decorators import action
|
|
3
3
|
from rest_framework.response import Response
|
|
4
4
|
|
|
5
5
|
from wbcore import viewsets
|
|
6
6
|
|
|
7
|
+
from ..contrib.icons import WBIcon
|
|
7
8
|
from .buttons import ReleaseNotesButtonConfig
|
|
8
9
|
from .display import ReleaseNoteDisplayConfig
|
|
9
10
|
from .filters import ReleaseNoteFilterSet
|
|
@@ -26,7 +27,12 @@ class ReleaseNoteReadOnlyModelViewSet(viewsets.ReadOnlyModelViewSet):
|
|
|
26
27
|
return (
|
|
27
28
|
super()
|
|
28
29
|
.get_queryset()
|
|
29
|
-
.annotate(
|
|
30
|
+
.annotate(
|
|
31
|
+
user_read=Exists(ReleaseNote.objects.filter(id=OuterRef("id"), read_by=self.request.user)),
|
|
32
|
+
user_read_icon=Case(
|
|
33
|
+
When(user_read=True, then=Value(WBIcon.VIEW.icon)), default=Value(WBIcon.IGNORE.icon)
|
|
34
|
+
),
|
|
35
|
+
)
|
|
30
36
|
)
|
|
31
37
|
|
|
32
38
|
def retrieve(self, request, *args, **kwargs):
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
|
|
1
3
|
from django.contrib.contenttypes.models import ContentType
|
|
4
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
2
5
|
from django.utils.translation import gettext as _
|
|
3
6
|
|
|
4
7
|
from wbcore.metadata.configs.titles import TitleViewConfig
|
|
@@ -9,10 +12,8 @@ class VersionTitleConfig(TitleViewConfig):
|
|
|
9
12
|
if (content_type_id := self.view.request.GET.get("content_type", None)) and (
|
|
10
13
|
object_id := self.view.request.GET.get("object_id", None)
|
|
11
14
|
):
|
|
12
|
-
|
|
15
|
+
with suppress(ObjectDoesNotExist):
|
|
13
16
|
content_type = ContentType.objects.get(id=content_type_id)
|
|
14
17
|
obj = content_type.model_class().objects.get(id=object_id)
|
|
15
18
|
return _("Versions For {obj}").format(obj=str(obj))
|
|
16
|
-
except Exception:
|
|
17
|
-
pass
|
|
18
19
|
return _("Versions")
|
wbcore/serializers/__init__.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from .fields import (
|
|
2
2
|
AdditionalResourcesField,
|
|
3
|
+
IconSelectField,
|
|
3
4
|
BooleanField,
|
|
4
5
|
CharField,
|
|
5
6
|
ChoiceField,
|
|
@@ -7,6 +8,7 @@ from .fields import (
|
|
|
7
8
|
ColorPickerField,
|
|
8
9
|
DateField,
|
|
9
10
|
DateRangeField,
|
|
11
|
+
TimeRange,
|
|
10
12
|
DateTimeField,
|
|
11
13
|
DateTimeRangeField,
|
|
12
14
|
DecimalField,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from wbcore.contrib.icons.serializers import IconSelectField
|
|
2
2
|
|
|
3
3
|
from .boolean import BooleanField
|
|
4
|
-
from .choice import ChoiceField, MultipleChoiceField
|
|
4
|
+
from .choice import ChoiceField, MultipleChoiceField, LanguageChoiceField
|
|
5
5
|
from .datetime import (
|
|
6
6
|
DateField,
|
|
7
7
|
DateRangeField,
|
|
8
8
|
DateTimeField,
|
|
9
9
|
DateTimeRangeField,
|
|
10
|
+
TimeRange,
|
|
10
11
|
DurationField,
|
|
11
12
|
TimeField,
|
|
12
13
|
TimeZoneField,
|
|
@@ -17,7 +17,7 @@ class BooleanField(WBCoreSerializerFieldMixin, serializers.BooleanField):
|
|
|
17
17
|
color: str | Iterable[str] | None = None,
|
|
18
18
|
active_background_color: str | Iterable[str] | None = None,
|
|
19
19
|
active_color: str | Iterable[str] | None = None,
|
|
20
|
-
**kwargs
|
|
20
|
+
**kwargs,
|
|
21
21
|
):
|
|
22
22
|
self.labels = labels
|
|
23
23
|
self.values = values
|
|
@@ -5,22 +5,42 @@ from .mixins import WBCoreSerializerFieldMixin
|
|
|
5
5
|
from .types import WBCoreType
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class
|
|
8
|
+
class ChoiceMixin:
|
|
9
|
+
choices: dict[str, str]
|
|
10
|
+
|
|
11
|
+
def __init__(self, *args, group_key_mapping: dict[str, str] | None = None, **kwargs):
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
group_key_mapping (dict[str, str] | None, optional): An optional mapping that provides
|
|
16
|
+
a group for every choice value to group the choices in the drop down by. Defaults to None.
|
|
17
|
+
"""
|
|
18
|
+
self.group_key_mapping = group_key_mapping or dict()
|
|
19
|
+
super().__init__(*args, **kwargs)
|
|
20
|
+
|
|
21
|
+
def _get_choices_representation(self):
|
|
22
|
+
return [
|
|
23
|
+
{"group": self.group_key_mapping.get(value), "value": value, "label": label}
|
|
24
|
+
for value, label in self.choices.items()
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ChoiceField(WBCoreSerializerFieldMixin, ChoiceMixin, serializers.ChoiceField):
|
|
9
29
|
field_type = WBCoreType.SELECT.value
|
|
10
30
|
|
|
11
31
|
def get_representation(self, request, field_name) -> tuple[str, dict]:
|
|
12
32
|
key, representation = super().get_representation(request, field_name)
|
|
13
|
-
representation["choices"] =
|
|
33
|
+
representation["choices"] = self._get_choices_representation()
|
|
14
34
|
return key, representation
|
|
15
35
|
|
|
16
36
|
|
|
17
|
-
class MultipleChoiceField(ListFieldMixin, WBCoreSerializerFieldMixin, serializers.MultipleChoiceField):
|
|
37
|
+
class MultipleChoiceField(ListFieldMixin, WBCoreSerializerFieldMixin, ChoiceMixin, serializers.MultipleChoiceField):
|
|
18
38
|
field_type = WBCoreType.SELECT.value
|
|
19
39
|
|
|
20
40
|
def get_representation(self, request, field_name) -> tuple[str, dict]:
|
|
21
41
|
key, representation = super().get_representation(request, field_name)
|
|
22
42
|
representation["multiple"] = True
|
|
23
|
-
representation["choices"] =
|
|
43
|
+
representation["choices"] = self._get_choices_representation()
|
|
24
44
|
return key, representation
|
|
25
45
|
|
|
26
46
|
def to_internal_value(self, data):
|
|
@@ -34,3 +54,7 @@ class MultipleChoiceField(ListFieldMixin, WBCoreSerializerFieldMixin, serializer
|
|
|
34
54
|
if isinstance(data, set):
|
|
35
55
|
data = list(data)
|
|
36
56
|
return data
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class LanguageChoiceField(ChoiceField):
|
|
60
|
+
field_type = WBCoreType.LANGUAGE.value
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
from datetime import timedelta
|
|
1
|
+
from datetime import date, datetime, timedelta
|
|
2
2
|
|
|
3
3
|
import pytz
|
|
4
|
-
from psycopg.types.range import DateRange, TimestamptzRange
|
|
4
|
+
from psycopg.types.range import DateRange, TimestampRange, TimestamptzRange
|
|
5
5
|
from rest_framework import serializers
|
|
6
|
+
from rest_framework.settings import api_settings
|
|
6
7
|
from timezone_field.choices import standard, with_gmt_offset
|
|
7
8
|
from timezone_field.rest_framework import TimeZoneSerializerField
|
|
8
9
|
|
|
@@ -49,13 +50,9 @@ class DateRangeField(RangeMixin, ShortcutMixin, serializers.DateField):
|
|
|
49
50
|
self.outward_bounds_transform = outward_bounds_transform # Allow to specify another bound representation than the default and used for db (]. If specified, will switch the range around to and from the serializer
|
|
50
51
|
self.inward_bounds_transform = inward_bounds_transform
|
|
51
52
|
if self.outward_bounds_transform not in ["[]", "[)", "()", "(]"]:
|
|
52
|
-
raise ValueError(
|
|
53
|
-
f"Outward bound transform {self.outward_bounds_transform} is not a valid choice"
|
|
54
|
-
)
|
|
53
|
+
raise ValueError(f"Outward bound transform {self.outward_bounds_transform} is not a valid choice")
|
|
55
54
|
if self.inward_bounds_transform not in ["[]", "[)", "()", "(]"]:
|
|
56
|
-
raise ValueError(
|
|
57
|
-
f"Inward bound transform {self.inward_bounds_transform} is not a valid choice"
|
|
58
|
-
)
|
|
55
|
+
raise ValueError(f"Inward bound transform {self.inward_bounds_transform} is not a valid choice")
|
|
59
56
|
|
|
60
57
|
super().__init__(*args, **kwargs)
|
|
61
58
|
|
|
@@ -70,9 +67,7 @@ class DateRangeField(RangeMixin, ShortcutMixin, serializers.DateField):
|
|
|
70
67
|
Returns:
|
|
71
68
|
The shifted bound
|
|
72
69
|
"""
|
|
73
|
-
bound_transform =
|
|
74
|
-
self.inward_bounds_transform if inward else self.outward_bounds_transform
|
|
75
|
-
)
|
|
70
|
+
bound_transform = self.inward_bounds_transform if inward else self.outward_bounds_transform
|
|
76
71
|
if lower and bound_transform[0] == "(":
|
|
77
72
|
lower = lower + timedelta(days=1) if inward else lower - timedelta(days=1)
|
|
78
73
|
if upper and bound_transform[1] == "]":
|
|
@@ -84,54 +79,70 @@ class DateTimeRangeField(RangeMixin, ShortcutMixin, serializers.DateTimeField):
|
|
|
84
79
|
field_type = WBCoreType.DATETIMERANGE.value
|
|
85
80
|
internal_field = TimestamptzRange
|
|
86
81
|
|
|
87
|
-
def __init__(
|
|
88
|
-
self, *args, lower_time_choices=None, upper_time_choices=None, **kwargs
|
|
89
|
-
):
|
|
82
|
+
def __init__(self, *args, lower_time_choices=None, upper_time_choices=None, **kwargs):
|
|
90
83
|
self.lower_time_choices = lower_time_choices
|
|
91
84
|
if (
|
|
92
85
|
self.lower_time_choices
|
|
93
86
|
and not isinstance(self.lower_time_choices, list)
|
|
94
87
|
and not callable(self.lower_time_choices)
|
|
95
88
|
):
|
|
96
|
-
raise ValueError(
|
|
97
|
-
"lower_time_choices can only be a static list or a callable."
|
|
98
|
-
)
|
|
89
|
+
raise ValueError("lower_time_choices can only be a static list or a callable.")
|
|
99
90
|
self.upper_time_choices = upper_time_choices
|
|
100
91
|
if (
|
|
101
92
|
self.upper_time_choices
|
|
102
93
|
and not isinstance(self.upper_time_choices, list)
|
|
103
94
|
and not callable(self.upper_time_choices)
|
|
104
95
|
):
|
|
105
|
-
raise ValueError(
|
|
106
|
-
"upper_time_choices can only be a static list or a callable."
|
|
107
|
-
)
|
|
96
|
+
raise ValueError("upper_time_choices can only be a static list or a callable.")
|
|
108
97
|
super().__init__(*args, **kwargs)
|
|
109
98
|
|
|
110
99
|
def get_representation(self, request, field_name) -> tuple[str, dict]:
|
|
111
100
|
key, representation = super().get_representation(request, field_name)
|
|
112
101
|
if self.lower_time_choices is not None:
|
|
113
102
|
if callable(self.lower_time_choices):
|
|
114
|
-
representation["lower_time_choices"] = self.lower_time_choices(
|
|
115
|
-
self, request
|
|
116
|
-
)
|
|
103
|
+
representation["lower_time_choices"] = self.lower_time_choices(self, request)
|
|
117
104
|
else:
|
|
118
105
|
representation["lower_time_choices"] = self.lower_time_choices
|
|
119
106
|
|
|
120
107
|
if self.upper_time_choices is not None:
|
|
121
108
|
if callable(self.upper_time_choices):
|
|
122
|
-
representation["upper_time_choices"] = self.upper_time_choices(
|
|
123
|
-
self, request
|
|
124
|
-
)
|
|
109
|
+
representation["upper_time_choices"] = self.upper_time_choices(self, request)
|
|
125
110
|
else:
|
|
126
111
|
representation["upper_time_choices"] = self.upper_time_choices
|
|
127
|
-
if timezone := getattr(self, "timezone", None):
|
|
128
|
-
representation["timezone"] = str(timezone)
|
|
129
112
|
return key, representation
|
|
130
113
|
|
|
131
114
|
|
|
132
|
-
class
|
|
133
|
-
|
|
134
|
-
|
|
115
|
+
class TimeRange(RangeMixin, ShortcutMixin, serializers.TimeField):
|
|
116
|
+
field_type = WBCoreType.TIMERANGE.value
|
|
117
|
+
internal_field = TimestampRange
|
|
118
|
+
|
|
119
|
+
def __init__(self, *args, timerange_fields: tuple[str, str] | None = None, **kwargs):
|
|
120
|
+
self.timerange_fields = timerange_fields
|
|
121
|
+
super().__init__(*args, **kwargs)
|
|
122
|
+
self.default_date_repr = date.min.strftime(getattr(self, "format", api_settings.DATE_FORMAT))
|
|
123
|
+
if self.timerange_fields:
|
|
124
|
+
self.source = "*"
|
|
125
|
+
|
|
126
|
+
def _transform_range(self, lower, upper, **kwargs):
|
|
127
|
+
if isinstance(lower, datetime):
|
|
128
|
+
lower = lower.time()
|
|
129
|
+
if isinstance(upper, datetime):
|
|
130
|
+
upper = upper.time()
|
|
131
|
+
return lower, upper
|
|
132
|
+
|
|
133
|
+
def get_attribute(self, instance):
|
|
134
|
+
if self.timerange_fields:
|
|
135
|
+
return [getattr(instance, self.timerange_fields[0]), getattr(instance, self.timerange_fields[1])]
|
|
136
|
+
return super().get_attribute(instance)
|
|
137
|
+
|
|
138
|
+
def to_internal_value(self, data):
|
|
139
|
+
ts_range = super().to_internal_value(data)
|
|
140
|
+
if self.timerange_fields:
|
|
141
|
+
return dict(zip(self.timerange_fields, (ts_range.lower, ts_range.upper), strict=False))
|
|
142
|
+
return ts_range
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class DurationField(NumberFieldMixin, WBCoreSerializerFieldMixin, serializers.DurationField):
|
|
135
146
|
field_type = WBCoreType.DURATION.value
|
|
136
147
|
|
|
137
148
|
def __init__(self, *args, **kwargs):
|
|
@@ -144,7 +155,7 @@ class TimeZoneField(WBCoreSerializerFieldMixin, TimeZoneSerializerField):
|
|
|
144
155
|
|
|
145
156
|
def __init__(self, choices=None, choices_display=None, *args, **kwargs):
|
|
146
157
|
if choices:
|
|
147
|
-
values, displays = zip(*choices)
|
|
158
|
+
values, displays = zip(*choices, strict=False)
|
|
148
159
|
else:
|
|
149
160
|
values = pytz.common_timezones
|
|
150
161
|
displays = None
|
|
@@ -154,11 +165,9 @@ class TimeZoneField(WBCoreSerializerFieldMixin, TimeZoneSerializerField):
|
|
|
154
165
|
elif choices_display == "STANDARD":
|
|
155
166
|
choices = standard(values)
|
|
156
167
|
elif choices_display is None:
|
|
157
|
-
choices = zip(values, displays) if displays else standard(values)
|
|
168
|
+
choices = zip(values, displays, strict=False) if displays else standard(values)
|
|
158
169
|
else:
|
|
159
|
-
raise ValueError(
|
|
160
|
-
f"Unrecognized value for kwarg 'choices_display' of '{choices_display}'"
|
|
161
|
-
)
|
|
170
|
+
raise ValueError(f"Unrecognized value for kwarg 'choices_display' of '{choices_display}'")
|
|
162
171
|
|
|
163
172
|
self.choices = choices
|
|
164
173
|
super().__init__(*args, **kwargs)
|
|
@@ -91,7 +91,7 @@ class DynamicButtonField(WBCoreSerializerFieldMixin, serializers.ReadOnlyField):
|
|
|
91
91
|
)
|
|
92
92
|
for prefix, btns in dynamic_buttons:
|
|
93
93
|
for btn in btns:
|
|
94
|
-
|
|
94
|
+
btn.prefix_key = prefix
|
|
95
95
|
buttons.append(btn.serialize(request))
|
|
96
96
|
if (view := self.parent.context.get("view", None)) and not (getattr(view, "action", "list") == "list"):
|
|
97
97
|
for _, button_func in getmembers(self.parent.__class__, _is_instance_dynamic_button):
|
wbcore/serializers/fields/fsm.py
CHANGED
|
@@ -8,7 +8,7 @@ class FSMStatusField(CharField):
|
|
|
8
8
|
def __init__(self, *args, **kwargs):
|
|
9
9
|
self.choices = kwargs.pop("choices")
|
|
10
10
|
read_only = kwargs.pop("read_only", True)
|
|
11
|
-
super().__init__(read_only=read_only,
|
|
11
|
+
super().__init__(*args, read_only=read_only, **kwargs)
|
|
12
12
|
|
|
13
13
|
def get_representation(self, request, field_name) -> tuple[str, dict]:
|
|
14
14
|
key, representation = super().get_representation(request, field_name)
|
|
@@ -35,9 +35,6 @@ class ListFieldMixin:
|
|
|
35
35
|
return super().run_validation(data)
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
list
|
|
39
|
-
|
|
40
|
-
|
|
41
38
|
class ListField(ListFieldMixin, WBCoreSerializerFieldMixin, serializers.ListField):
|
|
42
39
|
field_type = WBCoreType.LIST.value
|
|
43
40
|
|
|
@@ -101,7 +98,7 @@ class SparklineField(WBCoreSerializerFieldMixin, serializers.ListField):
|
|
|
101
98
|
return key, representation
|
|
102
99
|
|
|
103
100
|
def to_representation(self, obj):
|
|
104
|
-
representation =
|
|
101
|
+
representation = [[]] # if row is [] or null, we default to an empty list of list
|
|
105
102
|
if (x_data := getattr(obj, self.x_data_label, None)) and (y_data := getattr(obj, self.y_data_label, None)):
|
|
106
|
-
representation = zip(x_data, y_data)
|
|
103
|
+
representation = zip(x_data, y_data, strict=False)
|
|
107
104
|
return representation
|