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/markdown/models.py
CHANGED
|
@@ -17,7 +17,15 @@ class Asset(models.Model):
|
|
|
17
17
|
file = models.FileField(max_length=256, upload_to=upload_to)
|
|
18
18
|
content_type = models.CharField(max_length=32, null=True, blank=True)
|
|
19
19
|
file_url_name = models.CharField(max_length=1024, null=True, blank=True)
|
|
20
|
+
|
|
20
21
|
# public = models.BooleanField(default=True)
|
|
22
|
+
class Meta:
|
|
23
|
+
verbose_name = _("Asset")
|
|
24
|
+
verbose_name_plural = _("Assets")
|
|
25
|
+
db_table = "bridger_asset"
|
|
26
|
+
|
|
27
|
+
def __str__(self) -> str:
|
|
28
|
+
return str(self.id)
|
|
21
29
|
|
|
22
30
|
@property
|
|
23
31
|
def filename(self):
|
|
@@ -25,11 +33,6 @@ class Asset(models.Model):
|
|
|
25
33
|
return f"{self.id}{suffix}"
|
|
26
34
|
return self.id
|
|
27
35
|
|
|
28
|
-
class Meta:
|
|
29
|
-
verbose_name = _("Asset")
|
|
30
|
-
verbose_name_plural = _("Assets")
|
|
31
|
-
db_table = "bridger_asset"
|
|
32
|
-
|
|
33
36
|
|
|
34
37
|
@receiver(models.signals.pre_save, sender="wbcore.Asset")
|
|
35
38
|
def generate_content_type(sender, instance, **kwargs):
|
wbcore/markdown/views.py
CHANGED
|
@@ -48,7 +48,7 @@ class AssetCreateView(APIView):
|
|
|
48
48
|
try:
|
|
49
49
|
asset = Asset.objects.create(file=request.data["file"])
|
|
50
50
|
return Response(reverse("wbcore:asset-retrieve", args=[asset.id], request=request))
|
|
51
|
-
except KeyError:
|
|
51
|
+
except (KeyError, AttributeError):
|
|
52
52
|
return Response("file missing", status=400)
|
|
53
53
|
|
|
54
54
|
|
wbcore/menus/menus.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
|
-
from typing import Callable, Dict, List, Optional, Union
|
|
2
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
3
3
|
|
|
4
4
|
from django.utils.http import urlencode
|
|
5
5
|
from rest_framework.request import Request
|
|
@@ -32,7 +32,7 @@ class MenuItem:
|
|
|
32
32
|
endpoint: str
|
|
33
33
|
endpoint_args: List[str] = field(default_factory=list)
|
|
34
34
|
endpoint_kwargs: Dict[str, str] = field(default_factory=dict)
|
|
35
|
-
endpoint_get_parameters: Dict[str,
|
|
35
|
+
endpoint_get_parameters: Callable | Dict[str, Any] = field(default_factory=dict)
|
|
36
36
|
reverse: bool = True
|
|
37
37
|
|
|
38
38
|
permission: Optional[ItemPermission] = None
|
|
@@ -24,8 +24,8 @@ class ButtonConfig:
|
|
|
24
24
|
def __post_init__(self):
|
|
25
25
|
if post_init := getattr(super(), "__post_init__", None):
|
|
26
26
|
post_init()
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
if not self.label and not self.icon:
|
|
28
|
+
raise ValueError("No label or icon specified")
|
|
29
29
|
|
|
30
30
|
def __iter__(self):
|
|
31
31
|
if iter := getattr(super(), "__iter__", None):
|
|
@@ -37,7 +37,10 @@ class ButtonConfig:
|
|
|
37
37
|
yield key, value
|
|
38
38
|
color = getattr(self.color, "value", self.color)
|
|
39
39
|
yield "color", color
|
|
40
|
-
yield
|
|
40
|
+
yield (
|
|
41
|
+
"level",
|
|
42
|
+
color,
|
|
43
|
+
) # TODO: we return level for backward compatibility reason. to be removed once we move to frontend version 2
|
|
41
44
|
yield "disabled", self.disabled # set to True if you want to set the css "disabled" property to that button
|
|
42
45
|
yield "always_render", self.always_render # set to True the button always needs to be rendered (even if empty)
|
|
43
46
|
yield "placeholder", self.placeholder # set to a valid string if a placeholder is needed onhover
|
|
@@ -54,8 +57,8 @@ class ButtonTypeMixin:
|
|
|
54
57
|
def __post_init__(self):
|
|
55
58
|
if post_init := getattr(super(), "__post_init__", None):
|
|
56
59
|
post_init()
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
if not hasattr(self, "button_type"):
|
|
61
|
+
raise TypeError("button_type cannot be None.")
|
|
59
62
|
|
|
60
63
|
def __iter__(self):
|
|
61
64
|
if iter := getattr(super(), "__iter__", None):
|
|
@@ -79,8 +82,8 @@ class ButtonUrlMixin:
|
|
|
79
82
|
def __post_init__(self):
|
|
80
83
|
if post_init := getattr(super(), "__post_init__", None):
|
|
81
84
|
post_init()
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
if bool(self.key) == bool(self.endpoint):
|
|
86
|
+
raise ValueError("Either key or endpoint has to be defined. (Not both)")
|
|
84
87
|
|
|
85
88
|
def __iter__(self):
|
|
86
89
|
if iter := getattr(super(), "__iter__", None):
|
|
@@ -23,7 +23,8 @@ class DropDownButton(ButtonTypeMixin, ButtonConfig):
|
|
|
23
23
|
if hasattr(super(), "__post_init__"):
|
|
24
24
|
super().__post_init__()
|
|
25
25
|
self.buttons = tuple(self.buttons)
|
|
26
|
-
|
|
26
|
+
if not isinstance(self.buttons, tuple):
|
|
27
|
+
raise TypeError(f"{type(self.buttons)} is not a tuple")
|
|
27
28
|
|
|
28
29
|
def serialize(self, request, **kwargs):
|
|
29
30
|
res = super().serialize(request, **kwargs)
|
|
@@ -1,6 +1,56 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
class Button(Enum):
|
|
5
|
+
# Buttons
|
|
6
|
+
REFRESH = "refresh"
|
|
7
|
+
NEW = "new"
|
|
8
|
+
DELETE = "delete"
|
|
9
|
+
|
|
10
|
+
# Buttons and Create Buttons
|
|
11
|
+
SAVE = "save"
|
|
12
|
+
SAVE_AND_CLOSE = "save_and_close"
|
|
13
|
+
SAVE_AND_NEW = "save_and_new"
|
|
14
|
+
|
|
15
|
+
# Create Buttons
|
|
16
|
+
RESET = "reset"
|
|
17
|
+
|
|
18
|
+
# Custom Buttons
|
|
19
|
+
DROPDOWN = "dropdown"
|
|
20
|
+
HYPERLINK = "hyperlink"
|
|
21
|
+
WIDGET = "widget"
|
|
22
|
+
ACTION = "action"
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def buttons(cls):
|
|
26
|
+
return [
|
|
27
|
+
cls.REFRESH.value,
|
|
28
|
+
cls.NEW.value,
|
|
29
|
+
cls.DELETE.value,
|
|
30
|
+
cls.SAVE.value,
|
|
31
|
+
cls.SAVE_AND_CLOSE.value,
|
|
32
|
+
cls.SAVE_AND_NEW.value,
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def create_buttons(cls):
|
|
37
|
+
return [
|
|
38
|
+
cls.SAVE.value,
|
|
39
|
+
cls.SAVE_AND_CLOSE.value,
|
|
40
|
+
cls.SAVE_AND_NEW.value,
|
|
41
|
+
cls.RESET.value,
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def custom_buttons(cls):
|
|
46
|
+
return [
|
|
47
|
+
cls.DROPDOWN.value,
|
|
48
|
+
cls.HYPERLINK.value,
|
|
49
|
+
cls.WIDGET.value,
|
|
50
|
+
cls.ACTION.value,
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
4
54
|
class ButtonDefaultColor(Enum):
|
|
5
55
|
SUCCESS = "success"
|
|
6
56
|
ERROR = "error"
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
from
|
|
2
|
-
from typing import Generator
|
|
1
|
+
from typing import Generator, Iterable
|
|
3
2
|
|
|
4
3
|
from wbcore.contrib.icons import WBIcon
|
|
5
|
-
from wbcore.enums import Button
|
|
6
4
|
from wbcore.metadata.configs.buttons.buttons import DropDownButton
|
|
7
5
|
from wbcore.signals.instance_buttons import (
|
|
8
6
|
add_button,
|
|
@@ -12,9 +10,12 @@ from wbcore.signals.instance_buttons import (
|
|
|
12
10
|
from wbcore.utils.importlib import parse_signal_received_for_module
|
|
13
11
|
|
|
14
12
|
from ..base import WBCoreViewConfig
|
|
13
|
+
from .enums import Button
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
class ButtonViewConfig(WBCoreViewConfig):
|
|
17
|
+
SHOW_INLINE: bool = False # set to true if the class needs to show custom button on inline
|
|
18
|
+
|
|
18
19
|
metadata_key = "buttons"
|
|
19
20
|
config_class_attribute = "button_config_class"
|
|
20
21
|
|
|
@@ -28,7 +29,9 @@ class ButtonViewConfig(WBCoreViewConfig):
|
|
|
28
29
|
Returns:
|
|
29
30
|
Yield the serialized button, without duplicates and appends the module prefix to the remote button
|
|
30
31
|
"""
|
|
31
|
-
base_buttons = list(
|
|
32
|
+
base_buttons = list(
|
|
33
|
+
zip([None] * len(base_buttons), base_buttons, strict=False)
|
|
34
|
+
) # append an empty perfix for base buttons
|
|
32
35
|
for prefix, btn in parse_signal_received_for_module(remote_resources):
|
|
33
36
|
base_buttons.append((prefix, btn))
|
|
34
37
|
|
|
@@ -53,57 +56,20 @@ class ButtonViewConfig(WBCoreViewConfig):
|
|
|
53
56
|
FSM_WEIGHT = 100
|
|
54
57
|
|
|
55
58
|
def get_fsm_buttons(self) -> set:
|
|
56
|
-
if self.FSM_DROPDOWN and (
|
|
59
|
+
if self.FSM_DROPDOWN and (fsm_buttons := self.view.FSM_BUTTONS) and len(fsm_buttons) > 0:
|
|
57
60
|
return {
|
|
58
61
|
DropDownButton(
|
|
59
62
|
label=self.FSM_DROPDOWN_LABEL,
|
|
60
63
|
icon=self.FSM_DROPDOWN_ICON,
|
|
61
64
|
title=self.FSM_DROPDOWN_LABEL,
|
|
62
65
|
weight=self.FSM_WEIGHT,
|
|
63
|
-
buttons=tuple(
|
|
66
|
+
buttons=tuple(fsm_buttons),
|
|
64
67
|
)
|
|
65
68
|
}
|
|
66
69
|
return getattr(self.view, "FSM_BUTTONS", set())
|
|
67
70
|
|
|
68
|
-
# list Button Configuration
|
|
69
|
-
LIST_BUTTONS = frozenset({Button.NEW.value, Button.REFRESH.value})
|
|
70
|
-
LIST_BUTTONS_ORDERING = [Button.NEW.value, Button.REFRESH.value]
|
|
71
|
-
|
|
72
|
-
def get_list_buttons(self) -> Generator[None, str, None]:
|
|
73
|
-
if content_type := self.view.get_content_type():
|
|
74
|
-
buttons = set(self.LIST_BUTTONS)
|
|
75
|
-
add_permission = f"{content_type.app_label}.add_{content_type.model}"
|
|
76
|
-
|
|
77
|
-
if not self.request.user.has_perm(add_permission):
|
|
78
|
-
with suppress(KeyError):
|
|
79
|
-
buttons.remove(Button.NEW.value)
|
|
80
|
-
|
|
81
|
-
yield from self.order_buttons(buttons, self.LIST_BUTTONS_ORDERING)
|
|
82
|
-
|
|
83
|
-
# Instance Button Configuration
|
|
84
|
-
INSTANCE_BUTTONS = frozenset({Button.SAVE.value, Button.REFRESH.value, Button.DELETE.value})
|
|
85
|
-
INSTANCE_BUTTONS_ORDERING = [Button.SAVE.value, Button.REFRESH.value, Button.DELETE.value]
|
|
86
|
-
|
|
87
|
-
def get_instance_buttons(self) -> Generator[None, str, None]:
|
|
88
|
-
if content_type := self.view.get_content_type():
|
|
89
|
-
buttons = set(self.INSTANCE_BUTTONS)
|
|
90
|
-
change_permission = f"{content_type.app_label}.change_{content_type.model}"
|
|
91
|
-
delete_permission = f"{content_type.app_label}.delete_{content_type.model}"
|
|
92
|
-
|
|
93
|
-
if not self.request.user.has_perm(change_permission):
|
|
94
|
-
with suppress(KeyError):
|
|
95
|
-
buttons.remove(Button.SAVE.value)
|
|
96
|
-
|
|
97
|
-
if not self.request.user.has_perm(delete_permission):
|
|
98
|
-
with suppress(KeyError):
|
|
99
|
-
buttons.remove(Button.DELETE.value)
|
|
100
|
-
|
|
101
|
-
yield from self.order_buttons(buttons, self.INSTANCE_BUTTONS_ORDERING)
|
|
102
|
-
|
|
103
71
|
# Create Button Configuration
|
|
104
|
-
CREATE_BUTTONS = frozenset(
|
|
105
|
-
{Button.SAVE.value, Button.SAVE_AND_CLOSE.value, Button.SAVE_AND_NEW.value, Button.RESET.value}
|
|
106
|
-
)
|
|
72
|
+
CREATE_BUTTONS = frozenset({Button.SAVE.value})
|
|
107
73
|
CREATE_BUTTONS_ORDERING = [
|
|
108
74
|
Button.SAVE.value,
|
|
109
75
|
Button.SAVE_AND_CLOSE.value,
|
|
@@ -111,7 +77,7 @@ class ButtonViewConfig(WBCoreViewConfig):
|
|
|
111
77
|
Button.RESET.value,
|
|
112
78
|
]
|
|
113
79
|
|
|
114
|
-
def get_create_buttons(self) ->
|
|
80
|
+
def get_create_buttons(self) -> Iterable:
|
|
115
81
|
buttons = set(self.CREATE_BUTTONS)
|
|
116
82
|
yield from self.order_buttons(buttons, self.CREATE_BUTTONS_ORDERING)
|
|
117
83
|
|
|
@@ -172,7 +138,8 @@ class ButtonViewConfig(WBCoreViewConfig):
|
|
|
172
138
|
return {
|
|
173
139
|
"custom_instance": self._get_custom_instance_buttons(),
|
|
174
140
|
"custom": self._get_custom_buttons()
|
|
175
|
-
if not self.view.inline
|
|
141
|
+
if (not self.view.inline or self.SHOW_INLINE)
|
|
176
142
|
else [], # we do not show button for list display generated through inline
|
|
177
143
|
"extra": self._get_custom_extra_buttons(),
|
|
144
|
+
"save": self.get_create_buttons(),
|
|
178
145
|
}
|
|
@@ -41,8 +41,8 @@ class Operator(Enum):
|
|
|
41
41
|
operator_dict = {o.value: o for o in cls}
|
|
42
42
|
try:
|
|
43
43
|
return operator_dict[op]
|
|
44
|
-
except KeyError:
|
|
45
|
-
raise InvalidOperatorError(f"`{op}` is not a valid operator")
|
|
44
|
+
except KeyError as e:
|
|
45
|
+
raise InvalidOperatorError(f"`{op}` is not a valid operator") from e
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def fr(fractions: int) -> str:
|
|
@@ -9,21 +9,20 @@ class Condition:
|
|
|
9
9
|
value: str | float | int | bool
|
|
10
10
|
|
|
11
11
|
def __post_init__(self) -> None:
|
|
12
|
-
if self.operator == Operator.EXISTS:
|
|
13
|
-
|
|
12
|
+
if self.operator == Operator.EXISTS and not isinstance(self.value, bool):
|
|
13
|
+
raise TypeError(f"{Operator.EXISTS.value} is only compatible with bool")
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@dataclass(unsafe_hash=True)
|
|
17
17
|
class FormattingRule:
|
|
18
|
-
icon: str | None = None
|
|
19
18
|
style: dict | None = None
|
|
20
19
|
condition: Condition | tuple | list[tuple] | None = None
|
|
21
20
|
|
|
22
21
|
def __post_init__(self) -> None:
|
|
23
|
-
|
|
22
|
+
if not self.style:
|
|
23
|
+
raise ValueError("Style cannot be empty")
|
|
24
24
|
|
|
25
25
|
def __iter__(self):
|
|
26
|
-
yield "icon", self.icon
|
|
27
26
|
yield "style", self.style
|
|
28
27
|
if self.condition:
|
|
29
28
|
if isinstance(self.condition, tuple):
|
|
@@ -40,10 +39,8 @@ class Formatting:
|
|
|
40
39
|
column: str | None = None
|
|
41
40
|
|
|
42
41
|
def __post_init__(self) -> None:
|
|
43
|
-
if self.column is None:
|
|
44
|
-
|
|
45
|
-
[not bool(rule.condition) for rule in self.formatting_rules]
|
|
46
|
-
), "Specifying conditions, without a reference column is not possible."
|
|
42
|
+
if self.column is None and not all([not bool(rule.condition) for rule in self.formatting_rules]):
|
|
43
|
+
raise ValueError("Specifying conditions, without a reference column is not possible.")
|
|
47
44
|
|
|
48
45
|
def __iter__(self):
|
|
49
46
|
yield "column", self.column
|
|
@@ -17,7 +17,9 @@ class Display:
|
|
|
17
17
|
pages: Iterable[Page]
|
|
18
18
|
navigation_type: NavigationType = NavigationType.TAB
|
|
19
19
|
|
|
20
|
-
def serialize(
|
|
20
|
+
def serialize(
|
|
21
|
+
self, view_config=None, view=None, request=None, key_prefix=None, parent_page=None
|
|
22
|
+
) -> SerializedDisplay:
|
|
21
23
|
"""Serializes a `Display`
|
|
22
24
|
|
|
23
25
|
Returns:
|
|
@@ -28,7 +30,8 @@ class Display:
|
|
|
28
30
|
page.serialize(key_prefix=key_prefix, view_config=view_config, view=view, request=request)
|
|
29
31
|
for page in self.pages
|
|
30
32
|
]
|
|
31
|
-
if
|
|
33
|
+
# we only allow remote page registration if the display is the top display. We could argue that we want to allow dev to register pages for any nested display by passing down the page argument. This involves refactoring of the signal receivers
|
|
34
|
+
if view_config and parent_page is None:
|
|
32
35
|
for prefix, remote_pages in parse_signal_received_for_module(
|
|
33
36
|
add_display_pages.send(view_config.__class__, request=request, view=view)
|
|
34
37
|
):
|
|
@@ -34,7 +34,7 @@ class Page:
|
|
|
34
34
|
|
|
35
35
|
elif self.display is not None:
|
|
36
36
|
page["display"] = self.display.serialize(
|
|
37
|
-
view_config=view_config, view=view, request=request, key_prefix=key_prefix
|
|
37
|
+
view_config=view_config, view=view, request=request, key_prefix=key_prefix, parent_page=self
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
return page
|
|
@@ -43,7 +43,7 @@ def create_simple_section(
|
|
|
43
43
|
grid_template_areas: list[list[str]] | None = None,
|
|
44
44
|
inline_key: str | None = None,
|
|
45
45
|
extra_display_kwargs: dict | None = None,
|
|
46
|
-
**kwargs
|
|
46
|
+
**kwargs,
|
|
47
47
|
) -> Section:
|
|
48
48
|
"""Creates a simple section without having to specify everything
|
|
49
49
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
2
|
from typing import Iterable, Literal, Optional
|
|
3
3
|
|
|
4
|
+
from slugify import slugify
|
|
5
|
+
|
|
4
6
|
from wbcore.metadata.configs.display.formatting import Formatting, FormattingRule
|
|
5
7
|
|
|
6
8
|
|
|
@@ -34,7 +36,7 @@ class Tooltip:
|
|
|
34
36
|
|
|
35
37
|
@dataclass(unsafe_hash=True)
|
|
36
38
|
class Field:
|
|
37
|
-
key: str
|
|
39
|
+
key: str | None
|
|
38
40
|
label: str
|
|
39
41
|
formatting_rules: Iterable[FormattingRule] = field(default_factory=list)
|
|
40
42
|
width: int | None = None
|
|
@@ -56,6 +58,11 @@ class Field:
|
|
|
56
58
|
menu: bool = True
|
|
57
59
|
size_to_fit: bool = True
|
|
58
60
|
|
|
61
|
+
def __post_init__(self):
|
|
62
|
+
self.identifier = (
|
|
63
|
+
self.key if self.key else slugify(str(self.label))
|
|
64
|
+
) # we cast to str explicitly in case label is in a translation wrapper
|
|
65
|
+
|
|
59
66
|
def iterate_leaf_fields(self, aggregated_parent_label: str = ""):
|
|
60
67
|
label = self.label
|
|
61
68
|
if aggregated_parent_label:
|
|
@@ -66,50 +73,55 @@ class Field:
|
|
|
66
73
|
else:
|
|
67
74
|
yield self.key, label
|
|
68
75
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
def serialize(self, parent_identifier: str | None = None):
|
|
77
|
+
identifier = parent_identifier + "_" + self.identifier if parent_identifier else self.identifier
|
|
78
|
+
repr = {
|
|
79
|
+
"identifier": identifier,
|
|
80
|
+
"key": self.key,
|
|
81
|
+
"label": self.label,
|
|
82
|
+
"formatting_rules": [dict(rule) for rule in self.formatting_rules],
|
|
83
|
+
}
|
|
73
84
|
|
|
74
85
|
if self.width:
|
|
75
|
-
|
|
86
|
+
repr["width"] = self.width
|
|
76
87
|
|
|
77
88
|
if self.hide:
|
|
78
|
-
|
|
89
|
+
repr["hide"] = self.hide
|
|
79
90
|
|
|
80
91
|
if self.pinned and self.pinned in ["left", "right"]:
|
|
81
|
-
|
|
92
|
+
repr["pinned"] = self.pinned
|
|
82
93
|
|
|
83
94
|
if self.children:
|
|
84
|
-
|
|
85
|
-
|
|
95
|
+
repr["children"] = [child.serialize(identifier) for child in self.children]
|
|
96
|
+
repr["marry_children"] = self.marry_children is True # Convert None into False
|
|
86
97
|
|
|
87
98
|
if self.show:
|
|
88
|
-
|
|
99
|
+
repr["show"] = self.show
|
|
89
100
|
|
|
90
101
|
if self.open_by_default is not None:
|
|
91
|
-
|
|
102
|
+
repr["open_by_default"] = self.open_by_default
|
|
92
103
|
|
|
93
104
|
if not self.movable:
|
|
94
|
-
|
|
105
|
+
repr["movable"] = self.movable
|
|
95
106
|
|
|
96
107
|
if not self.resizable:
|
|
97
|
-
|
|
108
|
+
repr["resizable"] = self.resizable
|
|
98
109
|
|
|
99
110
|
if self.lock_position:
|
|
100
|
-
|
|
111
|
+
repr["lock_position"] = self.lock_position
|
|
101
112
|
|
|
102
113
|
if not self.auto_size:
|
|
103
|
-
|
|
114
|
+
repr["auto_size"] = self.auto_size
|
|
104
115
|
|
|
105
116
|
if not self.menu:
|
|
106
|
-
|
|
117
|
+
repr["menu"] = self.menu
|
|
107
118
|
|
|
108
119
|
if not self.size_to_fit:
|
|
109
|
-
|
|
120
|
+
repr["size_to_fit"] = self.size_to_fit
|
|
110
121
|
|
|
111
122
|
if self.tooltip:
|
|
112
|
-
|
|
123
|
+
repr["tooltip"] = self.tooltip.serialize()
|
|
124
|
+
return repr
|
|
113
125
|
|
|
114
126
|
|
|
115
127
|
@dataclass(unsafe_hash=True)
|
|
@@ -133,10 +145,8 @@ class Legend:
|
|
|
133
145
|
key: str | None = None
|
|
134
146
|
|
|
135
147
|
def __post_init__(self):
|
|
136
|
-
if self.key:
|
|
137
|
-
|
|
138
|
-
[item.value is not None for item in self.items]
|
|
139
|
-
), "If key is set, all items need to specify a value."
|
|
148
|
+
if self.key and not all([item.value is not None for item in self.items]):
|
|
149
|
+
raise ValueError("If key is set, all items need to specify a value.")
|
|
140
150
|
|
|
141
151
|
def __iter__(self):
|
|
142
152
|
if self.label:
|
|
@@ -148,11 +158,8 @@ class Legend:
|
|
|
148
158
|
yield "items", [dict(item) for item in self.items]
|
|
149
159
|
|
|
150
160
|
|
|
151
|
-
@dataclass(unsafe_hash=True)
|
|
152
|
-
class
|
|
153
|
-
list_endpoint: str
|
|
154
|
-
reorder_endpoint: str | None = None
|
|
155
|
-
reparent_endpoint: str | None = None
|
|
161
|
+
@dataclass(unsafe_hash=True, kw_only=True)
|
|
162
|
+
class BaseTreeGroupLevelOption:
|
|
156
163
|
lookup: str = "_group_key"
|
|
157
164
|
filter_key: str = "group_keys"
|
|
158
165
|
filter_whitelist: list[str] = field(default_factory=list)
|
|
@@ -162,10 +169,26 @@ class TreeGroupLevelOption:
|
|
|
162
169
|
# Set to True if preselected filters other than required ones need to be cleared out before fetching the tree group
|
|
163
170
|
)
|
|
164
171
|
filter_depth: int | None = 1 # None would actually return all group keys concatenated.
|
|
172
|
+
|
|
173
|
+
def __iter__(self):
|
|
174
|
+
yield "lookup", self.lookup
|
|
175
|
+
yield "filter_key", self.filter_key
|
|
176
|
+
yield "filter_whitelist", self.filter_whitelist
|
|
177
|
+
yield "filter_blacklist", self.filter_blacklist
|
|
178
|
+
yield "clear_filter", self.clear_filter
|
|
179
|
+
yield "filter_depth", self.filter_depth
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@dataclass(unsafe_hash=True, kw_only=True)
|
|
183
|
+
class TreeGroupLevelOption(BaseTreeGroupLevelOption):
|
|
184
|
+
list_endpoint: str
|
|
185
|
+
reorder_endpoint: str | None = None
|
|
186
|
+
reparent_endpoint: str | None = None
|
|
165
187
|
parent_field: str = "parent"
|
|
166
188
|
ordering_field: str = "order"
|
|
167
189
|
|
|
168
190
|
def __iter__(self):
|
|
191
|
+
yield from super().__iter__()
|
|
169
192
|
endpoints = {"list": self.list_endpoint}
|
|
170
193
|
if self.reorder_endpoint:
|
|
171
194
|
endpoints["reorder"] = self.reorder_endpoint
|
|
@@ -174,12 +197,7 @@ class TreeGroupLevelOption:
|
|
|
174
197
|
endpoints["reparent"] = self.reparent_endpoint
|
|
175
198
|
|
|
176
199
|
yield "endpoints", endpoints
|
|
177
|
-
|
|
178
|
-
yield "filter_key", self.filter_key
|
|
179
|
-
yield "filter_whitelist", self.filter_whitelist
|
|
180
|
-
yield "filter_blacklist", self.filter_blacklist
|
|
181
|
-
yield "clear_filter", self.clear_filter
|
|
182
|
-
yield "filter_depth", self.filter_depth
|
|
200
|
+
|
|
183
201
|
yield "parent_field", self.parent_field
|
|
184
202
|
yield "ordering_field", self.ordering_field
|
|
185
203
|
|
|
@@ -198,14 +216,12 @@ class ListDisplay:
|
|
|
198
216
|
auto_height: bool = False
|
|
199
217
|
|
|
200
218
|
tree: bool = False
|
|
201
|
-
tree_group_pinned: str = "left"
|
|
202
219
|
tree_group_parent_pointer: str | None = (
|
|
203
220
|
None # if specified, the display assumes the whole tree data is given in the initial request and that it can be grouped by the given group key
|
|
204
221
|
)
|
|
205
222
|
tree_group_key: str = "_group_key" # The field the `tree_group_parent_pointer` points to
|
|
206
223
|
|
|
207
224
|
tree_group_field: str | None = None
|
|
208
|
-
tree_group_label: str | None = None
|
|
209
225
|
tree_group_open_level: int = 0
|
|
210
226
|
tree_group_field_sortable: bool = False
|
|
211
227
|
|
|
@@ -219,7 +235,7 @@ class ListDisplay:
|
|
|
219
235
|
|
|
220
236
|
def __iter__(self):
|
|
221
237
|
yield "editable", self.editable
|
|
222
|
-
yield "fields", [
|
|
238
|
+
yield "fields", [field.serialize() for field in self.fields if field]
|
|
223
239
|
yield "legends", [dict(legend) for legend in self.legends if legend]
|
|
224
240
|
yield "formatting", [dict(formatting) for formatting in self.formatting if formatting]
|
|
225
241
|
yield "hide_control_bar", self.hide_control_bar
|
|
@@ -233,9 +249,7 @@ class ListDisplay:
|
|
|
233
249
|
if self.tree:
|
|
234
250
|
yield "tree", self.tree
|
|
235
251
|
tree_group = {
|
|
236
|
-
"
|
|
237
|
-
"field": self.tree_group_field if self.tree_group_field else self.fields[0].key,
|
|
238
|
-
"label": self.tree_group_label if self.tree_group_label else self.fields[0].key,
|
|
252
|
+
"field": (self.tree_group_field if self.tree_group_field else self.fields[0].key),
|
|
239
253
|
"open_level": self.tree_group_open_level,
|
|
240
254
|
"field_sortable": self.tree_group_field_sortable,
|
|
241
255
|
"group_key": self.tree_group_key,
|
|
@@ -10,6 +10,9 @@ class Preset(models.Model):
|
|
|
10
10
|
display_identifier = models.CharField(max_length=512)
|
|
11
11
|
display = models.JSONField(null=True, blank=True)
|
|
12
12
|
|
|
13
|
+
def __str__(self) -> str:
|
|
14
|
+
return f"{self.title} - {self.user} ({self.display_identifier})"
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
class AppliedPreset(models.Model):
|
|
15
18
|
user = models.ForeignKey(to=get_user_model(), related_name="applied_presets", on_delete=models.CASCADE)
|
|
@@ -18,3 +21,6 @@ class AppliedPreset(models.Model):
|
|
|
18
21
|
to=Preset, related_name="applied_presets", on_delete=models.SET_NULL, null=True, blank=True
|
|
19
22
|
)
|
|
20
23
|
display = models.JSONField(null=True, blank=True)
|
|
24
|
+
|
|
25
|
+
def __str__(self) -> str:
|
|
26
|
+
return f"{self.display_identifier_path} ({self.user})"
|
|
@@ -53,13 +53,9 @@ class DisplayViewConfig(WBCoreViewConfig):
|
|
|
53
53
|
if window := self.get_window():
|
|
54
54
|
display["window"] = window.serialize()
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
# with the current display identifier. If there is an applied preset for this user - we return it.
|
|
58
|
-
path = self.request.META.get("HTTP_WB_DISPLAY_IDENTIFIER", None)
|
|
59
|
-
display_identifier = self.view.display_identifier_config_class(
|
|
56
|
+
display_identifier_path = self.view.display_identifier_config_class(
|
|
60
57
|
self.view, self.request, self.instance
|
|
61
|
-
).
|
|
62
|
-
display_identifier_path = ".".join(filter(lambda element: element is not None, [path, display_identifier]))
|
|
58
|
+
).display_identifier_path()
|
|
63
59
|
|
|
64
60
|
with suppress(AppliedPreset.DoesNotExist):
|
|
65
61
|
display["preset"] = AppliedPreset.objects.get(
|
|
@@ -73,11 +69,17 @@ class DisplayIdentifierViewConfig(WBCoreViewConfig):
|
|
|
73
69
|
metadata_key = "display_identifier"
|
|
74
70
|
config_class_attribute = "display_identifier_config_class"
|
|
75
71
|
|
|
76
|
-
def
|
|
72
|
+
def display_identifier_path(self) -> str:
|
|
77
73
|
display = self.view.display_config_class
|
|
78
74
|
slugified_display_module = slugify(display.__module__.replace(".", "-"))
|
|
79
75
|
slugified_display_class = slugify(display.__name__)
|
|
80
|
-
|
|
76
|
+
display_identifier_path = f"{slugified_display_module}-{slugified_display_class}"
|
|
77
|
+
|
|
78
|
+
# We get the path from the header (if it exists, only for nested tables inside forms) and then join it
|
|
79
|
+
# with the current display identifier. If there is an applied preset for this user - we return it.
|
|
80
|
+
if inline_path := self.request.META.get("HTTP_WB_DISPLAY_IDENTIFIER", None):
|
|
81
|
+
display_identifier_path = f"{inline_path}.{display_identifier_path}"
|
|
82
|
+
return display_identifier_path
|
|
81
83
|
|
|
82
84
|
def get_metadata(self):
|
|
83
|
-
return self.
|
|
85
|
+
return self.display_identifier_path()
|