From 4573078d0ba5cf69120727c95c28c09b6b2f9faf Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Tue, 14 Jan 2025 11:40:01 +0000
Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=9A=91=20Fix=20issues=20with=20struct?=
 =?UTF-8?q?ure=20and=20form?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py          | 18 +++++++++++++
 netbox_sys_plugin/migrations/0001_initial.py |  7 ++---
 netbox_sys_plugin/models/provider.py         |  2 ++
 netbox_sys_plugin/validators.py              | 28 ++++++++++++++++++++
 4 files changed, 52 insertions(+), 3 deletions(-)
 create mode 100644 netbox_sys_plugin/validators.py

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 69d4d3a..aec6076 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -55,6 +55,14 @@ class ClusterFormList(NetBoxModelForm, forms.Form):
 class VirtualMachineForm(NetBoxModelForm):
     """Form for Virtual Machines."""
 
+    role = DynamicModelChoiceField(
+        queryset=DeviceRole.objects.all(), required=True, label="Owner",
+    )
+
+    platform = DynamicModelChoiceField(
+        queryset=Platform.objects.all(), required=True, label="Platform",
+    )
+
     class Meta:
         model = VirtualMachine
         fields = ('name', 'cluster', 'status','role','platform', 'description','tags')
@@ -341,12 +349,22 @@ class CreateVmForm(NetBoxModelForm):
     def create_virtual_machine(data, cluster):
         """Create and save a VirtualMachine object."""
         role_id = data.get('vms_new-0-role', '')
+
+        role_raw=role_id.strip()
+        if not role_raw:
+            raise ValueError("Owner cannot be empty")
+
         try:
             role = DeviceRole.objects.get(pk=role_id)
         except DeviceRole.DoesNotExist as e:
             raise ValueError(f"Invalid Device Role ID: {role_id}") from e
 
         platform_id = data.get('vms_new-0-platform', '')
+
+        platform_raw=platform_id.strip()
+        if not platform_raw:
+            raise ValueError("Platform cannot be empty")
+
         try:
             platform = Platform.objects.get(pk=platform_id)
         except Platform.DoesNotExist as e:
diff --git a/netbox_sys_plugin/migrations/0001_initial.py b/netbox_sys_plugin/migrations/0001_initial.py
index 8d4fadf..d3b0feb 100644
--- a/netbox_sys_plugin/migrations/0001_initial.py
+++ b/netbox_sys_plugin/migrations/0001_initial.py
@@ -1,8 +1,9 @@
-# Generated by Django 4.2.16 on 2025-01-03 15:28
+# Generated by Django 4.2.16 on 2025-01-14 11:34
 
 import django.core.validators
 from django.db import migrations, models
 import django.db.models.deletion
+import netbox_sys_plugin.validators
 import taggit.managers
 import utilities.json
 
@@ -12,8 +13,8 @@ class Migration(migrations.Migration):
     initial = True
 
     dependencies = [
-        ('contenttypes', '0002_remove_content_type_name'),
         ('extras', '0098_webhook_custom_field_data_webhook_tags'),
+        ('contenttypes', '0002_remove_content_type_name'),
     ]
 
     operations = [
@@ -25,7 +26,7 @@ class Migration(migrations.Migration):
                 ('last_updated', models.DateTimeField(auto_now=True, null=True)),
                 ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
                 ('extra_config_name', models.CharField(blank=True, default=None, max_length=50, null=True)),
-                ('extra_config_structure', models.JSONField(blank=True, null=True)),
+                ('extra_config_structure', models.JSONField(blank=True, null=True, validators=[netbox_sys_plugin.validators.validate_extra_config_structure])),
                 ('extra_config_description', models.CharField(default=None, max_length=100)),
                 ('assigned_object_id', models.PositiveBigIntegerField(blank=True, null=True)),
                 ('assigned_object_type', models.ForeignKey(blank=True, limit_choices_to=models.Q(models.Q(('app_label', 'virtualization'), ('model', 'ClusterType'))), null=True, on_delete=django.db.models.deletion.PROTECT, to='contenttypes.contenttype')),
diff --git a/netbox_sys_plugin/models/provider.py b/netbox_sys_plugin/models/provider.py
index a42f9d4..0e1441e 100644
--- a/netbox_sys_plugin/models/provider.py
+++ b/netbox_sys_plugin/models/provider.py
@@ -6,6 +6,7 @@ from django.contrib.contenttypes.models import ContentType
 from django.db import models
 from virtualization.models import Cluster, ClusterType
 from netbox.models import NetBoxModel
+from ..validators import validate_extra_config_structure
 
 CLUSTER_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model="Cluster"))
 CLUSTER_TYPE_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model="ClusterType"))
@@ -117,6 +118,7 @@ class ProviderTypeExtraConfig(NetBoxModel):
     extra_config_structure = models.JSONField(
         blank=True,
         null=True,
+        validators=[validate_extra_config_structure]
     )
     extra_config_description = models.CharField(
         default=None, blank=False, null=False,
diff --git a/netbox_sys_plugin/validators.py b/netbox_sys_plugin/validators.py
new file mode 100644
index 0000000..91a6976
--- /dev/null
+++ b/netbox_sys_plugin/validators.py
@@ -0,0 +1,28 @@
+"""Validators definitions"""
+
+from django.core.exceptions import ValidationError
+
+def validate_extra_config_structure(value):
+    """
+    Validates the `extra_config_structure` field to ensure it matches the required JSON structure.
+    """
+    if not isinstance(value, dict):
+        raise ValidationError("The structure must be a dictionary.")
+    
+    if len(value) != 1:
+        raise ValidationError("The structure must contain exactly one key.")
+
+    for struct_name, fields in value.items():
+        if not isinstance(fields, list):
+            raise ValidationError("The value of the root key must be a list.")
+        
+        for field in fields:
+            if not isinstance(field, dict):
+                raise ValidationError("Each field in the list must be a dictionary.")
+            
+            for field_name, field_properties in field.items():
+                if not isinstance(field_properties, dict):
+                    raise ValidationError(f"The field `{field_name}` must be a dictionary.")
+                
+                if "type" not in field_properties or "required" not in field_properties:
+                    raise ValidationError(f"The field `{field_name}` must contain `type` and `required` keys.")
-- 
GitLab


From 5a3e74b38ebdffcf8cfe8e86cbdbcae312a95119 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Tue, 14 Jan 2025 11:45:54 +0000
Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20Lint=20issues=20in=20v?=
 =?UTF-8?q?alidators?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/validators.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/netbox_sys_plugin/validators.py b/netbox_sys_plugin/validators.py
index 91a6976..3425f70 100644
--- a/netbox_sys_plugin/validators.py
+++ b/netbox_sys_plugin/validators.py
@@ -8,21 +8,21 @@ def validate_extra_config_structure(value):
     """
     if not isinstance(value, dict):
         raise ValidationError("The structure must be a dictionary.")
-    
+
     if len(value) != 1:
         raise ValidationError("The structure must contain exactly one key.")
 
-    for struct_name, fields in value.items():
+    for fields in value.items():
         if not isinstance(fields, list):
             raise ValidationError("The value of the root key must be a list.")
-        
+
         for field in fields:
             if not isinstance(field, dict):
                 raise ValidationError("Each field in the list must be a dictionary.")
-            
+
             for field_name, field_properties in field.items():
                 if not isinstance(field_properties, dict):
                     raise ValidationError(f"The field `{field_name}` must be a dictionary.")
-                
+
                 if "type" not in field_properties or "required" not in field_properties:
                     raise ValidationError(f"The field `{field_name}` must contain `type` and `required` keys.")
-- 
GitLab