Просмотр исходного кода

Closes #5400: Store custom field defaults as JSON values

Jeremy Stretch 5 лет назад
Родитель
Сommit
cc271aefe1

+ 1 - 1
netbox/extras/admin.py

@@ -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,

+ 2 - 9
netbox/extras/api/customfields.py

@@ -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
 

+ 1 - 1
netbox/extras/filters.py

@@ -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):

+ 6 - 0
netbox/extras/migrations/0050_customfield_changes.py

@@ -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',

+ 25 - 0
netbox/extras/migrations/0051_migrate_customfields.py

@@ -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
         ),

+ 9 - 0
netbox/extras/migrations/0052_delete_customfieldchoice_customfieldvalue.py → netbox/extras/migrations/0052_customfield_cleanup.py

@@ -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',
         ),

+ 13 - 5
netbox/extras/models/customfields.py

@@ -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)
             )

+ 1 - 1
netbox/ipam/migrations/0041_routetarget.py

@@ -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'),
     ]