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:
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
),
|
||||
|
@ -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',
|
||||
),
|
@ -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)
|
||||
)
|
||||
|
@ -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'),
|
||||
]
|
||||
|
||||
|
Reference in New Issue
Block a user