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,11 +28,12 @@ def handle_inbound(sender, event, esp_name, **kwargs):
|
|
|
28
28
|
conditions |= Q(import_parameters__inbound_address__contains=t.addr_spec)
|
|
29
29
|
|
|
30
30
|
sources = Source.objects.filter(
|
|
31
|
-
conditions
|
|
31
|
+
conditions
|
|
32
|
+
& Q(data_backend__backend_class_path="wbcore.contrib.io.import_export.backends.mail")
|
|
33
|
+
& Q(is_active=True)
|
|
32
34
|
)
|
|
33
35
|
if s := re.search(r"\[([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\]", subject):
|
|
34
36
|
sources = sources.filter(uuid=s.group(1))
|
|
35
|
-
|
|
36
37
|
for source in sources:
|
|
37
38
|
if is_sender_allowed(from_email, source.import_parameters.get("whitelisted_emails", []), admin_emails):
|
|
38
39
|
source.trigger_workflow(message=message)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from contextlib import suppress
|
|
2
|
-
|
|
3
1
|
from ..models import DataBackend, Provider
|
|
4
2
|
|
|
5
3
|
|
|
@@ -21,22 +19,21 @@ def register(
|
|
|
21
19
|
raise ValueError("At least one name must be passed to register.")
|
|
22
20
|
|
|
23
21
|
def _decorator(backend_class):
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
key=provider_key, defaults={"title": provider_key.capitalize()}
|
|
29
|
-
)
|
|
30
|
-
DataBackend.objects.update_or_create(
|
|
31
|
-
backend_class_path=backend_class.__module__,
|
|
32
|
-
backend_class_name=backend_class.__name__,
|
|
33
|
-
defaults={
|
|
34
|
-
"title": f"{backend_title} ({provider.title})" if provider else backend_title,
|
|
35
|
-
"save_data_in_import_source": save_data_in_import_source,
|
|
36
|
-
"passive_only": passive_only,
|
|
37
|
-
"provider": provider,
|
|
38
|
-
},
|
|
22
|
+
provider = None
|
|
23
|
+
if provider_key:
|
|
24
|
+
provider, created = Provider.objects.get_or_create(
|
|
25
|
+
key=provider_key, defaults={"title": provider_key.capitalize()}
|
|
39
26
|
)
|
|
27
|
+
DataBackend.objects.update_or_create(
|
|
28
|
+
backend_class_path=backend_class.__module__,
|
|
29
|
+
backend_class_name=backend_class.__name__,
|
|
30
|
+
defaults={
|
|
31
|
+
"title": f"{backend_title} ({provider.title})" if provider else backend_title,
|
|
32
|
+
"save_data_in_import_source": save_data_in_import_source,
|
|
33
|
+
"passive_only": passive_only,
|
|
34
|
+
"provider": provider,
|
|
35
|
+
},
|
|
36
|
+
)
|
|
40
37
|
return backend_class
|
|
41
38
|
|
|
42
39
|
return _decorator
|
wbcore/contrib/io/exceptions.py
CHANGED
|
@@ -8,6 +8,14 @@ class DeserializationError(Exception):
|
|
|
8
8
|
pass
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
class SkipImportError(Exception):
|
|
12
|
+
"""
|
|
13
|
+
Exception excepted when a deserialized skip happens during the process object phase in the Handler.
|
|
14
|
+
|
|
15
|
+
This exception won't stop the importing process and won't trigger a warning
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
11
19
|
class ImportError(Exception):
|
|
12
20
|
"""
|
|
13
21
|
Exception returns when something wrong happens during the processing of the import source and means that not all data were succesfully imported
|
wbcore/contrib/io/factories.py
CHANGED
|
@@ -176,7 +176,7 @@ class SourceFactory(factory.django.DjangoModelFactory):
|
|
|
176
176
|
|
|
177
177
|
class ParserHandlerFactory(factory.django.DjangoModelFactory):
|
|
178
178
|
parser = factory.Sequence(lambda n: f"parser_{n}")
|
|
179
|
-
handler =
|
|
179
|
+
handler = "io.ImportModel"
|
|
180
180
|
allow_file_type = None
|
|
181
181
|
|
|
182
182
|
class Meta:
|
|
@@ -10,6 +10,7 @@ from wbcore.contrib.io.backends.abstract import AbstractDataBackend
|
|
|
10
10
|
from wbcore.contrib.io.backends.utils import register
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
#### IMPORTANT: If this class path change, it needs to be adapted also on the io.backends.handle_inbound function
|
|
13
14
|
@register("Default Mail", save_data_in_import_source=True, passive_only=True)
|
|
14
15
|
class DataBackend(AbstractDataBackend):
|
|
15
16
|
def get_files(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import io
|
|
2
|
+
import logging
|
|
2
3
|
import re
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
from io import BytesIO
|
|
@@ -6,10 +7,13 @@ from typing import Generator, Optional
|
|
|
6
7
|
|
|
7
8
|
import fabric
|
|
8
9
|
from django.conf import settings
|
|
10
|
+
from paramiko.ssh_exception import SSHException
|
|
9
11
|
from wbcore.contrib.io.backends.abstract import AbstractDataBackend
|
|
10
12
|
from wbcore.contrib.io.backends.utils import register
|
|
11
13
|
from wbcore.contrib.io.models import ImportCredential
|
|
12
14
|
|
|
15
|
+
logger = logging.getLogger("io")
|
|
16
|
+
|
|
13
17
|
|
|
14
18
|
@register("SFTP", save_data_in_import_source=True, passive_only=True)
|
|
15
19
|
class DataBackend(AbstractDataBackend):
|
|
@@ -25,6 +29,7 @@ class DataBackend(AbstractDataBackend):
|
|
|
25
29
|
self.password = import_credential.password
|
|
26
30
|
self.host = import_credential.additional_resources.get("host", "")
|
|
27
31
|
self.port = import_credential.additional_resources.get("port", "")
|
|
32
|
+
super().__init__(**kwargs)
|
|
28
33
|
|
|
29
34
|
def get_files(
|
|
30
35
|
self,
|
|
@@ -38,23 +43,27 @@ class DataBackend(AbstractDataBackend):
|
|
|
38
43
|
with fabric.Connection(
|
|
39
44
|
host=self.host, port=self.port, user=self.username, connect_kwargs={"password": self.password}
|
|
40
45
|
) as conn:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
try:
|
|
47
|
+
sftp = conn.sftp()
|
|
48
|
+
# Change working directory
|
|
49
|
+
sftp.chdir(sftp_folder)
|
|
50
|
+
|
|
51
|
+
# Filter all the files we need
|
|
52
|
+
file_names = sftp.listdir()
|
|
53
|
+
if file_name_regex:
|
|
54
|
+
# Compile the regex for later filtering the files
|
|
55
|
+
file_names = filter(lambda x: re.match(file_name_regex, x), file_names)
|
|
56
|
+
|
|
57
|
+
for file_name in file_names:
|
|
58
|
+
# Create a Buffer where we write the file to
|
|
59
|
+
sftp_file = io.BytesIO()
|
|
60
|
+
sftp.getfo(file_name, sftp_file)
|
|
61
|
+
yield file_name, sftp_file
|
|
62
|
+
|
|
63
|
+
if cleanup_files:
|
|
64
|
+
# Delete the file from the server
|
|
65
|
+
sftp.remove(file_name)
|
|
66
|
+
except SSHException as e:
|
|
67
|
+
logger.warning(
|
|
68
|
+
f"While fetching file from data backend {self.data_backend}, we encountered a SSH Exception: {e}"
|
|
69
|
+
)
|
|
@@ -35,7 +35,7 @@ class DataBackend(AbstractDataBackend):
|
|
|
35
35
|
self.url = url
|
|
36
36
|
|
|
37
37
|
@classmethod
|
|
38
|
-
def _check_content_type(
|
|
38
|
+
def _check_content_type(cls, output: BytesIO, filename: str) -> bool:
|
|
39
39
|
"""
|
|
40
40
|
Check if given bytes stream matches the corresponding filename extension
|
|
41
41
|
|
|
@@ -80,7 +80,7 @@ class DataBackend(AbstractDataBackend):
|
|
|
80
80
|
"""
|
|
81
81
|
with suppress(requests.ConnectionError):
|
|
82
82
|
headers = kwargs.get("headers", {})
|
|
83
|
-
r = requests.get(self.url, headers=headers)
|
|
83
|
+
r = requests.get(self.url, headers=headers, timeout=10)
|
|
84
84
|
if r.ok and (content := r.content):
|
|
85
85
|
content_file = BytesIO()
|
|
86
86
|
content_file.write(content)
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import re
|
|
3
|
+
from _csv import Error
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def parse(import_source):
|
|
10
|
+
# try to guess the delimiter
|
|
11
|
+
try:
|
|
12
|
+
with import_source.file.open(mode="r") as file:
|
|
13
|
+
sep = csv.Sniffer().sniff(file.readline()).delimiter
|
|
14
|
+
except Error:
|
|
15
|
+
sep = ","
|
|
16
|
+
|
|
17
|
+
df = pd.read_csv(import_source.file, sep=sep)
|
|
18
|
+
df = df.loc[:, ~df.columns.str.contains("^Unnamed")] # remove columns without names
|
|
19
|
+
# in case we have import kwargs, we set them as column values
|
|
20
|
+
columns_mapping = import_source.resource_kwargs.get("columns_mapping", {})
|
|
21
|
+
extra_columns_values = import_source.resource_kwargs.get("extra_columns_values", {})
|
|
22
|
+
for k, v in extra_columns_values.items():
|
|
23
|
+
df[k] = v
|
|
24
|
+
df = df.rename(columns=columns_mapping)
|
|
25
|
+
df = df.replace([np.inf, -np.inf, np.nan], None)
|
|
26
|
+
rows = []
|
|
27
|
+
for row in df.convert_dtypes().to_dict(orient="records"):
|
|
28
|
+
for k, v in row.items():
|
|
29
|
+
if isinstance(v, str):
|
|
30
|
+
# we remove any whitespace special characters
|
|
31
|
+
row[k] = re.sub(r"\s+", "", v.strip())
|
|
32
|
+
rows.append(row)
|
|
33
|
+
data = {"data": rows}
|
|
34
|
+
if extra_columns_values:
|
|
35
|
+
data["history"] = extra_columns_values
|
|
36
|
+
return data
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import magic
|
|
2
|
+
from django.apps import apps
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
from import_export.formats.base_formats import CSV
|
|
5
|
+
from import_export.results import RowResult
|
|
6
|
+
from import_export.signals import post_import
|
|
7
|
+
from wbcore.utils.importlib import import_from_dotted_path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def default_import_parse(import_source):
|
|
11
|
+
resource_path = import_source.resource_kwargs["resource_path"]
|
|
12
|
+
resource_kwargs = import_source.resource_kwargs["resource_kwargs"]
|
|
13
|
+
resource_class = import_from_dotted_path(resource_path)
|
|
14
|
+
resource = resource_class(**resource_kwargs)
|
|
15
|
+
input_format = CSV(encoding="utf-8-sig")
|
|
16
|
+
|
|
17
|
+
file_stream = import_source.file.read()
|
|
18
|
+
if input_format.CONTENT_TYPE == magic.from_buffer(file_stream, mime=True):
|
|
19
|
+
dataset = input_format.create_dataset(file_stream)
|
|
20
|
+
|
|
21
|
+
model = apps.get_model(import_source.parser_handler.handler)
|
|
22
|
+
result = resource.import_data(
|
|
23
|
+
dataset,
|
|
24
|
+
dry_run=False,
|
|
25
|
+
file_name=import_source.file.name,
|
|
26
|
+
user=import_source.creator,
|
|
27
|
+
rollback_on_validation_errors=True,
|
|
28
|
+
raise_errors=settings.DEBUG,
|
|
29
|
+
)
|
|
30
|
+
import_source.file.close()
|
|
31
|
+
post_import.send(sender=None, model=model)
|
|
32
|
+
success_message = """
|
|
33
|
+
{} import finished:
|
|
34
|
+
* new {}
|
|
35
|
+
* updated {}
|
|
36
|
+
* skipped {}
|
|
37
|
+
* failed {}
|
|
38
|
+
* deleted {}
|
|
39
|
+
* invalid {}
|
|
40
|
+
""".format(
|
|
41
|
+
model._meta.verbose_name_plural,
|
|
42
|
+
result.totals[RowResult.IMPORT_TYPE_NEW],
|
|
43
|
+
result.totals[RowResult.IMPORT_TYPE_UPDATE],
|
|
44
|
+
result.totals[RowResult.IMPORT_TYPE_SKIP],
|
|
45
|
+
result.totals[RowResult.IMPORT_TYPE_ERROR],
|
|
46
|
+
result.totals[RowResult.IMPORT_TYPE_DELETE],
|
|
47
|
+
result.totals[RowResult.IMPORT_TYPE_INVALID],
|
|
48
|
+
)
|
|
49
|
+
import_source.log = success_message
|
|
50
|
+
import_source.save()
|
wbcore/contrib/io/imports.py
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import enum
|
|
2
2
|
from collections.abc import Iterable
|
|
3
|
+
from contextlib import suppress
|
|
3
4
|
from decimal import Decimal
|
|
4
5
|
from typing import Any, Dict, List, Optional, Type
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
7
8
|
from django.apps import apps
|
|
8
9
|
from django.contrib.contenttypes.models import ContentType
|
|
10
|
+
from django.core.exceptions import FieldDoesNotExist
|
|
9
11
|
from django.db import models
|
|
10
12
|
from django.db.models import Model
|
|
11
13
|
from tqdm import tqdm
|
|
12
14
|
|
|
13
|
-
from .exceptions import DeserializationError, ImportError
|
|
15
|
+
from .exceptions import DeserializationError, ImportError, SkipImportError
|
|
14
16
|
from .models import ImportedObjectProviderRelationship, ImportSource
|
|
15
17
|
from .utils import nest_row
|
|
16
18
|
|
|
@@ -28,6 +30,7 @@ class ImportExportHandler:
|
|
|
28
30
|
def __init__(self, import_source: ImportSource, **kwargs):
|
|
29
31
|
self.import_source: ImportSource = import_source
|
|
30
32
|
self.model: Type[Model] = apps.get_model(self.MODEL_APP_LABEL)
|
|
33
|
+
self.processed_ids: list[int] = []
|
|
31
34
|
|
|
32
35
|
def _inject_internal_id_from_data(self, data: dict[str, Any]) -> tuple[int, ContentType] | None:
|
|
33
36
|
if provider_id := data.pop("provider_id", None):
|
|
@@ -43,7 +46,7 @@ class ImportExportHandler:
|
|
|
43
46
|
else:
|
|
44
47
|
return provider_id, content_type
|
|
45
48
|
|
|
46
|
-
def _model_dict_diff(self, model: Any, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
49
|
+
def _model_dict_diff(self, model: Any, data: Dict[str, Any]) -> Dict[str, Any]: # noqa: C901
|
|
47
50
|
"""
|
|
48
51
|
Given a model and its dictionary representation, compare them and find out the fields that are different
|
|
49
52
|
Args:
|
|
@@ -67,22 +70,23 @@ class ImportExportHandler:
|
|
|
67
70
|
if res_list:
|
|
68
71
|
change_data[k] = res_list
|
|
69
72
|
else:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
v
|
|
73
|
-
|
|
74
|
-
if
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if
|
|
84
|
-
|
|
85
|
-
|
|
73
|
+
with suppress(FieldDoesNotExist):
|
|
74
|
+
field_obj = model._meta.get_field(k)
|
|
75
|
+
if v in [np.nan, np.inf, -np.inf]:
|
|
76
|
+
v = None
|
|
77
|
+
if v is not None:
|
|
78
|
+
if isinstance(field_obj, models.DecimalField):
|
|
79
|
+
v = round(Decimal(v), field_obj.decimal_places)
|
|
80
|
+
if isinstance(field_obj, models.FloatField):
|
|
81
|
+
v = float(v)
|
|
82
|
+
if isinstance(field_obj, models.IntegerField):
|
|
83
|
+
v = int(v)
|
|
84
|
+
if isinstance(field_obj, models.CharField):
|
|
85
|
+
v = str(v)
|
|
86
|
+
if isinstance(field, models.Model) and isinstance(v, models.Model):
|
|
87
|
+
if v.pk != field.pk:
|
|
88
|
+
change_data[k] = v
|
|
89
|
+
# v = field.__class__.objects.get(id=v.pk) # not sure why this was there. We comment it out and monitor
|
|
86
90
|
if k not in change_data and field != v:
|
|
87
91
|
change_data[k] = v
|
|
88
92
|
return change_data
|
|
@@ -164,7 +168,7 @@ class ImportExportHandler:
|
|
|
164
168
|
error_msg = f"\nError {e} while saving data {change_data} for object id {_object.pk}"
|
|
165
169
|
self.import_source.log += error_msg
|
|
166
170
|
if not getattr(self, "allow_update_save_failure", False):
|
|
167
|
-
raise ImportError(error_msg)
|
|
171
|
+
raise ImportError(error_msg) from e
|
|
168
172
|
return True
|
|
169
173
|
return False
|
|
170
174
|
|
|
@@ -225,6 +229,7 @@ class ImportExportHandler:
|
|
|
225
229
|
history: Optional[models.QuerySet] = None,
|
|
226
230
|
read_only=False,
|
|
227
231
|
include_update_fields=None,
|
|
232
|
+
raise_exception: bool = True,
|
|
228
233
|
**kwargs,
|
|
229
234
|
):
|
|
230
235
|
data = nest_row(data)
|
|
@@ -251,8 +256,9 @@ class ImportExportHandler:
|
|
|
251
256
|
_object = self._create_instance(data, **kwargs)
|
|
252
257
|
self._post_processing_created_object(_object)
|
|
253
258
|
import_state = ImportState.CREATED
|
|
254
|
-
if not _object:
|
|
255
|
-
|
|
259
|
+
if not _object and raise_exception:
|
|
260
|
+
data_repr = " ".join([f'{k}="{v}"' for k, v in data.items()])
|
|
261
|
+
raise DeserializationError(f"{self.model._meta.verbose_name} data couldn't be parsed ({data_repr})")
|
|
256
262
|
if inject_internal_id_res:
|
|
257
263
|
ImportedObjectProviderRelationship.objects.get_or_create(
|
|
258
264
|
object_id=_object.pk,
|
|
@@ -290,12 +296,14 @@ class ImportExportHandler:
|
|
|
290
296
|
unmodified_objs.append(_object)
|
|
291
297
|
if (
|
|
292
298
|
len(self.import_source.log) > self.MAX_ALLOWED_LOG_SIZE
|
|
293
|
-
): # In case we are
|
|
299
|
+
): # In case we are exceeding the max log size, we reset the log to avoid issue when saving it
|
|
294
300
|
self.import_source.log = ""
|
|
301
|
+
if object_id := getattr(_object, "id", None):
|
|
302
|
+
self.processed_ids.append(object_id)
|
|
303
|
+
except SkipImportError as e:
|
|
304
|
+
self.import_source.log += f"skipping Row {self.import_source.progress_index}: {str(e)}\n"
|
|
295
305
|
except DeserializationError as e:
|
|
296
|
-
self.import_source.errors_log += (
|
|
297
|
-
f"\nRow {self.import_source.progress_index} ignored because error encountered: {str(e)}"
|
|
298
|
-
)
|
|
306
|
+
self.import_source.errors_log += f"Warning Row {self.import_source.progress_index}: {str(e)}\n"
|
|
299
307
|
self.import_source.progress_index += 1
|
|
300
308
|
if with_post_processing:
|
|
301
309
|
if history.exists():
|
|
@@ -1,52 +1,144 @@
|
|
|
1
|
-
#
|
|
1
|
+
# SOME DESCRIPTIVE TITLE.
|
|
2
2
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
3
|
# This file is distributed under the same license as the PACKAGE package.
|
|
4
4
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
5
5
|
#
|
|
6
|
+
# Translators:
|
|
7
|
+
# Kevin Decoster, 2025
|
|
8
|
+
#
|
|
6
9
|
msgid ""
|
|
7
10
|
msgstr ""
|
|
8
|
-
"Project-Id-Version: \n"
|
|
11
|
+
"Project-Id-Version: PACKAGE VERSION\n"
|
|
9
12
|
"Report-Msgid-Bugs-To: \n"
|
|
10
|
-
"POT-Creation-Date:
|
|
11
|
-
"PO-Revision-Date:
|
|
12
|
-
"Last-Translator: \n"
|
|
13
|
-
"Language-Team: \n"
|
|
14
|
-
"Language: de\n"
|
|
13
|
+
"POT-Creation-Date: 2025-05-30 12:10+0200\n"
|
|
14
|
+
"PO-Revision-Date: 2025-05-30 09:40+0000\n"
|
|
15
|
+
"Last-Translator: Kevin Decoster, 2025\n"
|
|
16
|
+
"Language-Team: German (https://app.transifex.com/stainly/teams/171242/de/)\n"
|
|
15
17
|
"MIME-Version: 1.0\n"
|
|
16
18
|
"Content-Type: text/plain; charset=UTF-8\n"
|
|
17
19
|
"Content-Transfer-Encoding: 8bit\n"
|
|
20
|
+
"Language: de\n"
|
|
18
21
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
19
|
-
"X-Generator: Poedit 3.2.2\n"
|
|
20
22
|
|
|
21
|
-
#:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
msgstr "Importieren"
|
|
23
|
+
#: contrib/io/models.py:95
|
|
24
|
+
msgid "Parser-Handler"
|
|
25
|
+
msgstr ""
|
|
25
26
|
|
|
26
|
-
#:
|
|
27
|
-
msgid "
|
|
28
|
-
msgstr "
|
|
27
|
+
#: contrib/io/models.py:96
|
|
28
|
+
msgid "Parsers-Handlers"
|
|
29
|
+
msgstr ""
|
|
30
|
+
|
|
31
|
+
#: contrib/io/models.py:133
|
|
32
|
+
msgid "Content object Provider Identifier relationship"
|
|
33
|
+
msgstr ""
|
|
29
34
|
|
|
30
|
-
#:
|
|
31
|
-
msgid "
|
|
32
|
-
msgstr "
|
|
35
|
+
#: contrib/io/models.py:134
|
|
36
|
+
msgid "Content object Provider Identifier relationships"
|
|
37
|
+
msgstr ""
|
|
33
38
|
|
|
34
|
-
#:
|
|
39
|
+
#: contrib/io/models.py:161 contrib/io/models.py:187
|
|
35
40
|
msgid "Provider"
|
|
36
41
|
msgstr "Anbieter"
|
|
37
42
|
|
|
38
|
-
#:
|
|
43
|
+
#: contrib/io/models.py:162
|
|
44
|
+
msgid "Providers"
|
|
45
|
+
msgstr ""
|
|
46
|
+
|
|
47
|
+
#: contrib/io/models.py:206 contrib/io/models.py:250
|
|
39
48
|
msgid "Data Backend"
|
|
40
49
|
msgstr "Daten-Backend"
|
|
41
50
|
|
|
42
|
-
#:
|
|
51
|
+
#: contrib/io/models.py:207
|
|
52
|
+
msgid "Data Backends"
|
|
53
|
+
msgstr ""
|
|
54
|
+
|
|
55
|
+
#: contrib/io/models.py:257
|
|
43
56
|
msgid "Crontab Schedule"
|
|
44
57
|
msgstr "Crontab Schedule"
|
|
45
58
|
|
|
46
|
-
#:
|
|
59
|
+
#: contrib/io/models.py:258
|
|
47
60
|
msgid ""
|
|
48
61
|
"Crontab Schedule to run the task on. Set only one schedule type, leave the "
|
|
49
62
|
"others null."
|
|
50
63
|
msgstr ""
|
|
51
64
|
"Crontab Schedule mit der die Aufgabe ausgeführt werden soll. Setzen Sie nur "
|
|
52
65
|
"einen Schedule Typen, lassen Sie die anderen leer."
|
|
66
|
+
|
|
67
|
+
#: contrib/io/models.py:299
|
|
68
|
+
msgid "Source"
|
|
69
|
+
msgstr ""
|
|
70
|
+
|
|
71
|
+
#: contrib/io/models.py:300
|
|
72
|
+
msgid "Sources"
|
|
73
|
+
msgstr ""
|
|
74
|
+
|
|
75
|
+
#: contrib/io/models.py:688
|
|
76
|
+
msgid "Format of file to be exported"
|
|
77
|
+
msgstr ""
|
|
78
|
+
|
|
79
|
+
#: contrib/io/models.py:691
|
|
80
|
+
msgid "Export job Content Type"
|
|
81
|
+
msgstr ""
|
|
82
|
+
|
|
83
|
+
#: contrib/io/models.py:694
|
|
84
|
+
msgid "Resource path to use when exporting"
|
|
85
|
+
msgstr ""
|
|
86
|
+
|
|
87
|
+
#: contrib/io/models.py:697
|
|
88
|
+
msgid "SQL query to be executed"
|
|
89
|
+
msgstr ""
|
|
90
|
+
|
|
91
|
+
#: contrib/io/models.py:699
|
|
92
|
+
msgid "SQL query parameters to be used with the sql query"
|
|
93
|
+
msgstr ""
|
|
94
|
+
|
|
95
|
+
#: contrib/io/models.py:703
|
|
96
|
+
msgid "Export Source"
|
|
97
|
+
msgstr ""
|
|
98
|
+
|
|
99
|
+
#: contrib/io/models.py:704
|
|
100
|
+
msgid "Export Sources"
|
|
101
|
+
msgstr ""
|
|
102
|
+
|
|
103
|
+
#: contrib/io/models.py:761
|
|
104
|
+
msgid "Your export file is available"
|
|
105
|
+
msgstr ""
|
|
106
|
+
|
|
107
|
+
#: contrib/io/models.py:762
|
|
108
|
+
msgid ""
|
|
109
|
+
"<p>The export job you requested is finished and available for one hour.</p>"
|
|
110
|
+
msgstr ""
|
|
111
|
+
|
|
112
|
+
#: contrib/io/models.py:813
|
|
113
|
+
msgid "Import Source"
|
|
114
|
+
msgstr ""
|
|
115
|
+
|
|
116
|
+
#: contrib/io/models.py:814
|
|
117
|
+
msgid "Import Sources"
|
|
118
|
+
msgstr ""
|
|
119
|
+
|
|
120
|
+
#: contrib/io/models.py:1022
|
|
121
|
+
msgid "Import Credential"
|
|
122
|
+
msgstr ""
|
|
123
|
+
|
|
124
|
+
#: contrib/io/models.py:1023
|
|
125
|
+
msgid "Import Credentials"
|
|
126
|
+
msgstr ""
|
|
127
|
+
|
|
128
|
+
#: contrib/io/viewset_mixins.py:240 contrib/io/viewset_mixins.py:241
|
|
129
|
+
#: contrib/io/viewset_mixins.py:242
|
|
130
|
+
msgid "Import"
|
|
131
|
+
msgstr "Importieren"
|
|
132
|
+
|
|
133
|
+
#: contrib/io/viewset_mixins.py:243
|
|
134
|
+
msgid "Please provide a valid CSV file (Coma Separated Value)"
|
|
135
|
+
msgstr ""
|
|
136
|
+
|
|
137
|
+
#: contrib/io/viewset_mixins.py:258 contrib/io/viewset_mixins.py:259
|
|
138
|
+
#: contrib/io/viewset_mixins.py:260
|
|
139
|
+
msgid "Export"
|
|
140
|
+
msgstr "Exportieren"
|
|
141
|
+
|
|
142
|
+
#: contrib/io/viewset_mixins.py:261
|
|
143
|
+
msgid "Select the export format"
|
|
144
|
+
msgstr ""
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# GERMAN TRANSLATIONS FOR IO
|
|
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: \n"
|
|
9
|
+
"Report-Msgid-Bugs-To: \n"
|
|
10
|
+
"POT-Creation-Date: 2025-05-20 09:30+0200\n"
|
|
11
|
+
"PO-Revision-Date: 2023-04-25 17:03+0200\n"
|
|
12
|
+
"Last-Translator: \n"
|
|
13
|
+
"Language-Team: \n"
|
|
14
|
+
"Language: de\n"
|
|
15
|
+
"MIME-Version: 1.0\n"
|
|
16
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
17
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
18
|
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
19
|
+
"X-Generator: Poedit 3.2.2\n"
|
|
20
|
+
|
|
21
|
+
#: wbcore/contrib/io/models.py:187
|
|
22
|
+
msgid "Provider"
|
|
23
|
+
msgstr "Anbieter"
|
|
24
|
+
|
|
25
|
+
#: wbcore/contrib/io/models.py:250
|
|
26
|
+
msgid "Data Backend"
|
|
27
|
+
msgstr "Daten-Backend"
|
|
28
|
+
|
|
29
|
+
#: wbcore/contrib/io/models.py:257
|
|
30
|
+
msgid "Crontab Schedule"
|
|
31
|
+
msgstr "Crontab Schedule"
|
|
32
|
+
|
|
33
|
+
#: wbcore/contrib/io/models.py:258
|
|
34
|
+
msgid ""
|
|
35
|
+
"Crontab Schedule to run the task on. Set only one schedule type, leave the "
|
|
36
|
+
"others null."
|
|
37
|
+
msgstr ""
|
|
38
|
+
"Crontab Schedule mit der die Aufgabe ausgeführt werden soll. Setzen Sie nur "
|
|
39
|
+
"einen Schedule Typen, lassen Sie die anderen leer."
|
|
40
|
+
|
|
41
|
+
#: wbcore/contrib/io/models.py:688
|
|
42
|
+
msgid "Format of file to be exported"
|
|
43
|
+
msgstr ""
|
|
44
|
+
|
|
45
|
+
#: wbcore/contrib/io/models.py:691
|
|
46
|
+
msgid "Export job Content Type"
|
|
47
|
+
msgstr ""
|
|
48
|
+
|
|
49
|
+
#: wbcore/contrib/io/models.py:694
|
|
50
|
+
msgid "Resource path to use when exporting"
|
|
51
|
+
msgstr ""
|
|
52
|
+
|
|
53
|
+
#: wbcore/contrib/io/models.py:697
|
|
54
|
+
msgid "SQL query to be executed"
|
|
55
|
+
msgstr ""
|
|
56
|
+
|
|
57
|
+
#: wbcore/contrib/io/models.py:699
|
|
58
|
+
msgid "SQL query parameters to be used with the sql query"
|
|
59
|
+
msgstr ""
|
|
60
|
+
|
|
61
|
+
#: wbcore/contrib/io/models.py:703
|
|
62
|
+
#, fuzzy
|
|
63
|
+
#| msgid "Export"
|
|
64
|
+
msgid "Export Source"
|
|
65
|
+
msgstr "Exportieren"
|
|
66
|
+
|
|
67
|
+
#: wbcore/contrib/io/models.py:704
|
|
68
|
+
#, fuzzy
|
|
69
|
+
#| msgid "Export"
|
|
70
|
+
msgid "Export Sources"
|
|
71
|
+
msgstr "Exportieren"
|
|
72
|
+
|
|
73
|
+
#: wbcore/contrib/io/models.py:761
|
|
74
|
+
msgid "Your export file is available"
|
|
75
|
+
msgstr ""
|
|
76
|
+
|
|
77
|
+
#: wbcore/contrib/io/models.py:762
|
|
78
|
+
msgid ""
|
|
79
|
+
"<p>The export job you requested is finished and available for one hour.</p>"
|
|
80
|
+
msgstr ""
|
|
81
|
+
|
|
82
|
+
#: wbcore/contrib/io/viewset_mixins.py:240
|
|
83
|
+
#: wbcore/contrib/io/viewset_mixins.py:241
|
|
84
|
+
#: wbcore/contrib/io/viewset_mixins.py:242
|
|
85
|
+
msgid "Import"
|
|
86
|
+
msgstr "Importieren"
|
|
87
|
+
|
|
88
|
+
#: wbcore/contrib/io/viewset_mixins.py:243
|
|
89
|
+
msgid "Please provide a valid CSV file (Coma Separated Value)"
|
|
90
|
+
msgstr ""
|
|
91
|
+
|
|
92
|
+
#: wbcore/contrib/io/viewset_mixins.py:258
|
|
93
|
+
#: wbcore/contrib/io/viewset_mixins.py:259
|
|
94
|
+
#: wbcore/contrib/io/viewset_mixins.py:260
|
|
95
|
+
msgid "Export"
|
|
96
|
+
msgstr "Exportieren"
|
|
97
|
+
|
|
98
|
+
#: wbcore/contrib/io/viewset_mixins.py:261
|
|
99
|
+
msgid "Select the export format"
|
|
100
|
+
msgstr ""
|
|
101
|
+
|
|
102
|
+
#~ msgid "Templates"
|
|
103
|
+
#~ msgstr "Vorlagen"
|