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
|
@@ -28,7 +28,10 @@ class Data(WBModel):
|
|
|
28
28
|
serializers.DateTimeField(),
|
|
29
29
|
serializers.BooleanField(),
|
|
30
30
|
]
|
|
31
|
-
return {
|
|
31
|
+
return {
|
|
32
|
+
data_type: serializer_field
|
|
33
|
+
for data_type, serializer_field in zip(cls, serializer_fields, strict=False)
|
|
34
|
+
}
|
|
32
35
|
|
|
33
36
|
@classmethod
|
|
34
37
|
def get_cast_mapping(cls) -> dict:
|
|
@@ -39,7 +42,7 @@ class Data(WBModel):
|
|
|
39
42
|
datetime.strptime,
|
|
40
43
|
bool,
|
|
41
44
|
]
|
|
42
|
-
return {data_type: cast_callable for data_type, cast_callable in zip(cls, cast_callables)}
|
|
45
|
+
return {data_type: cast_callable for data_type, cast_callable in zip(cls, cast_callables, strict=False)}
|
|
43
46
|
|
|
44
47
|
workflow = models.ForeignKey(
|
|
45
48
|
to="workflow.Workflow",
|
|
@@ -176,8 +179,8 @@ class Data(WBModel):
|
|
|
176
179
|
format_str = "%d.%m.%Y %H:%M:%S"
|
|
177
180
|
try:
|
|
178
181
|
casted_object = data_object.strftime(format_str)
|
|
179
|
-
except AttributeError:
|
|
180
|
-
raise ValueError(gettext("Date(time) type selected but no date(time) object provided!"))
|
|
182
|
+
except AttributeError as e:
|
|
183
|
+
raise ValueError(gettext("Date(time) type selected but no date(time) object provided!")) from e
|
|
181
184
|
|
|
182
185
|
elif data_type == cls.DataType.BOOL:
|
|
183
186
|
if data_object is True:
|
|
@@ -27,7 +27,7 @@ class Process(WBModel):
|
|
|
27
27
|
WBColor.GREEN_LIGHT.value,
|
|
28
28
|
WBColor.RED_LIGHT.value,
|
|
29
29
|
]
|
|
30
|
-
return [choice for choice in zip(cls, colors)]
|
|
30
|
+
return [choice for choice in zip(cls, colors, strict=False)]
|
|
31
31
|
|
|
32
32
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, verbose_name=_("UUID"))
|
|
33
33
|
workflow = models.ForeignKey(
|
|
@@ -124,7 +124,7 @@ class ProcessStep(WBModel):
|
|
|
124
124
|
WBColor.RED_LIGHT.value,
|
|
125
125
|
WBColor.GREY.value,
|
|
126
126
|
]
|
|
127
|
-
return [choice for choice in zip(cls, colors)]
|
|
127
|
+
return [choice for choice in zip(cls, colors, strict=False)]
|
|
128
128
|
|
|
129
129
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, verbose_name=_("UUID"))
|
|
130
130
|
process = models.ForeignKey(
|
|
@@ -39,7 +39,10 @@ class Step(WBModel):
|
|
|
39
39
|
|
|
40
40
|
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
|
41
41
|
workflow = models.ForeignKey(
|
|
42
|
-
to="workflow.Workflow",
|
|
42
|
+
to="workflow.Workflow",
|
|
43
|
+
verbose_name=_("Workflow"),
|
|
44
|
+
on_delete=models.CASCADE,
|
|
45
|
+
related_name="associated_steps",
|
|
43
46
|
)
|
|
44
47
|
code = models.PositiveIntegerField(verbose_name=_("Code"))
|
|
45
48
|
status = models.CharField(
|
|
@@ -62,6 +65,9 @@ class Step(WBModel):
|
|
|
62
65
|
help_text=_("Define which permission is needed to be able to view this step being executed."),
|
|
63
66
|
)
|
|
64
67
|
|
|
68
|
+
outgoing_transitions: models.QuerySet[Transition]
|
|
69
|
+
incoming_transitions: models.QuerySet[Transition]
|
|
70
|
+
|
|
65
71
|
def get_outgoing_transitions(self) -> models.QuerySet[Transition]:
|
|
66
72
|
return self.outgoing_transitions.all()
|
|
67
73
|
|
|
@@ -139,15 +145,24 @@ class Step(WBModel):
|
|
|
139
145
|
if len(valid_transitions := self.get_all_valid_outgoing_transitions(process_step)) == 1:
|
|
140
146
|
Step.start_next_step(process_step, valid_transitions[0])
|
|
141
147
|
elif not valid_transitions:
|
|
142
|
-
self.set_failed(
|
|
148
|
+
self.set_failed(
|
|
149
|
+
process_step,
|
|
150
|
+
gettext("No valid outgoing transitions found for this step!"),
|
|
151
|
+
)
|
|
143
152
|
else:
|
|
144
|
-
self.set_failed(
|
|
153
|
+
self.set_failed(
|
|
154
|
+
process_step,
|
|
155
|
+
gettext("More than one possible outgoing transition found for this step!"),
|
|
156
|
+
)
|
|
145
157
|
|
|
146
158
|
def run(self, process_step: ProcessStep):
|
|
147
159
|
self.set_finished(process_step)
|
|
148
160
|
|
|
149
161
|
def finish(self, process_step: ProcessStep):
|
|
150
|
-
if process_step.state in [
|
|
162
|
+
if process_step.state in [
|
|
163
|
+
ProcessStep.StepState.FINISHED,
|
|
164
|
+
ProcessStep.StepState.CANCELED,
|
|
165
|
+
]:
|
|
151
166
|
# Set finished field
|
|
152
167
|
process_step.finished = datetime.now()
|
|
153
168
|
process_step.save()
|
|
@@ -281,7 +296,10 @@ class UserStep(Step):
|
|
|
281
296
|
else:
|
|
282
297
|
self.set_failed(process_step, gettext("No assignees selected!"))
|
|
283
298
|
else:
|
|
284
|
-
self.set_failed(
|
|
299
|
+
self.set_failed(
|
|
300
|
+
process_step,
|
|
301
|
+
gettext("No valid outgoing transitions found for this step!"),
|
|
302
|
+
)
|
|
285
303
|
|
|
286
304
|
def set_failed(self, process_step: ProcessStep, error_message: str):
|
|
287
305
|
process_step.state = ProcessStep.StepState.FAILED
|
|
@@ -314,7 +332,10 @@ class UserStep(Step):
|
|
|
314
332
|
title=title,
|
|
315
333
|
body=body,
|
|
316
334
|
user=user,
|
|
317
|
-
endpoint=reverse(
|
|
335
|
+
endpoint=reverse(
|
|
336
|
+
f"{process_step.get_endpoint_basename()}-detail",
|
|
337
|
+
args=[process_step.id],
|
|
338
|
+
),
|
|
318
339
|
)
|
|
319
340
|
|
|
320
341
|
@classmethod
|
|
@@ -365,7 +386,10 @@ class DecisionStep(Step):
|
|
|
365
386
|
if transition := self.get_first_valid_transition(process_step):
|
|
366
387
|
Step.start_next_step(process_step, transition)
|
|
367
388
|
else:
|
|
368
|
-
self.set_failed(
|
|
389
|
+
self.set_failed(
|
|
390
|
+
process_step,
|
|
391
|
+
gettext("No valid outgoing transition found for this step!"),
|
|
392
|
+
)
|
|
369
393
|
|
|
370
394
|
@classmethod
|
|
371
395
|
def get_endpoint_basename(cls):
|
|
@@ -390,7 +414,10 @@ class SplitStep(Step):
|
|
|
390
414
|
next_step: Step = transition.to_step
|
|
391
415
|
activate_step.delay(next_step.pk, process_step.process.pk)
|
|
392
416
|
else:
|
|
393
|
-
self.set_failed(
|
|
417
|
+
self.set_failed(
|
|
418
|
+
process_step,
|
|
419
|
+
gettext("No valid outgoing transitions found for this step!"),
|
|
420
|
+
)
|
|
394
421
|
|
|
395
422
|
@classmethod
|
|
396
423
|
def get_endpoint_basename(cls):
|
|
@@ -407,7 +434,8 @@ class SplitStep(Step):
|
|
|
407
434
|
|
|
408
435
|
class JoinStep(Step):
|
|
409
436
|
"""A background step that joins multiple steps into one. Waits until all incoming transitions are done by default.
|
|
410
|
-
If wait_for_all is False the first incoming transition cancels all other active incoming transitions.
|
|
437
|
+
If wait_for_all is False the first incoming transition cancels all other active incoming transitions.
|
|
438
|
+
"""
|
|
411
439
|
|
|
412
440
|
wait_for_all = models.BooleanField(
|
|
413
441
|
default=True,
|
|
@@ -466,7 +494,6 @@ class JoinStep(Step):
|
|
|
466
494
|
)
|
|
467
495
|
):
|
|
468
496
|
self.cancel_if_leading_to_self(unfinished_process_step.step, unfinished_process_step)
|
|
469
|
-
|
|
470
497
|
self.execute_single_next_step(process_step)
|
|
471
498
|
|
|
472
499
|
@classmethod
|
|
@@ -528,10 +555,16 @@ class EmailStep(Step):
|
|
|
528
555
|
verbose_name=pgettext_lazy("Email context", "To"),
|
|
529
556
|
)
|
|
530
557
|
cc = models.ManyToManyField(
|
|
531
|
-
"directory.EmailContact",
|
|
558
|
+
"directory.EmailContact",
|
|
559
|
+
related_name="workflow_steps_cc",
|
|
560
|
+
verbose_name=_("CC"),
|
|
561
|
+
blank=True,
|
|
532
562
|
)
|
|
533
563
|
bcc = models.ManyToManyField(
|
|
534
|
-
"directory.EmailContact",
|
|
564
|
+
"directory.EmailContact",
|
|
565
|
+
related_name="workflow_steps_bcc",
|
|
566
|
+
verbose_name=_("BCC"),
|
|
567
|
+
blank=True,
|
|
535
568
|
)
|
|
536
569
|
|
|
537
570
|
def run(self, process_step: ProcessStep):
|
|
@@ -584,12 +617,16 @@ class FinishStep(Step):
|
|
|
584
617
|
# Finish if there are no running process steps for this process
|
|
585
618
|
if (
|
|
586
619
|
ProcessStep.objects.filter(
|
|
587
|
-
process=process_step.process,
|
|
620
|
+
process=process_step.process,
|
|
621
|
+
state__in=[ProcessStep.StepState.WAITING, ProcessStep.StepState.ACTIVE],
|
|
588
622
|
)
|
|
589
623
|
.exclude(id=process_step.pk)
|
|
590
624
|
.exists()
|
|
591
625
|
):
|
|
592
|
-
self.set_failed(
|
|
626
|
+
self.set_failed(
|
|
627
|
+
process_step,
|
|
628
|
+
gettext("There are process steps still running for this workflow!"),
|
|
629
|
+
)
|
|
593
630
|
else:
|
|
594
631
|
self.set_finished(process_step)
|
|
595
632
|
|
|
@@ -726,7 +763,12 @@ def process_can_finish(process_id: UUID):
|
|
|
726
763
|
& Q(
|
|
727
764
|
Q(process_steps__isnull=True)
|
|
728
765
|
| Q(
|
|
729
|
-
Q(
|
|
766
|
+
Q(
|
|
767
|
+
process_steps__state__in=[
|
|
768
|
+
ProcessStep.StepState.ACTIVE,
|
|
769
|
+
ProcessStep.StepState.WAITING,
|
|
770
|
+
]
|
|
771
|
+
)
|
|
730
772
|
& Q(process_steps__process=process)
|
|
731
773
|
)
|
|
732
774
|
)
|
|
@@ -43,7 +43,7 @@ class DataModelSerializer(wb_serializers.ModelSerializer):
|
|
|
43
43
|
if data_type:
|
|
44
44
|
try:
|
|
45
45
|
Data.cast_value_to_datatype(data_type, default)
|
|
46
|
-
except ValueError:
|
|
46
|
+
except ValueError as e:
|
|
47
47
|
if data_type == Data.DataType.DATE:
|
|
48
48
|
raise ValidationError(
|
|
49
49
|
{
|
|
@@ -51,7 +51,7 @@ class DataModelSerializer(wb_serializers.ModelSerializer):
|
|
|
51
51
|
"Invalid default value for this data type. Please use a date formatted to 'day.month.year'."
|
|
52
52
|
)
|
|
53
53
|
}
|
|
54
|
-
)
|
|
54
|
+
) from None
|
|
55
55
|
elif data_type == Data.DataType.DATETIME:
|
|
56
56
|
raise ValidationError(
|
|
57
57
|
{
|
|
@@ -59,8 +59,8 @@ class DataModelSerializer(wb_serializers.ModelSerializer):
|
|
|
59
59
|
"Invalid default value for this data type. Please use a datetime formatted to 'day.month.year hour:minute:second' in the 24h format."
|
|
60
60
|
)
|
|
61
61
|
}
|
|
62
|
-
)
|
|
63
|
-
raise ValidationError({"default": _("Invalid default value for this data type.")})
|
|
62
|
+
) from None
|
|
63
|
+
raise ValidationError({"default": _("Invalid default value for this data type.")}) from e
|
|
64
64
|
|
|
65
65
|
return data
|
|
66
66
|
|
|
@@ -99,7 +99,7 @@ class DataValueModelSerializer(wb_serializers.ModelSerializer):
|
|
|
99
99
|
if data_obj and value:
|
|
100
100
|
try:
|
|
101
101
|
Data.cast_value_to_datatype(data_obj.data_type, value)
|
|
102
|
-
except ValueError:
|
|
102
|
+
except ValueError as e:
|
|
103
103
|
if data_obj.data_type == Data.DataType.DATE:
|
|
104
104
|
raise ValidationError(
|
|
105
105
|
{
|
|
@@ -107,7 +107,7 @@ class DataValueModelSerializer(wb_serializers.ModelSerializer):
|
|
|
107
107
|
"Invalid value for this data type. Please use a date formatted to 'day.month.year'."
|
|
108
108
|
)
|
|
109
109
|
}
|
|
110
|
-
)
|
|
110
|
+
) from None
|
|
111
111
|
elif data_obj.data_type == Data.DataType.DATETIME:
|
|
112
112
|
raise ValidationError(
|
|
113
113
|
{
|
|
@@ -115,8 +115,8 @@ class DataValueModelSerializer(wb_serializers.ModelSerializer):
|
|
|
115
115
|
"Invalid value for this data type. Please use a datetime formatted to 'day.month.year hour:minute:second' in the 24h format."
|
|
116
116
|
)
|
|
117
117
|
}
|
|
118
|
-
)
|
|
119
|
-
raise ValidationError({"value": _("Invalid value for this data type.")})
|
|
118
|
+
) from None
|
|
119
|
+
raise ValidationError({"value": _("Invalid value for this data type.")}) from e
|
|
120
120
|
|
|
121
121
|
return data
|
|
122
122
|
|
|
@@ -35,7 +35,9 @@ class ProcessModelSerializer(wb_serializers.ModelSerializer):
|
|
|
35
35
|
return {}
|
|
36
36
|
return {
|
|
37
37
|
"process_steps": reverse(
|
|
38
|
-
"wbcore:workflow:processstep-process-list",
|
|
38
|
+
"wbcore:workflow:processstep-process-list",
|
|
39
|
+
args=[instance.pk],
|
|
40
|
+
request=self.context["request"],
|
|
39
41
|
)
|
|
40
42
|
}
|
|
41
43
|
|
|
@@ -154,7 +156,6 @@ class AssignedProcessStepSerializer(wb_serializers.ModelSerializer):
|
|
|
154
156
|
|
|
155
157
|
if not self.context.get("request"):
|
|
156
158
|
return ""
|
|
157
|
-
|
|
158
159
|
instance_object = obj.process.instance if obj.process.instance else obj
|
|
159
160
|
return reverse(
|
|
160
161
|
f"{instance_object.get_endpoint_basename()}-detail",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from django.apps import apps
|
|
3
3
|
from django.db.backends.base.base import BaseDatabaseWrapper
|
|
4
|
+
from pytest_mock import MockerFixture
|
|
5
|
+
from rest_framework.test import APIRequestFactory, APIClient
|
|
4
6
|
from django.db.models.signals import pre_migrate
|
|
5
7
|
from pytest_factoryboy import register
|
|
6
8
|
from wbcore.contrib.geography.tests.signals import app_pre_migration
|
|
@@ -23,6 +25,24 @@ from wbcore.contrib.workflow.factories import (
|
|
|
23
25
|
UserStepFactory,
|
|
24
26
|
WorkflowFactory,
|
|
25
27
|
)
|
|
28
|
+
from wbcore.contrib.authentication.factories import PermissionFactory
|
|
29
|
+
|
|
30
|
+
from wbcore.contrib.workflow.models import (
|
|
31
|
+
ProcessStep,
|
|
32
|
+
Step,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from wbcore.contrib.workflow.serializers import (
|
|
36
|
+
AssignedProcessStepSerializer,
|
|
37
|
+
ConditionModelSerializer,
|
|
38
|
+
DataModelSerializer,
|
|
39
|
+
DecisionStepModelSerializer,
|
|
40
|
+
ProcessModelSerializer,
|
|
41
|
+
ProcessStepModelSerializer,
|
|
42
|
+
TransitionModelSerializer,
|
|
43
|
+
WorkflowModelSerializer,
|
|
44
|
+
)
|
|
45
|
+
|
|
26
46
|
from wbcore.tests.conftest import *
|
|
27
47
|
|
|
28
48
|
register(WorkflowFactory)
|
|
@@ -44,6 +64,210 @@ register(DataValueFactory)
|
|
|
44
64
|
register(StartStepFactory)
|
|
45
65
|
|
|
46
66
|
|
|
67
|
+
# ============================== General Fixtures ==============================
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.fixture
|
|
71
|
+
def api_client(super_user):
|
|
72
|
+
client = APIClient()
|
|
73
|
+
client.force_authenticate(user=super_user)
|
|
74
|
+
return client
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.fixture
|
|
78
|
+
def request_factory():
|
|
79
|
+
return APIRequestFactory()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.fixture
|
|
83
|
+
def super_user(django_db_setup, django_db_blocker):
|
|
84
|
+
return UserFactory(is_superuser=True)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.fixture
|
|
88
|
+
def user(django_db_setup, django_db_blocker):
|
|
89
|
+
with django_db_blocker.unblock():
|
|
90
|
+
return UserFactory(is_superuser=False)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@pytest.fixture()
|
|
94
|
+
def user_step():
|
|
95
|
+
return UserStepFactory(step_type=Step.StepType.USERSTEP)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.fixture
|
|
99
|
+
def permission(django_db_setup, django_db_blocker):
|
|
100
|
+
with django_db_blocker.unblock():
|
|
101
|
+
return PermissionFactory()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ============================== Condition Fixtures ==============================
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@pytest.fixture
|
|
108
|
+
def condition(conditions):
|
|
109
|
+
return conditions[0]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@pytest.fixture
|
|
113
|
+
def condition_build():
|
|
114
|
+
transition = TransitionFactory()
|
|
115
|
+
instance = ConditionFactory.build(transition=transition)
|
|
116
|
+
return ConditionModelSerializer(instance).data
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@pytest.fixture
|
|
120
|
+
def conditions():
|
|
121
|
+
return ConditionFactory.create_batch(3)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ============================== Data Fixtures ==============================
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@pytest.fixture
|
|
128
|
+
def data():
|
|
129
|
+
return DataFactory.create_batch(3)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@pytest.fixture
|
|
133
|
+
def data_build(workflow):
|
|
134
|
+
instance = DataFactory.build(workflow=workflow)
|
|
135
|
+
return DataModelSerializer(instance).data
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.fixture
|
|
139
|
+
def singular_data(data):
|
|
140
|
+
return data[0]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ============================== Decision Step Fixtures ==============================
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@pytest.fixture
|
|
147
|
+
def decision_step(decision_steps):
|
|
148
|
+
return decision_steps[0]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@pytest.fixture
|
|
152
|
+
def decision_step_build(workflow):
|
|
153
|
+
instance = DecisionStepFactory.build(workflow=workflow)
|
|
154
|
+
return DecisionStepModelSerializer(instance).data
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@pytest.fixture
|
|
158
|
+
def decision_steps():
|
|
159
|
+
return DecisionStepFactory.create_batch(3)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ============================== Process Fixtures ==============================
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@pytest.fixture
|
|
166
|
+
def process(processes):
|
|
167
|
+
return processes[0]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@pytest.fixture
|
|
171
|
+
def process_build():
|
|
172
|
+
instance = ProcessFactory.build()
|
|
173
|
+
return ProcessModelSerializer(instance).data
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@pytest.fixture
|
|
177
|
+
def processes():
|
|
178
|
+
return ProcessFactory.create_batch(3)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ============================== Process Step Fixtures ==============================
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@pytest.fixture
|
|
185
|
+
def process_step(process_steps):
|
|
186
|
+
return process_steps[0]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@pytest.fixture
|
|
190
|
+
def process_steps():
|
|
191
|
+
return ProcessStepFactory.create_batch(3)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@pytest.fixture
|
|
195
|
+
def process_steps_build():
|
|
196
|
+
instance = ProcessStepFactory.build()
|
|
197
|
+
return ProcessStepModelSerializer(instance).data
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@pytest.fixture
|
|
201
|
+
def mocked_process_step(mocker: MockerFixture):
|
|
202
|
+
return mocker.MagicMock(spec=ProcessStep)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# ============================== Assigned Process Step Fixtures ==============================
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@pytest.fixture
|
|
209
|
+
def assigned_process_step(assigned_process_steps):
|
|
210
|
+
return assigned_process_steps[0]
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@pytest.fixture
|
|
214
|
+
def assigned_process_steps(super_user, user_step):
|
|
215
|
+
return ProcessStepFactory.create_batch(
|
|
216
|
+
3,
|
|
217
|
+
step=user_step,
|
|
218
|
+
state=ProcessStep.StepState.ACTIVE,
|
|
219
|
+
assignee=super_user,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@pytest.fixture
|
|
224
|
+
def assigned_process_steps_build(super_user, user_step):
|
|
225
|
+
instance = ProcessStepFactory.build(
|
|
226
|
+
step=user_step, state=ProcessStep.StepState.ACTIVE, assignee=super_user
|
|
227
|
+
)
|
|
228
|
+
return AssignedProcessStepSerializer(instance).data
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# ============================== Transition Fixtures ==============================
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@pytest.fixture
|
|
235
|
+
def transition(transitions):
|
|
236
|
+
return transitions[0]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@pytest.fixture
|
|
240
|
+
def transition_build():
|
|
241
|
+
to_step = FinishStepFactory()
|
|
242
|
+
from_step = StartStepFactory(workflow=to_step.workflow)
|
|
243
|
+
instance = TransitionFactory.build(to_step=to_step, from_step=from_step)
|
|
244
|
+
return TransitionModelSerializer(instance).data
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@pytest.fixture
|
|
248
|
+
def transitions():
|
|
249
|
+
return TransitionFactory.create_batch(3)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# ============================== Workflow Fixtures ==============================
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@pytest.fixture
|
|
256
|
+
def workflow(workflows):
|
|
257
|
+
return workflows[0]
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@pytest.fixture
|
|
261
|
+
def workflow_build():
|
|
262
|
+
instance = WorkflowFactory.build()
|
|
263
|
+
return WorkflowModelSerializer(instance).data
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@pytest.fixture
|
|
267
|
+
def workflows():
|
|
268
|
+
return WorkflowFactory.create_batch(3)
|
|
269
|
+
|
|
270
|
+
|
|
47
271
|
@pytest.fixture(autouse=True, scope="session")
|
|
48
272
|
def django_test_environment(django_test_environment):
|
|
49
273
|
from django.apps import apps
|