Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include allowed formats settings #1606

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,4 @@ The following is a list of much appreciated contributors:
* travenin (Lauri Virtanen)
* christophehenry (Christophe Henry)
* bgelov (Oleg Belov)
* EricOuma (Eric Ouma)
6 changes: 6 additions & 0 deletions docs/advanced_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,12 @@ You can optionally configure import-export to sanitize data on export. There ar
Enabling these settings only sanitizes data exported using the Admin Interface.
If exporting data :ref:`programmatically<exporting_data>`, then you will need to apply your own sanitization.

Limiting the available import or export types can be considered. This can be done using either of the following settings:

#. :ref:`IMPORT_EXPORT_FORMATS`
#. :ref:`IMPORT_FORMATS`
#. :ref:`EXPORT_FORMATS`

You should in all cases review `Django security documentation <https://docs.djangoproject.com/en/dev/topics/security/>`_
before deploying a live Admin interface instance.

Expand Down
42 changes: 42 additions & 0 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,48 @@ If set to ``True``, strings will be HTML escaped. By default this is ``False``.
If set to ``True``, strings will be sanitized by removing any leading '=' character. This is to prevent execution of
Excel formulae. By default this is ``False``.

``IMPORT_EXPORT_FORMATS``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A list that defines which file formats will be allowed during imports and exports. Defaults
to ``import_export.formats.base_formats.DEFAULT_FORMATS``.
The values must be those provided in ``import_export.formats.base_formats`` e.g

.. code-block:: python

# settings.py
from import_export.formats.base_formats import XLSX
IMPORT_EXPORT_FORMATS = [XLSX]


``IMPORT_FORMATS``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A list that defines which file formats will be allowed during imports. Defaults
to ``IMPORT_EXPORT_FORMATS``.
The values must be those provided in ``import_export.formats.base_formats`` e.g

.. code-block:: python

# settings.py
from import_export.formats.base_formats import CSV, XLSX
IMPORT_FORMATS = [CSV, XLSX]


``EXPORT_FORMATS``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A list that defines which file formats will be allowed during exports. Defaults
to ``IMPORT_EXPORT_FORMATS``.
The values must be those provided in ``import_export.formats.base_formats`` e.g

.. code-block:: python

# settings.py
from import_export.formats.base_formats import XLSX
EXPORT_FORMATS = [XLSX]


.. _exampleapp:

Example app
Expand Down
3 changes: 0 additions & 3 deletions import_export/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_POST

from .formats.base_formats import DEFAULT_FORMATS
from .forms import (
ConfirmImportForm,
ExportForm,
Expand Down Expand Up @@ -84,8 +83,6 @@ class ImportMixin(BaseImportMixin, ImportExportMixinBase):
import_export_change_list_template = "admin/import_export/change_list_import.html"
#: template for import view
import_template_name = "admin/import_export/import.html"
#: available import formats
formats = DEFAULT_FORMATS
#: form class to use for the initial import step
import_form_class = ImportForm
#: form class to use for the confirm import step
Expand Down
17 changes: 14 additions & 3 deletions import_export/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,21 @@


class BaseImportExportMixin:
formats = base_formats.DEFAULT_FORMATS
resource_class = None
resource_classes = []

@property
def formats(self):
return getattr(settings, "IMPORT_EXPORT_FORMATS", base_formats.DEFAULT_FORMATS)

@property
def export_formats(self):
return getattr(settings, "EXPORT_FORMATS", self.formats)

@property
def import_formats(self):
return getattr(settings, "IMPORT_FORMATS", self.formats)

def check_resource_classes(self, resource_classes):
if resource_classes and not hasattr(resource_classes, "__getitem__"):
raise Exception(
Expand Down Expand Up @@ -85,7 +96,7 @@ def get_import_formats(self):
"""
Returns available import formats.
"""
return [f for f in self.formats if f().can_import()]
return [f for f in self.import_formats if f().can_import()]

def get_import_resource_kwargs(self, request, *args, **kwargs):
return self.get_resource_kwargs(request, *args, **kwargs)
Expand Down Expand Up @@ -134,7 +145,7 @@ def get_export_formats(self):
"""
Returns available export formats.
"""
return [f for f in self.formats if f().can_export()]
return [f for f in self.export_formats if f().can_export()]

def get_export_resource_classes(self):
"""
Expand Down
75 changes: 72 additions & 3 deletions tests/core/tests/test_admin_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,25 @@ def change_list_template(self):
"failed to assign change_list_template attribute (see issue 1521)"
)

@override_settings(IMPORT_FORMATS=[base_formats.XLSX, base_formats.XLS])
def test_import_admin_uses_import_format_settings(self):
"""
Test that import form only avails the formats provided by the
IMPORT_FORMATS setting
"""
request = self.client.get(self.book_import_url).wsgi_request
mock_site = mock.MagicMock()
import_form = BookAdmin(Book, mock_site).create_import_form(request)

items = list(import_form.fields.items())
file_format = items[len(items) - 1][1]
choices = file_format.choices

self.assertEqual(len(choices), 3)
self.assertEqual(choices[0][1], "---")
self.assertEqual(choices[1][1], "xlsx")
self.assertEqual(choices[2][1], "xls")


class ExportAdminIntegrationTest(AdminTestMixin, TestCase):
def test_export(self):
Expand Down Expand Up @@ -1052,7 +1071,7 @@ class TestCategoryAdmin(ExportActionModelAdmin):
def __init__(self):
super().__init__(mock_model, mock_site)

formats = [base_formats.CSV]
export_formats = [base_formats.CSV]

m = TestCategoryAdmin()
action_form = m.action_form
Expand All @@ -1076,7 +1095,7 @@ class TestFormatsCategoryAdmin(ExportActionModelAdmin):
def __init__(self):
super().__init__(mock_model, mock_site)

formats = [base_formats.CSV, base_formats.JSON]
export_formats = [base_formats.CSV, base_formats.JSON]

m = TestCategoryAdmin()
action_form = m.action_form
Expand All @@ -1096,11 +1115,61 @@ def __init__(self):
choices = file_format.choices

self.assertEqual(choices[0][1], "---")
self.assertEqual(len(m.formats) + 1, len(choices))
self.assertEqual(len(m.export_formats) + 1, len(choices))

self.assertIn("csv", [c[1] for c in choices])
self.assertIn("json", [c[1] for c in choices])

@override_settings(EXPORT_FORMATS=[base_formats.XLSX, base_formats.CSV])
def test_export_admin_action_uses_export_format_settings(self):
"""
Test that export action only avails the formats provided by the
EXPORT_FORMATS setting
"""
mock_model = mock.MagicMock()
mock_site = mock.MagicMock()

class TestCategoryAdmin(ExportActionModelAdmin):
def __init__(self):
super().__init__(mock_model, mock_site)

m = TestCategoryAdmin()
action_form = m.action_form

items = list(action_form.base_fields.items())
file_format = items[len(items) - 1][1]
choices = file_format.choices

self.assertEqual(len(choices), 3)
self.assertEqual(choices[0][1], "---")
self.assertEqual(choices[1][1], "xlsx")
self.assertEqual(choices[2][1], "csv")

@override_settings(IMPORT_EXPORT_FORMATS=[base_formats.XLS, base_formats.CSV])
def test_export_admin_action_uses_import_export_format_settings(self):
"""
Test that export action only avails the formats provided by the
IMPORT_EXPORT_FORMATS setting
"""
mock_model = mock.MagicMock()
mock_site = mock.MagicMock()

class TestCategoryAdmin(ExportActionModelAdmin):
def __init__(self):
super().__init__(mock_model, mock_site)

m = TestCategoryAdmin()
action_form = m.action_form

items = list(action_form.base_fields.items())
file_format = items[len(items) - 1][1]
choices = file_format.choices

self.assertEqual(len(choices), 3)
self.assertEqual(choices[0][1], "---")
self.assertEqual(choices[1][1], "xls")
self.assertEqual(choices[2][1], "csv")


class TestExportEncoding(TestCase):
mock_request = MagicMock(spec=HttpRequest)
Expand Down
16 changes: 12 additions & 4 deletions tests/core/tests/test_mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,12 @@ class CannotImportFormat(Format):
def __init__(self):
super().__init__(2, False)

m = mixins.BaseImportMixin()
m.formats = [CanImportFormat, CannotImportFormat]
class TestBaseImportMixin(mixins.BaseImportMixin):
@property
def import_formats(self):
return [CanImportFormat, CannotImportFormat]

m = TestBaseImportMixin()

formats = m.get_import_formats()
self.assertEqual(1, len(formats))
Expand Down Expand Up @@ -337,8 +341,12 @@ class CannotExportFormat(Format):
def __init__(self):
super().__init__(False)

m = mixins.BaseExportMixin()
m.formats = [CanExportFormat, CannotExportFormat]
class TestBaseExportMixin(mixins.BaseExportMixin):
@property
def export_formats(self):
return [CanExportFormat, CannotExportFormat]

m = TestBaseExportMixin()

formats = m.get_export_formats()
self.assertEqual(1, len(formats))
Expand Down