mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-25 08:46:10 -06:00
Change extra_choices back to a nested ArrayField to preserve choice ordering
This commit is contained in:
parent
d8da99f225
commit
766b57670e
@ -128,7 +128,7 @@ class CustomFieldChoiceSetFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
def filter_by_choice(self, queryset, name, value):
|
def filter_by_choice(self, queryset, name, value):
|
||||||
# TODO: Support case-insensitive matching
|
# TODO: Support case-insensitive matching
|
||||||
return queryset.filter(extra_choices__has_any_keys=value)
|
return queryset.filter(extra_choices__overlap=value)
|
||||||
|
|
||||||
|
|
||||||
class CustomLinkFilterSet(BaseFilterSet):
|
class CustomLinkFilterSet(BaseFilterSet):
|
||||||
|
@ -68,6 +68,11 @@ class CustomFieldChoiceSetImportForm(CSVModelForm):
|
|||||||
required=False,
|
required=False,
|
||||||
help_text=_('The base set of predefined choices to use (if any)')
|
help_text=_('The base set of predefined choices to use (if any)')
|
||||||
)
|
)
|
||||||
|
extra_choices = SimpleArrayField(
|
||||||
|
base_field=forms.CharField(),
|
||||||
|
required=False,
|
||||||
|
help_text=_('Comma-separated list of field choices')
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CustomFieldChoiceSet
|
model = CustomFieldChoiceSet
|
||||||
|
@ -19,6 +19,7 @@ from utilities.forms.fields import (
|
|||||||
CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelChoiceField,
|
CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelChoiceField,
|
||||||
DynamicModelMultipleChoiceField, JSONField, SlugField,
|
DynamicModelMultipleChoiceField, JSONField, SlugField,
|
||||||
)
|
)
|
||||||
|
from utilities.forms.widgets import ChoicesWidget
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||||
|
|
||||||
|
|
||||||
@ -84,14 +85,24 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class CustomFieldChoiceSetForm(BootstrapMixin, forms.ModelForm):
|
class CustomFieldChoiceSetForm(BootstrapMixin, forms.ModelForm):
|
||||||
extra_choices = forms.JSONField(
|
extra_choices = forms.CharField(
|
||||||
required=False
|
widget=ChoicesWidget(),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CustomFieldChoiceSet
|
model = CustomFieldChoiceSet
|
||||||
fields = ('name', 'description', 'base_choices', 'extra_choices', 'order_alphabetically')
|
fields = ('name', 'description', 'base_choices', 'extra_choices', 'order_alphabetically')
|
||||||
|
|
||||||
|
def clean_extra_choices(self):
|
||||||
|
data = []
|
||||||
|
for line in self.cleaned_data['extra_choices'].splitlines():
|
||||||
|
try:
|
||||||
|
value, label = line.split(',', maxsplit=1)
|
||||||
|
except ValueError:
|
||||||
|
value, label = line, line
|
||||||
|
data.append((value, label))
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class CustomLinkForm(BootstrapMixin, forms.ModelForm):
|
class CustomLinkForm(BootstrapMixin, forms.ModelForm):
|
||||||
content_types = ContentTypeMultipleChoiceField(
|
content_types = ContentTypeMultipleChoiceField(
|
||||||
|
@ -19,7 +19,7 @@ def create_choice_sets(apps, schema_editor):
|
|||||||
for cf in choice_fields:
|
for cf in choice_fields:
|
||||||
choiceset = CustomFieldChoiceSet.objects.create(
|
choiceset = CustomFieldChoiceSet.objects.create(
|
||||||
name=f'{cf.name} Choices',
|
name=f'{cf.name} Choices',
|
||||||
extra_choices=dict(zip(cf.choices, cf.choices)) # Convert list to key:val dict
|
extra_choices=tuple(zip(cf.choices, cf.choices)) # Convert list to tuple of two-tuples
|
||||||
)
|
)
|
||||||
cf.choice_set = choiceset
|
cf.choice_set = choiceset
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ class Migration(migrations.Migration):
|
|||||||
('name', models.CharField(max_length=100, unique=True)),
|
('name', models.CharField(max_length=100, unique=True)),
|
||||||
('description', models.CharField(blank=True, max_length=200)),
|
('description', models.CharField(blank=True, max_length=200)),
|
||||||
('base_choices', models.CharField(blank=True, max_length=50)),
|
('base_choices', models.CharField(blank=True, max_length=50)),
|
||||||
('extra_choices', models.JSONField(blank=True, default=dict, null=True)),
|
('extra_choices', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=100), size=2), blank=True, null=True, size=None)),
|
||||||
('order_alphabetically', models.BooleanField(default=False)),
|
('order_alphabetically', models.BooleanField(default=False)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
|
@ -6,6 +6,7 @@ import django_filters
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.core.validators import RegexValidator, ValidationError
|
from django.core.validators import RegexValidator, ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -657,8 +658,11 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel
|
|||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Base set of predefined choices (optional)')
|
help_text=_('Base set of predefined choices (optional)')
|
||||||
)
|
)
|
||||||
extra_choices = models.JSONField(
|
extra_choices = ArrayField(
|
||||||
default=dict,
|
ArrayField(
|
||||||
|
base_field=models.CharField(max_length=100),
|
||||||
|
size=2
|
||||||
|
),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
@ -684,14 +688,14 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel
|
|||||||
Returns a concatenation of the base and extra choices.
|
Returns a concatenation of the base and extra choices.
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, '_choices'):
|
if not hasattr(self, '_choices'):
|
||||||
self._choices = {}
|
self._choices = []
|
||||||
if self.base_choices:
|
if self.base_choices:
|
||||||
self._choices.update(dict(CHOICE_SETS.get(self.base_choices)))
|
self._choices.extend(CHOICE_SETS.get(self.base_choices))
|
||||||
if self.extra_choices:
|
if self.extra_choices:
|
||||||
self._choices.update(self.extra_choices)
|
self._choices.extend(self.extra_choices)
|
||||||
if self.order_alphabetically:
|
if self.order_alphabetically:
|
||||||
self._choices = dict(sorted(self._choices.items()))
|
self._choices = sorted(self._choices, key=lambda x: x[0])
|
||||||
return list(self._choices.items())
|
return self._choices
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def choices_count(self):
|
def choices_count(self):
|
||||||
@ -705,6 +709,6 @@ class CustomFieldChoiceSet(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel
|
|||||||
|
|
||||||
# Sort choices if alphabetical ordering is enforced
|
# Sort choices if alphabetical ordering is enforced
|
||||||
if self.order_alphabetically:
|
if self.order_alphabetically:
|
||||||
self.extra_choices = dict(sorted(self.extra_choices.items()))
|
self.extra_choices = sorted(self.extra_choices, key=lambda x: x[0])
|
||||||
|
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
@ -66,6 +66,9 @@ class CustomFieldTable(NetBoxTable):
|
|||||||
required = columns.BooleanColumn()
|
required = columns.BooleanColumn()
|
||||||
ui_visibility = columns.ChoiceFieldColumn(verbose_name="UI visibility")
|
ui_visibility = columns.ChoiceFieldColumn(verbose_name="UI visibility")
|
||||||
description = columns.MarkdownColumn()
|
description = columns.MarkdownColumn()
|
||||||
|
choice_set = tables.Column(
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
choices = columns.ChoicesColumn(
|
choices = columns.ChoicesColumn(
|
||||||
max_items=10,
|
max_items=10,
|
||||||
orderable=False
|
orderable=False
|
||||||
@ -76,8 +79,8 @@ class CustomFieldTable(NetBoxTable):
|
|||||||
model = CustomField
|
model = CustomField
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'content_types', 'label', 'type', 'group_name', 'required', 'default', 'description',
|
'pk', 'id', 'name', 'content_types', 'label', 'type', 'group_name', 'required', 'default', 'description',
|
||||||
'search_weight', 'filter_logic', 'ui_visibility', 'is_cloneable', 'weight', 'choices', 'created',
|
'search_weight', 'filter_logic', 'ui_visibility', 'is_cloneable', 'weight', 'choice_set', 'choices',
|
||||||
'last_updated',
|
'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'content_types', 'label', 'group_name', 'type', 'required', 'description')
|
default_columns = ('pk', 'name', 'content_types', 'label', 'group_name', 'type', 'required', 'description')
|
||||||
|
|
||||||
|
@ -139,15 +139,27 @@ class CustomFieldChoiceSetTest(APIViewTestCases.APIViewTestCase):
|
|||||||
create_data = [
|
create_data = [
|
||||||
{
|
{
|
||||||
'name': 'Choice Set 4',
|
'name': 'Choice Set 4',
|
||||||
'extra_choices': ['4A', '4B', '4C'],
|
'extra_choices': [
|
||||||
|
['4A', 'Choice 1'],
|
||||||
|
['4B', 'Choice 2'],
|
||||||
|
['4C', 'Choice 3'],
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Choice Set 5',
|
'name': 'Choice Set 5',
|
||||||
'extra_choices': ['5A', '5B', '5C'],
|
'extra_choices': [
|
||||||
|
['5A', 'Choice 1'],
|
||||||
|
['5B', 'Choice 2'],
|
||||||
|
['5C', 'Choice 3'],
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Choice Set 6',
|
'name': 'Choice Set 6',
|
||||||
'extra_choices': ['6A', '6B', '6C'],
|
'extra_choices': [
|
||||||
|
['6A', 'Choice 1'],
|
||||||
|
['6B', 'Choice 2'],
|
||||||
|
['6C', 'Choice 3'],
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
@ -155,7 +167,11 @@ class CustomFieldChoiceSetTest(APIViewTestCases.APIViewTestCase):
|
|||||||
}
|
}
|
||||||
update_data = {
|
update_data = {
|
||||||
'name': 'Choice Set X',
|
'name': 'Choice Set X',
|
||||||
'extra_choices': ['X1', 'X2', 'X3'],
|
'extra_choices': [
|
||||||
|
['X1', 'Choice 1'],
|
||||||
|
['X2', 'Choice 2'],
|
||||||
|
['X3', 'Choice 3'],
|
||||||
|
],
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ class ChangeLogViewTest(ModelViewTestCase):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
choice_set = CustomFieldChoiceSet.objects.create(
|
choice_set = CustomFieldChoiceSet.objects.create(
|
||||||
name='Choice Set 1',
|
name='Choice Set 1',
|
||||||
extra_choices={'bar': 'Bar', 'foo': 'Foo'}
|
extra_choices=(('foo', 'Foo'), ('bar', 'Bar'))
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a custom field on the Site model
|
# Create a custom field on the Site model
|
||||||
@ -226,7 +226,7 @@ class ChangeLogAPITest(APITestCase):
|
|||||||
# Create a select custom field on the Site model
|
# Create a select custom field on the Site model
|
||||||
choice_set = CustomFieldChoiceSet.objects.create(
|
choice_set = CustomFieldChoiceSet.objects.create(
|
||||||
name='Choice Set 1',
|
name='Choice Set 1',
|
||||||
extra_choices={'bar': 'Bar', 'foo': 'Foo'}
|
extra_choices=(('foo', 'Foo'), ('bar', 'Bar'))
|
||||||
)
|
)
|
||||||
cf_select = CustomField(
|
cf_select = CustomField(
|
||||||
type=CustomFieldTypeChoices.TYPE_SELECT,
|
type=CustomFieldTypeChoices.TYPE_SELECT,
|
||||||
|
@ -269,11 +269,11 @@ class CustomFieldTest(TestCase):
|
|||||||
self.assertIsNone(instance.custom_field_data.get(cf.name))
|
self.assertIsNone(instance.custom_field_data.get(cf.name))
|
||||||
|
|
||||||
def test_select_field(self):
|
def test_select_field(self):
|
||||||
CHOICES = {
|
CHOICES = (
|
||||||
'a': 'Option A',
|
('a', 'Option A'),
|
||||||
'b': 'Option B',
|
('b', 'Option B'),
|
||||||
'c': 'Option C',
|
('c', 'Option C'),
|
||||||
}
|
)
|
||||||
value = 'a'
|
value = 'a'
|
||||||
|
|
||||||
# Create a set of custom field choices
|
# Create a set of custom field choices
|
||||||
@ -306,11 +306,11 @@ class CustomFieldTest(TestCase):
|
|||||||
self.assertIsNone(instance.custom_field_data.get(cf.name))
|
self.assertIsNone(instance.custom_field_data.get(cf.name))
|
||||||
|
|
||||||
def test_multiselect_field(self):
|
def test_multiselect_field(self):
|
||||||
CHOICES = {
|
CHOICES = (
|
||||||
'a': 'Option A',
|
('a', 'Option A'),
|
||||||
'b': 'Option B',
|
('b', 'Option B'),
|
||||||
'c': 'Option C',
|
('c', 'Option C'),
|
||||||
}
|
)
|
||||||
value = ['a', 'b']
|
value = ['a', 'b']
|
||||||
|
|
||||||
# Create a set of custom field choices
|
# Create a set of custom field choices
|
||||||
@ -461,7 +461,7 @@ class CustomFieldAPITest(APITestCase):
|
|||||||
# Create a set of custom field choices
|
# Create a set of custom field choices
|
||||||
choice_set = CustomFieldChoiceSet.objects.create(
|
choice_set = CustomFieldChoiceSet.objects.create(
|
||||||
name='Custom Field Choice Set 1',
|
name='Custom Field Choice Set 1',
|
||||||
extra_choices={'foo': 'Foo', 'bar': 'Bar', 'baz': 'Baz'}
|
extra_choices=(('foo', 'Foo'), ('bar', 'Bar'), ('baz', 'Baz'))
|
||||||
)
|
)
|
||||||
|
|
||||||
custom_fields = (
|
custom_fields = (
|
||||||
@ -1049,7 +1049,11 @@ class CustomFieldImportTest(TestCase):
|
|||||||
# Create a set of custom field choices
|
# Create a set of custom field choices
|
||||||
choice_set = CustomFieldChoiceSet.objects.create(
|
choice_set = CustomFieldChoiceSet.objects.create(
|
||||||
name='Custom Field Choice Set 1',
|
name='Custom Field Choice Set 1',
|
||||||
extra_choices={'a': 'Option A', 'b': 'Option B', 'c': 'Option C'}
|
extra_choices=(
|
||||||
|
('a', 'Option A'),
|
||||||
|
('b', 'Option B'),
|
||||||
|
('c', 'Option C'),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
custom_fields = (
|
custom_fields = (
|
||||||
@ -1229,7 +1233,7 @@ class CustomFieldModelFilterTest(TestCase):
|
|||||||
|
|
||||||
choice_set = CustomFieldChoiceSet.objects.create(
|
choice_set = CustomFieldChoiceSet.objects.create(
|
||||||
name='Custom Field Choice Set 1',
|
name='Custom Field Choice Set 1',
|
||||||
extra_choices={'A': 'A', 'B': 'B', 'C': 'C', 'X': 'X'}
|
extra_choices=(('a', 'A'), ('b', 'B'), ('c', 'C'), ('x', 'X'))
|
||||||
)
|
)
|
||||||
|
|
||||||
# Integer filtering
|
# Integer filtering
|
||||||
|
@ -15,7 +15,7 @@ class CustomFieldModelFormTest(TestCase):
|
|||||||
obj_type = ContentType.objects.get_for_model(Site)
|
obj_type = ContentType.objects.get_for_model(Site)
|
||||||
choice_set = CustomFieldChoiceSet.objects.create(
|
choice_set = CustomFieldChoiceSet.objects.create(
|
||||||
name='Choice Set 1',
|
name='Choice Set 1',
|
||||||
extra_choices={'a': 'A', 'b': 'B', 'c': 'C'}
|
extra_choices=(('a', 'A'), ('b', 'B'), ('c', 'C'))
|
||||||
)
|
)
|
||||||
|
|
||||||
cf_text = CustomField.objects.create(name='text', type=CustomFieldTypeChoices.TYPE_TEXT)
|
cf_text = CustomField.objects.create(name='text', type=CustomFieldTypeChoices.TYPE_TEXT)
|
||||||
|
@ -24,7 +24,11 @@ class CustomFieldTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
site_ct = ContentType.objects.get_for_model(Site)
|
site_ct = ContentType.objects.get_for_model(Site)
|
||||||
CustomFieldChoiceSet.objects.create(
|
CustomFieldChoiceSet.objects.create(
|
||||||
name='Choice Set 1',
|
name='Choice Set 1',
|
||||||
extra_choices={'A': 'A', 'B': 'B', 'C': 'C'}
|
extra_choices=(
|
||||||
|
('A', 'A'),
|
||||||
|
('B', 'B'),
|
||||||
|
('C', 'C'),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
custom_fields = (
|
custom_fields = (
|
||||||
@ -79,36 +83,36 @@ class CustomFieldChoiceSetTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
choice_sets = (
|
choice_sets = (
|
||||||
CustomFieldChoiceSet(
|
CustomFieldChoiceSet(
|
||||||
name='Choice Set 1',
|
name='Choice Set 1',
|
||||||
extra_choices={'A1': 'Choice 1', 'A2': 'Choice 2', 'A3': 'Choice 3'}
|
extra_choices=(('A1', 'Choice 1'), ('A2', 'Choice 2'), ('A3', 'Choice 3'))
|
||||||
),
|
),
|
||||||
CustomFieldChoiceSet(
|
CustomFieldChoiceSet(
|
||||||
name='Choice Set 2',
|
name='Choice Set 2',
|
||||||
extra_choices={'B1': 'Choice 1', 'B2': 'Choice 2', 'B3': 'Choice 3'}
|
extra_choices=(('B1', 'Choice 1'), ('B2', 'Choice 2'), ('B3', 'Choice 3'))
|
||||||
),
|
),
|
||||||
CustomFieldChoiceSet(
|
CustomFieldChoiceSet(
|
||||||
name='Choice Set 3',
|
name='Choice Set 3',
|
||||||
extra_choices={'C1': 'Choice 1', 'C2': 'Choice 2', 'C3': 'Choice 3'}
|
extra_choices=(('C1', 'Choice 1'), ('C2', 'Choice 2'), ('C3', 'Choice 3'))
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
CustomFieldChoiceSet.objects.bulk_create(choice_sets)
|
CustomFieldChoiceSet.objects.bulk_create(choice_sets)
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'name': 'Choice Set X',
|
'name': 'Choice Set X',
|
||||||
'extra_choices': json.dumps({'X1': 'Choice 1', 'X2': 'Choice 2', 'X3': 'Choice 3'})
|
'extra_choices': '\n'.join(['X1,Choice 1', 'X2,Choice 2', 'X3,Choice 3'])
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
'name,extra_choices',
|
'name,extra_choices',
|
||||||
'Choice Set 4,"{""D1"": ""Choice 1"", ""D2"": ""Choice 2"", ""D3"": ""Choice 3""}"',
|
'Choice Set 4,"D1,D2,D3"',
|
||||||
'Choice Set 5,"{""E1"": ""Choice 1"", ""E2"": ""Choice 2"", ""E3"": ""Choice 3""}"',
|
'Choice Set 5,"E1,E2,E3"',
|
||||||
'Choice Set 6,"{""F1"": ""Choice 1"", ""F2"": ""Choice 2"", ""F3"": ""Choice 3""}"',
|
'Choice Set 6,"F1,F2,F3"',
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.csv_update_data = (
|
cls.csv_update_data = (
|
||||||
'id,extra_choices',
|
'id,extra_choices',
|
||||||
f'{choice_sets[0].pk},"{{""a"": ""A"", ""b"": ""B"", ""c"": ""C""}}"',
|
f'{choice_sets[0].pk},"A,B,C"',
|
||||||
f'{choice_sets[1].pk},"{{""a"": ""A"", ""b"": ""B"", ""c"": ""C""}}"',
|
f'{choice_sets[1].pk},"A,B,C"',
|
||||||
f'{choice_sets[2].pk},"{{""a"": ""A"", ""b"": ""B"", ""c"": ""C""}}"',
|
f'{choice_sets[2].pk},"A,B,C"',
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
<th>Label</th>
|
<th>Label</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{% for value, label in object.choices.items %}
|
{% for value, label in object.choices %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ value }}</td>
|
<td>{{ value }}</td>
|
||||||
<td>{{ label }}</td>
|
<td>{{ label }}</td>
|
||||||
|
@ -64,4 +64,6 @@ class ChoicesWidget(forms.Textarea):
|
|||||||
def format_value(self, value):
|
def format_value(self, value):
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
return '\n'.join([f'{k},{v}' for k, v in value.items()])
|
if type(value) is list:
|
||||||
|
return '\n'.join([f'{k},{v}' for k, v in value])
|
||||||
|
return value
|
||||||
|
@ -129,13 +129,18 @@ class ModelTestCase(TestCase):
|
|||||||
model_dict[key] = str(value)
|
model_dict[key] = str(value)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
field = instance._meta.get_field(key)
|
||||||
|
|
||||||
# Convert ArrayFields to CSV strings
|
# Convert ArrayFields to CSV strings
|
||||||
if type(instance._meta.get_field(key)) is ArrayField:
|
if type(field) is ArrayField:
|
||||||
|
if type(field.base_field) is ArrayField:
|
||||||
|
# Handle nested arrays (e.g. choice sets)
|
||||||
|
model_dict[key] = '\n'.join([f'{k},{v}' for k, v in value])
|
||||||
|
else:
|
||||||
model_dict[key] = ','.join([str(v) for v in value])
|
model_dict[key] = ','.join([str(v) for v in value])
|
||||||
|
|
||||||
# JSON
|
# JSON
|
||||||
if type(instance._meta.get_field(key)) is JSONField and value is not None:
|
if type(field) is JSONField and value is not None:
|
||||||
model_dict[key] = json.dumps(value)
|
model_dict[key] = json.dumps(value)
|
||||||
|
|
||||||
return model_dict
|
return model_dict
|
||||||
|
Loading…
Reference in New Issue
Block a user