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

Issue:
Add support for PositiveBigIntegerField
**Describe the bug**

If model has PositiveBigIntegerField(null=True) resource has autogenerated field with default widget and imported dataset contains empty cells - there is import error in saving imported instance. Probably it happens because `ModelResource.WIDGETS_MAP` does not contains widget class for PositiveBigIntegerField.

```
Field 'field_example' expected a number but got ''. OrderedDict({'id': 1, 'field_example': ''})
```

**To Reproduce**

Steps to reproduce the behavior:
1. Create model with PositiveBigIntegerField(null=True)  and Resource for this
```python
from django.db import models
from import_export import resources


class ExampleModel(models.Model):
    field_example = models.PositiveBigIntegerField(null=True)

class ExampleResource(resources.ModelResource):
    class Meta:
        model = ExampleModel

```
2. Create some instances
```python
ExampleModel.objects.create()
ExampleModel.objects.create(field_example=123)
```
3. Export data to dataset
```python
resource = ExampleResource()
dataset = resource.export()
```

4. Clear db
```python
ExampleModel.objects.all().delete()
```
5. Import this dataset and check result
```python
result = resource.import_data(dataset)
self.assertFalse(result.has_errors())
```

**Versions (please complete the following information):**
 - Django Import Export: 3.3.8
 - Python 3.12
 - Django 4.2.11

**Expected behavior**
Dataset imported correctly

**Additional context**

Maybe create test for check that all django model fields are supported(maybe except FileField/ImageField)? It looks like `PositiveSmallIntegerField` is missing to.



** Full test to reproduce **

```python
# models.py
from django.db import models

class ExampleModel(models.Model):
    field_example = models.PositiveBigIntegerField(null=True)

```

```python
# tests.py

from django.test import TestCase
from import_export import resources
from import_export.results import Error

from example_app.models import ExampleModel

class ExampleResource(resources.ModelResource):
    class Meta:
        model = ExampleModel


class TestImportExportBug(TestCase):
    def setUp(self) -> None:
        ExampleModel.objects.create()
        ExampleModel.objects.create(field_example=123)

    def test_bug(self):
        resource = ExampleResource()
        dataset = resource.export()

        ExampleModel.objects.all().delete()

        result = resource.import_data(dataset)

        self.assertFalse(
            result.has_errors(),
            msg=(
                    "\n".join(self._format_error(error) for error in result.base_errors)
                    + "\n\n"
                    + "\n".join(
                "\n".join(self._format_error(error) for error in errors) for _, errors in result.row_errors()
            )
            )
        )

    def _format_error(self, error: Error) -> str:
        return f"{error.error} {error.row}\n{error.traceback}"
```



Discussion:
Thanks for raising - will take a look.  It might be worth checking if it is present in the [v4 rc](https://pypi.org/project/django-import-export/4.0.0rc3/).
Reproducing in main branch and in 4.0.0-rc3.

Create smaller test for widget type

```python
from django.db import models


class ExampleModel(models.Model):
    field_example1 = models.PositiveBigIntegerField(null=True)
    field_example2 = models.PositiveSmallIntegerField(null=True)


class ExampleResource(ModelResource):
    class Meta:
        model = ExampleModel


class TestImportExportBug(TestCase):

    def test_field_has_correct_widget(self):
        resource = ExampleResource()
        with self.subTest("PositiveBigIntegerField"):
            self.assertIsInstance(resource.fields["field_example1"], widgets.IntegerWidget)
        with self.subTest("PositiveSmallIntegerField"):
            self.assertIsInstance(resource.fields["field_example2"], widgets.IntegerWidget)

```


Resolving patch:
diff --git a/import_export/resources.py b/import_export/resources.py
--- a/import_export/resources.py
+++ b/import_export/resources.py
@@ -1171,6 +1171,7 @@ class ModelResource(Resource, metaclass=ModelDeclarativeMetaclass):
         "IntegerField": widgets.IntegerWidget,
         "PositiveIntegerField": widgets.IntegerWidget,
         "BigIntegerField": widgets.IntegerWidget,
+        "PositiveBigIntegerField": widgets.IntegerWidget,
         "PositiveSmallIntegerField": widgets.IntegerWidget,
         "SmallIntegerField": widgets.IntegerWidget,
         "SmallAutoField": widgets.IntegerWidget,


Test patch:
diff --git a/tests/core/migrations/0015_withpositiveintegerfields.py b/tests/core/migrations/0015_withpositiveintegerfields.py
new file mode 100644
--- /dev/null
+++ b/tests/core/migrations/0015_withpositiveintegerfields.py
@@ -0,0 +1,28 @@
+# Generated by Django 5.0.4 on 2024-04-23 22:46
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("core", "0014_bookwithchapternumbers"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="WithPositiveIntegerFields",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("big", models.PositiveBigIntegerField(null=True)),
+                ("small", models.PositiveSmallIntegerField(null=True)),
+            ],
+        ),
+    ]
diff --git a/tests/core/models.py b/tests/core/models.py
--- a/tests/core/models.py
+++ b/tests/core/models.py
@@ -165,3 +165,8 @@ class UUIDBook(models.Model):
     id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
     name = models.CharField("Book name", max_length=100)
     categories = models.ManyToManyField(UUIDCategory, blank=True)
+
+
+class WithPositiveIntegerFields(models.Model):
+    big = models.PositiveBigIntegerField(null=True)
+    small = models.PositiveSmallIntegerField(null=True)
diff --git a/tests/core/tests/test_model_resource_fields_generate_widgets.py b/tests/core/tests/test_model_resource_fields_generate_widgets.py
new file mode 100644
--- /dev/null
+++ b/tests/core/tests/test_model_resource_fields_generate_widgets.py
@@ -0,0 +1,198 @@
+from unittest import TestCase
+
+import django
+from django.contrib.contenttypes import fields as contenttype_fields
+from django.contrib.postgres import fields as postgres
+from django.contrib.postgres import search as postgres_search
+from django.contrib.postgres.fields import ranges as postgres_ranges
+from django.db import models
+from django.db.models.fields.related import RelatedField
+
+from import_export import widgets
+from import_export.resources import ModelResource
+
+from ..models import WithPositiveIntegerFields
+
+
+class ExampleResource(ModelResource):
+    class Meta:
+        model = WithPositiveIntegerFields
+
+
+class TestFieldWidgetMapping(TestCase):
+    def test_field_has_correct_widget(self):
+        resource = ExampleResource()
+        with self.subTest("PositiveBigIntegerField"):
+            self.assertIsInstance(resource.fields["big"].widget, widgets.IntegerWidget)
+        with self.subTest("PositiveSmallIntegerField"):
+            self.assertIsInstance(
+                resource.fields["small"].widget,
+                widgets.IntegerWidget,
+            )
+
+    def test_all_db_fields_has_widgets(self):
+        all_django_fields_classes = self._get_all_django_model_field_subclasses()
+        expected_has_default_widget = self._get_fields_with_expected_default_widget()
+        expected_not_presented_fields = (
+            self._get_expected_not_presented_in_test_field_subclasses()
+        )
+        all_fields = self._get_django_fields_for_check_widget()
+
+        field_instance_by_field_cls = {field.__class__: field for field in all_fields}
+
+        for field_cls, field in field_instance_by_field_cls.items():
+            with self.subTest(msg=field_cls.__name__):
+                resource_field = ModelResource.field_from_django_field(
+                    "test", field, False
+                )
+                widget = resource_field.widget
+                if field_cls in expected_has_default_widget:
+                    self.assertEqual(
+                        widget.__class__,
+                        widgets.Widget,
+                        msg=(
+                            f"{field_cls.__name__} "
+                            "expected default widget "
+                            f"actual {widget.__class__}"
+                        ),
+                    )
+                else:
+                    self.assertNotEqual(
+                        widget.__class__,
+                        widgets.Widget,
+                        msg=f"{field_cls.__name__} has default widget class",
+                    )
+
+        # if in new version django will be added new field subclass
+        # this subtest should fail
+        for field_cls in all_django_fields_classes:
+            if field_cls in expected_not_presented_fields:
+                continue
+            with self.subTest(msg=field_cls.__name__):
+                self.assertIn(
+                    field_cls,
+                    field_instance_by_field_cls,
+                    msg=f"{field_cls.__name__} not presented in test fields",
+                )
+
+    def _get_fields_with_expected_default_widget(self):
+        """
+        Returns set of django.db.models.field.Field subclasses
+        which expected has default Widget in ModelResource
+        """
+        expected_has_default_widget = {
+            models.BinaryField,
+            models.FileField,
+            models.FilePathField,
+            models.GenericIPAddressField,
+            models.ImageField,
+            models.IPAddressField,
+            models.SlugField,
+            models.TextField,
+            models.UUIDField,
+            postgres.BigIntegerRangeField,
+            postgres.CITextField,
+            postgres.DateRangeField,
+            postgres.DateTimeRangeField,
+            postgres.DecimalRangeField,
+            postgres.HStoreField,
+            postgres.IntegerRangeField,
+            postgres.RangeField,
+        }
+        if django.VERSION < (5, 1):
+            ci_char_fields = {
+                postgres.CICharField,
+                postgres.CIEmailField,
+            }
+            expected_has_default_widget |= ci_char_fields
+        return expected_has_default_widget
+
+    def _get_expected_not_presented_in_test_field_subclasses(self):
+        """
+        Return set of django.db.models.field.Field subclasses
+        which expected NOT presented in this test in
+         _get_django_fields_for_check_widget
+        """
+        expected_not_presented_fields = {
+            contenttype_fields.GenericRelation,
+            models.ForeignObject,
+            postgres_search.SearchQueryField,
+            postgres_search.SearchVectorField,
+            RelatedField,
+            postgres_ranges.ContinuousRangeField,
+        }
+        if django.VERSION >= (4, 2):
+            expected_not_presented_fields |= {postgres_search._Float4Field}
+        if django.VERSION >= (5, 0):
+            expected_not_presented_fields |= {models.GeneratedField}
+        if django.VERSION >= (5, 1):
+            expected_not_presented_fields |= {contenttype_fields.GenericForeignKey}
+        return expected_not_presented_fields
+
+    def _get_all_django_model_field_subclasses(self):
+        """
+        returns list of classes - all subclasses for django.db.models.field.Field
+        """
+        return self._collect_all_clas_children(models.Field)
+
+    def _collect_all_clas_children(self, clas):
+        children = []
+        for child_clas in clas.__subclasses__():
+            children.append(child_clas)
+            children.extend(self._collect_all_clas_children(child_clas))
+        return children
+
+    def _get_django_fields_for_check_widget(self):
+        """
+        Return list of field instances for all checking field classes
+        """
+        fields = [
+            models.AutoField(),
+            models.BigAutoField(),
+            models.BigIntegerField(),
+            models.BinaryField(),
+            models.BooleanField(),
+            models.CharField(),
+            models.CommaSeparatedIntegerField(),
+            models.DateField(),
+            models.DateTimeField(),
+            models.DecimalField(),
+            models.DurationField(),
+            models.EmailField(),
+            models.FileField(),
+            models.FilePathField(),
+            models.FloatField(),
+            models.ForeignKey(WithPositiveIntegerFields, on_delete=models.PROTECT),
+            models.GenericIPAddressField(),
+            models.ImageField(),
+            models.IntegerField(),
+            models.IPAddressField(),
+            models.JSONField(),
+            models.ManyToManyField(WithPositiveIntegerFields),
+            models.NullBooleanField(),
+            models.OneToOneField(WithPositiveIntegerFields, on_delete=models.PROTECT),
+            models.OrderWrt(),
+            models.PositiveBigIntegerField(),
+            models.PositiveIntegerField(),
+            models.PositiveSmallIntegerField(),
+            models.SlugField(),
+            models.SmallAutoField(),
+            models.SmallIntegerField(),
+            models.TextField(),
+            models.TimeField(),
+            models.URLField(),
+            models.UUIDField(),
+            postgres.ArrayField(models.CharField),
+            postgres.BigIntegerRangeField(),
+            postgres.CICharField(),
+            postgres.CIEmailField(),
+            postgres.CITextField(),
+            postgres.DateRangeField(),
+            postgres.DateTimeRangeField(),
+            postgres.DecimalRangeField(),
+            postgres.HStoreField(),
+            postgres.IntegerRangeField(),
+            postgres.JSONField(),
+            postgres.RangeField(),
+        ]
+        return fields