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,3 +1,4 @@
|
|
|
1
|
+
from django.db.models import Case, Value, When
|
|
1
2
|
from django.db.models.query import F, QuerySet
|
|
2
3
|
from rest_framework import status
|
|
3
4
|
from rest_framework.request import Request
|
|
@@ -13,7 +14,8 @@ from wbcore.contrib.notifications.serializers import (
|
|
|
13
14
|
NotificationTypeSettingModelSerializer,
|
|
14
15
|
)
|
|
15
16
|
|
|
16
|
-
from
|
|
17
|
+
from ...icons import WBIcon
|
|
18
|
+
from .configs.notification_types import NotificationTypeSettingDisplayConfig, NotificationTypeSettingEndpointConfig
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
class NotificationTypeRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
@@ -25,6 +27,7 @@ class NotificationTypeSettingModelViewSet(viewsets.ModelViewSet):
|
|
|
25
27
|
queryset = NotificationTypeSetting.objects.all()
|
|
26
28
|
serializer_class = NotificationTypeSettingModelSerializer
|
|
27
29
|
|
|
30
|
+
endpoint_config_class = NotificationTypeSettingEndpointConfig
|
|
28
31
|
display_config_class = NotificationTypeSettingDisplayConfig
|
|
29
32
|
|
|
30
33
|
search_fields = ["notification_type__title", "notification_type__help_text"]
|
|
@@ -37,5 +40,12 @@ class NotificationTypeSettingModelViewSet(viewsets.ModelViewSet):
|
|
|
37
40
|
|
|
38
41
|
def get_queryset(self) -> QuerySet[NotificationTypeSetting]:
|
|
39
42
|
return (
|
|
40
|
-
super()
|
|
43
|
+
super()
|
|
44
|
+
.get_queryset()
|
|
45
|
+
.filter(user=self.request.user)
|
|
46
|
+
.annotate(
|
|
47
|
+
help_text=F("notification_type__help_text"),
|
|
48
|
+
locked=F("notification_type__is_lock"),
|
|
49
|
+
locked_icon=Case(When(locked=True, then=Value(WBIcon.LOCK.icon)), default=Value(None)),
|
|
50
|
+
)
|
|
41
51
|
)
|
wbcore/contrib/pandas/fields.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
-
from typing import List
|
|
3
|
+
from typing import List, Literal
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
4
6
|
|
|
5
7
|
from wbcore.serializers.fields.types import DisplayMode
|
|
6
8
|
|
|
@@ -24,6 +26,9 @@ class _BaseField:
|
|
|
24
26
|
base[_attr] = attr
|
|
25
27
|
return base
|
|
26
28
|
|
|
29
|
+
def to_representation(self, value: pd.Series) -> pd.Series:
|
|
30
|
+
return value
|
|
31
|
+
|
|
27
32
|
|
|
28
33
|
@dataclass
|
|
29
34
|
class PKField(_BaseField):
|
|
@@ -34,11 +39,17 @@ class PKField(_BaseField):
|
|
|
34
39
|
class CharField(_BaseField):
|
|
35
40
|
type: str = "text"
|
|
36
41
|
|
|
42
|
+
def to_representation(self, value: pd.Series) -> pd.Series:
|
|
43
|
+
return super().to_representation(value).astype("string", errors="ignore")
|
|
44
|
+
|
|
37
45
|
|
|
38
46
|
@dataclass
|
|
39
47
|
class DateField(_BaseField):
|
|
40
48
|
type: str = "date"
|
|
41
49
|
|
|
50
|
+
def to_representation(self, value: pd.Series) -> pd.Series:
|
|
51
|
+
return pd.to_datetime(super().to_representation(value)).dt.strftime("%Y-%m-%d")
|
|
52
|
+
|
|
42
53
|
|
|
43
54
|
@dataclass
|
|
44
55
|
class DateRangeField(_BaseField):
|
|
@@ -54,6 +65,9 @@ class BooleanField(_BaseField):
|
|
|
54
65
|
class TextField(_BaseField):
|
|
55
66
|
type: str = "texteditor"
|
|
56
67
|
|
|
68
|
+
def to_representation(self, value: pd.Series) -> pd.Series:
|
|
69
|
+
return super().to_representation(value).astype("string", errors="ignore")
|
|
70
|
+
|
|
57
71
|
|
|
58
72
|
@dataclass
|
|
59
73
|
class EmojiRatingField(_BaseField):
|
|
@@ -64,43 +78,44 @@ class EmojiRatingField(_BaseField):
|
|
|
64
78
|
class FloatField(_BaseField):
|
|
65
79
|
type: str = "number"
|
|
66
80
|
precision: int = 2
|
|
67
|
-
delimiter: str = ","
|
|
68
|
-
decimal_mark: str = "."
|
|
69
81
|
percent: bool = False
|
|
70
82
|
display_mode: DisplayMode = None
|
|
83
|
+
disable_formatting: bool = False
|
|
71
84
|
|
|
72
85
|
def to_dict(self):
|
|
73
86
|
base = super().to_dict()
|
|
74
87
|
base.update(
|
|
75
88
|
{
|
|
76
89
|
"precision": self.precision,
|
|
77
|
-
"delimiter": self.delimiter,
|
|
78
|
-
"decimal_mark": self.decimal_mark,
|
|
79
90
|
}
|
|
80
91
|
)
|
|
81
92
|
if self.percent:
|
|
82
93
|
base["type"] = "percent"
|
|
83
94
|
if self.display_mode:
|
|
84
95
|
base["display_mode"] = self.display_mode.value
|
|
96
|
+
base["disable_formatting"] = self.disable_formatting
|
|
85
97
|
return base
|
|
86
98
|
|
|
99
|
+
def to_representation(self, value: pd.Series) -> pd.Series:
|
|
100
|
+
return super().to_representation(value).astype("float", errors="ignore")
|
|
101
|
+
|
|
87
102
|
|
|
88
103
|
@dataclass
|
|
89
104
|
class IntegerField(FloatField):
|
|
90
105
|
type: str = "number"
|
|
91
106
|
precision: int = 0
|
|
92
107
|
|
|
108
|
+
def to_representation(self, value: pd.Series) -> pd.Series:
|
|
109
|
+
return super().to_representation(value).astype("Int64", errors="ignore")
|
|
110
|
+
|
|
93
111
|
|
|
94
112
|
@dataclass
|
|
95
113
|
class YearField(IntegerField):
|
|
96
114
|
precision: int = 0
|
|
97
|
-
|
|
98
|
-
decimal_mark: str = "."
|
|
115
|
+
disable_formatting: bool = True
|
|
99
116
|
|
|
100
117
|
def __post_init__(self):
|
|
101
118
|
self.precision = 0
|
|
102
|
-
self.delimiter = ""
|
|
103
|
-
self.decimal_mark = "."
|
|
104
119
|
|
|
105
120
|
|
|
106
121
|
@dataclass
|
|
@@ -116,12 +131,25 @@ class JsonField(_BaseField):
|
|
|
116
131
|
@dataclass
|
|
117
132
|
class SparklineField(ListField):
|
|
118
133
|
type: str = "sparkline"
|
|
134
|
+
dimension: Literal["single"] | Literal["double"] = (
|
|
135
|
+
"single" # "single" for data [y1, y2, y3... ] or "double" if contain already the X axis as [[x1, y1], [x2, y2], ... ]
|
|
136
|
+
)
|
|
119
137
|
|
|
120
138
|
def to_dict(self):
|
|
121
139
|
rv = super().to_dict()
|
|
122
|
-
rv["sparkline_type"] = "
|
|
140
|
+
rv["sparkline_type"] = "bar"
|
|
123
141
|
return rv
|
|
124
142
|
|
|
143
|
+
def _sanitize_row(self, row):
|
|
144
|
+
if not row:
|
|
145
|
+
row = [[]] # if row is [] or null, we default to an empty list of list
|
|
146
|
+
if self.dimension == "single": # This ensure that the returned data format contains a list of tuple of x,y
|
|
147
|
+
row = list(map(lambda o: (o[0], o[1]), enumerate(row)))
|
|
148
|
+
return row
|
|
149
|
+
|
|
150
|
+
def to_representation(self, value: pd.Series) -> pd.Series:
|
|
151
|
+
return super().to_representation(value).apply(lambda x: self._sanitize_row(x))
|
|
152
|
+
|
|
125
153
|
|
|
126
154
|
@dataclass(unsafe_hash=True)
|
|
127
155
|
class PandasFields:
|
wbcore/contrib/pandas/filters.py
CHANGED
|
@@ -44,7 +44,10 @@ class PandasDjangoFilterBackend(DjangoFilterBackend):
|
|
|
44
44
|
if isinstance(_filter, wb_filters.NumberFilter):
|
|
45
45
|
try:
|
|
46
46
|
conditions.append(
|
|
47
|
-
self.lookups_operator[lookup_expr](
|
|
47
|
+
self.lookups_operator[lookup_expr](
|
|
48
|
+
df[_filter.field_name],
|
|
49
|
+
float(value) if not _filter.percent else float(value) / 100,
|
|
50
|
+
)
|
|
48
51
|
)
|
|
49
52
|
except ValueError:
|
|
50
53
|
pass
|
|
@@ -16,12 +16,13 @@ class PandasFilterSetMixin:
|
|
|
16
16
|
queryset = self.filters[name].filter(queryset, value)
|
|
17
17
|
except FieldError:
|
|
18
18
|
pass
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
if not isinstance(queryset, models.QuerySet):
|
|
20
|
+
raise AssertionError(
|
|
21
|
+
"Expected '%s.%s' to return a QuerySet, but got a %s instead."
|
|
22
|
+
% (
|
|
23
|
+
type(self).__name__,
|
|
24
|
+
name,
|
|
25
|
+
type(queryset).__name__,
|
|
26
|
+
)
|
|
25
27
|
)
|
|
26
|
-
)
|
|
27
28
|
return queryset
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import pytest
|
|
2
|
-
from faker import Faker
|
|
3
2
|
|
|
4
3
|
from wbcore.contrib.pandas.fields import YearField as PandasYearField
|
|
5
4
|
|
|
6
|
-
fake = Faker()
|
|
7
|
-
|
|
8
5
|
|
|
9
6
|
class TestPandasYearField:
|
|
10
7
|
@pytest.mark.parametrize(
|
|
@@ -13,12 +10,10 @@ class TestPandasYearField:
|
|
|
13
10
|
"label",
|
|
14
11
|
),
|
|
15
12
|
[
|
|
16
|
-
(
|
|
13
|
+
("Foo", "Bar"),
|
|
17
14
|
],
|
|
18
15
|
)
|
|
19
16
|
def test_pandas_year_field_values(self, key, label):
|
|
20
|
-
field = PandasYearField(key=key, label=label, precision=2
|
|
17
|
+
field = PandasYearField(key=key, label=label, precision=2)
|
|
21
18
|
representation = field.to_dict()
|
|
22
19
|
assert representation["precision"] == 0
|
|
23
|
-
assert representation["decimal_mark"] == "."
|
|
24
|
-
assert representation["delimiter"] == ""
|
wbcore/contrib/pandas/utils.py
CHANGED
wbcore/contrib/pandas/views.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from contextlib import suppress
|
|
2
3
|
from functools import cached_property
|
|
3
4
|
|
|
4
5
|
import numpy as np
|
|
@@ -39,8 +40,8 @@ class PandasMixin(CacheMixin):
|
|
|
39
40
|
@cached_property
|
|
40
41
|
def df(self) -> pd.DataFrame:
|
|
41
42
|
if not hasattr(self, "_df"):
|
|
42
|
-
|
|
43
|
-
return
|
|
43
|
+
self._df = self._get_dataframe()
|
|
44
|
+
return self._df
|
|
44
45
|
|
|
45
46
|
# BASIC DATAFRAME GENERATION FRAMEWORK METHODS
|
|
46
47
|
def filter_queryset(self, queryset: QuerySet) -> QuerySet:
|
|
@@ -58,15 +59,13 @@ class PandasMixin(CacheMixin):
|
|
|
58
59
|
pd_fields = self.get_pandas_fields(self.request)
|
|
59
60
|
# Ensure the returned data satisfy the primitive field type
|
|
60
61
|
for field in pd_fields.fields:
|
|
62
|
+
if isinstance(field, fields.PKField) and field.key not in df.columns:
|
|
63
|
+
with suppress(
|
|
64
|
+
IndexError
|
|
65
|
+
): # if df is empty with an empty multi-index, the pandas subrountine fails with an IndexError
|
|
66
|
+
df = df.reset_index(names=field.key)
|
|
61
67
|
if field.key in df.columns:
|
|
62
|
-
|
|
63
|
-
df[field.key] = df[field.key].astype("float", errors="ignore")
|
|
64
|
-
if isinstance(field, fields.CharField) or isinstance(field, fields.TextField):
|
|
65
|
-
df[field.key] = df[field.key].astype("string", errors="ignore")
|
|
66
|
-
if isinstance(field, fields.IntegerField):
|
|
67
|
-
df[field.key] = df[field.key].astype("Int64", errors="ignore")
|
|
68
|
-
elif isinstance(field, fields.DateField):
|
|
69
|
-
df[field.key] = pd.to_datetime(df[field.key])
|
|
68
|
+
df.loc[:, field.key] = field.to_representation(df[field.key])
|
|
70
69
|
with pd.option_context("future.no_silent_downcasting", True):
|
|
71
70
|
return (
|
|
72
71
|
df.drop(columns=df.columns.difference(pd_fields.to_dict().keys()))
|
|
@@ -81,11 +80,13 @@ class PandasMixin(CacheMixin):
|
|
|
81
80
|
)
|
|
82
81
|
|
|
83
82
|
def get_queryset(self):
|
|
84
|
-
|
|
83
|
+
if not hasattr(self, "queryset"):
|
|
84
|
+
raise AssertionError("Either specify a queryset or implement the get_queryset method.")
|
|
85
85
|
return self.queryset
|
|
86
86
|
|
|
87
87
|
def get_dataframe(self, request, queryset, **kwargs):
|
|
88
|
-
|
|
88
|
+
if not self.get_pandas_fields(request):
|
|
89
|
+
raise AssertionError("No pandas_fields specified")
|
|
89
90
|
return pd.DataFrame(queryset.values(*self.get_pandas_fields(request).to_dict().keys()))
|
|
90
91
|
|
|
91
92
|
def manipulate_dataframe(self, df):
|
|
@@ -111,7 +112,7 @@ class PandasMixin(CacheMixin):
|
|
|
111
112
|
df = pd.DataFrame(
|
|
112
113
|
columns=[field.key for field in self.get_pandas_fields(self.request).fields]
|
|
113
114
|
) # if queryset is empty, we make sure the returning df contains all the columns to avoid keyerrors exception
|
|
114
|
-
|
|
115
|
+
self._df = df
|
|
115
116
|
df = self.filter_dataframe(df, **kwargs)
|
|
116
117
|
return df
|
|
117
118
|
|
|
@@ -17,7 +17,7 @@ class ManagedMixin(models.Model):
|
|
|
17
17
|
abstract = True
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
class Tag(
|
|
20
|
+
class Tag(ComplexToStringMixin, ManagedMixin):
|
|
21
21
|
title = models.CharField(max_length=255)
|
|
22
22
|
|
|
23
23
|
slug = models.CharField(max_length=255, null=True, blank=True)
|
|
@@ -35,6 +35,9 @@ class Tag(ManagedMixin, ComplexToStringMixin, WBModel):
|
|
|
35
35
|
verbose_name = "Tag"
|
|
36
36
|
verbose_name_plural = "Tags"
|
|
37
37
|
|
|
38
|
+
def __str__(self) -> str:
|
|
39
|
+
return super().__str__()
|
|
40
|
+
|
|
38
41
|
@classmethod
|
|
39
42
|
def get_endpoint_basename(cls):
|
|
40
43
|
return "wbcore:tags:tag"
|
|
@@ -12,9 +12,9 @@ fake = Faker()
|
|
|
12
12
|
def _generate_grid_areas() -> list[list[str]]:
|
|
13
13
|
outer_list = []
|
|
14
14
|
inner_list_length = random.randint(1, 5)
|
|
15
|
-
for
|
|
15
|
+
for _ in range(random.randint(1, 5)):
|
|
16
16
|
inner_list = []
|
|
17
|
-
for
|
|
17
|
+
for _ in range(inner_list_length):
|
|
18
18
|
inner_list.append(random.choice(PersonModelSerializer.Meta.fields))
|
|
19
19
|
outer_list.append(inner_list)
|
|
20
20
|
return outer_list
|
|
@@ -7,20 +7,21 @@ from wbcore.contrib.workflow.models import Transition
|
|
|
7
7
|
|
|
8
8
|
@factory.django.mute_signals(signals.post_save)
|
|
9
9
|
class TransitionFactory(factory.django.DjangoModelFactory):
|
|
10
|
-
name = factory.Faker("text", max_nb_chars=128)
|
|
11
|
-
to_step = factory.SubFactory("wbcore.contrib.workflow.factories.RandomChildStepFactory")
|
|
12
|
-
from_step = factory.SubFactory("wbcore.contrib.workflow.factories.RandomChildStepFactory")
|
|
13
|
-
icon = factory.Iterator(WBIcon.values)
|
|
14
|
-
|
|
15
|
-
@classmethod
|
|
16
|
-
def _create(cls, model_class, *args, **kwargs):
|
|
17
|
-
# Both steps need to belong to the same workflow
|
|
18
|
-
if (from_step := kwargs.get("from_step")) and (to_step := kwargs.get("to_step")):
|
|
19
|
-
if kwargs["from_step"].workflow != kwargs["to_step"].workflow:
|
|
20
|
-
from_step.workflow = to_step.workflow
|
|
21
|
-
from_step.save()
|
|
22
|
-
kwargs["from_step"] = from_step
|
|
23
|
-
return super()._create(model_class, *args, **kwargs)
|
|
24
|
-
|
|
25
10
|
class Meta:
|
|
26
11
|
model = Transition
|
|
12
|
+
exclude = ("_workflow",)
|
|
13
|
+
|
|
14
|
+
_workflow = factory.SubFactory("wbcore.contrib.workflow.factories.WorkflowFactory")
|
|
15
|
+
|
|
16
|
+
from_step = factory.SubFactory(
|
|
17
|
+
"wbcore.contrib.workflow.factories.StartStepFactory",
|
|
18
|
+
workflow=factory.SelfAttribute(".._workflow"),
|
|
19
|
+
)
|
|
20
|
+
to_step = factory.SubFactory(
|
|
21
|
+
"wbcore.contrib.workflow.factories.FinishStepFactory",
|
|
22
|
+
workflow=factory.SelfAttribute(".._workflow"),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
icon = factory.Iterator(WBIcon.values)
|
|
26
|
+
|
|
27
|
+
name = factory.Sequence(lambda n: f"Transition #{n}")
|