1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Closes #5400: Store custom field defaults as JSON values

This commit is contained in:
Jeremy Stretch
2020-12-01 16:05:23 -05:00
parent 0b57389af6
commit cc271aefe1
8 changed files with 58 additions and 17 deletions

View File

@ -2,7 +2,6 @@ from django import forms
from django.contrib import admin
from utilities.forms import LaxURLField
from .choices import CustomFieldTypeChoices
from .models import CustomField, CustomLink, ExportTemplate, JobResult, Webhook
@ -76,6 +75,7 @@ class CustomFieldForm(forms.ModelForm):
model = CustomField
exclude = []
widgets = {
'default': forms.TextInput(),
'validation_regex': forms.Textarea(
attrs={
'cols': 80,

View File

@ -26,15 +26,8 @@ class CustomFieldDefaultValues:
# Populate the default value for each CustomField
value = {}
for field in fields:
if field.default:
if field.type == CustomFieldTypeChoices.TYPE_INTEGER:
field_value = int(field.default)
elif field.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
# TODO: Fix default value assignment for boolean custom fields
field_value = False if field.default.lower() == 'false' else bool(field.default)
else:
field_value = field.default
value[field.name] = field_value
if field.default is not None:
value[field.name] = field.default
else:
value[field.name] = None

View File

@ -78,7 +78,7 @@ class CustomFieldFilterSet(django_filters.FilterSet):
class Meta:
model = CustomField
fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'default', 'weight']
fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight']
class ExportTemplateFilterSet(BaseFilterSet):

View File

@ -34,6 +34,12 @@ class Migration(migrations.Migration):
size=None
),
),
# Introduce new default field (to be renamed later)
migrations.AddField(
model_name='customfield',
name='default2',
field=models.JSONField(blank=True, null=True),
),
# Rename obj_type to content_types
migrations.RenameField(
model_name='customfield',

View File

@ -16,6 +16,28 @@ def deserialize_value(field, value):
return value
def migrate_customfield_defaults(apps, schema_editor):
"""
Copy old serialized defaults to native JSON types.
"""
CustomField = apps.get_model('extras', 'CustomField')
for customfield in CustomField.objects.exclude(default=''):
try:
if customfield.type == CustomFieldTypeChoices.TYPE_INTEGER:
value = int(customfield.default)
elif customfield.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
value = customfield.default in ['true', 'yes', '1']
else:
value = customfield.default
except ValueError:
raise ValueError(
f'Invalid default value "{customfield.default}" found for {customfield.type} '
f'custom field {customfield.name}'
)
CustomField.objects.filter(pk=customfield.pk).update(default2=value)
def migrate_customfieldchoices(apps, schema_editor):
"""
Collect all CustomFieldChoices for each applicable CustomField, and save them locally as an array on
@ -73,6 +95,9 @@ class Migration(migrations.Migration):
]
operations = [
migrations.RunPython(
code=migrate_customfield_defaults
),
migrations.RunPython(
code=migrate_customfieldchoices
),

View File

@ -8,6 +8,15 @@ class Migration(migrations.Migration):
]
operations = [
migrations.RemoveField(
model_name='CustomField',
name='default',
),
migrations.RenameField(
model_name='CustomField',
old_name='default2',
new_name='default'
),
migrations.DeleteModel(
name='CustomFieldChoice',
),

View File

@ -115,10 +115,11 @@ class CustomField(models.Model):
help_text='Loose matches any instance of a given string; exact '
'matches the entire field.'
)
default = models.CharField(
max_length=100,
default = models.JSONField(
blank=True,
help_text='Default value for the field. Use "true" or "false" for booleans.'
null=True,
help_text='Default value for the field (must be a JSON value). Encapsulate '
'strings with double quotes (e.g. "Foo").'
)
weight = models.PositiveSmallIntegerField(
default=100,
@ -171,6 +172,15 @@ class CustomField(models.Model):
obj.save()
def clean(self):
# Validate the field's default value (if any)
if self.default is not None:
try:
self.validate(self.default)
except ValidationError as err:
raise ValidationError({
'default': f'Invalid default value "{self.default}": {err.message}'
})
# Minimum/maximum values can be set only for numeric fields
if self.validation_minimum is not None and self.type != CustomFieldTypeChoices.TYPE_INTEGER:
raise ValidationError({
@ -232,8 +242,6 @@ class CustomField(models.Model):
(True, 'True'),
(False, 'False'),
)
if initial is not None:
initial = bool(initial)
field = forms.NullBooleanField(
required=required, initial=initial, widget=StaticSelect2(choices=choices)
)

View File

@ -10,7 +10,7 @@ class Migration(migrations.Migration):
dependencies = [
('tenancy', '0010_custom_field_data'),
('extras', '0052_delete_customfieldchoice_customfieldvalue'),
('extras', '0052_customfield_cleanup'),
('ipam', '0040_service_drop_port'),
]