Repository: django-import-export/django-import-export

Issue:
yaml export crashes for objects with decimal values
**Describe the bug**

Export is broken when one tries to export an object containing a `DecimalField` (e.g. Book).

The error is:

```
RepresenterError at /admin/core/book/export/
('cannot represent an object', Decimal('1.00'))
```

**To Reproduce**
Steps to reproduce the behavior:
1. Login to the Admin site
2. Create a book with a 'price' value
3. Export books (select 'yaml' format)
4. The crash should occur.

**Versions (please complete the following information):**
 - Django Import Export: 2.5.0
 - Python 3.9
 - Django 3.2

**Expected behavior**

Yaml export should work correctly.  Other exports work correctly.

**Stack trace**

```
Exception Type:	RepresenterError
Exception Value:	
('cannot represent an object', Decimal('1.00'))
Exception Location:	/home/matthew/.virtualenvs/django-import-export/lib/python3.9/site-packages/yaml/representer.py, line 231, in represent_undefined
```


Discussion:
Also #341
pyyaml has an open issue on this: https://github.com/yaml/pyyaml/issues/255
There even is a PR up to fix, but no move has been made on this issue for 2+ years.

As a quick fix, `DecimalWidget.render` could just return a string version of the decimal value.
> pyyaml has an open issue on this: [yaml/pyyaml#255](https://github.com/yaml/pyyaml/issues/255) There even is a PR up to fix, but no move has been made on this issue for 2+ years.
> 
> As a quick fix, `DecimalWidget.render` could just return a string version of the decimal value.

That has been done in #1501 and it seems to fix the problem.

Resolving patch:
diff --git a/docs/conf.py b/docs/conf.py
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -24,6 +24,8 @@
     "sphinx.ext.autosectionlabel",
 ]
 
+autoclass_content = "both"
+
 autosectionlabel_prefix_document = True
 
 # Add any paths that contain templates here, relative to this directory.
diff --git a/import_export/fields.py b/import_export/fields.py
--- a/import_export/fields.py
+++ b/import_export/fields.py
@@ -8,13 +8,13 @@
 
 class Field:
     """
-    Field represent mapping between `object` field and representation of
-    this field.
+    ``Field`` represents a mapping between an ``instance`` field and a representation of
+    the field's data.
 
-    :param attribute: A string of either an instance attribute or callable off
-        the object.
+    :param attribute: A string of either an instance attribute or callable of
+        the instance.
 
-    :param column_name: Lets you provide a name for the column that represents
+    :param column_name: An optional column name for the column that represents
         this field in the export.
 
     :param widget: Defines a widget that will be used to represent this
@@ -24,12 +24,16 @@ class Field:
         during import.
 
     :param default: This value will be returned by
-        :meth:`~import_export.fields.Field.clean` if this field's widget did
-        not return an adequate value.
+        :meth:`~import_export.fields.Field.clean` if this field's widget returned
+        a value defined in :attr:`~import_export.fields.empty_values`.
+
+    :param saves_null_values: Controls whether null values are saved on the instance.
+      This can be used if the widget returns null, but there is a default instance
+      value which should not be overwritten.
 
-    :param saves_null_values: Controls whether null values are saved on the object
     :param dehydrate_method: Lets you choose your own method for dehydration rather
         than using `dehydrate_{field_name}` syntax.
+
     :param m2m_add: changes save of this field to add the values, if they do not exist,
         to a ManyToMany field instead of setting all values.  Only useful if field is
         a ManyToMany field.
@@ -93,7 +97,7 @@ def clean(self, row, **kwargs):
 
     def get_value(self, instance):
         """
-        Returns the value of the object's attribute.
+        Returns the value of the instance's attribute.
         """
         if self.attribute is None:
             return None
@@ -119,7 +123,7 @@ def get_value(self, instance):
 
     def save(self, instance, row, is_m2m=False, **kwargs):
         """
-        If this field is not declared readonly, the object's attribute will
+        If this field is not declared readonly, the instance's attribute will
         be set to the value returned by :meth:`~import_export.fields.Field.clean`.
         """
         if not self.readonly:
@@ -142,8 +146,6 @@ def export(self, instance):
         representation.
         """
         value = self.get_value(instance)
-        if value is None:
-            return ""
         return self.widget.render(value, instance)
 
     def get_dehydrate_method(self, field_name=None):
diff --git a/import_export/widgets.py b/import_export/widgets.py
--- a/import_export/widgets.py
+++ b/import_export/widgets.py
@@ -1,6 +1,7 @@
 import json
 from datetime import date, datetime, time
 from decimal import Decimal
+from warnings import warn
 
 import django
 from django.conf import settings
@@ -23,48 +24,56 @@ def format_datetime(value, datetime_format):
 
 class Widget:
     """
-    A Widget takes care of converting between import and export representations.
-
-    This is achieved by the two methods,
-    :meth:`~import_export.widgets.Widget.clean` and
-    :meth:`~import_export.widgets.Widget.render`.
+    A Widget handles converting between import and export representations.
     """
 
-    def clean(self, value, row=None, **kwargs):
+    def __init__(self, coerce_to_string=True):
+        """
+        :param coerce_to_string: If True, :meth:`~import_export.widgets.Widget.render`
+          will return a string representation of the value, otherwise the value is
+          returned.
         """
-        Returns an appropriate Python object for an imported value.
+        self.coerce_to_string = coerce_to_string
 
-        For example, if you import a value from a spreadsheet,
-        :meth:`~import_export.widgets.Widget.clean` handles conversion
-        of this value into the corresponding Python object.
+    def clean(self, value, row=None, **kwargs):
+        """
+        Returns an appropriate python object for an imported value.
+        For example, a date string will be converted to a python datetime instance.
 
-        Numbers or dates can be *cleaned* to their respective data types and
-        don't have to be imported as Strings.
+        :param value: The value to be converted to a native type.
+        :param row: A dict containing row key/value pairs.
+        :param **kwargs: Optional kwargs.
         """
         return value
 
     def render(self, value, obj=None):
         """
-        Returns an export representation of a Python value.
+        Returns an export representation of a python value.
 
-        For example, if you have an object you want to export,
-        :meth:`~import_export.widgets.Widget.render` takes care of converting
-        the object's field to a value that can be written to a spreadsheet.
+        :param value: The python value to be rendered.
+        :param obj: The model instance from which the value is taken.
+          This parameter is deprecated and will be removed in a future release.
+
+        :return: By default, this value will be a string, with ``None`` values returned
+          as empty strings.
         """
         return force_str(value)
 
+    def _obj_deprecation_warning(self, obj):
+        if obj is not None:
+            warn(
+                "The 'obj' parameter is deprecated and will be removed "
+                "in a future release",
+                DeprecationWarning,
+                stacklevel=2,
+            )
+
 
 class NumberWidget(Widget):
     """
     Widget for converting numeric fields.
-
-    :param coerce_to_string: If True, render will return a string representation
-        of the value (None is returned as ""), otherwise the value is returned.
     """
 
-    def __init__(self, coerce_to_string=False):
-        self.coerce_to_string = coerce_to_string
-
     def is_empty(self, value):
         if isinstance(value, str):
             value = value.strip()
@@ -72,6 +81,7 @@ def is_empty(self, value):
         return value is None or value == ""
 
     def render(self, value, obj=None):
+        self._obj_deprecation_warning(obj)
         if self.coerce_to_string:
             return "" if value is None else number_format(value)
         return value
@@ -114,26 +124,26 @@ class CharWidget(Widget):
     """
     Widget for converting text fields.
 
-    :param coerce_to_string: If True, the value returned by clean() is cast to a
-      string.
-    :param allow_blank:  If True, and if coerce_to_string is True, then clean() will
-      return null values as empty strings, otherwise as null.
+    :param allow_blank:  If True, then :meth:`~import_export.widgets.Widget.clean`
+      will return null values as empty strings, otherwise as ``None``.
     """
 
-    def __init__(self, coerce_to_string=False, allow_blank=False):
+    def __init__(self, coerce_to_string=True, allow_blank=True):
         """ """
         self.coerce_to_string = coerce_to_string
         self.allow_blank = allow_blank
 
     def clean(self, value, row=None, **kwargs):
         val = super().clean(value, row, **kwargs)
-        if self.coerce_to_string is True:
-            if val is None:
-                if self.allow_blank is True:
-                    return ""
-            else:
-                return force_str(val)
-        return val
+        if val is None:
+            return "" if self.allow_blank is True else None
+        return force_str(val)
+
+    def render(self, value, obj=None):
+        self._obj_deprecation_warning(obj)
+        if self.coerce_to_string:
+            return "" if value is None else force_str(value)
+        return value
 
 
 class BooleanWidget(Widget):
@@ -157,35 +167,43 @@ class BooleanWidget(Widget):
         class BooleanExample(resources.ModelResource):
             warn = fields.Field(widget=widgets.BooleanWidget())
 
-            def before_import_row(self, row, row_number=None, **kwargs):
+            def before_import_row(self, row, **kwargs):
                 if "warn" in row.keys():
                     # munge "warn" to "True"
                     if row["warn"] in ["warn", "WARN"]:
                         row["warn"] = True
 
-                return super().before_import_row(row, row_number, **kwargs)
+                return super().before_import_row(row, **kwargs)
     """
 
     TRUE_VALUES = ["1", 1, True, "true", "TRUE", "True"]
     FALSE_VALUES = ["0", 0, False, "false", "FALSE", "False"]
     NULL_VALUES = ["", None, "null", "NULL", "none", "NONE", "None"]
 
+    def __init__(self, coerce_to_string=True):
+        """ """
+        self.coerce_to_string = coerce_to_string
+
+    def clean(self, value, row=None, **kwargs):
+        if value in self.NULL_VALUES:
+            return None
+        return True if value in self.TRUE_VALUES else False
+
     def render(self, value, obj=None):
         """
-        On export, ``True`` is represented as ``1``, ``False`` as ``0``, and
-        ``None``/NULL as a empty string.
+        :return: ``True`` is represented as ``1``, ``False`` as ``0``, and
+          ``None``/NULL as an empty string.
 
-        Note that these values are also used on the import confirmation view.
+          If ``coerce_to_string`` is ``False``, the python Boolean type is
+          returned (may be ``None``).
         """
+        self._obj_deprecation_warning(obj)
+        if self.coerce_to_string is False:
+            return value
         if value in self.NULL_VALUES:
             return ""
         return self.TRUE_VALUES[0] if value else self.FALSE_VALUES[0]
 
-    def clean(self, value, row=None, **kwargs):
-        if value in self.NULL_VALUES:
-            return None
-        return True if value in self.TRUE_VALUES else False
-
 
 class DateWidget(Widget):
     """
@@ -195,7 +213,8 @@ class DateWidget(Widget):
     ``settings.DATE_INPUT_FORMATS`` or ``"%Y-%m-%d"`` is used.
     """
 
-    def __init__(self, format=None):
+    def __init__(self, format=None, coerce_to_string=True):
+        super().__init__(coerce_to_string=coerce_to_string)
         if format is None:
             if not settings.DATE_INPUT_FORMATS:
                 formats = ("%Y-%m-%d",)
@@ -218,6 +237,9 @@ def clean(self, value, row=None, **kwargs):
         raise ValueError("Enter a valid date.")
 
     def render(self, value, obj=None):
+        self._obj_deprecation_warning(obj)
+        if self.coerce_to_string is False:
+            return value
         if not value:
             return ""
         return format_datetime(value, self.formats[0])
@@ -231,7 +253,8 @@ class DateTimeWidget(Widget):
     ``settings.DATETIME_INPUT_FORMATS`` or ``"%Y-%m-%d %H:%M:%S"`` is used.
     """
 
-    def __init__(self, format=None):
+    def __init__(self, format=None, coerce_to_string=True):
+        super().__init__(coerce_to_string=coerce_to_string)
         if format is None:
             if not settings.DATETIME_INPUT_FORMATS:
                 formats = ("%Y-%m-%d %H:%M:%S",)
@@ -260,6 +283,9 @@ def clean(self, value, row=None, **kwargs):
         raise ValueError("Enter a valid date/time.")
 
     def render(self, value, obj=None):
+        self._obj_deprecation_warning(obj)
+        if self.coerce_to_string is False:
+            return value
         if not value:
             return ""
         if settings.USE_TZ:
@@ -275,7 +301,8 @@ class TimeWidget(Widget):
     ``settings.DATETIME_INPUT_FORMATS`` or ``"%H:%M:%S"`` is used.
     """
 
-    def __init__(self, format=None):
+    def __init__(self, format=None, coerce_to_string=True):
+        super().__init__(coerce_to_string=coerce_to_string)
         if format is None:
             if not settings.TIME_INPUT_FORMATS:
                 formats = ("%H:%M:%S",)
@@ -298,6 +325,9 @@ def clean(self, value, row=None, **kwargs):
         raise ValueError("Enter a valid time.")
 
     def render(self, value, obj=None):
+        self._obj_deprecation_warning(obj)
+        if self.coerce_to_string is False:
+            return value
         if not value:
             return ""
         return value.strftime(self.formats[0])
@@ -318,6 +348,9 @@ def clean(self, value, row=None, **kwargs):
             raise ValueError("Enter a valid duration.")
 
     def render(self, value, obj=None):
+        self._obj_deprecation_warning(obj)
+        if self.coerce_to_string is False:
+            return value
         if value is None:
             return ""
         return str(value)
@@ -330,16 +363,23 @@ class SimpleArrayWidget(Widget):
     :param separator: Defaults to ``','``
     """
 
-    def __init__(self, separator=None):
+    def __init__(self, separator=None, coerce_to_string=True):
         if separator is None:
             separator = ","
         self.separator = separator
-        super().__init__()
+        super().__init__(coerce_to_string=coerce_to_string)
 
     def clean(self, value, row=None, **kwargs):
         return value.split(self.separator) if value else []
 
     def render(self, value, obj=None):
+        """
+        :return: A string with values separated by ``separator``.
+          If ``coerce_to_string`` is ``False``, the native array will be returned.
+        """
+        self._obj_deprecation_warning(obj)
+        if not self.coerce_to_string:
+            return value
         return self.separator.join(str(v) for v in value)
 
 
@@ -362,8 +402,14 @@ def clean(self, value, row=None, **kwargs):
                 return json.loads(val.replace("'", '"'))
 
     def render(self, value, obj=None):
+        """
+        :return: A JSON formatted string derived from ``value``.
+          ``coerce_to_string`` has no effect on the return value.
+        """
+        self._obj_deprecation_warning(obj)
         if value:
             return json.dumps(value)
+        return None
 
 
 class ForeignKeyWidget(Widget):
@@ -444,8 +490,8 @@ def get_queryset(self, value, row, *args, **kwargs):
 
     def clean(self, value, row=None, **kwargs):
         """
-        Return a single Foreign Key instance derived from the args.
-        ``None`` can be returned if the value passed is a null value.
+        :return: a single Foreign Key instance derived from the args.
+          ``None`` can be returned if the value passed is a null value.
 
         :param value: The field's value in the dataset.
         :param row: The dataset's current row.
@@ -467,8 +513,8 @@ def clean(self, value, row=None, **kwargs):
 
     def get_lookup_kwargs(self, value, row, **kwargs):
         """
-        Returns the key value pairs used to identify a model instance.
-        Override this to customize instance look up.
+        :return: the key value pairs used to identify a model instance.
+          Override this to customize instance lookup.
 
         :param value: The field's value in the dataset.
         :param row: The dataset's current row.
@@ -478,6 +524,12 @@ def get_lookup_kwargs(self, value, row, **kwargs):
         return {self.field: value}
 
     def render(self, value, obj=None):
+        """
+        :return: A string representation of the related value.
+          If ``use_natural_foreign_keys``, the value's natural key is returned.
+          ``coerce_to_string`` has no effect on the return value.
+        """
+        self._obj_deprecation_warning(obj)
         if value is None:
             return ""
 
@@ -530,5 +582,13 @@ def clean(self, value, row=None, **kwargs):
         return self.model.objects.filter(**{"%s__in" % self.field: ids})
 
     def render(self, value, obj=None):
-        ids = [smart_str(getattr(obj, self.field)) for obj in value.all()]
-        return self.separator.join(ids)
+        """
+        :return: A string with values separated by ``separator``.
+          ``None`` values are returned as empty strings.
+          ``coerce_to_string`` has no effect on the return value.
+        """
+        self._obj_deprecation_warning(obj)
+        if value is not None:
+            ids = [smart_str(getattr(obj, self.field)) for obj in value.all()]
+            return self.separator.join(ids)
+        return ""


Test patch:
diff --git a/tests/core/migrations/0014_bookwithchapternumbers.py b/tests/core/migrations/0014_bookwithchapternumbers.py
new file mode 100644
--- /dev/null
+++ b/tests/core/migrations/0014_bookwithchapternumbers.py
@@ -0,0 +1,51 @@
+from django.db import migrations, models
+
+can_use_postgres_fields = False
+
+# Dummy fields
+chapter_numbers_field = models.Field()
+
+try:
+    from django.contrib.postgres.fields import ArrayField
+
+    chapter_numbers_field = ArrayField(
+        base_field=models.PositiveSmallIntegerField(), default=list, size=None
+    )
+    can_use_postgres_fields = True
+except ImportError:
+    # We can't use ArrayField if psycopg2 is not installed - issue #1125
+    pass
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("core", "0013_alter_author_birthday"),
+    ]
+
+    operations = []
+
+    pg_only_operations = [
+        migrations.CreateModel(
+            name="BookWithChapterNumbers",
+            fields=[
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=100, verbose_name="Book name")),
+                ("chapter_numbers", chapter_numbers_field),
+            ],
+        ),
+    ]
+
+    def apply(self, project_state, schema_editor, collect_sql=False):
+        if can_use_postgres_fields and schema_editor.connection.vendor.startswith(
+            "postgres"
+        ):
+            self.operations = self.operations + self.pg_only_operations
+        return super().apply(project_state, schema_editor, collect_sql)
diff --git a/tests/core/tests/test_admin_integration.py b/tests/core/tests/test_admin_integration.py
--- a/tests/core/tests/test_admin_integration.py
+++ b/tests/core/tests/test_admin_integration.py
@@ -35,6 +35,8 @@
 from import_export.formats.base_formats import DEFAULT_FORMATS
 from import_export.tmp_storages import TempFolderStorage
 
+from .utils import ignore_widget_deprecation_warning
+
 
 class AdminTestMixin(object):
     book_import_url = "/admin/core/book/import/"
@@ -117,6 +119,7 @@ def test_import_export_template(self):
         self.assertContains(response, "Custom change list item")
 
     @override_settings(TEMPLATE_STRING_IF_INVALID="INVALID_VARIABLE")
+    @ignore_widget_deprecation_warning
     def test_import(self):
         # GET the import form
         response = self.client.get(self.book_import_url)
@@ -195,6 +198,7 @@ def test_correct_scripts_declared_when_debug_is_false(self):
         )
 
     @override_settings(TEMPLATE_STRING_IF_INVALID="INVALID_VARIABLE")
+    @ignore_widget_deprecation_warning
     def test_import_second_resource(self):
         Book.objects.create(id=1)
 
@@ -306,6 +310,7 @@ def test_import_action_handles_ValueError_as_form_error_with_translation(self):
         else:
             self.assertFormError(response, "form", "import_file", target_msg)
 
+    @ignore_widget_deprecation_warning
     def test_delete_from_admin(self):
         # test delete from admin site (see #432)
 
@@ -327,6 +332,7 @@ def test_delete_from_admin(self):
         self.assertEqual("", deleted_entry.object_repr)
 
     @override_settings(TEMPLATE_STRING_IF_INVALID="INVALID_VARIABLE")
+    @ignore_widget_deprecation_warning
     def test_import_mac(self):
         # GET the import form
         response = self.client.get(self.book_import_url)
@@ -391,6 +397,7 @@ def test_csrf(self):
         response = self.client.get(self.book_process_import_url)
         self.assertEqual(response.status_code, 405)
 
+    @ignore_widget_deprecation_warning
     def test_import_log_entry(self):
         response = self._do_import_post(self.book_import_url, "books.csv")
 
@@ -403,6 +410,7 @@ def test_import_log_entry(self):
         self.assertEqual(book.object_repr, "Some book")
         self.assertEqual(book.object_id, str(1))
 
+    @ignore_widget_deprecation_warning
     def test_import_log_entry_with_fk(self):
         Parent.objects.create(id=1234, name="Some Parent")
         response = self._do_import_post(self.child_import_url, "child.csv")
@@ -417,6 +425,7 @@ def test_import_log_entry_with_fk(self):
         self.assertEqual(child.object_repr, "Some - child of Some Parent")
         self.assertEqual(child.object_id, str(1))
 
+    @ignore_widget_deprecation_warning
     def test_import_with_customized_forms(self):
         """Test if admin import works if forms are customized"""
         # We reuse import scheme from `test_import` to import books.csv.
@@ -614,6 +623,7 @@ def test_returns_xlsx_export(self):
         )
 
     @override_settings(IMPORT_EXPORT_ESCAPE_FORMULAE_ON_EXPORT=True)
+    @ignore_widget_deprecation_warning
     def test_export_escape_formulae(self):
         Book.objects.create(id=1, name="=SUM(1+1)")
         Book.objects.create(id=2, name="<script>alert(1)</script>")
@@ -630,6 +640,7 @@ def test_export_escape_formulae(self):
         self.assertEqual("SUM(1+1)", wb.active["B3"].value)
 
     @override_settings(IMPORT_EXPORT_ESCAPE_FORMULAE_ON_EXPORT=True)
+    @ignore_widget_deprecation_warning
     def test_export_escape_formulae_csv(self):
         b1 = Book.objects.create(id=1, name="=SUM(1+1)")
         response = self.client.get("/admin/core/book/export/")
@@ -644,6 +655,7 @@ def test_export_escape_formulae_csv(self):
         )
 
     @override_settings(IMPORT_EXPORT_ESCAPE_FORMULAE_ON_EXPORT=False)
+    @ignore_widget_deprecation_warning
     def test_export_escape_formulae_csv_false(self):
         b1 = Book.objects.create(id=1, name="=SUM(1+1)")
         response = self.client.get("/admin/core/book/export/")
@@ -661,6 +673,7 @@ def test_export_escape_formulae_csv_false(self):
 class FilteredExportAdminIntegrationTest(AdminTestMixin, TestCase):
     fixtures = ["category", "book", "author"]
 
+    @ignore_widget_deprecation_warning
     def test_export_filters_by_form_param(self):
         # issue 1578
         author = Author.objects.get(name="Ian Fleming")
@@ -701,72 +714,84 @@ def _is_str_in_response(self, filename, input_format, encoding=None):
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.TempFolderStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_TempFolderStorage_read(self):
         self._is_str_in_response("books.csv", "0")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.TempFolderStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_TempFolderStorage_read_mac(self):
         self._is_str_in_response("books-mac.csv", "0")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.TempFolderStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_TempFolderStorage_read_iso_8859_1(self):
         self._is_str_in_response("books-ISO-8859-1.csv", "0", "ISO-8859-1")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.TempFolderStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_TempFolderStorage_read_binary(self):
         self._is_str_in_response("books.xls", "1")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.CacheStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_CacheStorage_read(self):
         self._is_str_in_response("books.csv", "0")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.CacheStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_CacheStorage_read_mac(self):
         self._is_str_in_response("books-mac.csv", "0")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.CacheStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_CacheStorage_read_iso_8859_1(self):
         self._is_str_in_response("books-ISO-8859-1.csv", "0", "ISO-8859-1")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.CacheStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_CacheStorage_read_binary(self):
         self._is_str_in_response("books.xls", "1")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.MediaStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_MediaStorage_read(self):
         self._is_str_in_response("books.csv", "0")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.MediaStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_MediaStorage_read_mac(self):
         self._is_str_in_response("books-mac.csv", "0")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.MediaStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_MediaStorage_read_iso_8859_1(self):
         self._is_str_in_response("books-ISO-8859-1.csv", "0", "ISO-8859-1")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.MediaStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_MediaStorage_read_binary(self):
         self._is_str_in_response("books.xls", "1")
 
@@ -792,72 +817,84 @@ def _is_str_in_response(self, filename, input_format, encoding=None):
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.TempFolderStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_TempFolderStorage_read(self):
         self._is_str_in_response("books.csv", "0")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.TempFolderStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_TempFolderStorage_read_mac(self):
         self._is_str_in_response("books-mac.csv", "0")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.TempFolderStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_TempFolderStorage_read_iso_8859_1(self):
         self._is_str_in_response("books-ISO-8859-1.csv", "0", "ISO-8859-1")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.TempFolderStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_TempFolderStorage_read_binary(self):
         self._is_str_in_response("books.xls", "1")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.CacheStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_CacheStorage_read(self):
         self._is_str_in_response("books.csv", "0")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.CacheStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_CacheStorage_read_mac(self):
         self._is_str_in_response("books-mac.csv", "0")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.CacheStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_CacheStorage_read_iso_8859_1(self):
         self._is_str_in_response("books-ISO-8859-1.csv", "0", "ISO-8859-1")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.CacheStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_CacheStorage_read_binary(self):
         self._is_str_in_response("books.xls", "1")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.MediaStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_MediaStorage_read(self):
         self._is_str_in_response("books.csv", "0")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.MediaStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_MediaStorage_read_mac(self):
         self._is_str_in_response("books-mac.csv", "0")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.MediaStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_MediaStorage_read_iso_8859_1(self):
         self._is_str_in_response("books-ISO-8859-1.csv", "0", "ISO-8859-1")
 
     @override_settings(
         IMPORT_EXPORT_TMP_STORAGE_CLASS="import_export.tmp_storages.MediaStorage"
     )
+    @ignore_widget_deprecation_warning
     def test_import_action_handles_MediaStorage_read_binary(self):
         self._is_str_in_response("books.xls", "1")
 
@@ -880,6 +917,7 @@ def setUp(self):
         self.cat1 = Category.objects.create(name="Cat 1")
         self.cat2 = Category.objects.create(name="Cat 2")
 
+    @ignore_widget_deprecation_warning
     def test_export(self):
         data = {
             "action": ["export_admin_action"],
@@ -1161,6 +1199,7 @@ def _is_regex_in_response(
         if regex_in_response is not None:
             self.assertRegex(str(response.content), regex_in_response)
 
+    @ignore_widget_deprecation_warning
     def test_import_action_create(self):
         self._is_str_in_response(
             "books.csv",
@@ -1170,6 +1209,7 @@ def test_import_action_create(self):
         )
         self.assertEqual(1, Book.objects.count())
 
+    @ignore_widget_deprecation_warning
     def test_import_action_invalid_date(self):
         # test that a row with an invalid date redirects to errors page
         index = self._get_input_format_index("csv")
@@ -1185,6 +1225,7 @@ def test_import_action_invalid_date(self):
         # no rows should be imported because we rollback on validation errors
         self.assertEqual(0, Book.objects.count())
 
+    @ignore_widget_deprecation_warning
     def test_import_action_error_on_save(self):
         with mock.patch("core.models.Book.save") as mock_save:
             mock_save.side_effect = ValueError("some unknown error")
@@ -1199,6 +1240,7 @@ def test_import_transaction_enabled_validation_error(self):
         self.assertEqual(0, Book.objects.count())
 
     @override_settings(IMPORT_EXPORT_USE_TRANSACTIONS=False)
+    @ignore_widget_deprecation_warning
     def test_import_transaction_disabled_validation_error(self):
         # with transactions disabled, a validation error should not cause the entire
         # import to fail
@@ -1206,6 +1248,7 @@ def test_import_transaction_disabled_validation_error(self):
         self.assertEqual(1, Book.objects.count())
 
     @override_settings(IMPORT_EXPORT_USE_TRANSACTIONS=True)
+    @ignore_widget_deprecation_warning
     def test_import_transaction_enabled_core_error(self):
         # test that if we send a file with multiple rows,
         # and transactions is enabled, a core error means that
@@ -1218,6 +1261,7 @@ def test_import_transaction_enabled_core_error(self):
         self.assertEqual(0, Book.objects.count())
 
     @override_settings(IMPORT_EXPORT_USE_TRANSACTIONS=False)
+    @ignore_widget_deprecation_warning
     def test_import_transaction_disabled_core_error(self):
         # with transactions disabled, a core (db constraint) error should not cause the
         # entire import to fail
@@ -1228,6 +1272,7 @@ def test_import_transaction_disabled_core_error(self):
         self.assertIn("some unknown error", str(response.content))
         self.assertEqual(2, Book.objects.count())
 
+    @ignore_widget_deprecation_warning
     def test_import_action_mac(self):
         self._is_str_in_response(
             "books-mac.csv",
@@ -1236,6 +1281,7 @@ def test_import_action_mac(self):
             str_in_response="Import finished, with 1 new and 0 updated books.",
         )
 
+    @ignore_widget_deprecation_warning
     def test_import_action_iso_8859_1(self):
         self._is_str_in_response(
             "books-ISO-8859-1.csv",
@@ -1257,6 +1303,7 @@ def test_import_action_decode_error(self):
             ),
         )
 
+    @ignore_widget_deprecation_warning
     def test_import_action_binary(self):
         self._is_str_in_response(
             "books.xls",
diff --git a/tests/core/tests/test_fields.py b/tests/core/tests/test_fields.py
--- a/tests/core/tests/test_fields.py
+++ b/tests/core/tests/test_fields.py
@@ -142,3 +142,12 @@ def test_m2m_add_False(self):
         self.assertEqual(m2m_related_manager.add.call_count, 0)
         self.assertEqual(m2m_related_manager.set.call_count, 1)
         m2m_related_manager.set.assert_called_once_with(["Foo", "Bar"])
+
+    def test_get_value_with_callable(self):
+        class CallableValue:
+            def __call__(self):
+                return "some val"
+
+        self.obj.name = CallableValue()
+        val = self.field.get_value(self.obj)
+        self.assertEqual("some val", val)
diff --git a/tests/core/tests/test_mixins.py b/tests/core/tests/test_mixins.py
--- a/tests/core/tests/test_mixins.py
+++ b/tests/core/tests/test_mixins.py
@@ -8,6 +8,8 @@
 
 from import_export import admin, formats, forms, mixins, resources
 
+from .utils import ignore_widget_deprecation_warning
+
 
 class ExportViewMixinTest(TestCase):
     class TestExportForm(forms.ExportForm):
@@ -27,6 +29,7 @@ def test_get(self):
         self.assertContains(response, self.cat1.name, status_code=200)
         self.assertEqual(response["Content-Type"], "text/html; charset=utf-8")
 
+    @ignore_widget_deprecation_warning
     def test_post(self):
         data = {
             "file_format": "0",
diff --git a/tests/core/tests/test_permissions.py b/tests/core/tests/test_permissions.py
--- a/tests/core/tests/test_permissions.py
+++ b/tests/core/tests/test_permissions.py
@@ -4,6 +4,8 @@
 from django.test.testcases import TestCase
 from django.test.utils import override_settings
 
+from .utils import ignore_widget_deprecation_warning
+
 
 class ImportExportPermissionTest(TestCase):
     def setUp(self):
@@ -20,6 +22,7 @@ def set_user_book_model_permission(self, action):
         self.user.user_permissions.add(permission)
 
     @override_settings(IMPORT_EXPORT_IMPORT_PERMISSION_CODE="change")
+    @ignore_widget_deprecation_warning
     def test_import(self):
         # user has no permission to import
         response = self.client.get("/admin/core/book/import/")
diff --git a/tests/core/tests/test_resources.py b/tests/core/tests/test_resources.py
--- a/tests/core/tests/test_resources.py
+++ b/tests/core/tests/test_resources.py
@@ -41,6 +41,7 @@
     WithDynamicDefault,
     WithFloatField,
 )
+from .utils import ignore_widget_deprecation_warning
 
 
 class MyResource(resources.Resource):
@@ -412,16 +413,19 @@ def test_get_export_headers(self):
             ],
         )
 
+    @ignore_widget_deprecation_warning
     def test_export(self):
         with self.assertNumQueries(2):
             dataset = self.resource.export(queryset=Book.objects.all())
             self.assertEqual(len(dataset), 1)
 
+    @ignore_widget_deprecation_warning
     def test_export_iterable(self):
         with self.assertNumQueries(2):
             dataset = self.resource.export(queryset=list(Book.objects.all()))
             self.assertEqual(len(dataset), 1)
 
+    @ignore_widget_deprecation_warning
     def test_export_prefetch_related(self):
         with self.assertNumQueries(3):
             dataset = self.resource.export(
@@ -429,6 +433,7 @@ def test_export_prefetch_related(self):
             )
             self.assertEqual(len(dataset), 1)
 
+    @ignore_widget_deprecation_warning
     def test_export_handles_named_queryset_parameter(self):
         class _BookResource(BookResource):
             def before_export(self, queryset, **kwargs):
@@ -474,6 +479,7 @@ class Meta:
             list(B().iter_queryset(qs))
             mocked_obj.assert_called_once_with(qs, 1000)
 
+    @ignore_widget_deprecation_warning
     def test_get_diff(self):
         diff = Diff(self.resource, self.book, False)
         book2 = Book(name="Some other book")
@@ -487,6 +493,7 @@ def test_get_diff(self):
         )
         self.assertFalse(html[headers.index("author_email")])
 
+    @ignore_widget_deprecation_warning
     def test_import_data_update(self):
         result = self.resource.import_data(self.dataset, raise_errors=True)
 
@@ -506,6 +513,7 @@ def test_import_data_update(self):
         self.assertEqual(instance.author_email, "test@example.com")
         self.assertEqual(instance.price, Decimal("10.25"))
 
+    @ignore_widget_deprecation_warning
     def test_import_data_new(self):
         Book.objects.all().delete()
         self.assertEqual(0, Book.objects.count())
@@ -526,6 +534,7 @@ def test_import_data_new(self):
         self.assertEqual(instance.author_email, "test@example.com")
         self.assertEqual(instance.price, Decimal("10.25"))
 
+    @ignore_widget_deprecation_warning
     def test_import_data_new_store_instance(self):
         self.resource = BookResourceWithStoreInstance()
         Book.objects.all().delete()
@@ -539,6 +548,7 @@ def test_import_data_new_store_instance(self):
         book = Book.objects.first()
         self.assertEqual(book.pk, result.rows[0].instance.pk)
 
+    @ignore_widget_deprecation_warning
     def test_import_data_update_store_instance(self):
         self.resource = BookResourceWithStoreInstance()
         result = self.resource.import_data(self.dataset, raise_errors=True)
@@ -553,6 +563,7 @@ def test_import_data_update_store_instance(self):
 
     @skipUnlessDBFeature("supports_transactions")
     @mock.patch("import_export.resources.connections")
+    @ignore_widget_deprecation_warning
     def test_import_data_no_transaction(self, mock_db_connections):
         class Features:
             supports_transactions = False
@@ -593,12 +604,14 @@ class DummyConnection(object):
                 use_transactions=True,
             )
 
+    @ignore_widget_deprecation_warning
     def test_importing_with_line_number_logging(self):
         resource = BookResourceWithLineNumberLogger()
         resource.import_data(self.dataset, raise_errors=True)
         self.assertEqual(resource.before_lines, [1])
         self.assertEqual(resource.after_lines, [1])
 
+    @ignore_widget_deprecation_warning
     def test_import_data_raises_field_specific_validation_errors(self):
         resource = AuthorResource()
         dataset = tablib.Dataset(headers=["id", "name", "birthday"])
@@ -610,6 +623,7 @@ def test_import_data_raises_field_specific_validation_errors(self):
         self.assertIs(result.rows[0].import_type, results.RowResult.IMPORT_TYPE_INVALID)
         self.assertIn("birthday", result.invalid_rows[0].field_specific_errors)
 
+    @ignore_widget_deprecation_warning
     def test_import_data_raises_field_specific_validation_errors_with_skip_unchanged(
         self,
     ):
@@ -635,6 +649,7 @@ def test_import_data_empty_dataset_with_collect_failed_rows(self):
         ):
             resource.import_data(tablib.Dataset(), collect_failed_rows=True)
 
+    @ignore_widget_deprecation_warning
     def test_collect_failed_rows(self):
         resource = ProfileResource()
         headers = ["id", "user"]
@@ -651,6 +666,7 @@ def test_collect_failed_rows(self):
         self.assertEqual(len(result.failed_dataset), 1)
         # We can't check the error message because it's package- and version-dependent
 
+    @ignore_widget_deprecation_warning
     def test_row_result_raise_errors(self):
         resource = ProfileResource()
         headers = ["id", "user"]
@@ -665,6 +681,7 @@ def test_row_result_raise_errors(self):
                 raise_errors=True,
             )
 
+    @ignore_widget_deprecation_warning
     def test_collect_failed_rows_validation_error(self):
         resource = ProfileResource()
         row = ["1"]
@@ -688,6 +705,7 @@ def test_collect_failed_rows_validation_error(self):
             "{'__all__': ['fail!']}", result.failed_dataset.dict[0]["Error"]
         )
 
+    @ignore_widget_deprecation_warning
     def test_row_result_raise_ValidationError(self):
         resource = ProfileResource()
         row = ["1"]
@@ -703,6 +721,7 @@ def test_row_result_raise_ValidationError(self):
                     raise_errors=True,
                 )
 
+    @ignore_widget_deprecation_warning
     def test_import_data_handles_widget_valueerrors_with_unicode_messages(self):
         resource = AuthorResourceWithCustomWidget()
         dataset = tablib.Dataset(headers=["id", "name", "birthday"])
@@ -733,6 +752,7 @@ class Meta:
         self.assertFalse(result.has_validation_errors())
         self.assertEqual(len(result.invalid_rows), 0)
 
+    @ignore_widget_deprecation_warning
     def test_model_validation_errors_raised_when_clean_model_instances_is_true(self):
         class TestResource(resources.ModelResource):
             class Meta:
@@ -762,6 +782,7 @@ class Meta:
         self.assertEqual(result.diff_headers, ["id", "name", "birthday"])
         self.assertEqual(invalid_row.values, ("1", "123", "---"))
 
+    @ignore_widget_deprecation_warning
     def test_known_invalid_fields_are_excluded_from_model_instance_cleaning(self):
         # The custom widget on the parent class should complain about
         # 'name' first, preventing Author.full_clean() from raising the
@@ -803,6 +824,7 @@ def test_import_data_error_saving_model(self):
             },
         )
 
+    @ignore_widget_deprecation_warning
     def test_import_data_delete(self):
         class B(BookResource):
             delete = fields.Field(widget=widgets.BooleanWidget())
@@ -821,6 +843,7 @@ def for_delete(self, row, instance):
         self.assertIsNone(result.rows[0].instance)
         self.assertIsNone(result.rows[0].original)
 
+    @ignore_widget_deprecation_warning
     def test_import_data_delete_store_instance(self):
         class B(BookResource):
             delete = fields.Field(widget=widgets.BooleanWidget())
@@ -839,6 +862,7 @@ class Meta:
         )
         self.assertIsNotNone(result.rows[0].instance)
 
+    @ignore_widget_deprecation_warning
     def test_save_instance_with_dry_run_flag(self):
         class B(BookResource):
             def before_save_instance(self, instance, row, **kwargs):
@@ -892,6 +916,7 @@ def test_delete_instance_noop(self, mock_book):
         )
         self.assertEqual(0, mock_book.call_count)
 
+    @ignore_widget_deprecation_warning
     def test_delete_instance_with_dry_run_flag(self):
         class B(BookResource):
             delete = fields.Field(widget=widgets.BooleanWidget())
@@ -936,6 +961,7 @@ def after_delete_instance(self, instance, row, **kwargs):
         self.assertFalse(resource.delete_instance_dry_run)
         self.assertFalse(resource.after_delete_instance_dry_run)
 
+    @ignore_widget_deprecation_warning
     def test_relationships_fields(self):
         class B(resources.ModelResource):
             class Meta:
@@ -1001,6 +1027,7 @@ class Meta:
         self.assertEqual(1, len(resource.fields))
         self.assertEqual("full_title", list(resource.fields.keys())[0])
 
+    @ignore_widget_deprecation_warning
     def test_widget_format_in_fk_field(self):
         class B(resources.ModelResource):
             class Meta:
@@ -1016,6 +1043,7 @@ class Meta:
         result = resource.fields["author__birthday"].export(self.book)
         self.assertEqual(result, str(date.today()))
 
+    @ignore_widget_deprecation_warning
     def test_widget_kwargs_for_field(self):
         class B(resources.ModelResource):
             class Meta:
@@ -1030,6 +1058,7 @@ class Meta:
         result = resource.fields["published"].export(self.book)
         self.assertEqual(result, "13.08.2012")
 
+    @ignore_widget_deprecation_warning
     def test_foreign_keys_export(self):
         author1 = Author.objects.create(name="Foo")
         self.book.author = author1
@@ -1038,6 +1067,7 @@ def test_foreign_keys_export(self):
         dataset = self.resource.export(Book.objects.all())
         self.assertEqual(dataset.dict[0]["author"], author1.pk)
 
+    @ignore_widget_deprecation_warning
     def test_foreign_keys_import(self):
         author2 = Author.objects.create(name="Bar")
         headers = ["id", "name", "author"]
@@ -1048,6 +1078,7 @@ def test_foreign_keys_import(self):
         book = Book.objects.get(name="FooBook")
         self.assertEqual(book.author, author2)
 
+    @ignore_widget_deprecation_warning
     def test_m2m_export(self):
         cat1 = Category.objects.create(name="Cat 1")
         cat2 = Category.objects.create(name="Cat 2")
@@ -1057,6 +1088,7 @@ def test_m2m_export(self):
         dataset = self.resource.export(Book.objects.all())
         self.assertEqual(dataset.dict[0]["categories"], "%d,%d" % (cat1.pk, cat2.pk))
 
+    @ignore_widget_deprecation_warning
     def test_m2m_import(self):
         cat1 = Category.objects.create(name="Cat 1")
         headers = ["id", "name", "categories"]
@@ -1067,6 +1099,7 @@ def test_m2m_import(self):
         book = Book.objects.get(name="FooBook")
         self.assertIn(cat1, book.categories.all())
 
+    @ignore_widget_deprecation_warning
     def test_m2m_options_import(self):
         cat1 = Category.objects.create(name="Cat 1")
         cat2 = Category.objects.create(name="Cat 2")
@@ -1089,6 +1122,7 @@ class Meta:
         self.assertIn(cat1, book.categories.all())
         self.assertIn(cat2, book.categories.all())
 
+    @ignore_widget_deprecation_warning
     def test_import_null_django_CharField_saved_as_empty_string(self):
         # issue 1485
         resource = BookResource()
@@ -1101,6 +1135,7 @@ def test_import_null_django_CharField_saved_as_empty_string(self):
         book = Book.objects.get(id=1)
         self.assertEqual("", book.author_email)
 
+    @ignore_widget_deprecation_warning
     def test_import_empty_django_CharField_saved_as_empty_string(self):
         resource = BookResource()
         self.assertTrue(resource._meta.model.author_email.field.blank)
@@ -1112,6 +1147,7 @@ def test_import_empty_django_CharField_saved_as_empty_string(self):
         book = Book.objects.get(id=1)
         self.assertEqual("", book.author_email)
 
+    @ignore_widget_deprecation_warning
     def test_m2m_add(self):
         cat1 = Category.objects.create(name="Cat 1")
         cat2 = Category.objects.create(name="Cat 2")
@@ -1155,6 +1191,7 @@ class Meta:
         self.assertIn(cat3, book2.categories.all())
         self.assertIn(cat4, book2.categories.all())
 
+    @ignore_widget_deprecation_warning
     def test_related_one_to_one(self):
         # issue #17 - Exception when attempting access something on the
         # related_name
@@ -1182,6 +1219,7 @@ def test_empty_get_queryset(self):
         dataset = self.resource.export(queryset=Book.objects.none())
         self.assertEqual(len(dataset), 0)
 
+    @ignore_widget_deprecation_warning
     def test_import_data_skip_unchanged(self):
         def attempted_save(instance, new, using_transactions, real_dry_run):
             self.fail("Resource attempted to save instead of skipping")
@@ -1212,6 +1250,7 @@ def attempted_save(instance, new, using_transactions, real_dry_run):
         self.assertFalse(result.has_errors())
         self.assertEqual(len(result.rows), 0)
 
+    @ignore_widget_deprecation_warning
     def test_before_import_access_to_kwargs(self):
         class B(BookResource):
             def before_import(self, dataset, **kwargs):
@@ -1240,6 +1279,7 @@ def before_import(self, dataset, **kwargs):
             resource.import_data(self.dataset, raise_errors=True)
         self.assertEqual("This is an invalid dataset", cm.exception.args[0])
 
+    @ignore_widget_deprecation_warning
     def test_after_import_raises_error(self):
         class B(BookResource):
             def after_import(
@@ -1310,6 +1350,7 @@ def field_from_django_field(self, field_name, django_field, readonly):
         B()
         self.assertEqual({"sound": "quack"}, B.fields["published"])
 
+    @ignore_widget_deprecation_warning
     def test_readonly_annotated_field_import_and_export(self):
         class B(BookResource):
             total_categories = fields.Field("total_categories", readonly=True)
@@ -1336,6 +1377,7 @@ class Meta:
         self.assertEqual(len(result.rows), len(dataset))
         self.assertEqual(result.rows[0].import_type, results.RowResult.IMPORT_TYPE_SKIP)
 
+    @ignore_widget_deprecation_warning
     def test_follow_relationship_for_modelresource(self):
         class EntryResource(resources.ModelResource):
             username = fields.Field(attribute="user__username", readonly=False)
@@ -1367,6 +1409,7 @@ def after_save_instance(self, instance, row, **kwargs):
         self.assertFalse(result.has_errors())
         self.assertEqual(User.objects.get(pk=user.pk).username, "bar")
 
+    @ignore_widget_deprecation_warning
     def test_import_data_dynamic_default_callable(self):
         class DynamicDefaultResource(resources.ModelResource):
             class Meta:
@@ -1391,6 +1434,7 @@ class Meta:
         objs = WithDynamicDefault.objects.all()
         self.assertNotEqual(objs[0].name, objs[1].name)
 
+    @ignore_widget_deprecation_warning
     def test_float_field(self):
         # 433
         class R(resources.ModelResource):
@@ -1486,6 +1530,7 @@ class Meta:
 
 class ModelResourceTransactionTest(TransactionTestCase):
     @skipUnlessDBFeature("supports_transactions")
+    @ignore_widget_deprecation_warning
     def test_m2m_import_with_transactions(self):
         resource = BookResource()
         cat1 = Category.objects.create(name="Cat 1")
@@ -1548,6 +1593,7 @@ def save_m2m(self, instance, *args, **kwargs):
         )
         self.assertTrue(result.has_errors())
 
+    @ignore_widget_deprecation_warning
     def test_rollback_on_validation_errors_false(self):
         """Should create only one instance as the second one
         raises a ``ValidationError``"""
@@ -1570,6 +1616,7 @@ def test_rollback_on_validation_errors_false(self):
         # Ensure that valid row resulted in an instance created.
         self.assertEqual(Author.objects.count(), 1)
 
+    @ignore_widget_deprecation_warning
     def test_rollback_on_validation_errors_true(self):
         """
         Should not create any instances as the second one raises a ``ValidationError``
@@ -1617,6 +1664,7 @@ class PostgresTests(TransactionTestCase):
     # Make sure to start the sequences back at 1
     reset_sequences = True
 
+    @ignore_widget_deprecation_warning
     def test_create_object_after_importing_dataset_with_id(self):
         dataset = tablib.Dataset(headers=["id", "name"])
         dataset.append([1, "Some book"])
@@ -1644,6 +1692,10 @@ class BookWithChapters(models.Model):
         chapters = ArrayField(models.CharField(max_length=100), default=list)
         data = models.JSONField(null=True)
 
+    class BookWithChapterNumbers(models.Model):
+        name = models.CharField("Book name", max_length=100)
+        chapter_numbers = ArrayField(models.PositiveSmallIntegerField(), default=list)
+
     class BookWithChaptersResource(resources.ModelResource):
         class Meta:
             model = BookWithChapters
@@ -1654,7 +1706,17 @@ class Meta:
                 "data",
             )
 
+    class BookWithChapterNumbersResource(resources.ModelResource):
+        class Meta:
+            model = BookWithChapterNumbers
+            fields = (
+                "id",
+                "name",
+                "chapter_numbers",
+            )
+
     class TestExportArrayField(TestCase):
+        @ignore_widget_deprecation_warning
         def test_exports_array_field(self):
             dataset_headers = ["id", "name", "chapters"]
             chapters = ["Introduction", "Middle Chapter", "Ending"]
@@ -1679,6 +1741,7 @@ def setUp(self):
             row = [self.book.id, "Some book", ",".join(self.chapters)]
             self.dataset.append(row)
 
+        @ignore_widget_deprecation_warning
         def test_import_of_data_with_array(self):
             self.assertListEqual(self.book.chapters, [])
             result = self.resource.import_data(self.dataset, raise_errors=True)
@@ -1689,11 +1752,35 @@ def test_import_of_data_with_array(self):
             self.book.refresh_from_db()
             self.assertEqual(self.book.chapters, self.chapters)
 
+    class TestImportIntArrayField(TestCase):
+        def setUp(self):
+            self.resource = BookWithChapterNumbersResource()
+            self.chapter_numbers = [1, 2, 3]
+            self.book = BookWithChapterNumbers.objects.create(
+                name="foo", chapter_numbers=[]
+            )
+            self.dataset = tablib.Dataset(
+                *[(1, "some book", "1,2,3")], headers=["id", "name", "chapter_numbers"]
+            )
+
+        @ignore_widget_deprecation_warning
+        def test_import_of_data_with_int_array(self):
+            # issue #1495
+            self.assertListEqual(self.book.chapter_numbers, [])
+            result = self.resource.import_data(self.dataset, raise_errors=True)
+
+            self.assertFalse(result.has_errors())
+            self.assertEqual(len(result.rows), 1)
+
+            self.book.refresh_from_db()
+            self.assertEqual(self.book.chapter_numbers, self.chapter_numbers)
+
     class TestExportJsonField(TestCase):
         def setUp(self):
             self.json_data = {"some_key": "some_value"}
             self.book = BookWithChapters.objects.create(name="foo", data=self.json_data)
 
+        @ignore_widget_deprecation_warning
         def test_export_field_with_appropriate_format(self):
             resource = resources.modelresource_factory(model=BookWithChapters)()
             result = resource.export(BookWithChapters.objects.all())
@@ -1710,6 +1797,7 @@ def setUp(self):
             row = [self.book.id, "Some book", self.json_data]
             self.dataset.append(row)
 
+        @ignore_widget_deprecation_warning
         def test_sets_json_data_when_model_field_is_empty(self):
             self.assertIsNone(self.book.data)
             result = self.resource.import_data(self.dataset, raise_errors=True)
@@ -1727,6 +1815,7 @@ def setUp(self):
         self.role = Role.objects.create(user=self.user)
         self.person = Person.objects.create(role=self.role)
 
+    @ignore_widget_deprecation_warning
     def test_export(self):
         class MyPersonResource(resources.ModelResource):
             role = fields.Field(
@@ -1742,7 +1831,7 @@ class Meta:
         resource = MyPersonResource()
         dataset = resource.export(Person.objects.all())
         self.assertEqual(len(dataset), 1)
-        self.assertEqual(1, dataset[0][0])
+        self.assertEqual("1", dataset[0][0])
         self.assertEqual("foo", dataset[0][1])
 
         self.role.user = None
@@ -1751,7 +1840,7 @@ class Meta:
         resource = MyPersonResource()
         dataset = resource.export(Person.objects.all())
         self.assertEqual(len(dataset), 1)
-        self.assertEqual(1, dataset[0][0])
+        self.assertEqual("1", dataset[0][0])
         self.assertEqual(None, dataset[0][1])
 
 
@@ -1761,6 +1850,7 @@ class ManyRelatedManagerDiffTest(TestCase):
     def setUp(self):
         pass
 
+    @ignore_widget_deprecation_warning
     def test_related_manager_diff(self):
         dataset_headers = ["id", "name", "categories"]
         dataset_row = ["1", "Test Book", "1"]
@@ -1797,6 +1887,7 @@ class ManyToManyWidgetDiffTest(TestCase):
     def setUp(self):
         pass
 
+    @ignore_widget_deprecation_warning
     def test_many_to_many_widget_create(self):
         # the book is associated with 0 categories
         # when we import a book with category 1, the book
@@ -1821,6 +1912,7 @@ def test_many_to_many_widget_create(self):
         )
         self.assertEqual(Category.objects.first(), book.categories.first())
 
+    @ignore_widget_deprecation_warning
     def test_many_to_many_widget_create_with_m2m_being_compared(self):
         # issue 1558 - when the object is a new instance and m2m is
         # evaluated for differences
@@ -1837,6 +1929,7 @@ def test_many_to_many_widget_create_with_m2m_being_compared(self):
         self.assertEqual(len(result.rows), 1)
         self.assertEqual(result.rows[0].import_type, results.RowResult.IMPORT_TYPE_NEW)
 
+    @ignore_widget_deprecation_warning
     def test_many_to_many_widget_update(self):
         # the book is associated with 1 category ('Category 2')
         # when we import a book with category 1, the book
@@ -1858,6 +1951,7 @@ def test_many_to_many_widget_update(self):
         self.assertEqual(1, book.categories.count())
         self.assertEqual(Category.objects.first(), book.categories.first())
 
+    @ignore_widget_deprecation_warning
     def test_many_to_many_widget_no_changes(self):
         # the book is associated with 1 category ('Category 2')
         # when we import a row with a book with category 1, the book
@@ -1876,6 +1970,7 @@ def test_many_to_many_widget_no_changes(self):
         self.assertEqual(result.rows[0].import_type, results.RowResult.IMPORT_TYPE_SKIP)
         self.assertEqual(1, book.categories.count())
 
+    @ignore_widget_deprecation_warning
     def test_many_to_many_widget_handles_ordering(self):
         # the book is associated with 2 categories ('Category 1', 'Category 2')
         # when we import a row with a book with both categories (in any order), the book
@@ -1910,6 +2005,7 @@ def test_many_to_many_widget_handles_ordering(self):
 
         self.assertEqual(2, book.categories.count())
 
+    @ignore_widget_deprecation_warning
     def test_many_to_many_widget_handles_uuid(self):
         # Test for #1435 - skip_row() handles M2M field when UUID pk used
         class _UUIDBookResource(resources.ModelResource):
@@ -1931,6 +2027,7 @@ class Meta:
         result = uuid_resource.import_data(dataset, dry_run=False)
         self.assertEqual(result.rows[0].import_type, results.RowResult.IMPORT_TYPE_SKIP)
 
+    @ignore_widget_deprecation_warning
     def test_skip_row_no_m2m_data_supplied(self):
         # issue #1437
         # test skip_row() when the model defines a m2m field
@@ -2082,6 +2179,7 @@ def init_update_test_data(self, model=Book):
 
 class BulkCreateTest(BulkTest):
     @mock.patch("core.models.Book.objects.bulk_create")
+    @ignore_widget_deprecation_warning
     def test_bulk_create_does_not_call_object_save(self, mock_bulk_create):
         with mock.patch("core.models.Book.save") as mock_obj_save:
             self.resource.import_data(self.dataset)
@@ -2089,6 +2187,7 @@ def test_bulk_create_does_not_call_object_save(self, mock_bulk_create):
         mock_bulk_create.assert_called_with(mock.ANY, batch_size=None)
 
     @mock.patch("core.models.Book.objects.bulk_create")
+    @ignore_widget_deprecation_warning
     def test_bulk_create_batch_size_of_5(self, mock_bulk_create):
         class _BookResource(resources.ModelResource):
             class Meta:
@@ -2103,6 +2202,7 @@ class Meta:
         self.assertEqual(10, result.total_rows)
 
     @mock.patch("core.models.UUIDBook.objects.bulk_create")
+    @ignore_widget_deprecation_warning
     def test_bulk_create_uuid_model(self, mock_bulk_create):
         """Test create of a Model which defines uuid not pk (issue #1274)"""
 
@@ -2123,6 +2223,7 @@ class Meta:
         self.assertEqual(10, result.total_rows)
 
     @mock.patch("core.models.Book.objects.bulk_create")
+    @ignore_widget_deprecation_warning
     def test_bulk_create_no_batch_size(self, mock_bulk_create):
         class _BookResource(resources.ModelResource):
             class Meta:
@@ -2138,6 +2239,7 @@ class Meta:
         self.assertEqual(10, result.totals["new"])
 
     @mock.patch("core.models.Book.objects.bulk_create")
+    @ignore_widget_deprecation_warning
     def test_bulk_create_called_dry_run(self, mock_bulk_create):
         class _BookResource(resources.ModelResource):
             class Meta:
@@ -2152,6 +2254,7 @@ class Meta:
         self.assertEqual(10, result.totals["new"])
 
     @mock.patch("core.models.Book.objects.bulk_create")
+    @ignore_widget_deprecation_warning
     def test_bulk_create_not_called_when_not_using_transactions(self, mock_bulk_create):
         class _BookResource(resources.ModelResource):
             def import_data(
@@ -2183,6 +2286,7 @@ class Meta:
         mock_bulk_create.assert_not_called()
 
     @mock.patch("core.models.Book.objects.bulk_create")
+    @ignore_widget_deprecation_warning
     def test_bulk_create_batch_size_of_4(self, mock_bulk_create):
         class _BookResource(resources.ModelResource):
             class Meta:
@@ -2196,6 +2300,7 @@ class Meta:
         self.assertEqual(10, result.total_rows)
         self.assertEqual(10, result.totals["new"])
 
+    @ignore_widget_deprecation_warning
     def test_no_changes_for_errors_if_use_transactions_enabled(self):
         with mock.patch("import_export.results.Result.has_errors") as mock_has_errors:
             mock_has_errors.return_val = True
@@ -2203,6 +2308,7 @@ def test_no_changes_for_errors_if_use_transactions_enabled(self):
         self.assertEqual(0, Book.objects.count())
 
     @mock.patch("core.models.Book.objects.bulk_create")
+    @ignore_widget_deprecation_warning
     def test_bulk_create_use_bulk_disabled(self, mock_bulk_create):
         class _BookResource(resources.ModelResource):
             class Meta:
@@ -2217,6 +2323,7 @@ class Meta:
         self.assertEqual(10, result.totals["new"])
 
     @mock.patch("core.models.Book.objects.bulk_create")
+    @ignore_widget_deprecation_warning
     def test_bulk_create_bad_batch_size_value(self, mock_bulk_create):
         class _BookResource(resources.ModelResource):
             class Meta:
@@ -2243,6 +2350,7 @@ class Meta:
         mock_bulk_create.assert_not_called()
 
     @mock.patch("core.models.Book.objects.bulk_create")
+    @ignore_widget_deprecation_warning
     def test_bulk_create_oversized_batch_size_value(self, mock_bulk_create):
         class _BookResource(resources.ModelResource):
             class Meta:
@@ -2258,6 +2366,7 @@ class Meta:
         self.assertEqual(10, result.totals["new"])
 
     @mock.patch("core.models.Book.objects.bulk_create")
+    @ignore_widget_deprecation_warning
     def test_bulk_create_logs_exception(self, mock_bulk_create):
         e = ValidationError("invalid field")
         mock_bulk_create.side_effect = e
@@ -2274,6 +2383,7 @@ class Meta:
             mock_exception.assert_called_with(e, exc_info=e)
 
     @mock.patch("core.models.Book.objects.bulk_create")
+    @ignore_widget_deprecation_warning
     def test_bulk_create_raises_exception(self, mock_bulk_create):
         mock_bulk_create.side_effect = ValidationError("invalid field")
 
@@ -2288,6 +2398,7 @@ class Meta:
             resource.import_data(self.dataset, raise_errors=True)
 
     @mock.patch("core.models.Book.objects.bulk_create")
+    @ignore_widget_deprecation_warning
     def test_bulk_create_exception_gathered_on_dry_run(self, mock_bulk_create):
         mock_bulk_create.side_effect = ValidationError("invalid field")
 
@@ -2301,6 +2412,7 @@ class Meta:
         result = resource.import_data(self.dataset, dry_run=True, raise_errors=False)
         self.assertTrue(result.has_errors())
 
+    @ignore_widget_deprecation_warning
     def test_m2m_not_called_for_bulk(self):
         mock_m2m_widget = mock.Mock(spec=widgets.ManyToManyWidget)
 
@@ -2359,6 +2471,7 @@ def setUp(self):
         self.init_update_test_data()
         self.resource = self._BookResource()
 
+    @ignore_widget_deprecation_warning
     def test_bulk_update(self):
         result = self.resource.import_data(self.dataset)
         [self.assertEqual("UPDATED", b.name) for b in Book.objects.all()]
@@ -2366,6 +2479,7 @@ def test_bulk_update(self):
         self.assertEqual(10, result.totals["update"])
 
     @mock.patch("core.models.Book.objects.bulk_update")
+    @ignore_widget_deprecation_warning
     def test_bulk_update_batch_size_of_4(self, mock_bulk_update):
         class _BookResource(resources.ModelResource):
             class Meta:
@@ -2380,6 +2494,7 @@ class Meta:
         self.assertEqual(10, result.totals["update"])
 
     @mock.patch("core.models.Book.objects.bulk_update")
+    @ignore_widget_deprecation_warning
     def test_bulk_update_batch_size_of_5(self, mock_bulk_update):
         class _BookResource(resources.ModelResource):
             class Meta:
@@ -2394,6 +2509,7 @@ class Meta:
         self.assertEqual(10, result.totals["update"])
 
     @mock.patch("core.models.Book.objects.bulk_update")
+    @ignore_widget_deprecation_warning
     def test_bulk_update_no_batch_size(self, mock_bulk_update):
         class _BookResource(resources.ModelResource):
             class Meta:
@@ -2409,6 +2525,7 @@ class Meta:
         self.assertEqual(10, result.totals["update"])
 
     @mock.patch("core.models.Book.objects.bulk_update")
+    @ignore_widget_deprecation_warning
     def test_bulk_update_not_called_when_not_using_transactions(self, mock_bulk_update):
         class _BookResource(resources.ModelResource):
             def import_data(
@@ -2440,11 +2557,13 @@ class Meta:
         mock_bulk_update.assert_not_called()
 
     @mock.patch("core.models.Book.objects.bulk_update")
+    @ignore_widget_deprecation_warning
     def test_bulk_update_called_for_dry_run(self, mock_bulk_update):
         self.resource.import_data(self.dataset, dry_run=True)
         self.assertEqual(1, mock_bulk_update.call_count)
 
     @mock.patch("core.models.Book.objects.bulk_update")
+    @ignore_widget_deprecation_warning
     def test_bulk_not_called_when_use_bulk_disabled(self, mock_bulk_update):
         class _BookResource(resources.ModelResource):
             class Meta:
@@ -2459,6 +2578,7 @@ class Meta:
         mock_bulk_update.assert_not_called()
 
     @mock.patch("core.models.Book.objects.bulk_update")
+    @ignore_widget_deprecation_warning
     def test_bulk_update_logs_exception(self, mock_bulk_update):
         e = ValidationError("invalid field")
         mock_bulk_update.side_effect = e
@@ -2474,6 +2594,7 @@ class Meta:
             mock_exception.assert_called_with(e, exc_info=e)
 
     @mock.patch("core.models.Book.objects.bulk_update")
+    @ignore_widget_deprecation_warning
     def test_bulk_update_raises_exception(self, mock_bulk_update):
         e = ValidationError("invalid field")
         mock_bulk_update.side_effect = e
@@ -2495,6 +2616,7 @@ def setUp(self):
         self.init_update_test_data(model=UUIDBook)
 
     @mock.patch("core.models.UUIDBook.objects.bulk_update")
+    @ignore_widget_deprecation_warning
     def test_bulk_update_uuid_model(self, mock_bulk_update):
         """Test update of a Model which defines uuid not pk (issue #1274)"""
 
@@ -2523,66 +2645,53 @@ def for_delete(self, row, instance):
         class Meta:
             model = Book
             use_bulk = True
+            # there are errors when diffing with mocks
+            # therefore disable diff with this flag
+            skip_diff = True
 
     def setUp(self):
         super().setUp()
         self.resource = self.DeleteBookResource()
+        self.resource._meta.batch_size = 1000
+        self.resource._meta.use_bulk = True
         self.init_update_test_data()
 
     @mock.patch("core.models.Book.delete")
+    @ignore_widget_deprecation_warning
     def test_bulk_delete_use_bulk_is_false(self, mock_obj_delete):
-        class _BookResource(self.DeleteBookResource):
-            class Meta:
-                model = Book
-                use_bulk = False
-
-        self.resource = _BookResource()
+        self.resource._meta.use_bulk = False
         self.resource.import_data(self.dataset)
         self.assertEqual(10, mock_obj_delete.call_count)
 
     @mock.patch("core.models.Book.objects")
+    @ignore_widget_deprecation_warning
     def test_bulk_delete_batch_size_of_4(self, mock_obj_manager):
-        class _BookResource(self.DeleteBookResource):
-            class Meta:
-                model = Book
-                use_bulk = True
-                batch_size = 4
-
-        self.resource = _BookResource()
+        self.resource._meta.batch_size = 4
         result = self.resource.import_data(self.dataset)
         self.assertEqual(3, mock_obj_manager.filter.return_value.delete.call_count)
         self.assertEqual(10, result.total_rows)
         self.assertEqual(10, result.totals["delete"])
 
     @mock.patch("core.models.Book.objects")
+    @ignore_widget_deprecation_warning
     def test_bulk_delete_batch_size_of_5(self, mock_obj_manager):
-        class _BookResource(self.DeleteBookResource):
-            class Meta:
-                model = Book
-                use_bulk = True
-                batch_size = 5
-
-        self.resource = _BookResource()
+        self.resource._meta.batch_size = 5
         result = self.resource.import_data(self.dataset)
         self.assertEqual(2, mock_obj_manager.filter.return_value.delete.call_count)
         self.assertEqual(10, result.total_rows)
         self.assertEqual(10, result.totals["delete"])
 
     @mock.patch("core.models.Book.objects")
+    @ignore_widget_deprecation_warning
     def test_bulk_delete_batch_size_is_none(self, mock_obj_manager):
-        class _BookResource(self.DeleteBookResource):
-            class Meta:
-                model = Book
-                use_bulk = True
-                batch_size = None
-
-        self.resource = _BookResource()
+        self.resource._meta.batch_size = None
         result = self.resource.import_data(self.dataset)
         self.assertEqual(1, mock_obj_manager.filter.return_value.delete.call_count)
         self.assertEqual(10, result.total_rows)
         self.assertEqual(10, result.totals["delete"])
 
     @mock.patch("core.models.Book.objects")
+    @ignore_widget_deprecation_warning
     def test_bulk_delete_not_called_when_not_using_transactions(self, mock_obj_manager):
         class _BookResource(self.DeleteBookResource):
             def import_data(
@@ -2605,48 +2714,35 @@ def import_data(
                     **kwargs,
                 )
 
-            class Meta:
-                model = Book
-                use_bulk = True
-
         resource = _BookResource()
         resource.import_data(self.dataset, dry_run=True)
         self.assertEqual(0, mock_obj_manager.filter.return_value.delete.call_count)
 
     @mock.patch("core.models.Book.objects")
+    @ignore_widget_deprecation_warning
     def test_bulk_delete_called_for_dry_run(self, mock_obj_manager):
         self.resource.import_data(self.dataset, dry_run=True)
         self.assertEqual(1, mock_obj_manager.filter.return_value.delete.call_count)
 
     @mock.patch("core.models.Book.objects")
+    @ignore_widget_deprecation_warning
     def test_bulk_delete_logs_exception(self, mock_obj_manager):
         e = Exception("invalid")
         mock_obj_manager.filter.return_value.delete.side_effect = e
 
-        class _BookResource(self.DeleteBookResource):
-            class Meta:
-                model = Book
-                use_bulk = True
-
-        resource = _BookResource()
         with mock.patch("logging.Logger.debug") as mock_exception:
-            resource.import_data(self.dataset)
+            self.resource.import_data(self.dataset)
             mock_exception.assert_called_with(e, exc_info=mock.ANY)
             self.assertEqual(1, mock_exception.call_count)
 
     @mock.patch("core.models.Book.objects")
+    @ignore_widget_deprecation_warning
     def test_bulk_delete_raises_exception(self, mock_obj_manager):
         e = Exception("invalid")
         mock_obj_manager.filter.return_value.delete.side_effect = e
 
-        class _BookResource(self.DeleteBookResource):
-            class Meta:
-                model = Book
-                use_bulk = True
-
-        resource = _BookResource()
         with self.assertRaises(Exception) as raised_exc:
-            resource.import_data(self.dataset, raise_errors=True)
+            self.resource.import_data(self.dataset, raise_errors=True)
             self.assertEqual(e, raised_exc)
 
 
@@ -2665,6 +2761,7 @@ def setUp(self):
         self.resource = self.DeleteBookResource()
         self.init_update_test_data(model=UUIDBook)
 
+    @ignore_widget_deprecation_warning
     def test_bulk_delete_batch_size_of_5(self):
         self.assertEqual(10, UUIDBook.objects.count())
         self.resource.import_data(self.dataset)
@@ -2685,6 +2782,7 @@ class Meta:
         row = [self.book.pk, "Some book", "test@example.com", "10.25"]
         self.dataset.append(row)
 
+    @ignore_widget_deprecation_warning
     def test_import_data(self):
         result = self.resource.import_data(self.dataset, raise_errors=True)
 
@@ -2740,6 +2838,7 @@ def setUp(self):
         row = [self.book.pk, "Some book", "2023-05-09"]
         self.dataset.append(row)
 
+    @ignore_widget_deprecation_warning
     def test_after_import_row_check_for_change(self):
         # issue 1583 - assert that `original` object is available to after_import_row()
         self.resource.import_data(self.dataset, raise_errors=True)
@@ -2795,22 +2894,26 @@ def setUp(self):
         row = [self.pk, "Some book", "19.99"]
         self.dataset.append(row)
 
+    @ignore_widget_deprecation_warning
     def test_defined_import_order(self):
         self.resource = ImportExportFieldOrderTest.OrderedBookResource()
         self.resource.import_data(self.dataset)
         self.assertEqual(["price", "name", "id"], self.resource.field_names)
 
+    @ignore_widget_deprecation_warning
     def test_undefined_import_order(self):
         self.resource = ImportExportFieldOrderTest.UnorderedBookResource()
         self.resource.import_data(self.dataset)
         self.assertEqual(["price", "id", "name"], self.resource.field_names)
 
+    @ignore_widget_deprecation_warning
     def test_defined_export_order(self):
         self.resource = ImportExportFieldOrderTest.OrderedBookResource()
         data = self.resource.export()
         target = f"price,name,id\r\n1.99,Ulysses,{self.pk}\r\n"
         self.assertEqual(target, data.csv)
 
+    @ignore_widget_deprecation_warning
     def test_undefined_export_order(self):
         # When export order is not defined,
         # exported order should correspond with 'fields' definition
@@ -2819,6 +2922,7 @@ def test_undefined_export_order(self):
         target = f"price,id,name\r\n1.99,{self.pk},Ulysses\r\n"
         self.assertEqual(target, data.csv)
 
+    @ignore_widget_deprecation_warning
     def test_subset_import_order(self):
         self.resource = ImportExportFieldOrderTest.SubsetOrderedBookResource()
         self.resource.import_data(self.dataset)
@@ -2826,28 +2930,33 @@ def test_subset_import_order(self):
             ["name", "price", "id", "published"], self.resource.field_names
         )
 
+    @ignore_widget_deprecation_warning
     def test_subset_export_order(self):
         self.resource = ImportExportFieldOrderTest.SubsetOrderedBookResource()
         data = self.resource.export()
         target = f"published,price,id,name\r\n,1.99,{self.pk},Ulysses\r\n"
         self.assertEqual(target, data.csv)
 
+    @ignore_widget_deprecation_warning
     def test_duplicate_import_order(self):
         self.resource = ImportExportFieldOrderTest.DuplicateFieldsBookResource()
         self.resource.import_data(self.dataset)
         self.assertEqual(["id", "price", "name"], self.resource.field_names)
 
+    @ignore_widget_deprecation_warning
     def test_duplicate_export_order(self):
         self.resource = ImportExportFieldOrderTest.DuplicateFieldsBookResource()
         data = self.resource.export()
         target = f"id,price,name\r\n{self.pk},1.99,Ulysses\r\n"
         self.assertEqual(target, data.csv)
 
+    @ignore_widget_deprecation_warning
     def test_fields_as_list_import_order(self):
         self.resource = ImportExportFieldOrderTest.FieldsAsListBookResource()
         self.resource.import_data(self.dataset)
         self.assertEqual(["id", "price", "name"], self.resource.field_names)
 
+    @ignore_widget_deprecation_warning
     def test_fields_as_list_export_order(self):
         self.resource = ImportExportFieldOrderTest.FieldsAsListBookResource()
         data = self.resource.export()
@@ -2903,6 +3012,7 @@ class ImportWithMissingFields(TestCase):
 
     @patch("import_export.resources.logger")
     @patch("import_export.fields.Field.save")
+    @ignore_widget_deprecation_warning
     def test_import_with_missing_instance_attribute(self, mock_field_save, mock_logger):
         class _BookResource(resources.ModelResource):
             name = fields.Field(column_name="name")
@@ -2923,6 +3033,7 @@ class Meta:
 
     @patch("import_export.resources.logger")
     @patch("import_export.fields.Field.save")
+    @ignore_widget_deprecation_warning
     def test_import_with_missing_field_in_row(self, mock_field_save, mock_logger):
         dataset = tablib.Dataset(*[(1, "Some book")], headers=["id", "name"])
         self.resource = BookResource()
diff --git a/tests/core/tests/test_widgets.py b/tests/core/tests/test_widgets.py
--- a/tests/core/tests/test_widgets.py
+++ b/tests/core/tests/test_widgets.py
@@ -24,7 +24,17 @@ def test_render(self):
         self.assertEqual("1", self.widget.render(1))
 
 
-class CharWidgetTest(TestCase):
+class RowDeprecationTestMixin(object):
+    def test_render_row_deprecation(self):
+        with self.assertWarnsRegex(
+            DeprecationWarning,
+            r"^The 'obj' parameter is deprecated and "
+            "will be removed in a future release$",
+        ):
+            self.widget.render(Book.objects.none(), obj={"a": 1})
+
+
+class CharWidgetTest(TestCase, RowDeprecationTestMixin):
     def setUp(self):
         self.widget = widgets.CharWidget()
 
@@ -34,27 +44,20 @@ def test_clean(self):
     def test_render(self):
         self.assertEqual("1", self.widget.render(1))
 
-    def test_clean_coerce_to_string(self):
-        self.widget = widgets.CharWidget(coerce_to_string=True)
-        self.assertEqual("1", self.widget.clean(1))
-
-    def test_clean_no_coerce_to_string(self):
-        self.assertEqual(1, self.widget.clean(1))
+    def test_render_no_coerce_to_string(self):
+        self.widget = widgets.CharWidget(coerce_to_string=False)
+        self.assertEqual(1, self.widget.render(1))
 
-    def test_clean_coerce_to_string_None(self):
-        self.widget = widgets.CharWidget(coerce_to_string=True)
+    def test_clean_with_allow_blank_is_False(self):
+        self.widget = widgets.CharWidget(allow_blank=False)
         self.assertIsNone(self.widget.clean(None))
 
-    def test_clean_coerce_to_string_with_allow_blank(self):
-        self.widget = widgets.CharWidget(coerce_to_string=True, allow_blank=True)
+    def test_clean_with_allow_blank_is_True(self):
+        self.widget = widgets.CharWidget(allow_blank=True)
         self.assertEqual("", self.widget.clean(None))
 
-    def test_clean_coerce_to_string_is_False_with_allow_blank(self):
-        self.widget = widgets.CharWidget(coerce_to_string=False, allow_blank=True)
-        self.assertIsNone(self.widget.clean(None))
-
 
-class BooleanWidgetTest(TestCase):
+class BooleanWidgetTest(TestCase, RowDeprecationTestMixin):
     def setUp(self):
         self.widget = widgets.BooleanWidget()
 
@@ -83,6 +86,12 @@ def test_render(self):
         self.assertEqual(self.widget.render(False), "0")
         self.assertEqual(self.widget.render(None), "")
 
+    def test_render_coerce_to_string_is_False(self):
+        self.widget = widgets.BooleanWidget(coerce_to_string=False)
+        self.assertTrue(self.widget.render(True))
+        self.assertFalse(self.widget.render(False))
+        self.assertIsNone(self.widget.render(None))
+
 
 class FormatDatetimeTest(TestCase):
     date = date(10, 8, 2)
@@ -106,7 +115,7 @@ def test_format_datetime_gte_django4(self):
         )
 
 
-class DateWidgetTest(TestCase):
+class DateWidgetTest(TestCase, RowDeprecationTestMixin):
     def setUp(self):
         self.date = date(2012, 8, 13)
         self.widget = widgets.DateWidget("%d.%m.%Y")
@@ -117,6 +126,10 @@ def test_render(self):
     def test_render_none(self):
         self.assertEqual(self.widget.render(None), "")
 
+    def test_render_coerce_to_string_is_False(self):
+        self.widget = widgets.DateWidget(coerce_to_string=False)
+        self.assertEqual(self.date, self.widget.render(self.date))
+
     def test_render_datetime_safe(self):
         """datetime_safe is supposed to be used to support dates older than 1000"""
         self.date = date(10, 8, 2)
@@ -147,7 +160,7 @@ def test_default_format(self):
         self.assertEqual(("%Y-%m-%d",), self.widget.formats)
 
 
-class DateTimeWidgetTest(TestCase):
+class DateTimeWidgetTest(TestCase, RowDeprecationTestMixin):
     def setUp(self):
         self.datetime = datetime(2012, 8, 13, 18, 0, 0)
         self.widget = widgets.DateTimeWidget("%d.%m.%Y %H:%M:%S")
@@ -158,6 +171,10 @@ def test_render(self):
     def test_render_none(self):
         self.assertEqual(self.widget.render(None), "")
 
+    def test_render_coerce_to_string_is_False(self):
+        self.widget = widgets.DateTimeWidget(coerce_to_string=False)
+        self.assertEqual(self.datetime, self.widget.render(self.datetime))
+
     def test_clean(self):
         self.assertEqual(self.widget.clean("13.08.2012 18:00:00"), self.datetime)
 
@@ -224,7 +241,7 @@ def test_clean(self):
         self.assertEqual(self.datetime, self.widget.clean("13.08.1868"))
 
 
-class TimeWidgetTest(TestCase):
+class TimeWidgetTest(TestCase, RowDeprecationTestMixin):
     def setUp(self):
         self.time = time(20, 15, 0)
         self.widget = widgets.TimeWidget("%H:%M:%S")
@@ -235,6 +252,10 @@ def test_render(self):
     def test_render_none(self):
         self.assertEqual(self.widget.render(None), "")
 
+    def test_render_coerce_to_string_is_False(self):
+        self.widget = widgets.TimeWidget(coerce_to_string=False)
+        self.assertEqual(self.time, self.widget.render(self.time))
+
     def test_clean(self):
         self.assertEqual(self.widget.clean("20:15:00"), self.time)
 
@@ -252,7 +273,7 @@ def test_clean_returns_time_when_time_passed(self):
         self.assertEqual(self.time, self.widget.clean(self.time))
 
 
-class DurationWidgetTest(TestCase):
+class DurationWidgetTest(TestCase, RowDeprecationTestMixin):
     def setUp(self):
         self.duration = timedelta(hours=1, minutes=57, seconds=0)
         self.widget = widgets.DurationWidget()
@@ -266,6 +287,10 @@ def test_render_none(self):
     def test_render_zero(self):
         self.assertEqual(self.widget.render(timedelta(0)), "0:00:00")
 
+    def test_render_coerce_to_string_is_False(self):
+        self.widget = widgets.DurationWidget(coerce_to_string=False)
+        self.assertEqual(self.duration, self.widget.render(self.duration))
+
     def test_clean(self):
         self.assertEqual(self.widget.clean("1:57:00"), self.duration)
 
@@ -281,7 +306,7 @@ def test_clean_raises_ValueError(self, _):
             self.widget.clean("x")
 
 
-class NumberWidgetTest(TestCase):
+class NumberWidgetTest(TestCase, RowDeprecationTestMixin):
     def setUp(self):
         self.value = 11.111
         self.widget = widgets.NumberWidget()
@@ -300,10 +325,10 @@ def test_is_empty_value_is_zero(self):
         self.assertFalse(self.widget.is_empty(0))
 
     def test_render(self):
-        self.assertEqual(self.value, self.widget.render(self.value))
+        self.assertEqual("11.111", self.widget.render(self.value))
 
     def test_render_None_coerce_to_string_False(self):
-        self.assertIsNone(self.widget.render(None))
+        self.assertEqual("", self.widget.render(None))
 
     @skipUnless(
         django.VERSION[0] < 4, f"skipping django {django.VERSION} version specific test"
@@ -324,7 +349,7 @@ def test_coerce_to_string_value_is_None(self):
         self.assertEqual("", self.widget_coerce_to_string.render(None))
 
 
-class FloatWidgetTest(TestCase):
+class FloatWidgetTest(TestCase, RowDeprecationTestMixin):
     def setUp(self):
         self.value = 11.111
         self.widget = widgets.FloatWidget()
@@ -334,7 +359,7 @@ def test_clean(self):
         self.assertEqual(self.widget.clean(11.111), self.value)
 
     def test_render(self):
-        self.assertEqual(self.widget.render(self.value), self.value)
+        self.assertEqual(self.widget.render(self.value), "11.111")
 
     def test_clean_string_zero(self):
         self.assertEqual(self.widget.clean("0"), 0.0)
@@ -361,19 +386,22 @@ def test_locale_render_coerce_to_string_gte4(self):
         self.assertEqual(self.widget_coerce_to_string.render(self.value), "11,111")
 
 
-class DecimalWidgetTest(TestCase):
+class DecimalWidgetTest(TestCase, RowDeprecationTestMixin):
     def setUp(self):
         self.value = Decimal("11.111")
         self.widget = widgets.DecimalWidget()
-        self.widget_coerce_to_string = widgets.DecimalWidget(coerce_to_string=True)
 
     def test_clean(self):
         self.assertEqual(self.widget.clean("11.111"), self.value)
         self.assertEqual(self.widget.clean(11.111), self.value)
 
-    def test_render(self):
+    def test_render_coerce_to_string_is_False(self):
+        self.widget = widgets.DecimalWidget(coerce_to_string=False)
         self.assertEqual(self.widget.render(self.value), self.value)
 
+    def test_render(self):
+        self.assertEqual(self.widget.render(self.value), "11.111")
+
     def test_clean_string_zero(self):
         self.assertEqual(self.widget.clean("0"), Decimal("0"))
         self.assertEqual(self.widget.clean("0.0"), Decimal("0"))
@@ -388,7 +416,7 @@ def test_clean_empty_string(self):
     )
     @override_settings(LANGUAGE_CODE="fr-fr", USE_L10N=True)
     def test_locale_render_coerce_to_string_lt4(self):
-        self.assertEqual(self.widget_coerce_to_string.render(self.value), "11,111")
+        self.assertEqual(self.widget.render(self.value), "11,111")
 
     @skipUnless(
         django.VERSION[0] >= 4,
@@ -396,10 +424,10 @@ def test_locale_render_coerce_to_string_lt4(self):
     )
     @override_settings(LANGUAGE_CODE="fr-fr")
     def test_locale_render_coerce_to_string_gte4(self):
-        self.assertEqual(self.widget_coerce_to_string.render(self.value), "11,111")
+        self.assertEqual(self.widget.render(self.value), "11,111")
 
 
-class IntegerWidgetTest(TestCase):
+class IntegerWidgetTest(TestCase, RowDeprecationTestMixin):
     def setUp(self):
         self.value = 0
         self.widget = widgets.IntegerWidget()
@@ -437,7 +465,7 @@ def test_locale_render_gte_django4(self):
         self.assertEqual(self.widget_coerce_to_string.render(self.value), "0")
 
 
-class ForeignKeyWidgetTest(TestCase):
+class ForeignKeyWidgetTest(TestCase, RowDeprecationTestMixin):
     def setUp(self):
         self.widget = widgets.ForeignKeyWidget(Author)
         self.natural_key_author_widget = widgets.ForeignKeyWidget(
@@ -548,7 +576,7 @@ def test_book_natural_key_render(self):
         )
 
 
-class ManyToManyWidget(TestCase):
+class ManyToManyWidget(TestCase, RowDeprecationTestMixin):
     def setUp(self):
         self.widget = widgets.ManyToManyWidget(Category)
         self.widget_name = widgets.ManyToManyWidget(Category, field="name")
@@ -609,8 +637,11 @@ def test_render(self):
             "%s,%s" % (self.cat1.name, self.cat2.name),
         )
 
+    def test_render_value_none_as_blank(self):
+        self.assertEqual("", self.widget.render(None))
 
-class JSONWidgetTest(TestCase):
+
+class JSONWidgetTest(TestCase, RowDeprecationTestMixin):
     def setUp(self):
         self.value = {"value": 23}
         self.widget = widgets.JSONWidget()
@@ -635,7 +666,7 @@ def test_render_none(self):
         self.assertEqual(self.widget.render({"value": None}), '{"value": null}')
 
 
-class SimpleArrayWidgetTest(TestCase):
+class SimpleArrayWidgetTest(TestCase, RowDeprecationTestMixin):
     def setUp(self):
         self.value = {"value": 23}
         self.widget = widgets.SimpleArrayWidget()
@@ -659,3 +690,8 @@ def test_render(self):
         v = ["a", "b", "c"]
         s = "a,b,c"
         self.assertEqual(s, self.widget.render(v))
+
+    def test_render_no_coerce_to_string(self):
+        v = [1, 2, 3]
+        self.widget = widgets.SimpleArrayWidget(coerce_to_string=False)
+        self.assertEqual(v, self.widget.render(v))
diff --git a/tests/core/tests/utils.py b/tests/core/tests/utils.py
new file mode 100644
--- /dev/null
+++ b/tests/core/tests/utils.py
@@ -0,0 +1,22 @@
+import functools
+import warnings
+
+
+def ignore_widget_deprecation_warning(fn):
+    """
+    Ignore the specific deprecation warning occurring during Widget.render() execution.
+    This can be removed when the deprecation is completed.
+    """
+
+    @functools.wraps(fn)
+    def inner(*args, **kwargs):
+        with warnings.catch_warnings():
+            warnings.filterwarnings(
+                "ignore",
+                message=r"^The 'obj' parameter is deprecated and "
+                "will be removed in a future release$",
+                category=DeprecationWarning,
+            )
+            fn(*args, **kwargs)
+
+    return inner