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
|
@@ -10,8 +10,10 @@ logger = logging.getLogger(__name__)
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def decorator(position: str, value: str, decorator_type: str = "icon") -> dict:
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
if position not in ("left", "right"):
|
|
14
|
+
raise ValueError("Decorator Position can only be right or left")
|
|
15
|
+
if decorator_type not in ("icon", "text"):
|
|
16
|
+
raise ValueError("Decorator Type can only be icon or text")
|
|
15
17
|
return {"position": position, "value": value, "type": decorator_type}
|
|
16
18
|
|
|
17
19
|
|
|
@@ -31,6 +33,8 @@ class WBCoreSerializerFieldMixin:
|
|
|
31
33
|
read_only=False,
|
|
32
34
|
copyable=None,
|
|
33
35
|
related_key=None,
|
|
36
|
+
on_unsatisfied_deps="read_only",
|
|
37
|
+
clear_dependent_fields=True,
|
|
34
38
|
**kwargs,
|
|
35
39
|
):
|
|
36
40
|
if not decorators:
|
|
@@ -48,6 +52,8 @@ class WBCoreSerializerFieldMixin:
|
|
|
48
52
|
self._callable_read_only = read_only
|
|
49
53
|
read_only = True
|
|
50
54
|
self.related_key = related_key
|
|
55
|
+
self.on_unsatisfied_deps = on_unsatisfied_deps
|
|
56
|
+
self.clear_dependent_fields = clear_dependent_fields
|
|
51
57
|
super().__init__(*args, read_only=read_only, **kwargs)
|
|
52
58
|
|
|
53
59
|
def _evaluate_read_only(self, field_name, parent):
|
|
@@ -105,12 +111,8 @@ class WBCoreSerializerFieldMixin:
|
|
|
105
111
|
|
|
106
112
|
default = getattr(self, "default", None)
|
|
107
113
|
if default is None or default == empty or default == NOT_PROVIDED:
|
|
108
|
-
|
|
109
|
-
default = self.parent.Meta.model._meta._forward_fields_map[
|
|
110
|
-
field_name
|
|
111
|
-
].default
|
|
112
|
-
except Exception: # TODO Add some explicit exception handling
|
|
113
|
-
pass
|
|
114
|
+
with suppress(Exception): # TODO Add some explicit exception handling
|
|
115
|
+
default = self.parent.Meta.model._meta._forward_fields_map[field_name].default
|
|
114
116
|
|
|
115
117
|
if default is not None and default != empty and default != NOT_PROVIDED:
|
|
116
118
|
if callable(default):
|
|
@@ -133,6 +135,10 @@ class WBCoreSerializerFieldMixin:
|
|
|
133
135
|
representation["math"] = self.math
|
|
134
136
|
if self.copyable:
|
|
135
137
|
representation["copyable"] = self.copyable
|
|
138
|
+
if self.on_unsatisfied_deps != "read_only":
|
|
139
|
+
representation["on_unsatisfied_deps"] = self.on_unsatisfied_deps
|
|
140
|
+
if self.clear_dependent_fields is not True:
|
|
141
|
+
representation["clear_dependent_fields"] = self.clear_dependent_fields
|
|
136
142
|
return field_name, representation
|
|
137
143
|
|
|
138
144
|
def validate_empty_values(self, data):
|
|
@@ -173,9 +179,16 @@ class RangeMixin(WBCoreSerializerFieldMixin, Field):
|
|
|
173
179
|
return self.internal_field(lower, upper)
|
|
174
180
|
|
|
175
181
|
def to_representation(self, instance):
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
182
|
+
if hasattr(instance, "lower"):
|
|
183
|
+
lower = instance.lower
|
|
184
|
+
else:
|
|
185
|
+
lower = instance[0]
|
|
186
|
+
|
|
187
|
+
if hasattr(instance, "upper"):
|
|
188
|
+
upper = instance.upper
|
|
189
|
+
else:
|
|
190
|
+
upper = instance[1]
|
|
191
|
+
lower, upper = self._transform_range(lower, upper, inward=False)
|
|
179
192
|
with suppress(Exception):
|
|
180
193
|
lower = super().to_representation(lower)
|
|
181
194
|
with suppress(Exception):
|
|
@@ -14,19 +14,12 @@ class NumberFieldMixin:
|
|
|
14
14
|
percent=False,
|
|
15
15
|
display_mode=DisplayMode.DECIMAL,
|
|
16
16
|
precision=2,
|
|
17
|
-
delimiter=",",
|
|
18
|
-
decimal_mark=".",
|
|
19
17
|
signed: bool = True,
|
|
20
18
|
**kwargs,
|
|
21
19
|
):
|
|
22
20
|
super().__init__(*args, **kwargs)
|
|
23
21
|
self.percent = percent
|
|
24
|
-
self.
|
|
25
|
-
self.decimal_mark = decimal_mark
|
|
26
|
-
if self.delimiter == self.decimal_mark:
|
|
27
|
-
raise ValueError(
|
|
28
|
-
"Decimal mark and delimiter need to be different character"
|
|
29
|
-
)
|
|
22
|
+
self.disable_formatting = getattr(self, "disable_formatting", kwargs.pop("disable_formatting", False))
|
|
30
23
|
self.display_mode = display_mode
|
|
31
24
|
self.precision = kwargs.get("decimal_places", precision)
|
|
32
25
|
self.max_digits = kwargs.get("max_digits", 34)
|
|
@@ -45,8 +38,7 @@ class NumberFieldMixin:
|
|
|
45
38
|
representation["display_mode"] = self.display_mode.value
|
|
46
39
|
representation["precision"] = self.precision
|
|
47
40
|
representation["max_digits"] = self.max_digits
|
|
48
|
-
representation["
|
|
49
|
-
representation["decimal_mark"] = self.decimal_mark
|
|
41
|
+
representation["disable_formatting"] = self.disable_formatting
|
|
50
42
|
representation["signed"] = self.signed
|
|
51
43
|
|
|
52
44
|
if self.percent: # TODO: Discuss with Christoph if this is necessary like this
|
|
@@ -55,9 +47,7 @@ class NumberFieldMixin:
|
|
|
55
47
|
return key, representation
|
|
56
48
|
|
|
57
49
|
|
|
58
|
-
class IntegerField(
|
|
59
|
-
NumberFieldMixin, WBCoreSerializerFieldMixin, serializers.IntegerField
|
|
60
|
-
):
|
|
50
|
+
class IntegerField(NumberFieldMixin, WBCoreSerializerFieldMixin, serializers.IntegerField):
|
|
61
51
|
field_type = WBCoreType.NUMBER.value
|
|
62
52
|
|
|
63
53
|
def __init__(self, *args, **kwargs):
|
|
@@ -66,15 +56,10 @@ class IntegerField(
|
|
|
66
56
|
|
|
67
57
|
|
|
68
58
|
class YearField(IntegerField):
|
|
69
|
-
|
|
70
|
-
super().__init__(*args, **kwargs)
|
|
71
|
-
self.delimiter = ""
|
|
72
|
-
self.decimal_mark = "."
|
|
59
|
+
disable_formatting = True
|
|
73
60
|
|
|
74
61
|
|
|
75
|
-
class DecimalField(
|
|
76
|
-
NumberFieldMixin, WBCoreSerializerFieldMixin, serializers.DecimalField
|
|
77
|
-
):
|
|
62
|
+
class DecimalField(NumberFieldMixin, WBCoreSerializerFieldMixin, serializers.DecimalField):
|
|
78
63
|
field_type = WBCoreType.NUMBER.value
|
|
79
64
|
|
|
80
65
|
# TODO: If this is used, then the validation for max_digits and decimal_fields is not done
|
|
@@ -97,9 +82,7 @@ class FloatField(NumberFieldMixin, WBCoreSerializerFieldMixin, serializers.Float
|
|
|
97
82
|
field_type = WBCoreType.NUMBER.value
|
|
98
83
|
|
|
99
84
|
|
|
100
|
-
class DecimalRangeField(
|
|
101
|
-
RangeMixin, WBCoreSerializerFieldMixin, serializers.DecimalField
|
|
102
|
-
):
|
|
85
|
+
class DecimalRangeField(RangeMixin, WBCoreSerializerFieldMixin, serializers.DecimalField):
|
|
103
86
|
field_type = WBCoreType.NUMBERRANGE.value
|
|
104
87
|
internal_field = NumericRange
|
|
105
88
|
|
|
@@ -7,20 +7,12 @@ from .number import IntegerField
|
|
|
7
7
|
|
|
8
8
|
class StarRatingField(IntegerField):
|
|
9
9
|
field_type = "starrating"
|
|
10
|
-
|
|
11
|
-
def __init__(self, *args, **kwargs):
|
|
12
|
-
super().__init__(*args, **kwargs)
|
|
13
|
-
self.delimiter = ""
|
|
14
|
-
self.decimal_mark = ""
|
|
10
|
+
disable_formatting = True
|
|
15
11
|
|
|
16
12
|
|
|
17
13
|
class EmojiRatingField(IntegerField):
|
|
18
14
|
field_type = "emojirating"
|
|
19
|
-
|
|
20
|
-
def __init__(self, *args, **kwargs):
|
|
21
|
-
super().__init__(*args, **kwargs)
|
|
22
|
-
self.delimiter = ""
|
|
23
|
-
self.decimal_mark = ""
|
|
15
|
+
disable_formatting = True
|
|
24
16
|
|
|
25
17
|
|
|
26
18
|
class RangeSelectField(WBCoreSerializerFieldMixin, serializers.FloatField):
|
|
@@ -29,7 +29,7 @@ class WBCoreManyRelatedField(ListFieldMixin, WBCoreSerializerFieldMixin, ManyRel
|
|
|
29
29
|
self.child_relation.context["view"] = self.view
|
|
30
30
|
self.child_relation._evaluate_read_only(field_name, parent)
|
|
31
31
|
if not self.child_relation.read_only and hasattr(self.child_relation, "_queryset"):
|
|
32
|
-
|
|
32
|
+
self.child_relation.queryset = self.child_relation._queryset
|
|
33
33
|
|
|
34
34
|
def get_representation(self, request: Request, field_name: str) -> tuple[str, dict]:
|
|
35
35
|
key, representation = self.child_relation.get_representation(request, field_name)
|
|
@@ -58,7 +58,7 @@ class PrimaryKeyRelatedField(WBCoreSerializerFieldMixin, serializers.PrimaryKeyR
|
|
|
58
58
|
)
|
|
59
59
|
|
|
60
60
|
def __init__(self, *args, queryset=None, read_only=False, **kwargs):
|
|
61
|
-
self.field_type = kwargs.pop("field_type", WBCoreType.
|
|
61
|
+
self.field_type = kwargs.pop("field_type", WBCoreType.PRIMARY_KEY.value)
|
|
62
62
|
if callable(read_only) and queryset is not None:
|
|
63
63
|
self._queryset = queryset # we unset any given queryset to be compliant with the RelatedField assertion
|
|
64
64
|
queryset = None
|
|
@@ -72,7 +72,7 @@ class PrimaryKeyRelatedField(WBCoreSerializerFieldMixin, serializers.PrimaryKeyR
|
|
|
72
72
|
super().bind(field_name, parent)
|
|
73
73
|
# In case we had to unset the queryset attribute because read_only was a callable, we reinstate it here.
|
|
74
74
|
if not self.read_only and hasattr(self, "_queryset"):
|
|
75
|
-
|
|
75
|
+
self.queryset = self._queryset
|
|
76
76
|
|
|
77
77
|
@classmethod
|
|
78
78
|
def many_init(cls, *args, **kwargs):
|
|
@@ -103,7 +103,7 @@ class PrimaryKeyRelatedField(WBCoreSerializerFieldMixin, serializers.PrimaryKeyR
|
|
|
103
103
|
# In case we annotate the representation, we need to ensure that the value is an object
|
|
104
104
|
if isinstance(value, (list, tuple, set)):
|
|
105
105
|
return [self.to_representation(d) for d in value]
|
|
106
|
-
|
|
106
|
+
with suppress(Exception): # TODO: investigate what exception are we expecting here
|
|
107
107
|
if isinstance(value, str):
|
|
108
108
|
try:
|
|
109
109
|
value = int(value)
|
|
@@ -112,8 +112,6 @@ class PrimaryKeyRelatedField(WBCoreSerializerFieldMixin, serializers.PrimaryKeyR
|
|
|
112
112
|
if isinstance(value, int):
|
|
113
113
|
value = PKOnlyObject(value)
|
|
114
114
|
return super().to_representation(value)
|
|
115
|
-
except Exception:
|
|
116
|
-
pass
|
|
117
115
|
return None
|
|
118
116
|
|
|
119
117
|
def get_queryset(self):
|
|
@@ -54,7 +54,7 @@ class CodeField(CharField):
|
|
|
54
54
|
try:
|
|
55
55
|
compile(data, "", "exec")
|
|
56
56
|
except Exception as e:
|
|
57
|
-
raise ValidationError(_("Compiling script failed with the exception: {}".format(e)))
|
|
57
|
+
raise ValidationError(_("Compiling script failed with the exception: {}".format(e))) from e
|
|
58
58
|
return super().to_internal_value(data)
|
|
59
59
|
|
|
60
60
|
|
|
@@ -14,6 +14,7 @@ class WBCoreType(Enum):
|
|
|
14
14
|
DATETIMERANGE = "datetimerange"
|
|
15
15
|
DATE = "date"
|
|
16
16
|
DATERANGE = "daterange"
|
|
17
|
+
TIMERANGE = "timerange"
|
|
17
18
|
DURATION = "duration"
|
|
18
19
|
TIME = "time"
|
|
19
20
|
PRIMARY_KEY = "primary_key"
|
|
@@ -29,6 +30,7 @@ class WBCoreType(Enum):
|
|
|
29
30
|
COLOR = "color"
|
|
30
31
|
URL = "url"
|
|
31
32
|
SPARKLINE = "sparkline"
|
|
33
|
+
LANGUAGE = "language"
|
|
32
34
|
|
|
33
35
|
|
|
34
36
|
class ReturnContentType(Enum):
|
|
@@ -23,6 +23,7 @@ from timezone_field import TimeZoneField
|
|
|
23
23
|
from wbcore.contrib.color.fields import ColorField
|
|
24
24
|
from wbcore.contrib.icons.models import IconField
|
|
25
25
|
from wbcore.contrib.icons.serializers import IconSelectField
|
|
26
|
+
from wbcore.metadata.configs.display.list_display import BaseTreeGroupLevelOption
|
|
26
27
|
from wbcore.models.fields import YearField
|
|
27
28
|
from wbcore.serializers import (
|
|
28
29
|
fields,
|
|
@@ -48,8 +49,8 @@ def validate_nested_representation(instance, value):
|
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
class WBCoreSerializerMetaClass(SerializerMetaclass):
|
|
51
|
-
def __new__(cls,
|
|
52
|
-
_class = super().__new__(cls,
|
|
52
|
+
def __new__(cls, *args, **kwargs): # noqa: C901
|
|
53
|
+
_class = super().__new__(cls, *args, **kwargs)
|
|
53
54
|
|
|
54
55
|
if _meta := getattr(_class, "Meta", None):
|
|
55
56
|
model = _meta.model
|
|
@@ -306,7 +307,7 @@ class ModelSerializer(
|
|
|
306
307
|
class RepresentationSerializer(WBCoreSerializerFieldMixin, ModelSerializer):
|
|
307
308
|
field_type = WBCoreType.SELECT.value
|
|
308
309
|
|
|
309
|
-
def __init__(self, *args, **kwargs):
|
|
310
|
+
def __init__(self, *args, tree_config: BaseTreeGroupLevelOption | None = None, **kwargs):
|
|
310
311
|
self.ignore_filter = kwargs.pop("ignore_filter", getattr(self, "ignore_filter", None))
|
|
311
312
|
self.filter_params = kwargs.pop("filter_params", getattr(self, "filter_params", None))
|
|
312
313
|
self.endpoint = kwargs.pop(
|
|
@@ -332,6 +333,8 @@ class RepresentationSerializer(WBCoreSerializerFieldMixin, ModelSerializer):
|
|
|
332
333
|
"optional_get_parameters",
|
|
333
334
|
getattr(self, "optional_get_parameters", None),
|
|
334
335
|
)
|
|
336
|
+
self.tree_config = tree_config
|
|
337
|
+
self.select_first_choice = kwargs.pop("select_first_choice", getattr(self, "select_first_choice", None))
|
|
335
338
|
super().__init__(*args, **kwargs)
|
|
336
339
|
|
|
337
340
|
def to_representation(self, value):
|
|
@@ -408,9 +411,15 @@ class RepresentationSerializer(WBCoreSerializerFieldMixin, ModelSerializer):
|
|
|
408
411
|
},
|
|
409
412
|
}
|
|
410
413
|
|
|
414
|
+
if self.select_first_choice:
|
|
415
|
+
representation["select_first_choice"] = True
|
|
416
|
+
|
|
411
417
|
if self.help_text:
|
|
412
418
|
representation["help_text"] = self.help_text
|
|
413
419
|
|
|
414
420
|
if self.optional_get_parameters:
|
|
415
421
|
representation["endpoint"]["optional_get_parameters"] = self.optional_get_parameters
|
|
422
|
+
|
|
423
|
+
if self.tree_config:
|
|
424
|
+
representation["tree_config"] = dict(self.tree_config)
|
|
416
425
|
return self.get_related_key(), representation
|
wbcore/signals/__init__.py
CHANGED
|
@@ -4,3 +4,4 @@ from .models import get_dependant_dynamic_fields_instances, pre_collection
|
|
|
4
4
|
from .permissions import load_permission_objects
|
|
5
5
|
from .serializers import add_additional_resource, add_instance_additional_resource, add_dynamic_button
|
|
6
6
|
from .merge import pre_merge
|
|
7
|
+
from .clone import post_clone
|
wbcore/signals/clone.py
ADDED
wbcore/signals/models.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
from django.db.models.signals import ModelSignal
|
|
2
2
|
|
|
3
|
-
get_dependant_dynamic_fields_instances = (
|
|
4
|
-
|
|
5
|
-
) # Signal use to gather the dependant fields to be computed before the sender (use for the dynamic field framework). Experimental state
|
|
6
|
-
pre_collection = (
|
|
7
|
-
ModelSignal()
|
|
8
|
-
) # pre_delete signal is sent after collection. Therefore, protect field will trigger an IntergrityError. This signal can be used to circuvent that.
|
|
3
|
+
get_dependant_dynamic_fields_instances = ModelSignal() # Signal use to gather the dependant fields to be computed before the sender (use for the dynamic field framework). Experimental state
|
|
4
|
+
pre_collection = ModelSignal() # pre_delete signal is sent after collection. Therefore, protect field will trigger an IntergrityError. This signal can be used to circuvent that.
|
wbcore/tasks.py
CHANGED
|
@@ -45,7 +45,7 @@ def recompute_computed_str(debug: bool = False):
|
|
|
45
45
|
When this task is executed, it will loop over all objects that inherit from ComplexToStringMixin and compare their current computed_str value with the expected one.
|
|
46
46
|
If different, the expected one is saved in place.
|
|
47
47
|
"""
|
|
48
|
-
|
|
48
|
+
bulk_size = 1000
|
|
49
49
|
for subclass in get_inheriting_subclasses(ComplexToStringMixin):
|
|
50
50
|
if getattr(subclass, "COMPUTED_STR_RECOMPUTE_PERIODICALLY", True):
|
|
51
51
|
objs = []
|
|
@@ -59,7 +59,7 @@ def recompute_computed_str(debug: bool = False):
|
|
|
59
59
|
if new_computed_str != instance.computed_str:
|
|
60
60
|
instance.computed_str = new_computed_str
|
|
61
61
|
objs.append(instance)
|
|
62
|
-
if len(objs) %
|
|
62
|
+
if len(objs) % bulk_size == 0:
|
|
63
63
|
subclass.objects.bulk_update(objs, ["computed_str"])
|
|
64
64
|
objs = []
|
|
65
65
|
if objs:
|
|
@@ -208,7 +208,7 @@
|
|
|
208
208
|
width: 100%;
|
|
209
209
|
}
|
|
210
210
|
.fixed {
|
|
211
|
-
width:
|
|
211
|
+
width: 750px;
|
|
212
212
|
}
|
|
213
213
|
#body-table {
|
|
214
214
|
width: 100%;
|
|
@@ -248,8 +248,8 @@
|
|
|
248
248
|
}
|
|
249
249
|
#body-table .content-row > .body-content {
|
|
250
250
|
background-color: #fff;
|
|
251
|
-
padding-right:
|
|
252
|
-
padding-left:
|
|
251
|
+
padding-right: 15px;
|
|
252
|
+
padding-left: 15px;
|
|
253
253
|
}
|
|
254
254
|
#body-table #spacer-row > .spacer-left,
|
|
255
255
|
#body-table #spacer-row > .spacer-middle,
|
|
@@ -2,7 +2,7 @@ from selenium.common.exceptions import TimeoutException
|
|
|
2
2
|
from selenium.webdriver.common.by import By
|
|
3
3
|
from selenium.webdriver.remote.webdriver import WebDriver
|
|
4
4
|
from selenium.webdriver.remote.webelement import WebElement
|
|
5
|
-
from selenium.webdriver.support import expected_conditions
|
|
5
|
+
from selenium.webdriver.support import expected_conditions
|
|
6
6
|
from selenium.webdriver.support.color import Color
|
|
7
7
|
from selenium.webdriver.support.wait import WebDriverWait
|
|
8
8
|
|
|
@@ -49,7 +49,9 @@ def is_string_not_visible(driver: WebDriver, string: str) -> bool:
|
|
|
49
49
|
bool: True if the text is not visible
|
|
50
50
|
"""
|
|
51
51
|
try:
|
|
52
|
-
WebDriverWait(driver, 5).until(
|
|
52
|
+
WebDriverWait(driver, 5).until(
|
|
53
|
+
expected_conditions.invisibility_of_element_located((By.XPATH, f"//*[text()='{string}']"))
|
|
54
|
+
)
|
|
53
55
|
return True
|
|
54
56
|
except TimeoutException:
|
|
55
57
|
return False
|
|
@@ -82,7 +84,9 @@ def is_tag_not_visible(driver: WebDriver, tag_label: str) -> bool:
|
|
|
82
84
|
"""
|
|
83
85
|
try:
|
|
84
86
|
WebDriverWait(driver, 2.5).until(
|
|
85
|
-
|
|
87
|
+
expected_conditions.invisibility_of_element_located(
|
|
88
|
+
(By.XPATH, f"//*[@class='tag-label' and text()='{tag_label}']")
|
|
89
|
+
)
|
|
86
90
|
)
|
|
87
91
|
return True
|
|
88
92
|
except TimeoutException:
|
|
@@ -103,7 +107,9 @@ def is_error_visible(driver: WebDriver):
|
|
|
103
107
|
|
|
104
108
|
if saving_failed_hint and saving_failed_hint.is_displayed():
|
|
105
109
|
WebDriverWait(driver, 10).until_not(
|
|
106
|
-
|
|
110
|
+
expected_conditions.invisibility_of_element_located(
|
|
111
|
+
(By.XPATH, "//div[contains(@class, 'task-dropper-content')]")
|
|
112
|
+
)
|
|
107
113
|
)
|
|
108
114
|
return error_element is not None
|
|
109
115
|
|
|
@@ -4,7 +4,7 @@ from selenium.webdriver.common.action_chains import ActionChains
|
|
|
4
4
|
from selenium.webdriver.common.by import By
|
|
5
5
|
from selenium.webdriver.remote.webdriver import WebDriver
|
|
6
6
|
from selenium.webdriver.remote.webelement import WebElement
|
|
7
|
-
from selenium.webdriver.support import expected_conditions
|
|
7
|
+
from selenium.webdriver.support import expected_conditions
|
|
8
8
|
from selenium.webdriver.support.wait import WebDriverWait
|
|
9
9
|
from wbcore import serializers as wb_serializers
|
|
10
10
|
|
|
@@ -157,7 +157,9 @@ def click_element_by_path(driver: WebDriver, xpath: str, wait_sec=WAIT_TIME_SEC)
|
|
|
157
157
|
xpath (str): The xpath leading to the element.
|
|
158
158
|
"""
|
|
159
159
|
try:
|
|
160
|
-
WebDriverWait(driver, wait_sec).until(
|
|
160
|
+
WebDriverWait(driver, wait_sec).until(
|
|
161
|
+
expected_conditions.presence_of_element_located((By.XPATH, xpath))
|
|
162
|
+
).click()
|
|
161
163
|
except TimeoutException:
|
|
162
164
|
return []
|
|
163
165
|
|
wbcore/test/mixins.py
CHANGED
|
@@ -173,7 +173,7 @@ class ParentViewset:
|
|
|
173
173
|
else get_model_factory(self.model)
|
|
174
174
|
)
|
|
175
175
|
|
|
176
|
-
def _get_mixins_data(self, type="GET", dump_data=False, data=None):
|
|
176
|
+
def _get_mixins_data(self, type="GET", dump_data=False, data=None): # noqa: C901
|
|
177
177
|
api_request = APIRequestFactory()
|
|
178
178
|
superuser = get_or_create_superuser()
|
|
179
179
|
kwargs = None
|
|
@@ -307,34 +307,27 @@ class TestViewSet(ParentViewset):
|
|
|
307
307
|
kwargs["pk"] = obj.pk
|
|
308
308
|
vs = self.mvs.as_view({"options": "options"})
|
|
309
309
|
response = vs(request, **kwargs)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
assert response.data.get("titles"), str(response.data.get("titles")) + " should not be None"
|
|
332
|
-
assert response.data.get("endpoints"), str(response.data.get("endpoints")) + " should not be None"
|
|
333
|
-
# assert response.data.get("preview")
|
|
334
|
-
else:
|
|
335
|
-
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED, (
|
|
336
|
-
str(response.status_code) + f" == 405 ({response.data})"
|
|
337
|
-
)
|
|
310
|
+
assert response.status_code == status.HTTP_200_OK, str(response.status_code) + f" == 200 ({response.data})"
|
|
311
|
+
assert response.data, str(response.data) + " should not be empty"
|
|
312
|
+
if "buttons" in response.data.keys():
|
|
313
|
+
if "custom_instance" in response.data.get("buttons").keys():
|
|
314
|
+
assert (
|
|
315
|
+
list(response.data["buttons"]["custom_instance"])
|
|
316
|
+
or len(list(response.data["buttons"]["custom_instance"])) == 0
|
|
317
|
+
)
|
|
318
|
+
assert response.data.get("fields"), str(response.data.get("fields")) + " should not be None"
|
|
319
|
+
assert response.data.get("identifier"), str(response.data.get("identifier")) + " should not be None"
|
|
320
|
+
# assert response.data.get("pagination")
|
|
321
|
+
# assert response.data.get("pk")
|
|
322
|
+
# assert response.data.get("type")
|
|
323
|
+
# assert response.data.get("filterset_fields")
|
|
324
|
+
# assert response.data.get("search_fields")
|
|
325
|
+
# assert response.data.get("ordering_fields")
|
|
326
|
+
# assert response.data.get("buttons"), str(response.data.get("buttons")) + " should not be None" # TODO: Refactor - buttons can be none
|
|
327
|
+
assert response.data.get("display"), str(response.data.get("display")) + " should not be None"
|
|
328
|
+
assert response.data.get("titles"), str(response.data.get("titles")) + " should not be None"
|
|
329
|
+
assert response.data.get("endpoints"), str(response.data.get("endpoints")) + " should not be None"
|
|
330
|
+
# assert response.data.get("preview")
|
|
338
331
|
print(f"- {self.__class__.__name__}:test_option_request", colored("PASSED", "green")) # noqa: T201
|
|
339
332
|
|
|
340
333
|
# ----- LIST ROUTE TEST ----- #
|
|
@@ -346,16 +339,8 @@ class TestViewSet(ParentViewset):
|
|
|
346
339
|
_, request, kwargs, _ = self._get_mixins_data("GET")
|
|
347
340
|
vs = self.mvs.as_view({"get": "list"})
|
|
348
341
|
response = vs(request, **kwargs)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
assert response.status_code == status.HTTP_200_OK, (
|
|
352
|
-
str(response.status_code) + f" == 200 ({response.data})"
|
|
353
|
-
)
|
|
354
|
-
assert response.data, str(response.data) + " should not be empty"
|
|
355
|
-
else:
|
|
356
|
-
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED, (
|
|
357
|
-
str(response.status_code) + f" == 405 ({response.data})"
|
|
358
|
-
)
|
|
342
|
+
assert response.status_code == status.HTTP_200_OK, str(response.status_code) + f" == 200 ({response.data})"
|
|
343
|
+
assert response.data, str(response.data) + " should not be empty"
|
|
359
344
|
print(f"- {self.__class__.__name__}:test_get_request", colored("PASSED", "green")) # noqa: T201
|
|
360
345
|
|
|
361
346
|
# Test viewset "get": "list" -> aggregation
|
|
@@ -367,24 +352,16 @@ class TestViewSet(ParentViewset):
|
|
|
367
352
|
|
|
368
353
|
vs = self.mvs.as_view({"get": "list"})
|
|
369
354
|
response = vs(request, **kwargs)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
355
|
+
assert response.status_code == status.HTTP_200_OK, str(response.status_code) + f" == 200 ({response.data})"
|
|
356
|
+
assert response.data, str(response.data) + " should not be empty"
|
|
357
|
+
if not response.data.get("aggregates"):
|
|
358
|
+
print( # noqa: T201
|
|
359
|
+
f"- {self.__class__.__name__}:test_aggregation:" + self.mvs.__name__,
|
|
360
|
+
colored("WARNING - aggregates not found in " + self.mvs.__name__, "yellow"),
|
|
374
361
|
)
|
|
375
|
-
assert response.data, str(response.data) + " should not be empty"
|
|
376
|
-
if not response.data.get("aggregates"):
|
|
377
|
-
print( # noqa: T201
|
|
378
|
-
f"- {self.__class__.__name__}:test_aggregation:" + self.mvs.__name__,
|
|
379
|
-
colored("WARNING - aggregates not found in " + self.mvs.__name__, "yellow"),
|
|
380
|
-
)
|
|
381
|
-
else:
|
|
382
|
-
if name_field:
|
|
383
|
-
assert response.data.get("aggregates").get(name_field), name_field + "not found in aggregates"
|
|
384
362
|
else:
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
)
|
|
363
|
+
if name_field:
|
|
364
|
+
assert response.data.get("aggregates").get(name_field), name_field + "not found in aggregates"
|
|
388
365
|
print(f"- {self.__class__.__name__}:test_aggregation", colored("PASSED", "green")) # noqa: T201
|
|
389
366
|
|
|
390
367
|
# Test viewset "get": "list" with client and endpoint
|
|
@@ -398,16 +375,9 @@ class TestViewSet(ParentViewset):
|
|
|
398
375
|
if ep.get_endpoint():
|
|
399
376
|
response = client.get(ep.get_endpoint())
|
|
400
377
|
else:
|
|
401
|
-
response = client.get(ep.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
str(response.status_code) + f" == 200 ({response.data})"
|
|
405
|
-
)
|
|
406
|
-
assert response.data, str(response.data) + " should not be empty"
|
|
407
|
-
else:
|
|
408
|
-
assert response.status_code == status.HTTP_404_NOT_FOUND, (
|
|
409
|
-
str(response.status_code) + f" == 404 ({response.data})"
|
|
410
|
-
)
|
|
378
|
+
response = client.get(ep._get_list_endpoint())
|
|
379
|
+
assert response.status_code == status.HTTP_200_OK, str(response.status_code) + f" == 200 ({response.data})"
|
|
380
|
+
assert response.data, str(response.data) + " should not be empty"
|
|
411
381
|
print(f"- {self.__class__.__name__}:test_get_endpoint", colored("PASSED", "green")) # noqa: T201
|
|
412
382
|
|
|
413
383
|
# Test viewset "post": "create"
|
|
@@ -513,17 +483,12 @@ class TestViewSet(ParentViewset):
|
|
|
513
483
|
obj, request, kwargs, _ = self._get_mixins_data("GET")
|
|
514
484
|
vs = self.mvs.as_view({"get": "retrieve"})
|
|
515
485
|
ep = self._get_endpoint_config(request, kwargs, obj)
|
|
516
|
-
ep_list = ep.get_list_endpoint()
|
|
517
486
|
response = vs(request, **kwargs)
|
|
518
|
-
if
|
|
487
|
+
if ep._get_instance_endpoint():
|
|
519
488
|
assert response.status_code == status.HTTP_200_OK, (
|
|
520
489
|
str(response.status_code) + f" == 200 ({response.data})"
|
|
521
490
|
)
|
|
522
491
|
assert response.data.get("instance"), str(response.data.get("instance")) + " should not be empty"
|
|
523
|
-
else:
|
|
524
|
-
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED, (
|
|
525
|
-
str(response.status_code) + f" == 405 ({response.data})"
|
|
526
|
-
)
|
|
527
492
|
print(f"- {self.__class__.__name__}:test_retrieve_request", colored("PASSED", "green")) # noqa: T201
|
|
528
493
|
|
|
529
494
|
# Test "delete": "destroy"
|
|
@@ -649,16 +614,8 @@ class TestPandasView(TestViewSet):
|
|
|
649
614
|
_, request, kwargs, _ = self._get_mixins_data("GET")
|
|
650
615
|
vs = self.mvs.as_view({"get": "list"})
|
|
651
616
|
response = vs(request, **kwargs)
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
assert response.status_code == status.HTTP_200_OK, (
|
|
655
|
-
str(response.status_code) + f" == 200 ({response.data})"
|
|
656
|
-
)
|
|
657
|
-
assert response.data, str(response.data) + " should not be empty"
|
|
658
|
-
else:
|
|
659
|
-
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED, (
|
|
660
|
-
str(response.status_code) + f" == 405 ({response.data})"
|
|
661
|
-
)
|
|
617
|
+
assert response.status_code == status.HTTP_200_OK, str(response.status_code) + f" == 200 ({response.data})"
|
|
618
|
+
assert response.data, str(response.data) + " should not be empty"
|
|
662
619
|
print(f"- {self.__class__.__name__}:test_get_request", colored("PASSED", "green")) # noqa: T201
|
|
663
620
|
|
|
664
621
|
def execute_test(self):
|
|
@@ -681,27 +638,20 @@ class TestChartViewSet(TestViewSet):
|
|
|
681
638
|
|
|
682
639
|
vs = self.mvs.as_view({"options": "options"})
|
|
683
640
|
response = vs(request, **kwargs)
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
)
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
assert response.data.get("display"), str(response.data.get("display")) + " should not be None"
|
|
699
|
-
assert response.data.get("titles"), str(response.data.get("titles")) + " should not be None"
|
|
700
|
-
assert response.data.get("endpoints"), str(response.data.get("endpoints")) + " should not be None"
|
|
701
|
-
else:
|
|
702
|
-
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED, (
|
|
703
|
-
str(response.status_code) + f" == 405 ({response.data})"
|
|
704
|
-
)
|
|
641
|
+
assert response.status_code == status.HTTP_200_OK, str(response.status_code) + f" == 200 ({response.data})"
|
|
642
|
+
assert response.data, str(response.data) + " should not be empty"
|
|
643
|
+
if "buttons" in response.data.keys():
|
|
644
|
+
if "custom_instance" in response.data.get("buttons").keys():
|
|
645
|
+
assert (
|
|
646
|
+
list(response.data["buttons"]["custom_instance"])
|
|
647
|
+
or len(list(response.data["buttons"]["custom_instance"])) == 0
|
|
648
|
+
)
|
|
649
|
+
assert response.data.get("identifier"), str(response.data.get("identifier")) + " should not be None"
|
|
650
|
+
assert response.data.get("type") == "chart", "type of view should be chart"
|
|
651
|
+
assert response.data.get("buttons"), str(response.data.get("buttons")) + " should not be None"
|
|
652
|
+
assert response.data.get("display"), str(response.data.get("display")) + " should not be None"
|
|
653
|
+
assert response.data.get("titles"), str(response.data.get("titles")) + " should not be None"
|
|
654
|
+
assert response.data.get("endpoints"), str(response.data.get("endpoints")) + " should not be None"
|
|
705
655
|
print(f"- {self.__class__.__name__}:test_option_request", colored("PASSED", "green")) # noqa: T201
|
|
706
656
|
|
|
707
657
|
def execute_test(self):
|