From 67f184201d04d58e0f2a361baef5b0667c0e07ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Thu, 21 Dec 2023 15:14:29 +0100 Subject: [PATCH] Fix for 974 (#1717) * fix for 974 * Update changelog.rst --------- Co-authored-by: Matt Hegarty --- docs/changelog.rst | 1 + import_export/resources.py | 13 +++++++++++-- import_export/widgets.py | 19 +++++++++++++++++-- .../tests/test_resources/test_resources.py | 18 ++++++++++++++++++ 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index fffbaac1f..16fa9844e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,6 +9,7 @@ Please refer to :doc:`release notes`. - Fix issue where declared Resource fields not defined in ``fields`` are still imported (#1702) - Added customizable ``MediaStorage`` (#1708) - Relocated admin integration section from advanced_usage.rst into new file (#1713) +- Fix slow export with ForeignKey id (#1717) 4.0.0-beta.2 (2023-12-09) ------------------------- diff --git a/import_export/resources.py b/import_export/resources.py index 7de21f5bb..1c0564b83 100644 --- a/import_export/resources.py +++ b/import_export/resources.py @@ -13,6 +13,7 @@ from django.core.paginator import Paginator from django.db import connections, router from django.db.models import fields +from django.db.models.fields.related import ForeignKey from django.db.models.query import QuerySet from django.db.transaction import TransactionManagementError, set_rollback from django.utils.encoding import force_str @@ -1196,9 +1197,17 @@ def field_from_django_field(cls, field_name, django_field, readonly): FieldWidget = cls.widget_from_django_field(django_field) widget_kwargs = cls.widget_kwargs_for_field(field_name, django_field) + + attribute = field_name + column_name = field_name + # To solve #974 + if isinstance(django_field, ForeignKey) and "__" not in column_name: + attribute += "_id" + widget_kwargs["key_is_id"] = True + field = cls.DEFAULT_RESOURCE_FIELD( - attribute=field_name, - column_name=field_name, + attribute=attribute, + column_name=column_name, widget=FieldWidget(**widget_kwargs), readonly=readonly, default=django_field.default, diff --git a/import_export/widgets.py b/import_export/widgets.py index 77391aaa0..17ba09c07 100644 --- a/import_export/widgets.py +++ b/import_export/widgets.py @@ -476,9 +476,17 @@ class Meta: related object, default to False """ - def __init__(self, model, field="pk", use_natural_foreign_keys=False, **kwargs): + def __init__( + self, + model, + field="pk", + use_natural_foreign_keys=False, + key_is_id=False, + **kwargs, + ): self.model = model self.field = field + self.key_is_id = key_is_id self.use_natural_foreign_keys = use_natural_foreign_keys super().__init__(**kwargs) @@ -528,7 +536,10 @@ def clean(self, value, row=None, **kwargs): return self.model.objects.get_by_natural_key(*value) else: lookup_kwargs = self.get_lookup_kwargs(value, row, **kwargs) - return self.get_queryset(value, row, **kwargs).get(**lookup_kwargs) + obj = self.get_queryset(value, row, **kwargs).get(**lookup_kwargs) + if self.key_is_id: + return obj.id + return obj else: return None @@ -551,6 +562,10 @@ def render(self, value, obj=None): ``coerce_to_string`` has no effect on the return value. """ self._obj_deprecation_warning(obj) + + if self.key_is_id: + return value or "" + if value is None: return "" diff --git a/tests/core/tests/test_resources/test_resources.py b/tests/core/tests/test_resources/test_resources.py index fa126bcf0..fe506f577 100644 --- a/tests/core/tests/test_resources/test_resources.py +++ b/tests/core/tests/test_resources/test_resources.py @@ -289,6 +289,24 @@ def test_export(self): dataset = self.resource.export(queryset=Book.objects.all()) self.assertEqual(len(dataset), 1) + @ignore_widget_deprecation_warning + def test_export_with_foreign_keys(self): + """ + Test that export() containing foreign keys doesn't generate + extra query for every row. + Fixes #974 + """ + author = Author.objects.create() + self.book.author = author + self.book.save() + Book.objects.create(name="Second book", author=Author.objects.create()) + Book.objects.create(name="Third book", author=Author.objects.create()) + + with self.assertNumQueries(3): + dataset = self.resource.export(Book.objects.prefetch_related("categories")) + self.assertEqual(dataset.dict[0]["author"], author.pk) + self.assertEqual(len(dataset), 3) + @ignore_widget_deprecation_warning def test_export_iterable(self): with self.assertNumQueries(2):