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
|
@@ -50,7 +50,7 @@ class TestExportSourceModel:
|
|
|
50
50
|
parser_handler_factory.create() # noise
|
|
51
51
|
|
|
52
52
|
# check with customer queryset and filtering
|
|
53
|
-
query_str, query_params = ParserHandler.objects.filter(
|
|
53
|
+
query_str, query_params = ParserHandler.objects.filter(parser=ph1.parser).query.sql_with_params()
|
|
54
54
|
export_source = export_source_factory.create(query_str=query_str, query_params=query_params)
|
|
55
55
|
|
|
56
56
|
assert set(export_source.queryset) == {ph1}
|
|
@@ -128,7 +128,7 @@ class TestImportSourceModel:
|
|
|
128
128
|
assert model.name != comparison_model.name
|
|
129
129
|
|
|
130
130
|
def test_process_wrongly_formatted_import_data(self, handler, import_source):
|
|
131
|
-
with pytest.raises(
|
|
131
|
+
with pytest.raises(KeyError):
|
|
132
132
|
handler.process(dict(a=1, b="b"))
|
|
133
133
|
|
|
134
134
|
def test_process_basic(self, handler, import_source, parser_handler_factory):
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import logging
|
|
2
3
|
from io import BytesIO
|
|
3
4
|
from unittest.mock import patch
|
|
4
5
|
|
|
@@ -14,7 +15,6 @@ from faker import Faker
|
|
|
14
15
|
from pytest_factoryboy import LazyFixture
|
|
15
16
|
|
|
16
17
|
from ..backends.abstract import AbstractDataBackend
|
|
17
|
-
from ..exceptions import ImportError
|
|
18
18
|
from ..models import (
|
|
19
19
|
ImportCredential,
|
|
20
20
|
ImportSource,
|
|
@@ -25,6 +25,8 @@ from ..models import (
|
|
|
25
25
|
)
|
|
26
26
|
from ..utils import nest_row
|
|
27
27
|
|
|
28
|
+
logger = logging.getLogger("io")
|
|
29
|
+
|
|
28
30
|
fake = Faker()
|
|
29
31
|
|
|
30
32
|
|
|
@@ -33,7 +35,13 @@ def parse(data):
|
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
def test_nest_row():
|
|
36
|
-
a = {
|
|
38
|
+
a = {
|
|
39
|
+
"nest1__field1": "val1",
|
|
40
|
+
"nest2__field2": "val2",
|
|
41
|
+
"field3": "val3",
|
|
42
|
+
"nest1__field4": "val4",
|
|
43
|
+
"field4": "val5",
|
|
44
|
+
}
|
|
37
45
|
expected_res = {
|
|
38
46
|
"nest1": {
|
|
39
47
|
"field1": "val1",
|
|
@@ -99,7 +107,7 @@ class TestSourceModel:
|
|
|
99
107
|
assert source.crontab_repr == ""
|
|
100
108
|
|
|
101
109
|
@pytest.mark.parametrize("source__crontab", [None, LazyFixture("crontab_schedule")])
|
|
102
|
-
@pytest.mark.parametrize("_datetime", [
|
|
110
|
+
@pytest.mark.parametrize("_datetime", [fake.date_time()])
|
|
103
111
|
def test_is_valid_date(self, source, _datetime):
|
|
104
112
|
if not source.crontab:
|
|
105
113
|
assert not source.is_valid_date(_datetime)
|
|
@@ -232,7 +240,8 @@ class TestImportSourceModel:
|
|
|
232
240
|
@patch.object(ParserHandler, "parse")
|
|
233
241
|
@patch.object(ParserHandler, "handle")
|
|
234
242
|
@pytest.mark.parametrize(
|
|
235
|
-
"import_source__file, import_source__save_data",
|
|
243
|
+
"import_source__file, import_source__save_data",
|
|
244
|
+
[(get_test_file(), True), (get_test_file(), False)],
|
|
236
245
|
)
|
|
237
246
|
def test_import_data_success(self, mock_handle, mock_parse, import_source):
|
|
238
247
|
file_data = json.loads(import_source.file.read().decode("utf-8"))
|
|
@@ -248,16 +257,12 @@ class TestImportSourceModel:
|
|
|
248
257
|
else:
|
|
249
258
|
assert import_source.data == {}
|
|
250
259
|
|
|
251
|
-
def test_import_data_error(self, import_source):
|
|
260
|
+
def test_import_data_error(self, caplog, import_source):
|
|
252
261
|
assert import_source.status == ImportSource.Status.PENDING
|
|
253
|
-
with
|
|
262
|
+
with caplog.at_level(logging.ERROR):
|
|
254
263
|
import_source.import_data()
|
|
255
264
|
assert import_source.status == ImportSource.Status.ERROR
|
|
256
265
|
|
|
257
|
-
def test_not_silent_raise_error(self, import_source):
|
|
258
|
-
with pytest.raises(Exception):
|
|
259
|
-
import_source.import_data(silent=False)
|
|
260
|
-
|
|
261
266
|
|
|
262
267
|
@pytest.mark.django_db
|
|
263
268
|
class TestImportCredentialModel:
|
|
@@ -274,8 +279,22 @@ class TestImportCredentialModel:
|
|
|
274
279
|
@pytest.mark.parametrize(
|
|
275
280
|
"type, password, username, authentication_token, certificate_pem, certificate_key",
|
|
276
281
|
[
|
|
277
|
-
(
|
|
278
|
-
|
|
282
|
+
(
|
|
283
|
+
ImportCredential.Type.CREDENTIAL,
|
|
284
|
+
None,
|
|
285
|
+
"username",
|
|
286
|
+
None,
|
|
287
|
+
"certificate_pem",
|
|
288
|
+
"certificate_key",
|
|
289
|
+
),
|
|
290
|
+
(
|
|
291
|
+
ImportCredential.Type.CREDENTIAL,
|
|
292
|
+
"password",
|
|
293
|
+
None,
|
|
294
|
+
None,
|
|
295
|
+
"certificate_pem",
|
|
296
|
+
"certificate_key",
|
|
297
|
+
),
|
|
279
298
|
(
|
|
280
299
|
ImportCredential.Type.AUTHENTICATION_TOKEN,
|
|
281
300
|
"password",
|
|
@@ -284,7 +303,14 @@ class TestImportCredentialModel:
|
|
|
284
303
|
"certificate_pem",
|
|
285
304
|
"certificate_key",
|
|
286
305
|
),
|
|
287
|
-
(
|
|
306
|
+
(
|
|
307
|
+
ImportCredential.Type.CERTIFICATE,
|
|
308
|
+
"password",
|
|
309
|
+
"username",
|
|
310
|
+
"token",
|
|
311
|
+
None,
|
|
312
|
+
"certificate_key",
|
|
313
|
+
),
|
|
288
314
|
],
|
|
289
315
|
)
|
|
290
316
|
def test_unvalid_credential(
|
|
@@ -310,7 +336,14 @@ class TestImportCredentialModel:
|
|
|
310
336
|
@pytest.mark.parametrize(
|
|
311
337
|
"type, username, password, authentication_token, certificate_pem, certificate_key",
|
|
312
338
|
[
|
|
313
|
-
(
|
|
339
|
+
(
|
|
340
|
+
ImportCredential.Type.CREDENTIAL,
|
|
341
|
+
None,
|
|
342
|
+
None,
|
|
343
|
+
fake.random_letters(16),
|
|
344
|
+
None,
|
|
345
|
+
None,
|
|
346
|
+
),
|
|
314
347
|
(
|
|
315
348
|
ImportCredential.Type.AUTHENTICATION_TOKEN,
|
|
316
349
|
fake.random_letters(8),
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pytest import FixtureRequest
|
|
3
|
+
from rest_framework import status
|
|
4
|
+
from rest_framework.test import APIRequestFactory
|
|
5
|
+
|
|
6
|
+
from wbcore.contrib.authentication.factories import SuperUserFactory
|
|
7
|
+
from wbcore.contrib.io.factories import (
|
|
8
|
+
DataBackendFactory,
|
|
9
|
+
ImportSourceFactory,
|
|
10
|
+
ParserHandlerFactory,
|
|
11
|
+
SourceFactory,
|
|
12
|
+
)
|
|
13
|
+
from wbcore.contrib.io.models import DataBackend, ImportSource, ParserHandler
|
|
14
|
+
from wbcore.contrib.io.serializers import (
|
|
15
|
+
ImportSourceModelSerializer,
|
|
16
|
+
ParserHandlerModelSerializer,
|
|
17
|
+
)
|
|
18
|
+
from wbcore.contrib.io.viewsets import (
|
|
19
|
+
DataBackendRepresentationViewSet,
|
|
20
|
+
ImportSourceModelViewSet,
|
|
21
|
+
ImportSourceRepresentationViewSet,
|
|
22
|
+
ParserHandlerModelViewSet,
|
|
23
|
+
ParserHandlerRepresentationViewSet,
|
|
24
|
+
SourceModelViewSet,
|
|
25
|
+
SourceRepresentationViewSet,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
BATCH_SIZE = 3
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.django_db
|
|
32
|
+
@pytest.mark.with_db
|
|
33
|
+
class TestViewsets:
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def request_factory(self):
|
|
36
|
+
return APIRequestFactory()
|
|
37
|
+
|
|
38
|
+
@pytest.fixture
|
|
39
|
+
def super_user(self):
|
|
40
|
+
return SuperUserFactory()
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def import_sources(self):
|
|
44
|
+
return ImportSourceFactory.create_batch(BATCH_SIZE)
|
|
45
|
+
|
|
46
|
+
@pytest.fixture
|
|
47
|
+
def import_source(self, import_sources):
|
|
48
|
+
return import_sources[0]
|
|
49
|
+
|
|
50
|
+
@pytest.fixture
|
|
51
|
+
def import_source_build(self):
|
|
52
|
+
parser_handler = ParserHandlerFactory()
|
|
53
|
+
source = SourceFactory()
|
|
54
|
+
|
|
55
|
+
instance = ImportSourceFactory.build(parser_handler=None, source=None)
|
|
56
|
+
|
|
57
|
+
serialized_data = ImportSourceModelSerializer(instance).data
|
|
58
|
+
|
|
59
|
+
serialized_data["parser_handler"] = parser_handler.id
|
|
60
|
+
serialized_data["source"] = source.id
|
|
61
|
+
|
|
62
|
+
return serialized_data
|
|
63
|
+
|
|
64
|
+
@pytest.fixture
|
|
65
|
+
def sources(self):
|
|
66
|
+
return SourceFactory.create_batch(BATCH_SIZE)
|
|
67
|
+
|
|
68
|
+
@pytest.fixture
|
|
69
|
+
def source(self, sources):
|
|
70
|
+
return sources[0]
|
|
71
|
+
|
|
72
|
+
@pytest.fixture
|
|
73
|
+
def parser_handlers(self):
|
|
74
|
+
return ParserHandlerFactory.create_batch(BATCH_SIZE)
|
|
75
|
+
|
|
76
|
+
@pytest.fixture
|
|
77
|
+
def parser_handler(self, parser_handlers):
|
|
78
|
+
return parser_handlers[0]
|
|
79
|
+
|
|
80
|
+
@pytest.fixture
|
|
81
|
+
def parser_handler_build(self):
|
|
82
|
+
instance = ParserHandlerFactory.build()
|
|
83
|
+
return ParserHandlerModelSerializer(instance).data
|
|
84
|
+
|
|
85
|
+
@pytest.fixture
|
|
86
|
+
def data_backends(self):
|
|
87
|
+
DataBackend.objects.all().delete()
|
|
88
|
+
return DataBackendFactory.create_batch(BATCH_SIZE)
|
|
89
|
+
|
|
90
|
+
@pytest.fixture
|
|
91
|
+
def data_backend(self, data_backends):
|
|
92
|
+
return data_backends[0]
|
|
93
|
+
|
|
94
|
+
@pytest.mark.parametrize(
|
|
95
|
+
"vs, batch",
|
|
96
|
+
[
|
|
97
|
+
(ImportSourceRepresentationViewSet, "import_sources"),
|
|
98
|
+
(ImportSourceModelViewSet, "import_sources"),
|
|
99
|
+
(SourceRepresentationViewSet, "sources"),
|
|
100
|
+
(SourceModelViewSet, "sources"),
|
|
101
|
+
(ParserHandlerRepresentationViewSet, "parser_handlers"),
|
|
102
|
+
(ParserHandlerModelViewSet, "parser_handlers"),
|
|
103
|
+
(DataBackendRepresentationViewSet, "data_backends"),
|
|
104
|
+
],
|
|
105
|
+
)
|
|
106
|
+
def test_get(self, request_factory, super_user, vs, batch, request: FixtureRequest):
|
|
107
|
+
# Arrange
|
|
108
|
+
request.getfixturevalue(batch)
|
|
109
|
+
get_request = request_factory.get("")
|
|
110
|
+
get_request.user = super_user
|
|
111
|
+
viewset = vs.as_view({"get": "list"})
|
|
112
|
+
# Act
|
|
113
|
+
response = viewset(get_request)
|
|
114
|
+
# Assert
|
|
115
|
+
assert len(response.data["results"]) == BATCH_SIZE
|
|
116
|
+
assert response.status_code == status.HTTP_200_OK
|
|
117
|
+
|
|
118
|
+
@pytest.mark.parametrize(
|
|
119
|
+
"vs, instance",
|
|
120
|
+
[
|
|
121
|
+
(ImportSourceRepresentationViewSet, "import_source"),
|
|
122
|
+
(ImportSourceModelViewSet, "import_source"),
|
|
123
|
+
(SourceRepresentationViewSet, "source"),
|
|
124
|
+
(SourceModelViewSet, "source"),
|
|
125
|
+
(ParserHandlerRepresentationViewSet, "parser_handler"),
|
|
126
|
+
(ParserHandlerModelViewSet, "parser_handler"),
|
|
127
|
+
(DataBackendRepresentationViewSet, "data_backend"),
|
|
128
|
+
],
|
|
129
|
+
)
|
|
130
|
+
def test_retrieve(self, request_factory, super_user, vs, instance, request: FixtureRequest):
|
|
131
|
+
# Arrange
|
|
132
|
+
entry = request.getfixturevalue(instance)
|
|
133
|
+
get_request = request_factory.get("")
|
|
134
|
+
get_request.user = super_user
|
|
135
|
+
viewset = vs.as_view({"get": "retrieve"})
|
|
136
|
+
# We need to work with lookup_field since SourceModelViewSet uses uuid as lookup field
|
|
137
|
+
lookup_field = getattr(vs, "lookup_field", "pk")
|
|
138
|
+
lookup_value = getattr(entry, lookup_field)
|
|
139
|
+
# Act
|
|
140
|
+
response = viewset(get_request, **{lookup_field: lookup_value})
|
|
141
|
+
lookup_field = "id" if lookup_field != "uuid" else "uuid"
|
|
142
|
+
instance = response.data.get("instance")
|
|
143
|
+
# Assert
|
|
144
|
+
assert instance is not None
|
|
145
|
+
assert str(instance[lookup_field]) == str(lookup_value)
|
|
146
|
+
assert not response.data.get("results")
|
|
147
|
+
assert response.status_code == status.HTTP_200_OK
|
|
148
|
+
|
|
149
|
+
@pytest.mark.parametrize(
|
|
150
|
+
"vs, data, creatable",
|
|
151
|
+
[
|
|
152
|
+
(ImportSourceModelViewSet, "import_source_build", True),
|
|
153
|
+
(ParserHandlerModelViewSet, "parser_handler_build", False),
|
|
154
|
+
],
|
|
155
|
+
)
|
|
156
|
+
def test_create(self, request_factory, super_user, vs, data, creatable, request: FixtureRequest):
|
|
157
|
+
# Arrange
|
|
158
|
+
build_data = request.getfixturevalue(data)
|
|
159
|
+
post_request = request_factory.post("", data=build_data, format="json")
|
|
160
|
+
post_request.user = super_user
|
|
161
|
+
viewset = vs.as_view({"post": "create"})
|
|
162
|
+
# Act
|
|
163
|
+
response = viewset(post_request)
|
|
164
|
+
expected_status_code = status.HTTP_201_CREATED if creatable else status.HTTP_405_METHOD_NOT_ALLOWED
|
|
165
|
+
# Assert
|
|
166
|
+
assert response.status_code == expected_status_code
|
|
167
|
+
|
|
168
|
+
@pytest.mark.parametrize(
|
|
169
|
+
"vs, batch, model, deletable",
|
|
170
|
+
[
|
|
171
|
+
(ImportSourceModelViewSet, "import_sources", ImportSource, True),
|
|
172
|
+
(ParserHandlerModelViewSet, "parser_handlers", ParserHandler, False),
|
|
173
|
+
],
|
|
174
|
+
)
|
|
175
|
+
def test_delete(
|
|
176
|
+
self,
|
|
177
|
+
request_factory,
|
|
178
|
+
super_user,
|
|
179
|
+
vs,
|
|
180
|
+
batch,
|
|
181
|
+
model,
|
|
182
|
+
deletable,
|
|
183
|
+
request: FixtureRequest,
|
|
184
|
+
):
|
|
185
|
+
# Arrange
|
|
186
|
+
entries = request.getfixturevalue(batch)
|
|
187
|
+
entry_id = entries[0].id
|
|
188
|
+
delete_request = request_factory.delete("", args=entry_id)
|
|
189
|
+
delete_request.user = super_user
|
|
190
|
+
viewset = vs.as_view({"delete": "destroy"})
|
|
191
|
+
# Act
|
|
192
|
+
response = viewset(delete_request, pk=entry_id)
|
|
193
|
+
# Assert
|
|
194
|
+
if deletable:
|
|
195
|
+
assert response.status_code == status.HTTP_204_NO_CONTENT
|
|
196
|
+
assert model.objects.count() == BATCH_SIZE - 1
|
|
197
|
+
assert not model.objects.filter(id=entry_id).exists()
|
|
198
|
+
else:
|
|
199
|
+
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
|
|
200
|
+
assert model.objects.count() == BATCH_SIZE
|
|
201
|
+
assert model.objects.filter(id=entry_id).exists()
|
|
202
|
+
|
|
203
|
+
@pytest.mark.parametrize(
|
|
204
|
+
"vs, entry, new_data, updatable",
|
|
205
|
+
[
|
|
206
|
+
(ImportSourceModelViewSet, "import_source", "import_source_build", True),
|
|
207
|
+
(
|
|
208
|
+
ParserHandlerModelViewSet,
|
|
209
|
+
"parser_handler",
|
|
210
|
+
"parser_handler_build",
|
|
211
|
+
False,
|
|
212
|
+
),
|
|
213
|
+
],
|
|
214
|
+
)
|
|
215
|
+
def test_put(
|
|
216
|
+
self,
|
|
217
|
+
request_factory,
|
|
218
|
+
super_user,
|
|
219
|
+
vs,
|
|
220
|
+
entry,
|
|
221
|
+
new_data,
|
|
222
|
+
updatable,
|
|
223
|
+
request: FixtureRequest,
|
|
224
|
+
):
|
|
225
|
+
# Arrange
|
|
226
|
+
entry = request.getfixturevalue(entry)
|
|
227
|
+
new_data = request.getfixturevalue(new_data)
|
|
228
|
+
new_data["id"] = entry.id
|
|
229
|
+
put_request = request_factory.put("", data=new_data, format="json")
|
|
230
|
+
put_request.user = super_user
|
|
231
|
+
viewset = vs.as_view({"put": "update"})
|
|
232
|
+
# Act
|
|
233
|
+
response = viewset(put_request, pk=entry.id)
|
|
234
|
+
expected_status_code = status.HTTP_200_OK if updatable else status.HTTP_405_METHOD_NOT_ALLOWED
|
|
235
|
+
# Assert
|
|
236
|
+
assert response.status_code == expected_status_code
|
|
237
|
+
|
|
238
|
+
@pytest.mark.parametrize(
|
|
239
|
+
"vs, entry, field, updatable",
|
|
240
|
+
[
|
|
241
|
+
(ImportSourceModelViewSet, "import_source", "origin", True),
|
|
242
|
+
(ParserHandlerModelViewSet, "parser_handler", "parser", False),
|
|
243
|
+
],
|
|
244
|
+
)
|
|
245
|
+
def test_patch(
|
|
246
|
+
self,
|
|
247
|
+
request_factory,
|
|
248
|
+
super_user,
|
|
249
|
+
vs,
|
|
250
|
+
entry,
|
|
251
|
+
field,
|
|
252
|
+
updatable,
|
|
253
|
+
request: FixtureRequest,
|
|
254
|
+
):
|
|
255
|
+
# Arrange
|
|
256
|
+
instance = request.getfixturevalue(entry)
|
|
257
|
+
old_field_data = getattr(instance, field)
|
|
258
|
+
new_field_data = "Foo Bar"
|
|
259
|
+
patch_request = request_factory.patch("", data={field: new_field_data})
|
|
260
|
+
patch_request.user = super_user
|
|
261
|
+
viewset = vs.as_view({"patch": "partial_update"})
|
|
262
|
+
# Act
|
|
263
|
+
response = viewset(patch_request, pk=instance.id)
|
|
264
|
+
instance.refresh_from_db()
|
|
265
|
+
# Assert
|
|
266
|
+
if updatable:
|
|
267
|
+
assert response.status_code == status.HTTP_200_OK
|
|
268
|
+
assert getattr(instance, field) == new_field_data
|
|
269
|
+
else:
|
|
270
|
+
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
|
|
271
|
+
assert getattr(instance, field) == old_field_data
|
|
@@ -8,7 +8,6 @@ from django.http import HttpResponse
|
|
|
8
8
|
from django.utils.functional import cached_property
|
|
9
9
|
from django.utils.translation import gettext as _
|
|
10
10
|
from import_export.admin import ImportExportMixin
|
|
11
|
-
from import_export.signals import post_export
|
|
12
11
|
from rest_framework import status
|
|
13
12
|
from rest_framework.authentication import SessionAuthentication
|
|
14
13
|
from rest_framework.decorators import action, parser_classes
|
|
@@ -20,6 +19,7 @@ from rest_framework.response import Response
|
|
|
20
19
|
from wbcore import serializers
|
|
21
20
|
from wbcore.contrib.authentication.authentication import JWTCookieAuthentication
|
|
22
21
|
from wbcore.contrib.icons import WBIcon
|
|
22
|
+
from wbcore.contrib.io.import_export.parsers.base_csv import parse
|
|
23
23
|
from wbcore.enums import RequestType
|
|
24
24
|
from wbcore.metadata.configs import buttons as bt
|
|
25
25
|
from wbcore.metadata.configs.display.instance_display import create_simple_display
|
|
@@ -51,8 +51,8 @@ class ImportExportDRFMixin(ImportExportMixin):
|
|
|
51
51
|
def model(self):
|
|
52
52
|
try:
|
|
53
53
|
return getattr(self.queryset, "model", None)
|
|
54
|
-
except AttributeError:
|
|
55
|
-
raise ParseError("Malformed Queryset")
|
|
54
|
+
except AttributeError as e:
|
|
55
|
+
raise ParseError("Malformed Queryset") from e
|
|
56
56
|
|
|
57
57
|
@cached_property
|
|
58
58
|
def opts(self):
|
|
@@ -70,14 +70,26 @@ class ImportExportDRFMixin(ImportExportMixin):
|
|
|
70
70
|
"""
|
|
71
71
|
return ViewResource
|
|
72
72
|
|
|
73
|
-
def
|
|
74
|
-
return
|
|
73
|
+
def get_import_resource_kwargs(self):
|
|
74
|
+
return {
|
|
75
|
+
"extra_columns_values": self.kwargs, # contains the extra column to inject into the dataset during parsing
|
|
76
|
+
"columns_mapping": {}, # Store the column mapping to rename dataset column during parsing
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
def get_import_parser_path(self) -> str:
|
|
80
|
+
return parse.__module__
|
|
75
81
|
|
|
76
82
|
def get_resource_serializer_class(self):
|
|
77
83
|
resource_kwargs = {}
|
|
78
84
|
with suppress(AttributeError, AssertionError):
|
|
79
|
-
|
|
85
|
+
# we have to mocky patch the action to be "list" because sometime we differentiate the serializer to use
|
|
86
|
+
previous_action = getattr(self, "action", "list")
|
|
87
|
+
self.action = "list"
|
|
88
|
+
serializer_class = getattr(
|
|
89
|
+
self, "serializer_class", self.get_serializer_class()
|
|
90
|
+
) # we prioritize the default serializer class attribute
|
|
80
91
|
resource_kwargs["serializer_class_path"] = serializer_class.__module__ + "." + serializer_class.__name__
|
|
92
|
+
self.action = previous_action
|
|
81
93
|
return resource_kwargs
|
|
82
94
|
|
|
83
95
|
def _get_data_for_export(self, request, queryset, *args, **kwargs) -> tablib.Dataset:
|
|
@@ -103,7 +115,7 @@ class ImportExportDRFMixin(ImportExportMixin):
|
|
|
103
115
|
detail=False,
|
|
104
116
|
methods=["PATCH"],
|
|
105
117
|
permission_classes=[IsAuthenticated],
|
|
106
|
-
authentication_classes=[SessionAuthentication, JWTCookieAuthentication],
|
|
118
|
+
# authentication_classes=[SessionAuthentication, JWTCookieAuthentication],
|
|
107
119
|
url_name="processimport",
|
|
108
120
|
)
|
|
109
121
|
@parser_classes([MultiPartParser])
|
|
@@ -114,7 +126,7 @@ class ImportExportDRFMixin(ImportExportMixin):
|
|
|
114
126
|
if not self.has_import_permission(request):
|
|
115
127
|
raise PermissionDenied
|
|
116
128
|
if request.data:
|
|
117
|
-
if
|
|
129
|
+
if file_tmp := request.FILES.get("file"):
|
|
118
130
|
from wbcore.contrib.io.models import ( # for circular dependency. Will fix later
|
|
119
131
|
ImportSource,
|
|
120
132
|
ParserHandler,
|
|
@@ -122,10 +134,14 @@ class ImportExportDRFMixin(ImportExportMixin):
|
|
|
122
134
|
)
|
|
123
135
|
|
|
124
136
|
parser_handler, _ = ParserHandler.objects.get_or_create(
|
|
125
|
-
handler=f"{self.model._meta.app_label}.{self.model.__name__}", parser=
|
|
137
|
+
handler=f"{self.model._meta.app_label}.{self.model.__name__}", parser=self.get_import_parser_path()
|
|
126
138
|
)
|
|
127
139
|
import_source = ImportSource.objects.create(
|
|
128
|
-
parser_handler=parser_handler,
|
|
140
|
+
parser_handler=parser_handler,
|
|
141
|
+
file=file_tmp,
|
|
142
|
+
origin="Internal Import",
|
|
143
|
+
creator=request.user,
|
|
144
|
+
resource_kwargs=self.get_import_resource_kwargs(),
|
|
129
145
|
)
|
|
130
146
|
import_data_as_task.delay(import_source.id)
|
|
131
147
|
|
|
@@ -193,46 +209,24 @@ class ImportExportDRFMixin(ImportExportMixin):
|
|
|
193
209
|
"""
|
|
194
210
|
This action export a template into a file
|
|
195
211
|
"""
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
self.get_export_filename(request, None, file_format),
|
|
205
|
-
)
|
|
206
|
-
post_export.send(sender=None, model=self.model)
|
|
207
|
-
return response
|
|
212
|
+
file_format = get_django_import_export_format(ExportFormat.CSV)()
|
|
213
|
+
export_data = self.get_export_data(file_format, request, None, **kwargs)
|
|
214
|
+
content_type = file_format.get_content_type()
|
|
215
|
+
response = HttpResponse(export_data, content_type=content_type)
|
|
216
|
+
response["Content-Disposition"] = 'attachment; filename="%s"' % (
|
|
217
|
+
self.get_export_filename(request, None, file_format),
|
|
218
|
+
)
|
|
219
|
+
return response
|
|
208
220
|
|
|
209
221
|
|
|
210
222
|
@receiver(add_extra_button)
|
|
211
223
|
def add_template_extra_button(sender, instance, request, view, pk=None, **kwargs):
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
):
|
|
219
|
-
buttons = []
|
|
220
|
-
for format in ExportFormat:
|
|
221
|
-
buttons.append(
|
|
222
|
-
bt.HyperlinkButton(
|
|
223
|
-
endpoint=parse_endpoint(request, "export_template", export_format=format.value),
|
|
224
|
-
title=format.label,
|
|
225
|
-
label=format.label,
|
|
226
|
-
)
|
|
227
|
-
)
|
|
228
|
-
if buttons:
|
|
229
|
-
return bt.DropDownButton(
|
|
230
|
-
title=_("Templates"),
|
|
231
|
-
label=_("Templates"),
|
|
232
|
-
icon=WBIcon.UPLOAD.icon,
|
|
233
|
-
buttons=tuple(buttons),
|
|
234
|
-
weight=2,
|
|
235
|
-
)
|
|
224
|
+
if view and not instance and hasattr(view, "has_import_permission") and view.has_import_permission(request):
|
|
225
|
+
return bt.HyperlinkButton(
|
|
226
|
+
endpoint=parse_endpoint(request, "export_template", export_format=ExportFormat.CSV),
|
|
227
|
+
title="Import Template (CSV)",
|
|
228
|
+
label="Import Template (CSV)",
|
|
229
|
+
)
|
|
236
230
|
|
|
237
231
|
|
|
238
232
|
@receiver(add_extra_button)
|
|
@@ -240,7 +234,6 @@ def add_import_extra_button(sender, instance, request, view, pk=None, **kwargs):
|
|
|
240
234
|
if (
|
|
241
235
|
view
|
|
242
236
|
and not instance
|
|
243
|
-
and not view.inline
|
|
244
237
|
and hasattr(view, "has_import_permission")
|
|
245
238
|
and view.has_import_permission(request)
|
|
246
239
|
and view.get_resource_class()
|
|
@@ -262,13 +255,7 @@ def add_import_extra_button(sender, instance, request, view, pk=None, **kwargs):
|
|
|
262
255
|
|
|
263
256
|
@receiver(add_extra_button)
|
|
264
257
|
def add_export_extra_button(sender, instance, request, view, pk=None, **kwargs):
|
|
265
|
-
if (
|
|
266
|
-
view
|
|
267
|
-
and not instance
|
|
268
|
-
and not view.inline
|
|
269
|
-
and hasattr(view, "has_export_permission")
|
|
270
|
-
and view.has_export_permission(request)
|
|
271
|
-
):
|
|
258
|
+
if view and not instance and hasattr(view, "has_export_permission") and view.has_export_permission(request):
|
|
272
259
|
return bt.ActionButton(
|
|
273
260
|
method=RequestType.PATCH,
|
|
274
261
|
icon=WBIcon.DOWNLOAD.icon,
|
|
@@ -16,7 +16,7 @@ def notification_type_post_migrate(app_config, using=DEFAULT_DB_ALIAS, **kwargs)
|
|
|
16
16
|
contenttype = ContentType.objects.db_manager(using).get_for_model(klass, for_concrete_model=False) # type: ignore
|
|
17
17
|
NotificationType.objects.db_manager(using).filter(contenttype=contenttype).update(stale=True)
|
|
18
18
|
|
|
19
|
-
for code, title, help_text, web, mobile, email, resource_button_label in getattr(
|
|
19
|
+
for code, title, help_text, web, mobile, email, resource_button_label, is_lock in getattr(
|
|
20
20
|
klass._meta, "notification_types", []
|
|
21
21
|
):
|
|
22
22
|
NotificationType.objects.using(using).update_or_create(
|
|
@@ -30,6 +30,7 @@ def notification_type_post_migrate(app_config, using=DEFAULT_DB_ALIAS, **kwargs)
|
|
|
30
30
|
"default_enable_email": email,
|
|
31
31
|
"resource_button_label": resource_button_label,
|
|
32
32
|
"stale": False,
|
|
33
|
+
"is_lock": is_lock,
|
|
33
34
|
},
|
|
34
35
|
)
|
|
35
36
|
|
|
@@ -5,9 +5,7 @@ from wbcore.contrib.notifications.models import Notification
|
|
|
5
5
|
|
|
6
6
|
class AbstractNotificationBackend(ABC):
|
|
7
7
|
@abstractclassmethod
|
|
8
|
-
def send_notification(cls, notification: Notification):
|
|
9
|
-
...
|
|
8
|
+
def send_notification(cls, notification: Notification): ...
|
|
10
9
|
|
|
11
10
|
@abstractclassmethod
|
|
12
|
-
def get_configuration(cls) -> dict:
|
|
13
|
-
...
|
|
11
|
+
def get_configuration(cls) -> dict: ...
|
|
@@ -5,6 +5,7 @@ import firebase_admin
|
|
|
5
5
|
from django.utils.html import strip_tags
|
|
6
6
|
from firebase_admin import messaging
|
|
7
7
|
from firebase_admin.credentials import Certificate
|
|
8
|
+
from firebase_admin.exceptions import InvalidArgumentError
|
|
8
9
|
|
|
9
10
|
from wbcore.contrib.notifications.backends.abstract_backend import (
|
|
10
11
|
AbstractNotificationBackend,
|
|
@@ -70,8 +71,10 @@ class NotificationBackend(AbstractNotificationBackend):
|
|
|
70
71
|
)
|
|
71
72
|
try:
|
|
72
73
|
messaging.send(message, False, app)
|
|
73
|
-
except messaging.UnregisteredError:
|
|
74
|
+
except (messaging.UnregisteredError, messaging.QuotaExceededError):
|
|
74
75
|
expired_tokens.append(token)
|
|
76
|
+
except InvalidArgumentError: # this happens if the body is too big for the mobile push
|
|
77
|
+
pass
|
|
75
78
|
|
|
76
79
|
for token in tokens.filter(device_type=NotificationUserToken.NotificationDeviceType.WEB):
|
|
77
80
|
data = {
|
|
@@ -89,7 +92,7 @@ class NotificationBackend(AbstractNotificationBackend):
|
|
|
89
92
|
)
|
|
90
93
|
try:
|
|
91
94
|
messaging.send(message, False, app)
|
|
92
|
-
except messaging.UnregisteredError:
|
|
95
|
+
except (messaging.UnregisteredError, messaging.QuotaExceededError):
|
|
93
96
|
expired_tokens.append(token)
|
|
94
97
|
|
|
95
98
|
for expired_token in expired_tokens:
|