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
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# SOME DESCRIPTIVE TITLE.
|
|
2
|
+
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
|
+
# This file is distributed under the same license as the PACKAGE package.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
5
|
+
#
|
|
6
|
+
msgid ""
|
|
7
|
+
msgstr ""
|
|
8
|
+
"Project-Id-Version: PACKAGE VERSION\n"
|
|
9
|
+
"Report-Msgid-Bugs-To: \n"
|
|
10
|
+
"POT-Creation-Date: 2025-05-30 12:10+0200\n"
|
|
11
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
12
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
13
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
14
|
+
"Language: \n"
|
|
15
|
+
"MIME-Version: 1.0\n"
|
|
16
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
17
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
18
|
+
|
|
19
|
+
#: contrib/io/models.py:95
|
|
20
|
+
msgid "Parser-Handler"
|
|
21
|
+
msgstr ""
|
|
22
|
+
|
|
23
|
+
#: contrib/io/models.py:96
|
|
24
|
+
msgid "Parsers-Handlers"
|
|
25
|
+
msgstr ""
|
|
26
|
+
|
|
27
|
+
#: contrib/io/models.py:133
|
|
28
|
+
msgid "Content object Provider Identifier relationship"
|
|
29
|
+
msgstr ""
|
|
30
|
+
|
|
31
|
+
#: contrib/io/models.py:134
|
|
32
|
+
msgid "Content object Provider Identifier relationships"
|
|
33
|
+
msgstr ""
|
|
34
|
+
|
|
35
|
+
#: contrib/io/models.py:161 contrib/io/models.py:187
|
|
36
|
+
msgid "Provider"
|
|
37
|
+
msgstr ""
|
|
38
|
+
|
|
39
|
+
#: contrib/io/models.py:162
|
|
40
|
+
msgid "Providers"
|
|
41
|
+
msgstr ""
|
|
42
|
+
|
|
43
|
+
#: contrib/io/models.py:206 contrib/io/models.py:250
|
|
44
|
+
msgid "Data Backend"
|
|
45
|
+
msgstr ""
|
|
46
|
+
|
|
47
|
+
#: contrib/io/models.py:207
|
|
48
|
+
msgid "Data Backends"
|
|
49
|
+
msgstr ""
|
|
50
|
+
|
|
51
|
+
#: contrib/io/models.py:257
|
|
52
|
+
msgid "Crontab Schedule"
|
|
53
|
+
msgstr ""
|
|
54
|
+
|
|
55
|
+
#: contrib/io/models.py:258
|
|
56
|
+
msgid ""
|
|
57
|
+
"Crontab Schedule to run the task on. Set only one schedule type, leave the "
|
|
58
|
+
"others null."
|
|
59
|
+
msgstr ""
|
|
60
|
+
|
|
61
|
+
#: contrib/io/models.py:299
|
|
62
|
+
msgid "Source"
|
|
63
|
+
msgstr ""
|
|
64
|
+
|
|
65
|
+
#: contrib/io/models.py:300
|
|
66
|
+
msgid "Sources"
|
|
67
|
+
msgstr ""
|
|
68
|
+
|
|
69
|
+
#: contrib/io/models.py:688
|
|
70
|
+
msgid "Format of file to be exported"
|
|
71
|
+
msgstr ""
|
|
72
|
+
|
|
73
|
+
#: contrib/io/models.py:691
|
|
74
|
+
msgid "Export job Content Type"
|
|
75
|
+
msgstr ""
|
|
76
|
+
|
|
77
|
+
#: contrib/io/models.py:694
|
|
78
|
+
msgid "Resource path to use when exporting"
|
|
79
|
+
msgstr ""
|
|
80
|
+
|
|
81
|
+
#: contrib/io/models.py:697
|
|
82
|
+
msgid "SQL query to be executed"
|
|
83
|
+
msgstr ""
|
|
84
|
+
|
|
85
|
+
#: contrib/io/models.py:699
|
|
86
|
+
msgid "SQL query parameters to be used with the sql query"
|
|
87
|
+
msgstr ""
|
|
88
|
+
|
|
89
|
+
#: contrib/io/models.py:703
|
|
90
|
+
msgid "Export Source"
|
|
91
|
+
msgstr ""
|
|
92
|
+
|
|
93
|
+
#: contrib/io/models.py:704
|
|
94
|
+
msgid "Export Sources"
|
|
95
|
+
msgstr ""
|
|
96
|
+
|
|
97
|
+
#: contrib/io/models.py:761
|
|
98
|
+
msgid "Your export file is available"
|
|
99
|
+
msgstr ""
|
|
100
|
+
|
|
101
|
+
#: contrib/io/models.py:762
|
|
102
|
+
msgid ""
|
|
103
|
+
"<p>The export job you requested is finished and available for one hour.</p>"
|
|
104
|
+
msgstr ""
|
|
105
|
+
|
|
106
|
+
#: contrib/io/models.py:813
|
|
107
|
+
msgid "Import Source"
|
|
108
|
+
msgstr ""
|
|
109
|
+
|
|
110
|
+
#: contrib/io/models.py:814
|
|
111
|
+
msgid "Import Sources"
|
|
112
|
+
msgstr ""
|
|
113
|
+
|
|
114
|
+
#: contrib/io/models.py:1022
|
|
115
|
+
msgid "Import Credential"
|
|
116
|
+
msgstr ""
|
|
117
|
+
|
|
118
|
+
#: contrib/io/models.py:1023
|
|
119
|
+
msgid "Import Credentials"
|
|
120
|
+
msgstr ""
|
|
121
|
+
|
|
122
|
+
#: contrib/io/viewset_mixins.py:240 contrib/io/viewset_mixins.py:241
|
|
123
|
+
#: contrib/io/viewset_mixins.py:242
|
|
124
|
+
msgid "Import"
|
|
125
|
+
msgstr ""
|
|
126
|
+
|
|
127
|
+
#: contrib/io/viewset_mixins.py:243
|
|
128
|
+
msgid "Please provide a valid CSV file (Coma Separated Value)"
|
|
129
|
+
msgstr ""
|
|
130
|
+
|
|
131
|
+
#: contrib/io/viewset_mixins.py:258 contrib/io/viewset_mixins.py:259
|
|
132
|
+
#: contrib/io/viewset_mixins.py:260
|
|
133
|
+
msgid "Export"
|
|
134
|
+
msgstr ""
|
|
135
|
+
|
|
136
|
+
#: contrib/io/viewset_mixins.py:261
|
|
137
|
+
msgid "Select the export format"
|
|
138
|
+
msgstr ""
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# SOME DESCRIPTIVE TITLE.
|
|
2
|
+
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
|
+
# This file is distributed under the same license as the PACKAGE package.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
5
|
+
#
|
|
6
|
+
msgid ""
|
|
7
|
+
msgstr ""
|
|
8
|
+
"Project-Id-Version: PACKAGE VERSION\n"
|
|
9
|
+
"Report-Msgid-Bugs-To: \n"
|
|
10
|
+
"POT-Creation-Date: 2025-05-30 12:10+0200\n"
|
|
11
|
+
"PO-Revision-Date: 2025-05-30 09:40+0000\n"
|
|
12
|
+
"Language-Team: French (https://app.transifex.com/stainly/teams/171242/fr/)\n"
|
|
13
|
+
"MIME-Version: 1.0\n"
|
|
14
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
15
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
16
|
+
"Language: fr\n"
|
|
17
|
+
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
|
18
|
+
|
|
19
|
+
#: contrib/io/models.py:95
|
|
20
|
+
msgid "Parser-Handler"
|
|
21
|
+
msgstr ""
|
|
22
|
+
|
|
23
|
+
#: contrib/io/models.py:96
|
|
24
|
+
msgid "Parsers-Handlers"
|
|
25
|
+
msgstr ""
|
|
26
|
+
|
|
27
|
+
#: contrib/io/models.py:133
|
|
28
|
+
msgid "Content object Provider Identifier relationship"
|
|
29
|
+
msgstr ""
|
|
30
|
+
|
|
31
|
+
#: contrib/io/models.py:134
|
|
32
|
+
msgid "Content object Provider Identifier relationships"
|
|
33
|
+
msgstr ""
|
|
34
|
+
|
|
35
|
+
#: contrib/io/models.py:161 contrib/io/models.py:187
|
|
36
|
+
msgid "Provider"
|
|
37
|
+
msgstr ""
|
|
38
|
+
|
|
39
|
+
#: contrib/io/models.py:162
|
|
40
|
+
msgid "Providers"
|
|
41
|
+
msgstr ""
|
|
42
|
+
|
|
43
|
+
#: contrib/io/models.py:206 contrib/io/models.py:250
|
|
44
|
+
msgid "Data Backend"
|
|
45
|
+
msgstr ""
|
|
46
|
+
|
|
47
|
+
#: contrib/io/models.py:207
|
|
48
|
+
msgid "Data Backends"
|
|
49
|
+
msgstr ""
|
|
50
|
+
|
|
51
|
+
#: contrib/io/models.py:257
|
|
52
|
+
msgid "Crontab Schedule"
|
|
53
|
+
msgstr ""
|
|
54
|
+
|
|
55
|
+
#: contrib/io/models.py:258
|
|
56
|
+
msgid ""
|
|
57
|
+
"Crontab Schedule to run the task on. Set only one schedule type, leave the "
|
|
58
|
+
"others null."
|
|
59
|
+
msgstr ""
|
|
60
|
+
|
|
61
|
+
#: contrib/io/models.py:299
|
|
62
|
+
msgid "Source"
|
|
63
|
+
msgstr ""
|
|
64
|
+
|
|
65
|
+
#: contrib/io/models.py:300
|
|
66
|
+
msgid "Sources"
|
|
67
|
+
msgstr ""
|
|
68
|
+
|
|
69
|
+
#: contrib/io/models.py:688
|
|
70
|
+
msgid "Format of file to be exported"
|
|
71
|
+
msgstr ""
|
|
72
|
+
|
|
73
|
+
#: contrib/io/models.py:691
|
|
74
|
+
msgid "Export job Content Type"
|
|
75
|
+
msgstr ""
|
|
76
|
+
|
|
77
|
+
#: contrib/io/models.py:694
|
|
78
|
+
msgid "Resource path to use when exporting"
|
|
79
|
+
msgstr ""
|
|
80
|
+
|
|
81
|
+
#: contrib/io/models.py:697
|
|
82
|
+
msgid "SQL query to be executed"
|
|
83
|
+
msgstr ""
|
|
84
|
+
|
|
85
|
+
#: contrib/io/models.py:699
|
|
86
|
+
msgid "SQL query parameters to be used with the sql query"
|
|
87
|
+
msgstr ""
|
|
88
|
+
|
|
89
|
+
#: contrib/io/models.py:703
|
|
90
|
+
msgid "Export Source"
|
|
91
|
+
msgstr ""
|
|
92
|
+
|
|
93
|
+
#: contrib/io/models.py:704
|
|
94
|
+
msgid "Export Sources"
|
|
95
|
+
msgstr ""
|
|
96
|
+
|
|
97
|
+
#: contrib/io/models.py:761
|
|
98
|
+
msgid "Your export file is available"
|
|
99
|
+
msgstr ""
|
|
100
|
+
|
|
101
|
+
#: contrib/io/models.py:762
|
|
102
|
+
msgid ""
|
|
103
|
+
"<p>The export job you requested is finished and available for one hour.</p>"
|
|
104
|
+
msgstr ""
|
|
105
|
+
|
|
106
|
+
#: contrib/io/models.py:813
|
|
107
|
+
msgid "Import Source"
|
|
108
|
+
msgstr ""
|
|
109
|
+
|
|
110
|
+
#: contrib/io/models.py:814
|
|
111
|
+
msgid "Import Sources"
|
|
112
|
+
msgstr ""
|
|
113
|
+
|
|
114
|
+
#: contrib/io/models.py:1022
|
|
115
|
+
msgid "Import Credential"
|
|
116
|
+
msgstr ""
|
|
117
|
+
|
|
118
|
+
#: contrib/io/models.py:1023
|
|
119
|
+
msgid "Import Credentials"
|
|
120
|
+
msgstr ""
|
|
121
|
+
|
|
122
|
+
#: contrib/io/viewset_mixins.py:240 contrib/io/viewset_mixins.py:241
|
|
123
|
+
#: contrib/io/viewset_mixins.py:242
|
|
124
|
+
msgid "Import"
|
|
125
|
+
msgstr ""
|
|
126
|
+
|
|
127
|
+
#: contrib/io/viewset_mixins.py:243
|
|
128
|
+
msgid "Please provide a valid CSV file (Coma Separated Value)"
|
|
129
|
+
msgstr ""
|
|
130
|
+
|
|
131
|
+
#: contrib/io/viewset_mixins.py:258 contrib/io/viewset_mixins.py:259
|
|
132
|
+
#: contrib/io/viewset_mixins.py:260
|
|
133
|
+
msgid "Export"
|
|
134
|
+
msgstr ""
|
|
135
|
+
|
|
136
|
+
#: contrib/io/viewset_mixins.py:261
|
|
137
|
+
msgid "Select the export format"
|
|
138
|
+
msgstr ""
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django 5.0.12 on 2025-02-18 09:04
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('io', '0007_alter_exportsource_query_params'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name='importsource',
|
|
15
|
+
name='resource_kwargs',
|
|
16
|
+
field=models.JSONField(blank=True, default=dict),
|
|
17
|
+
),
|
|
18
|
+
]
|
wbcore/contrib/io/models.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import importlib
|
|
2
|
+
import io
|
|
2
3
|
import json
|
|
3
4
|
import logging
|
|
4
5
|
import os
|
|
@@ -40,7 +41,7 @@ from wbcore.utils.importlib import import_from_dotted_path
|
|
|
40
41
|
from wbcore.utils.models import ComplexToStringMixin
|
|
41
42
|
|
|
42
43
|
from .enums import ExportFormat, get_django_import_export_format
|
|
43
|
-
from .
|
|
44
|
+
from .signals import post_import
|
|
44
45
|
|
|
45
46
|
logger = logging.getLogger("io")
|
|
46
47
|
|
|
@@ -60,6 +61,10 @@ class ParserHandler(models.Model):
|
|
|
60
61
|
def __str__(self) -> str:
|
|
61
62
|
return f"{self.parser}::{self.handler}"
|
|
62
63
|
|
|
64
|
+
@cached_property
|
|
65
|
+
def model(self):
|
|
66
|
+
return apps.get_model(self.handler)
|
|
67
|
+
|
|
63
68
|
def parse(self, import_source: "ImportSource") -> Dict[str, Any]:
|
|
64
69
|
"""
|
|
65
70
|
call the parser on the provided data
|
|
@@ -80,30 +85,29 @@ class ParserHandler(models.Model):
|
|
|
80
85
|
parsed_data: The parsed_data
|
|
81
86
|
"""
|
|
82
87
|
if parsed_data:
|
|
83
|
-
|
|
84
|
-
if handler_class := getattr(model, "import_export_handler_class", None):
|
|
88
|
+
if handler_class := getattr(self.model, "import_export_handler_class", None):
|
|
85
89
|
handler = handler_class(import_source)
|
|
86
90
|
handler.process(parsed_data, **kwargs)
|
|
87
91
|
|
|
88
92
|
class Meta:
|
|
89
93
|
unique_together = ("parser", "handler")
|
|
90
|
-
verbose_name = "Parser-Handler"
|
|
91
|
-
verbose_name_plural = "Parsers-Handlers"
|
|
94
|
+
verbose_name = _("Parser-Handler")
|
|
95
|
+
verbose_name_plural = _("Parsers-Handlers")
|
|
92
96
|
|
|
93
97
|
@classmethod
|
|
94
|
-
def get_representation_value_key(
|
|
98
|
+
def get_representation_value_key(cls) -> str:
|
|
95
99
|
return "id"
|
|
96
100
|
|
|
97
101
|
@classmethod
|
|
98
|
-
def get_representation_label_key(
|
|
102
|
+
def get_representation_label_key(cls) -> str:
|
|
99
103
|
return "{{parser}}::{{handler}}"
|
|
100
104
|
|
|
101
105
|
@classmethod
|
|
102
|
-
def get_representation_endpoint(
|
|
106
|
+
def get_representation_endpoint(cls) -> str:
|
|
103
107
|
return "wbcore:io:parserhandlerrepresentation-list"
|
|
104
108
|
|
|
105
109
|
|
|
106
|
-
class ImportedObjectProviderRelationship(ComplexToStringMixin
|
|
110
|
+
class ImportedObjectProviderRelationship(ComplexToStringMixin):
|
|
107
111
|
"""
|
|
108
112
|
A model that represent the relationship/link between the imported object and a provider.
|
|
109
113
|
|
|
@@ -125,8 +129,8 @@ class ImportedObjectProviderRelationship(ComplexToStringMixin, models.Model):
|
|
|
125
129
|
return f"{self.provider.title} ({self.provider_identifier})"
|
|
126
130
|
|
|
127
131
|
class Meta:
|
|
128
|
-
verbose_name = "Content object Provider Identifier relationship"
|
|
129
|
-
verbose_name_plural = "Content object Provider Identifier relationships"
|
|
132
|
+
verbose_name = _("Content object Provider Identifier relationship")
|
|
133
|
+
verbose_name_plural = _("Content object Provider Identifier relationships")
|
|
130
134
|
unique_together = [
|
|
131
135
|
("content_type", "object_id", "provider"),
|
|
132
136
|
("content_type", "provider_identifier", "provider"),
|
|
@@ -153,8 +157,8 @@ class Provider(models.Model):
|
|
|
153
157
|
return f"{self.title} ({self.key})"
|
|
154
158
|
|
|
155
159
|
class Meta:
|
|
156
|
-
verbose_name = "Provider"
|
|
157
|
-
verbose_name_plural = "Providers"
|
|
160
|
+
verbose_name = _("Provider")
|
|
161
|
+
verbose_name_plural = _("Providers")
|
|
158
162
|
|
|
159
163
|
|
|
160
164
|
class DataBackend(models.Model):
|
|
@@ -198,8 +202,8 @@ class DataBackend(models.Model):
|
|
|
198
202
|
return rel.content_object
|
|
199
203
|
|
|
200
204
|
class Meta:
|
|
201
|
-
verbose_name = "Data Backend"
|
|
202
|
-
verbose_name_plural = "Data Backends"
|
|
205
|
+
verbose_name = _("Data Backend")
|
|
206
|
+
verbose_name_plural = _("Data Backends")
|
|
203
207
|
|
|
204
208
|
@cached_property
|
|
205
209
|
def backend_class(self) -> Any:
|
|
@@ -214,15 +218,15 @@ class DataBackend(models.Model):
|
|
|
214
218
|
return self.title
|
|
215
219
|
|
|
216
220
|
@classmethod
|
|
217
|
-
def get_representation_value_key(
|
|
221
|
+
def get_representation_value_key(cls) -> str:
|
|
218
222
|
return "id"
|
|
219
223
|
|
|
220
224
|
@classmethod
|
|
221
|
-
def get_representation_label_key(
|
|
225
|
+
def get_representation_label_key(cls) -> str:
|
|
222
226
|
return "{{title}}"
|
|
223
227
|
|
|
224
228
|
@classmethod
|
|
225
|
-
def get_representation_endpoint(
|
|
229
|
+
def get_representation_endpoint(cls) -> str:
|
|
226
230
|
return "wbcore:io:databackendrepresentation-list"
|
|
227
231
|
|
|
228
232
|
|
|
@@ -291,8 +295,8 @@ class Source(models.Model):
|
|
|
291
295
|
return self.title if self.title else f"{self.uuid}"
|
|
292
296
|
|
|
293
297
|
class Meta:
|
|
294
|
-
verbose_name = "Source"
|
|
295
|
-
verbose_name_plural = "Sources"
|
|
298
|
+
verbose_name = _("Source")
|
|
299
|
+
verbose_name_plural = _("Sources")
|
|
296
300
|
|
|
297
301
|
@property
|
|
298
302
|
def current_valid_credential(self) -> "ImportCredential":
|
|
@@ -518,7 +522,7 @@ class Source(models.Model):
|
|
|
518
522
|
return res
|
|
519
523
|
|
|
520
524
|
@classmethod
|
|
521
|
-
def load_sources_from_settings(
|
|
525
|
+
def load_sources_from_settings(cls, settings: list[tuple[list[tuple[str, str]], str, dict[str, Any]]]):
|
|
522
526
|
"""
|
|
523
527
|
Utility classmethod to parser sources from the settings.
|
|
524
528
|
|
|
@@ -584,15 +588,15 @@ class Source(models.Model):
|
|
|
584
588
|
source.save()
|
|
585
589
|
|
|
586
590
|
@classmethod
|
|
587
|
-
def get_representation_value_key(
|
|
591
|
+
def get_representation_value_key(cls) -> str:
|
|
588
592
|
return "id"
|
|
589
593
|
|
|
590
594
|
@classmethod
|
|
591
|
-
def get_representation_label_key(
|
|
595
|
+
def get_representation_label_key(cls) -> str:
|
|
592
596
|
return "{{title}} ({{id}})"
|
|
593
597
|
|
|
594
598
|
@classmethod
|
|
595
|
-
def get_representation_endpoint(
|
|
599
|
+
def get_representation_endpoint(cls) -> str:
|
|
596
600
|
return "wbcore:io:sourcerepresentation-list"
|
|
597
601
|
|
|
598
602
|
|
|
@@ -635,15 +639,9 @@ def process_import_sources_as_task(import_source_ids: list[int]):
|
|
|
635
639
|
"""
|
|
636
640
|
Call the `import_source` as a celery task
|
|
637
641
|
"""
|
|
638
|
-
errors = []
|
|
639
642
|
for import_source_id in import_source_ids:
|
|
640
643
|
import_source = ImportSource.objects.get(id=import_source_id)
|
|
641
|
-
|
|
642
|
-
import_source.import_data(force_reimport=True)
|
|
643
|
-
except ImportError:
|
|
644
|
-
errors.append(import_source_id)
|
|
645
|
-
if len(errors) > 0:
|
|
646
|
-
raise ImportError(f"Error while processing import source {errors}")
|
|
644
|
+
import_source.import_data(force_reimport=True)
|
|
647
645
|
|
|
648
646
|
|
|
649
647
|
class ImportExportSource(models.Model):
|
|
@@ -669,6 +667,7 @@ class ImportExportSource(models.Model):
|
|
|
669
667
|
"authentication.User", null=True, blank=True, verbose_name="Creator", on_delete=models.SET_NULL
|
|
670
668
|
)
|
|
671
669
|
data = models.JSONField(default=dict, null=True, blank=True)
|
|
670
|
+
resource_kwargs = models.JSONField(default=dict, blank=True, null=False)
|
|
672
671
|
|
|
673
672
|
class Meta:
|
|
674
673
|
abstract = True
|
|
@@ -688,8 +687,6 @@ class ExportSource(ImportExportSource):
|
|
|
688
687
|
verbose_name=_("Resource path to use when exporting"), max_length=255, default="", blank=True, null=True
|
|
689
688
|
)
|
|
690
689
|
|
|
691
|
-
resource_kwargs = models.JSONField(default=dict, blank=True, null=False)
|
|
692
|
-
|
|
693
690
|
query_str = models.TextField(verbose_name=_("SQL query to be executed"), blank=True, null=False)
|
|
694
691
|
query_params = PickledObjectField( # we have to use picklefield because the sql parameters are given as python object. However, no django model should be stored in this field, which should be the case as these objects are given through their IDs
|
|
695
692
|
blank=True, verbose_name=_("SQL query parameters to be used with the sql query"), default=list
|
|
@@ -716,6 +713,9 @@ class ExportSource(ImportExportSource):
|
|
|
716
713
|
)
|
|
717
714
|
]
|
|
718
715
|
|
|
716
|
+
def __str__(self) -> str:
|
|
717
|
+
return str(self.id)
|
|
718
|
+
|
|
719
719
|
@property
|
|
720
720
|
def file_format(self):
|
|
721
721
|
return get_django_import_export_format(self.format)()
|
|
@@ -793,6 +793,7 @@ class ExportSource(ImportExportSource):
|
|
|
793
793
|
self.log = f"{ex_type}: {ex_value}\n"
|
|
794
794
|
self.log += traceback.format_exc()
|
|
795
795
|
self.save()
|
|
796
|
+
logger.error(f"Export source {self.id} error: {e}")
|
|
796
797
|
|
|
797
798
|
|
|
798
799
|
class ImportSource(ImportExportSource):
|
|
@@ -806,8 +807,8 @@ class ImportSource(ImportExportSource):
|
|
|
806
807
|
errors_log = models.TextField(null=True, blank=True)
|
|
807
808
|
|
|
808
809
|
class Meta:
|
|
809
|
-
verbose_name = "Import Source"
|
|
810
|
-
verbose_name_plural = "Import Sources"
|
|
810
|
+
verbose_name = _("Import Source")
|
|
811
|
+
verbose_name_plural = _("Import Sources")
|
|
811
812
|
notification_types = [
|
|
812
813
|
create_notification_type(
|
|
813
814
|
code="io.import_done",
|
|
@@ -884,29 +885,48 @@ class ImportSource(ImportExportSource):
|
|
|
884
885
|
try:
|
|
885
886
|
data = self._parse_data()
|
|
886
887
|
self._process_data(data, debug=debug, **kwargs)
|
|
888
|
+
post_import.send(sender=self.parser_handler.model, import_source=self)
|
|
889
|
+
|
|
887
890
|
except Exception as e:
|
|
888
891
|
ex_type, ex_value, _ = sys.exc_info()
|
|
889
892
|
self.status = self.Status.ERROR.name
|
|
890
893
|
self.log = f"{ex_type}: {ex_value}\n"
|
|
891
894
|
self.log += traceback.format_exc()
|
|
892
895
|
self.save()
|
|
893
|
-
if
|
|
894
|
-
raise ImportError(
|
|
895
|
-
f"Could not import file {self.file.name} for backend {self.source.data_backend.title} and parser/handler {self.parser_handler}"
|
|
896
|
-
)
|
|
897
|
-
else:
|
|
896
|
+
if debug:
|
|
898
897
|
raise e
|
|
898
|
+
elif not self.creator: # if a creator is set in this import source, they will receive a proper notification with feedback. No need then to pollute the logger
|
|
899
|
+
logger.error(f"Could not import file {self.file.name} (parser/handler: {self.parser_handler})")
|
|
900
|
+
self.notify()
|
|
901
|
+
|
|
902
|
+
def notify(self):
|
|
903
|
+
if self.creator:
|
|
904
|
+
body = f"""<p><strong>File Name Processed:</strong> {self.file.name}</p>
|
|
905
|
+
<p><strong>Number of Rows:</strong> {self.progress_index}</p>
|
|
906
|
+
<p><strong>Status:</strong> {self.Status[self.status].label}</p>"""
|
|
907
|
+
if self.status == self.Status.ERROR:
|
|
908
|
+
body += "<p>While processing the import, we encountered an unrecoverable error. Please contact a system administrator.</p>"
|
|
909
|
+
elif self.status == self.Status.WARNING and self.errors_log:
|
|
910
|
+
body += f"""<p><strong>Warning:</strong> Some rows were ignored during import.</p>
|
|
911
|
+
<p><strong>Ignored Rows:</strong></p>
|
|
912
|
+
<ul>{"".join(['<li>' + line + '</li>' for line in io.StringIO(self.errors_log)])}</ul>"""
|
|
913
|
+
send_notification(
|
|
914
|
+
code="io.import_done",
|
|
915
|
+
title=f"Your import finished with status {self.Status[self.status].label}",
|
|
916
|
+
body=body,
|
|
917
|
+
user=self.creator,
|
|
918
|
+
)
|
|
899
919
|
|
|
900
920
|
@classmethod
|
|
901
|
-
def get_representation_value_key(
|
|
921
|
+
def get_representation_value_key(cls) -> str:
|
|
902
922
|
return "id"
|
|
903
923
|
|
|
904
924
|
@classmethod
|
|
905
|
-
def get_representation_label_key(
|
|
925
|
+
def get_representation_label_key(cls) -> str:
|
|
906
926
|
return "{{file}}"
|
|
907
927
|
|
|
908
928
|
@classmethod
|
|
909
|
-
def get_representation_endpoint(
|
|
929
|
+
def get_representation_endpoint(cls) -> str:
|
|
910
930
|
return "wbcore:io:importsourcerepresentation-list"
|
|
911
931
|
|
|
912
932
|
|
|
@@ -996,8 +1016,8 @@ class ImportCredential(models.Model):
|
|
|
996
1016
|
super().save(*args, **kwargs)
|
|
997
1017
|
|
|
998
1018
|
class Meta:
|
|
999
|
-
verbose_name = "Import Credential"
|
|
1000
|
-
verbose_name_plural = "Import Credentials"
|
|
1019
|
+
verbose_name = _("Import Credential")
|
|
1020
|
+
verbose_name_plural = _("Import Credentials")
|
|
1001
1021
|
|
|
1002
1022
|
def __str__(self) -> str:
|
|
1003
1023
|
dates_repr = ""
|
wbcore/contrib/io/resources.py
CHANGED
|
@@ -171,12 +171,6 @@ class ViewResource(ExportResourceMixin, resources.Resource):
|
|
|
171
171
|
columns_map = {"id": "id"}
|
|
172
172
|
# Get default fields from the list display
|
|
173
173
|
if list_display := view.display_config_class(view, view.request, instance=None).get_list_display():
|
|
174
|
-
if list_display.tree_group_field:
|
|
175
|
-
columns_map[list_display.tree_group_field] = (
|
|
176
|
-
list_display.tree_group_label
|
|
177
|
-
if list_display.tree_group_label
|
|
178
|
-
else list_display.tree_group_field.title()
|
|
179
|
-
)
|
|
180
174
|
for key, label in list_display.flatten_fields:
|
|
181
175
|
columns_map[key] = label
|
|
182
176
|
return columns_map
|
wbcore/contrib/io/serializers.py
CHANGED
|
@@ -88,8 +88,8 @@ class ImportSourceModelSerializer(ModelSerializer):
|
|
|
88
88
|
parser_lookup = dict(id=parser_handler)
|
|
89
89
|
try:
|
|
90
90
|
data["parser_handler"] = ParserHandler.objects.get(**parser_lookup)
|
|
91
|
-
except (ParserHandler.DoesNotExist, ValueError):
|
|
92
|
-
raise ValidationError({"parser_handler": "Invalid parser handler"})
|
|
91
|
+
except (ParserHandler.DoesNotExist, ValueError) as e:
|
|
92
|
+
raise ValidationError({"parser_handler": "Invalid parser handler"}) from e
|
|
93
93
|
return data
|
|
94
94
|
|
|
95
95
|
def create(self, validated_data):
|
|
@@ -6,8 +6,6 @@ from anymail.signals import AnymailInboundEvent, EventType, inbound
|
|
|
6
6
|
from django.utils import timezone
|
|
7
7
|
from faker import Faker
|
|
8
8
|
|
|
9
|
-
from ..import_export.backends.mail import DataBackend as MailDataBackend
|
|
10
|
-
from ..import_export.backends.sftp import DataBackend as SFTPDataBackend
|
|
11
9
|
from ..models import Source
|
|
12
10
|
from .test_models import get_byte_stream
|
|
13
11
|
|
|
@@ -43,6 +41,8 @@ VFS = {
|
|
|
43
41
|
@pytest.mark.django_db
|
|
44
42
|
class TestBackend:
|
|
45
43
|
def test_mail_backend(self):
|
|
44
|
+
from wbcore.contrib.io.import_export.backends.mail import DataBackend as MailDataBackend
|
|
45
|
+
|
|
46
46
|
b1 = get_byte_stream()
|
|
47
47
|
b2 = get_byte_stream()
|
|
48
48
|
filename1 = "file_valid_12385.json"
|
|
@@ -69,15 +69,20 @@ class TestBackend:
|
|
|
69
69
|
assert (res[0][1]).getvalue() == b1.getvalue()
|
|
70
70
|
|
|
71
71
|
@pytest.mark.parametrize(
|
|
72
|
-
"source__import_parameters,
|
|
72
|
+
"source__import_parameters,from_email",
|
|
73
73
|
[
|
|
74
|
-
({"whitelisted_emails": ["test@test.ch"]}, "
|
|
75
|
-
({"whitelisted_emails": ["test@test.ch"]}, "
|
|
76
|
-
({"whitelisted_emails": ["test@test.ch"]}, "wbcore.contrib.io.backends.unvalid", "test@test.ch"),
|
|
74
|
+
({"whitelisted_emails": ["test@test.ch"]}, "test@test.ch"),
|
|
75
|
+
({"whitelisted_emails": ["test@test.ch"]}, "spam@ test.ch"),
|
|
77
76
|
],
|
|
78
77
|
)
|
|
79
|
-
@patch.object(Source, "
|
|
80
|
-
def test_inbound_mail(self, mock_process_source, source,
|
|
78
|
+
@patch.object(Source, "trigger_workflow")
|
|
79
|
+
def test_inbound_mail(self, mock_process_source, source, from_email):
|
|
80
|
+
from wbcore.contrib.io.models import DataBackend
|
|
81
|
+
|
|
82
|
+
data_backend = DataBackend.objects.get(title="Default Mail")
|
|
83
|
+
|
|
84
|
+
assert data_backend.backend_class_path == "wbcore.contrib.io.import_export.backends.mail"
|
|
85
|
+
|
|
81
86
|
source.data_backend = data_backend
|
|
82
87
|
source.save()
|
|
83
88
|
message = AnymailInboundMessage.construct(
|
|
@@ -95,23 +100,24 @@ class TestBackend:
|
|
|
95
100
|
message=message,
|
|
96
101
|
)
|
|
97
102
|
inbound.send(sender="TEST", event=event, esp_name="test")
|
|
98
|
-
if (
|
|
99
|
-
source.data_backend.backend_class_path == "wbcore.contrib.io.backends.mail"
|
|
100
|
-
and source.import_parameters.get("whitelisted_emails", [None])[0] == from_email
|
|
101
|
-
):
|
|
103
|
+
if source.import_parameters.get("whitelisted_emails", [None])[0] == from_email:
|
|
102
104
|
assert mock_process_source.call_count == 1
|
|
103
105
|
else:
|
|
104
106
|
assert mock_process_source.call_count == 0
|
|
105
107
|
|
|
106
108
|
def test_sftp_backend_without_credential(self):
|
|
109
|
+
from wbcore.contrib.io.import_export.backends.sftp import DataBackend as SFTPDataBackend
|
|
110
|
+
|
|
107
111
|
with pytest.raises(ValueError):
|
|
108
112
|
SFTPDataBackend()
|
|
109
113
|
|
|
110
114
|
def test_sftp_backend(self, sftpserver, import_credential_factory):
|
|
115
|
+
from wbcore.contrib.io.import_export.backends.sftp import DataBackend as SFTPDataBackend
|
|
116
|
+
|
|
111
117
|
with sftpserver.serve_content(VFS):
|
|
112
118
|
import_credential = import_credential_factory.create(
|
|
113
119
|
username="user",
|
|
114
|
-
password="password",
|
|
120
|
+
password="password", # noqa
|
|
115
121
|
additional_resources={
|
|
116
122
|
"host": sftpserver.host,
|
|
117
123
|
"port": sftpserver.port,
|