Skip to content

Commit

Permalink
Merge pull request #1 from mishka251/1793-widgets-testing
Browse files Browse the repository at this point in the history
Fix and tests for #1793
  • Loading branch information
mishka251 committed Apr 24, 2024
2 parents 5c3c8dc + 15207ee commit 4fdf845
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 0 deletions.
1 change: 1 addition & 0 deletions import_export/resources.py
Expand Up @@ -1245,6 +1245,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,
Expand Down
28 changes: 28 additions & 0 deletions tests/core/migrations/0012_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", "0011_uuidcategory_legacybook_alter_uuidbook_id_and_more"),
]

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)),
],
),
]
5 changes: 5 additions & 0 deletions tests/core/models.py
Expand Up @@ -176,3 +176,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)
199 changes: 199 additions & 0 deletions tests/core/tests/test_model_resource_fields_generate_widgets.py
@@ -0,0 +1,199 @@
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 TestImportExportBug(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,
}
if django.VERSION >= (4, 1):
expected_not_presented_fields |= {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, cls):
children = []
for child_cls in cls.__subclasses__():
children.append(child_cls)
children.extend(self._collect_all_clas_children(child_cls))
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

0 comments on commit 4fdf845

Please sign in to comment.