Skip to content

Commit

Permalink
Handle custom column name during export (#1821)
Browse files Browse the repository at this point in the history
* updated ebook test

* added fix and tests

* updated tests

* tidied method implementations

* updated changelog
  • Loading branch information
matthewhegarty committed May 13, 2024
1 parent 6eb2d21 commit bdc35eb
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 26 deletions.
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ Changelog

Version 4 introduces breaking changes. Please refer to :doc:`release notes<release_notes>`.

4.0.2 (unreleased)
------------------

- fix export with custom column name (`1821 <https://github.com/django-import-export/django-import-export/pull/1821>`_)

4.0.1 (2024-05-08)
------------------

Expand Down
2 changes: 1 addition & 1 deletion import_export/declarative.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def __new__(cls, name, bases, attrs):
if opts.exclude and f.name in opts.exclude:
continue

if f.name in declared_fields:
if f.name in set(declared_fields.keys()):
# If model field is declared in `ModelResource`,
# remove it from `declared_fields`
# to keep exact order of model fields
Expand Down
18 changes: 11 additions & 7 deletions import_export/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,9 @@ def import_field(self, field, instance, row, is_m2m=False, **kwargs):
field.save(instance, row, is_m2m, **kwargs)

def get_import_fields(self):
import_fields = []
for f in self.fields:
import_fields.append(f)
return [self.fields[f] for f in self.get_import_order()]

def import_obj(self, obj, data, dry_run, **kwargs):
Expand Down Expand Up @@ -1037,7 +1040,8 @@ def export_field(self, field, instance):
return field.export(instance)

def get_export_fields(self):
return [self.fields[f] for f in self.get_export_order()]
export_order = self.get_export_order()
return [self.fields[f] for f in export_order]

def export_resource(self, instance, fields=None):
export_fields = self.get_export_fields()
Expand All @@ -1046,18 +1050,17 @@ def export_resource(self, instance, fields=None):
return [
self.export_field(field, instance)
for field in export_fields
if field.column_name in fields
if field.attribute in fields
]

return [self.export_field(field, instance) for field in export_fields]

def get_export_headers(self, fields=None):
headers = [force_str(field.column_name) for field in self.get_export_fields()]

export_fields = self.get_export_fields()
if isinstance(fields, list) and fields:
return [f for f in headers if f in fields]
return [f.column_name for f in export_fields if f.attribute in fields]

return headers
return [force_str(field.column_name) for field in export_fields]

def get_user_visible_fields(self):
return self.get_fields()
Expand Down Expand Up @@ -1097,7 +1100,8 @@ def export(self, queryset=None, **kwargs):
dataset = tablib.Dataset(headers=headers)

for obj in self.iter_queryset(queryset):
dataset.append(self.export_resource(obj, fields=export_fields))
r = self.export_resource(obj, fields=export_fields)
dataset.append(r)

self.after_export(queryset, dataset, **kwargs)

Expand Down
15 changes: 7 additions & 8 deletions tests/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
ImportExportModelAdmin,
ImportMixin,
)
from import_export.fields import Field
from import_export.resources import ModelResource

from .forms import CustomConfirmImportForm, CustomExportForm, CustomImportForm
Expand Down Expand Up @@ -50,6 +51,8 @@ class AuthorAdmin(ImportMixin, admin.ModelAdmin):


class EBookResource(ModelResource):
published = Field(attribute="published", column_name="published_date")

def __init__(self, **kwargs):
super().__init__()
self.author_id = kwargs.get("author_id")
Expand All @@ -59,11 +62,7 @@ def filter_export(self, queryset, **kwargs):

class Meta:
model = EBook
fields = (
"id",
"author_email",
"name",
)
fields = ("id", "author_email", "name", "published")


class CustomBookAdmin(ImportExportModelAdmin):
Expand Down Expand Up @@ -97,10 +96,10 @@ def get_export_resource_kwargs(self, request, **kwargs):
# this is overridden to demonstrate that custom form fields can be used
# to override the export query.
# The dict returned here will be passed as kwargs to EBookResource
export_form = kwargs["export_form"]
export_form = kwargs.get("export_form")
if export_form:
return dict(author_id=export_form.cleaned_data["author"].id)
return {}
kwargs.update(author_id=export_form.cleaned_data["author"].id)
return kwargs


admin.site.register(Book, BookAdmin)
Expand Down
8 changes: 6 additions & 2 deletions tests/core/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from django import forms

from import_export.forms import ConfirmImportForm, ExportForm, ImportForm
from import_export.forms import (
ConfirmImportForm,
ImportForm,
SelectableFieldsExportForm,
)

from .models import Author

Expand All @@ -21,7 +25,7 @@ class CustomConfirmImportForm(AuthorFormMixin, ConfirmImportForm):
pass


class CustomExportForm(AuthorFormMixin, ExportForm):
class CustomExportForm(AuthorFormMixin, SelectableFieldsExportForm):
"""Customized ExportForm, with author field required"""

pass
36 changes: 33 additions & 3 deletions tests/core/tests/admin_integration/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,29 @@ def test_export_model_with_custom_PK(self):
self.assertEqual(response.status_code, 200)
self.assertContains(response, "UUIDCategoryResource")

def test_export_with_custom_field(self):
# issue 1808
a = Author.objects.create(id=11, name="Ian Fleming")
data = {
"format": "0",
"author": a.id,
"resource": "",
"ebookresource_id": True,
"ebookresource_author_email": True,
"ebookresource_name": True,
"ebookresource_published": True,
}
date_str = datetime.now().strftime("%Y-%m-%d")
response = self.client.post(self.ebook_export_url, data)
self.assertEqual(response.status_code, 200)
self.assertTrue(response.has_header("Content-Disposition"))
self.assertEqual(response["Content-Type"], "text/csv")
self.assertEqual(
response["Content-Disposition"],
'attachment; filename="EBook-{}.csv"'.format(date_str),
)
self.assertEqual(b"id,author_email,name,published_date\r\n", response.content)


class FilteredExportAdminIntegrationTest(AdminTestMixin, TestCase):
fixtures = ["category", "book", "author"]
Expand All @@ -316,7 +339,14 @@ def test_export_filters_by_form_param(self):
# issue 1578
author = Author.objects.get(name="Ian Fleming")

data = {"format": "0", "author": str(author.id)}
data = {
"format": "0",
"author": str(author.id),
"ebookresource_id": True,
"ebookresource_author_email": True,
"ebookresource_name": True,
"ebookresource_published": True,
}
date_str = datetime.now().strftime("%Y-%m-%d")
response = self.client.post(self.ebook_export_url, data)
self.assertEqual(response.status_code, 200)
Expand All @@ -327,8 +357,8 @@ def test_export_filters_by_form_param(self):
'attachment; filename="EBook-{}.csv"'.format(date_str),
)
self.assertEqual(
b"id,author_email,name\r\n"
b"5,ian@example.com,The Man with the Golden Gun\r\n",
b"id,author_email,name,published_date\r\n"
b"5,ian@example.com,The Man with the Golden Gun,1965-04-01\r\n",
response.content,
)

Expand Down
11 changes: 9 additions & 2 deletions tests/core/tests/admin_integration/test_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -932,8 +932,14 @@ def test_import_preview_order(self):
)
# test header rendered in correct order
target_header_re = (
r"<thead>[\\n\s]+<tr>[\\n\s]+<th></th>[\\n\s]+<th>id</th>"
r"[\\n\s]+<th>author_email</th>[\\n\s]+<th>name</th>[\\n\s]+</tr>[\\n\s]+"
r"<thead>[\\n\s]+"
r"<tr>[\\n\s]+"
r"<th></th>[\\n\s]+"
r"<th>id</th>[\\n\s]+"
r"<th>author_email</th>[\\n\s]+"
r"<th>name</th>[\\n\s]+"
r"<th>published_date</th>[\\n\s]+"
r"</tr>[\\n\s]+"
"</thead>"
)
self.assertRegex(str(response.content), target_header_re)
Expand All @@ -944,6 +950,7 @@ def test_import_preview_order(self):
r'<td><ins style="background:#e6ffe6;">1</ins></td>[\\n\s]+'
r'<td><ins style="background:#e6ffe6;">test@example.com</ins></td>[\\n\s]+'
r'<td><ins style="background:#e6ffe6;">Some book</ins></td>[\\n\s]+'
r"<td><span>None</span></td>[\\n\s]+"
"</tr>"
)
self.assertRegex(str(response.content), target_row_re)
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import tablib
from core.admin import BookResource
from core.models import Author, Book
from django.test import TestCase

from tests.core.tests.resources import BookResource


class ExportFunctionalityTest(TestCase):
def setUp(self):
Expand All @@ -22,7 +21,8 @@ def test_get_export_headers(self):
"name",
"author",
"author_email",
"published_date",
"imported",
"published",
"published_time",
"price",
"added",
Expand Down

0 comments on commit bdc35eb

Please sign in to comment.