From da55ce2464cf68a9d191a083bef004a43955b761 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Fri, 22 Nov 2024 17:38:35 +0000
Subject: [PATCH 01/28] =?UTF-8?q?=E2=9C=A8=20Add=20new=20form=20to=20creat?=
 =?UTF-8?q?eVM?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/__init__.py           |  3 +-
 netbox_sys_plugin/forms/createvm.py           | 51 +++++++++++++++++++
 netbox_sys_plugin/navigation.py               | 11 +++-
 .../netbox_sys_plugin/cluster_management.html | 15 ++++++
 netbox_sys_plugin/urls.py                     |  4 ++
 netbox_sys_plugin/views.py                    | 22 ++++++++
 6 files changed, 104 insertions(+), 2 deletions(-)
 create mode 100644 netbox_sys_plugin/forms/createvm.py
 create mode 100644 netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html

diff --git a/netbox_sys_plugin/forms/__init__.py b/netbox_sys_plugin/forms/__init__.py
index c3fb247..b26bccd 100644
--- a/netbox_sys_plugin/forms/__init__.py
+++ b/netbox_sys_plugin/forms/__init__.py
@@ -1,4 +1,5 @@
 """Forms definitions"""
 
 from .provider import *
-from .machine import *
\ No newline at end of file
+from .machine import *
+from .createvm import *
\ No newline at end of file
diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
new file mode 100644
index 0000000..78a38de
--- /dev/null
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -0,0 +1,51 @@
+from django import forms
+from django.forms import inlineformset_factory
+from netbox.forms import NetBoxModelForm
+from virtualization.models import ClusterType, Cluster, VirtualMachine
+
+# Base forms
+class ClusterTypeForm(NetBoxModelForm):
+    """Form for Cluster Types."""
+    class Meta:
+        model = ClusterType
+        fields = ('name', 'description')
+
+class ClusterForm(NetBoxModelForm):
+    """Form for Clusters."""
+    class Meta:
+        model = Cluster
+        fields = ('name', 'type', 'description')
+
+class VirtualMachineForm(NetBoxModelForm):
+    """Form for Virtual Machines."""
+    class Meta:
+        model = VirtualMachine
+        fields = ('name', 'cluster', 'status', 'role', 'description')
+
+# Inline formsets for managing relationships
+ClusterFormSet = inlineformset_factory(
+    ClusterType, Cluster, form=ClusterForm, extra=1, can_delete=True
+)
+
+VirtualMachineFormSet = inlineformset_factory(
+    Cluster, VirtualMachine, form=VirtualMachineForm, extra=1, can_delete=True
+)
+
+# Combined form
+class ClusterManagementForm(ClusterTypeForm):
+    """Combined form for managing ClusterType, Cluster, and VirtualMachine."""
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.cluster_formset = ClusterFormSet(instance=self.instance, prefix='clusters')
+
+    def is_valid(self):
+        """Validate main form and inline formsets."""
+        return super().is_valid() and self.cluster_formset.is_valid()
+
+    def save(self, commit=True):
+        """Save main form and inline formsets."""
+        instance = super().save(commit=commit)
+        self.cluster_formset.instance = instance
+        self.cluster_formset.save(commit=commit)
+        return instance
\ No newline at end of file
diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py
index c9e7582..9d52a7f 100644
--- a/netbox_sys_plugin/navigation.py
+++ b/netbox_sys_plugin/navigation.py
@@ -74,7 +74,15 @@ operItem =[
     PluginMenuItem(
         link="plugins:netbox_sys_plugin:virtualmachinetype_list",
         link_text="Virtual Machine Types",
-        buttons=vm_machine_type_buttons,
+        buttons=vm_machine_type_buttons
+    ),
+]
+
+
+createVMItem =[
+    PluginMenuItem(
+        link="plugins:netbox_sys_plugin:clustermanagement_list",
+        link_text="Create Virtual Machine ",
     ),
 
 ]
@@ -85,6 +93,7 @@ menu = PluginMenu(
         ("Virtual Machine", (vmMachineItem)),
         ("Provider", clusterProviderCredentialsItem),
         ("Operation", (operItem)),
+        ("SYS", (createVMItem)),
             ),
     icon_class="mdi mdi-all-inclusive-box-outline",
 )
\ No newline at end of file
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html
new file mode 100644
index 0000000..8e17cf9
--- /dev/null
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html
@@ -0,0 +1,15 @@
+{% extends 'base/layout.html' %}
+{% load static %}
+
+{% block content %}
+<h1>Create Virtual machine</h1>
+<form method="post">
+    {% csrf_token %}
+    {{ form.as_p }}
+    {{ form.cluster_formset.management_form }}
+    {% for cluster_form in form.cluster_formset %}
+        {{ cluster_form.as_p }}
+    {% endfor %}
+    <button type="submit" class="btn btn-primary">Save</button>
+</form>
+{% endblock %}
\ No newline at end of file
diff --git a/netbox_sys_plugin/urls.py b/netbox_sys_plugin/urls.py
index 94b7d55..4421c8b 100644
--- a/netbox_sys_plugin/urls.py
+++ b/netbox_sys_plugin/urls.py
@@ -3,6 +3,7 @@
 from django.urls import path
 from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
 from netbox_sys_plugin import models, views
+from .views import ClusterManagementView
 
 urlpatterns = (
     #maintenance
@@ -38,6 +39,9 @@ urlpatterns = (
     path('vm-type/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='virtualmachinetype_changelog', kwargs={'model': models.VirtualMachineType}),
     path('vm-type/<int:pk>/journal/', ObjectJournalView.as_view(), name='virtualmachinetype_journal', kwargs={'model': models.VirtualMachineType}),
 
+    #CreateVM
+    
+    path('cluster-management/', ClusterManagementView.as_view(), name='clustermanagement_list'),
 
 
 )
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index f93e69d..55b4680 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -1,7 +1,11 @@
 """Model views definitions"""
 
+from django.shortcuts import render, redirect
 from netbox.views import generic
 from . import forms, models, tables, filtersets
+from virtualization.models import ClusterType
+
+
 
 #VmMaintenance
 class  VmMaintenanceView(generic.ObjectView):
@@ -115,3 +119,21 @@ class VmTypeEditView(generic.ObjectEditView):
     """
     queryset = models.VirtualMachineType.objects.all()
     form = forms.VmTypeForm
+
+class ClusterManagementView(generic.ObjectEditView):
+    """View for managing ClusterType, Cluster, and VirtualMachine in one form."""
+    queryset = ClusterType.objects.all()
+    form = forms.ClusterManagementForm
+
+    def get(self, request, *args, **kwargs):
+        instance = self.get_object()
+        form = self.form(instance=instance)
+        return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form})
+
+    def post(self, request, *args, **kwargs):
+        instance = self.get_object()
+        form = self.form(data=request.POST, instance=instance)
+        if form.is_valid():
+            form.save()
+            return redirect('plugins:netbox_sys_plugin:clustermanagement_list')
+        return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form})
\ No newline at end of file
-- 
GitLab


From 72bce8328bb1364ba41d4e4c912b6ca8fccffd54 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Mon, 25 Nov 2024 10:51:05 +0000
Subject: [PATCH 02/28] =?UTF-8?q?=F0=9F=9A=A7=20Add=20new=20elements?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py | 58 +++++++++++++++++++++++++++--
 1 file changed, 54 insertions(+), 4 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 78a38de..440654d 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -6,18 +6,45 @@ from virtualization.models import ClusterType, Cluster, VirtualMachine
 # Base forms
 class ClusterTypeForm(NetBoxModelForm):
     """Form for Cluster Types."""
+
+    name = forms.CharField(
+        max_length=200, min_length=1, required=True, label="Cluster Type Name"
+    )
+    
+    description = forms.CharField(
+        max_length=200, min_length=1, required=True, label="Cluster Type Description"
+    )
+
     class Meta:
         model = ClusterType
         fields = ('name', 'description')
 
 class ClusterForm(NetBoxModelForm):
     """Form for Clusters."""
+
+    name = forms.CharField(
+        max_length=200, min_length=1, required=True, label="Cluster Name"
+    )
+    
+    description = forms.CharField(
+        max_length=200, min_length=1, required=True, label="Cluster Description"
+    )
+
     class Meta:
         model = Cluster
         fields = ('name', 'type', 'description')
 
 class VirtualMachineForm(NetBoxModelForm):
     """Form for Virtual Machines."""
+
+    name = forms.CharField(
+        max_length=200, min_length=1, required=True, label="Virtual Machine Name"
+    )
+    
+    description = forms.CharField(
+        max_length=200, min_length=1, required=True, label="Virtual Machine Description"
+    )
+
     class Meta:
         model = VirtualMachine
         fields = ('name', 'cluster', 'status', 'role', 'description')
@@ -32,20 +59,43 @@ VirtualMachineFormSet = inlineformset_factory(
 )
 
 # Combined form
-class ClusterManagementForm(ClusterTypeForm):
+class ClusterManagementForm(ClusterForm):
     """Combined form for managing ClusterType, Cluster, and VirtualMachine."""
 
     def __init__(self, *args, **kwargs):
+        data = kwargs.pop('data',None)
         super().__init__(*args, **kwargs)
-        self.cluster_formset = ClusterFormSet(instance=self.instance, prefix='clusters')
+
+        #Save temp
+        if not self.instance.pk:
+            self.instance.save()
+
+        #init formsets
+        self.cluster_formsets = ClusterFormSet(instance=self.instance, data=data,prefix='clusters')
+        self.virtual_machine_formsets = []
+        
+        for cluster in self.instance.clusters.all():
+            vm_formset= VirtualMachineFormSet(
+                instance=cluster, data=kwargs.get('data'), prefix=f'vms_{cluster.pk}'
+            )
+            self.virtual_machine_formsets.append((cluster.pk,vm_formset))
 
     def is_valid(self):
         """Validate main form and inline formsets."""
-        return super().is_valid() and self.cluster_formset.is_valid()
+        valid = super().is_valid() and self.cluster_formset.is_valid()
+        valid = valid and all(vm_formset.is_valid() for _, vm_formset in self.virtual_machine_formsets)
+        return valid
 
+    
     def save(self, commit=True):
         """Save main form and inline formsets."""
         instance = super().save(commit=commit)
+        #save clusters    
         self.cluster_formset.instance = instance
-        self.cluster_formset.save(commit=commit)
+        clusters = self.cluster_formset.save(commit=commit)
+
+        #save virtual machine for eact cluster
+        for cluster, formset in zip(clusters, self.virtual_machine_formsets):
+            formset.instance = cluster
+            formset.save(commit=commit)
         return instance
\ No newline at end of file
-- 
GitLab


From de0e2d977ea559c2dab3d5ee650e3b38b2c2da27 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Mon, 25 Nov 2024 14:41:57 +0000
Subject: [PATCH 03/28] =?UTF-8?q?=F0=9F=94=8A=20Add=20logs?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py           | 123 ++++++++++--------
 .../netbox_sys_plugin/cluster_management.html |  29 ++++-
 netbox_sys_plugin/views.py                    |   1 +
 3 files changed, 96 insertions(+), 57 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 440654d..74c91db 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -6,96 +6,113 @@ from virtualization.models import ClusterType, Cluster, VirtualMachine
 # Base forms
 class ClusterTypeForm(NetBoxModelForm):
     """Form for Cluster Types."""
-
-    name = forms.CharField(
-        max_length=200, min_length=1, required=True, label="Cluster Type Name"
-    )
-    
-    description = forms.CharField(
-        max_length=200, min_length=1, required=True, label="Cluster Type Description"
-    )
-
     class Meta:
         model = ClusterType
         fields = ('name', 'description')
 
 class ClusterForm(NetBoxModelForm):
     """Form for Clusters."""
-
-    name = forms.CharField(
-        max_length=200, min_length=1, required=True, label="Cluster Name"
-    )
-    
-    description = forms.CharField(
-        max_length=200, min_length=1, required=True, label="Cluster Description"
-    )
-
     class Meta:
         model = Cluster
         fields = ('name', 'type', 'description')
 
 class VirtualMachineForm(NetBoxModelForm):
     """Form for Virtual Machines."""
-
-    name = forms.CharField(
-        max_length=200, min_length=1, required=True, label="Virtual Machine Name"
-    )
-    
-    description = forms.CharField(
-        max_length=200, min_length=1, required=True, label="Virtual Machine Description"
-    )
-
     class Meta:
         model = VirtualMachine
         fields = ('name', 'cluster', 'status', 'role', 'description')
 
 # Inline formsets for managing relationships
 ClusterFormSet = inlineformset_factory(
-    ClusterType, Cluster, form=ClusterForm, extra=1, can_delete=True
+    ClusterType, Cluster, form=ClusterForm, extra=1 #, can_delete=True
 )
 
 VirtualMachineFormSet = inlineformset_factory(
-    Cluster, VirtualMachine, form=VirtualMachineForm, extra=1, can_delete=True
+    Cluster, VirtualMachine, form=VirtualMachineForm, extra=1 #, can_delete=True
 )
 
 # Combined form
-class ClusterManagementForm(ClusterForm):
+class ClusterManagementForm(NetBoxModelForm):
     """Combined form for managing ClusterType, Cluster, and VirtualMachine."""
 
+    class Meta:
+        model = ClusterType
+        fields = ('name', 'description')
+
     def __init__(self, *args, **kwargs):
-        data = kwargs.pop('data',None)
+        data = kwargs.pop('data', None)
         super().__init__(*args, **kwargs)
 
-        #Save temp
-        if not self.instance.pk:
-            self.instance.save()
+        # Initialize Cluster formset
+        self.cluster_formset = ClusterFormSet(instance=self.instance, data=data, prefix='clusters')
 
-        #init formsets
-        self.cluster_formsets = ClusterFormSet(instance=self.instance, data=data,prefix='clusters')
+        # Populate the 'type' field for each cluster form
+        for cluster_form in self.cluster_formset:
+            cluster_form.initial['type'] = self.instance.pk
+
+        # Handle clusters when the parent instance is not saved yet
         self.virtual_machine_formsets = []
-        
-        for cluster in self.instance.clusters.all():
-            vm_formset= VirtualMachineFormSet(
-                instance=cluster, data=kwargs.get('data'), prefix=f'vms_{cluster.pk}'
+        if self.instance.pk:
+            clusters = self.instance.clusters.all()  # Get existing clusters for a saved ClusterType
+        else:
+            clusters = []  # For unsaved ClusterType, no clusters exist yet
+
+        # Initialize Virtual Machine formsets for each cluster
+        for cluster in clusters:
+            vm_formset = VirtualMachineFormSet(
+                instance=cluster, data=data, prefix=f'vms_{cluster.pk}'
             )
-            self.virtual_machine_formsets.append((cluster.pk,vm_formset))
+            # Populate the 'cluster' field for each virtual machine form
+            for vm_form in vm_formset:
+                vm_form.initial['cluster'] = cluster.pk
 
-    def is_valid(self):
-        """Validate main form and inline formsets."""
-        valid = super().is_valid() and self.cluster_formset.is_valid()
-        valid = valid and all(vm_formset.is_valid() for _, vm_formset in self.virtual_machine_formsets)
-        return valid
+            self.virtual_machine_formsets.append((cluster.pk, vm_formset))
+
+        # Add an empty virtual machine formset for new clusters
+        if not clusters and not data:
+            empty_vm_formset = VirtualMachineFormSet(
+                instance=Cluster(), data=data, prefix='vms_new'
+            )
+            self.virtual_machine_formsets.append(('new', empty_vm_formset))
 
+    def clean(self):
+        cleaned_data = super().clean()
+        if not cleaned_data.get('name'):
+            raise forms.ValidationError({'name':"This field is requiered."})
+        return cleaned_data
+
+    def is_valid(self):
+        """Validate the main form and all inline formsets."""
+        valid = super().is_valid()
+    
+        # Validate Cluster formset
+        cluster_valid = self.cluster_formset.is_valid()
+        if not cluster_valid:
+            print("Cluster formset validation failed:", self.cluster_formset.errors)
     
+        # Validate Virtual Machine formsets
+        vm_valid = True
+        for _, vm_formset in self.virtual_machine_formsets:
+            if not vm_formset.is_valid():
+                print("VM formset validation failed:", vm_formset.errors)
+                vm_valid = False
+    
+        return valid and cluster_valid and vm_valid
+
     def save(self, commit=True):
-        """Save main form and inline formsets."""
+        """Save the main form and all inline formsets."""
         instance = super().save(commit=commit)
-        #save clusters    
+    
+        # Save Cluster formset
         self.cluster_formset.instance = instance
         clusters = self.cluster_formset.save(commit=commit)
-
-        #save virtual machine for eact cluster
-        for cluster, formset in zip(clusters, self.virtual_machine_formsets):
-            formset.instance = cluster
-            formset.save(commit=commit)
+    
+        # Save Virtual Machine formsets for each cluster
+        for cluster, vm_formset in zip(clusters, self.virtual_machine_formsets):
+            vm_formset.instance = cluster
+            for vm_form in vm_formset:
+                if not vm_form.cleaned_data.get('cluster'):
+                    vm_form.cleaned_data['cluster'] = cluster
+            vm_formset.save(commit=commit)
+    
         return instance
\ No newline at end of file
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html
index 8e17cf9..323c212 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html
@@ -1,15 +1,36 @@
-{% extends 'base/layout.html' %}
-{% load static %}
+{% extends "base/layout.html" %}
 
 {% block content %}
-<h1>Create Virtual machine</h1>
+<h1>Manage Cluster Types, Clusters, and Virtual Machines</h1>
 <form method="post">
     {% csrf_token %}
     {{ form.as_p }}
+
+    <h2>Clusters</h2>
     {{ form.cluster_formset.management_form }}
     {% for cluster_form in form.cluster_formset %}
-        {{ cluster_form.as_p }}
+        <div class="form-group">
+            {{ cluster_form.as_p }}
+            {% for error in cluster_form.errors %}
+                <p class="text-danger">{{ error }}</p>
+            {% endfor %}
+        </div>
+    {% endfor %}
+
+    <h2>Virtual Machines</h2>
+    {% for cluster_id, vm_formset in form.virtual_machine_formsets %}
+        <h3>Cluster {{ cluster_id }}</h3>
+        {{ vm_formset.management_form }}
+        {% for vm_form in vm_formset %}
+            <div class="form-group">
+                {{ vm_form.as_p }}
+                {% for error in vm_form.errors %}
+                    <p class="text-danger">{{ error }}</p>
+                {% endfor %}
+            </div>
+        {% endfor %}
     {% endfor %}
+
     <button type="submit" class="btn btn-primary">Save</button>
 </form>
 {% endblock %}
\ No newline at end of file
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index 55b4680..90f82a2 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -131,6 +131,7 @@ class ClusterManagementView(generic.ObjectEditView):
         return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form})
 
     def post(self, request, *args, **kwargs):
+        print('POST DATA: ',request.POST)
         instance = self.get_object()
         form = self.form(data=request.POST, instance=instance)
         if form.is_valid():
-- 
GitLab


From c26c22ce77aa8ce85a128411983412d3b6999c1a Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Mon, 25 Nov 2024 15:26:34 +0000
Subject: [PATCH 04/28] =?UTF-8?q?=F0=9F=92=A9=20=F0=9F=92=84=20Add=20Ui=20?=
 =?UTF-8?q?elements=20and=20not=20working=20code?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py           |  5 +-
 .../netbox_sys_plugin/cluster_management.html | 51 +++++++++++++++----
 2 files changed, 44 insertions(+), 12 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 74c91db..c198a54 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -111,8 +111,9 @@ class ClusterManagementForm(NetBoxModelForm):
         for cluster, vm_formset in zip(clusters, self.virtual_machine_formsets):
             vm_formset.instance = cluster
             for vm_form in vm_formset:
-                if not vm_form.cleaned_data.get('cluster'):
-                    vm_form.cleaned_data['cluster'] = cluster
+                #if not vm_form.cleaned_data.get('cluster'):
+                #   vm_form.cleaned_data['cluster'] = cluster
+                vm_form.instance.cluster = cluster
             vm_formset.save(commit=commit)
     
         return instance
\ No newline at end of file
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html
index 323c212..115e891 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html
@@ -1,15 +1,38 @@
-{% extends "base/layout.html" %}
+{% extends 'base/layout.html' %}
+{% load buttons %}
+{% load custom_links %}
+{% load helpers %}
+{% load perms %}
+{% load plugins %}
+{% load tabs %}
+{% load i18n %}
 
+
+
+{% block title %}
+SYS - Virtual Machine
+{% endblock title %}
+{% block tabs %}
+  <ul class="nav nav-tabs px-3">
+    <li class="nav-item" role="presentation">
+      <button class="nav-link active" id="edit-form-tab" data-bs-toggle="tab" data-bs-target="#edit-form" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
+        Create      </button>
+    </li>
+  </ul>
+{% endblock tabs %}
+{% block content-wrapper %}
+<div class="tab-content">
+    <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab">
 {% block content %}
-<h1>Manage Cluster Types, Clusters, and Virtual Machines</h1>
-<form method="post">
+<form method="post" enctype="multipart/form-data" class="form-object-edit mt-5">
+    <h5>Provider Type</h5>
     {% csrf_token %}
     {{ form.as_p }}
 
-    <h2>Clusters</h2>
+    <h5>Provider Credentials</h5>
     {{ form.cluster_formset.management_form }}
     {% for cluster_form in form.cluster_formset %}
-        <div class="form-group">
+        <div class="form-group" class="field-group mb-5">
             {{ cluster_form.as_p }}
             {% for error in cluster_form.errors %}
                 <p class="text-danger">{{ error }}</p>
@@ -17,12 +40,11 @@
         </div>
     {% endfor %}
 
-    <h2>Virtual Machines</h2>
+    <h5>Virtual Machine Info</h5>
     {% for cluster_id, vm_formset in form.virtual_machine_formsets %}
-        <h3>Cluster {{ cluster_id }}</h3>
         {{ vm_formset.management_form }}
         {% for vm_form in vm_formset %}
-            <div class="form-group">
+            <div class="form-group" class="field-group mb-5">
                 {{ vm_form.as_p }}
                 {% for error in vm_form.errors %}
                     <p class="text-danger">{{ error }}</p>
@@ -30,7 +52,16 @@
             </div>
         {% endfor %}
     {% endfor %}
-
+    <div class="text-end my-3">
+        {% block buttons %}
     <button type="submit" class="btn btn-primary">Save</button>
+    {% endblock buttons %}
+</div>
 </form>
-{% endblock %}
\ No newline at end of file
+</div>
+</div>
+{% endblock %}
+{% endblock content-wrapper %}
+
+{# User messages #}
+{% include 'inc/messages.html' %}
\ No newline at end of file
-- 
GitLab


From 7a85e2849b06835fc527d7856023164a194d815f Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Tue, 26 Nov 2024 14:22:52 +0000
Subject: [PATCH 05/28] =?UTF-8?q?=F0=9F=9A=A7=20Working=20form!?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py | 100 +++++-----------------------
 netbox_sys_plugin/navigation.py     |  11 +++
 netbox_sys_plugin/views.py          |  55 ++++++++++++---
 3 files changed, 75 insertions(+), 91 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index c198a54..7c063c4 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -3,32 +3,25 @@ from django.forms import inlineformset_factory
 from netbox.forms import NetBoxModelForm
 from virtualization.models import ClusterType, Cluster, VirtualMachine
 
-# Base forms
-class ClusterTypeForm(NetBoxModelForm):
-    """Form for Cluster Types."""
-    class Meta:
-        model = ClusterType
-        fields = ('name', 'description')
-
 class ClusterForm(NetBoxModelForm):
     """Form for Clusters."""
     class Meta:
         model = Cluster
-        fields = ('name', 'type', 'description')
+        fields = ('name', 'type', 'description','status','tags')
 
 class VirtualMachineForm(NetBoxModelForm):
     """Form for Virtual Machines."""
     class Meta:
         model = VirtualMachine
-        fields = ('name', 'cluster', 'status', 'role', 'description')
+        fields = ('name', 'cluster', 'status', 'role', 'description','tags')
 
 # Inline formsets for managing relationships
 ClusterFormSet = inlineformset_factory(
-    ClusterType, Cluster, form=ClusterForm, extra=1 #, can_delete=True
+    ClusterType, Cluster, form=ClusterForm, extra=1, can_delete=False
 )
 
 VirtualMachineFormSet = inlineformset_factory(
-    Cluster, VirtualMachine, form=VirtualMachineForm, extra=1 #, can_delete=True
+    Cluster, VirtualMachine, form=VirtualMachineForm, extra=1, can_delete=False
 )
 
 # Combined form
@@ -37,83 +30,24 @@ class ClusterManagementForm(NetBoxModelForm):
 
     class Meta:
         model = ClusterType
-        fields = ('name', 'description')
+        fields = ('name', 'slug','description','tags')
 
     def __init__(self, *args, **kwargs):
         data = kwargs.pop('data', None)
         super().__init__(*args, **kwargs)
 
-        # Initialize Cluster formset
-        self.cluster_formset = ClusterFormSet(instance=self.instance, data=data, prefix='clusters')
-
-        # Populate the 'type' field for each cluster form
-        for cluster_form in self.cluster_formset:
-            cluster_form.initial['type'] = self.instance.pk
-
-        # Handle clusters when the parent instance is not saved yet
+        # Initialize formsets
+        #Cluster + Cluster type
+        self.cluster_formset = ClusterFormSet(data=data, prefix='clusters')
+        #Virtual Machine 
         self.virtual_machine_formsets = []
-        if self.instance.pk:
-            clusters = self.instance.clusters.all()  # Get existing clusters for a saved ClusterType
-        else:
-            clusters = []  # For unsaved ClusterType, no clusters exist yet
-
-        # Initialize Virtual Machine formsets for each cluster
-        for cluster in clusters:
-            vm_formset = VirtualMachineFormSet(
-                instance=cluster, data=data, prefix=f'vms_{cluster.pk}'
-            )
-            # Populate the 'cluster' field for each virtual machine form
-            for vm_form in vm_formset:
-                vm_form.initial['cluster'] = cluster.pk
-
-            self.virtual_machine_formsets.append((cluster.pk, vm_formset))
-
-        # Add an empty virtual machine formset for new clusters
-        if not clusters and not data:
-            empty_vm_formset = VirtualMachineFormSet(
-                instance=Cluster(), data=data, prefix='vms_new'
-            )
-            self.virtual_machine_formsets.append(('new', empty_vm_formset))
-
-    def clean(self):
-        cleaned_data = super().clean()
-        if not cleaned_data.get('name'):
-            raise forms.ValidationError({'name':"This field is requiered."})
-        return cleaned_data
+        empty_vm_formset = VirtualMachineFormSet(data=data, prefix='vms_new')
+        self.virtual_machine_formsets.append(('new', empty_vm_formset))
 
     def is_valid(self):
-        """Validate the main form and all inline formsets."""
-        valid = super().is_valid()
-    
-        # Validate Cluster formset
-        cluster_valid = self.cluster_formset.is_valid()
-        if not cluster_valid:
-            print("Cluster formset validation failed:", self.cluster_formset.errors)
-    
-        # Validate Virtual Machine formsets
-        vm_valid = True
-        for _, vm_formset in self.virtual_machine_formsets:
-            if not vm_formset.is_valid():
-                print("VM formset validation failed:", vm_formset.errors)
-                vm_valid = False
-    
-        return valid and cluster_valid and vm_valid
-
-    def save(self, commit=True):
-        """Save the main form and all inline formsets."""
-        instance = super().save(commit=commit)
-    
-        # Save Cluster formset
-        self.cluster_formset.instance = instance
-        clusters = self.cluster_formset.save(commit=commit)
-    
-        # Save Virtual Machine formsets for each cluster
-        for cluster, vm_formset in zip(clusters, self.virtual_machine_formsets):
-            vm_formset.instance = cluster
-            for vm_form in vm_formset:
-                #if not vm_form.cleaned_data.get('cluster'):
-                #   vm_form.cleaned_data['cluster'] = cluster
-                vm_form.instance.cluster = cluster
-            vm_formset.save(commit=commit)
-    
-        return instance
\ No newline at end of file
+        """Validate the main form only."""
+        main_form_valid = super().is_valid()
+        print("Main form valid:", main_form_valid)
+        if not main_form_valid:
+            print("Main form errors:", self.errors)
+        return main_form_valid
diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py
index 9d52a7f..69afd5a 100644
--- a/netbox_sys_plugin/navigation.py
+++ b/netbox_sys_plugin/navigation.py
@@ -47,6 +47,16 @@ vm_machine_type_buttons = [
     ),
 ]
 
+create_vm_buttons = [
+    PluginMenuButton(
+        link="plugins:netbox_sys_plugin:clustermanagement_list",
+        title="Add",
+        icon_class="mdi mdi-plus-thick",
+        color=ButtonColorChoices.GREEN,
+        permissions=["netbox_sys_plugin.add_createvm"],
+    ),
+]
+
 #Items
 
 clusterProviderCredentialsItem = [
@@ -83,6 +93,7 @@ createVMItem =[
     PluginMenuItem(
         link="plugins:netbox_sys_plugin:clustermanagement_list",
         link_text="Create Virtual Machine ",
+        buttons=create_vm_buttons
     ),
 
 ]
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index 90f82a2..fb28a5e 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -3,7 +3,8 @@
 from django.shortcuts import render, redirect
 from netbox.views import generic
 from . import forms, models, tables, filtersets
-from virtualization.models import ClusterType
+from virtualization.models import ClusterType, Cluster, VirtualMachine
+from django.http import JsonResponse
 
 
 
@@ -131,10 +132,48 @@ class ClusterManagementView(generic.ObjectEditView):
         return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form})
 
     def post(self, request, *args, **kwargs):
-        print('POST DATA: ',request.POST)
-        instance = self.get_object()
-        form = self.form(data=request.POST, instance=instance)
-        if form.is_valid():
-            form.save()
-            return redirect('plugins:netbox_sys_plugin:clustermanagement_list')
-        return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form})
\ No newline at end of file
+        form = forms.ClusterManagementForm(data=request.POST)
+        if request.method == 'POST':
+            print("POST DATA:", request.POST)
+
+            #cluster type
+            name = request.POST.get('name', '')
+            slug = request.POST.get('slug', '')
+            description = request.POST.get('description', '')
+
+            #cluster
+            cluster_name = request.POST.get('clusters-0-name', '')
+            cluster_description = request.POST.get('clusters-0-description', '')
+            cluster_status = request.POST.get('clusters-0-status', '')
+            
+            #vm
+            vm_name = request.POST.get('vms_new-0-name', '')
+            vm_status = request.POST.get('vms_new-0-status', '')
+            vm_role = request.POST.get('vms_new-0-role', '')
+            vm_description = request.POST.get('vms_new-0-description', '')
+
+
+            try:
+                #cluster type
+                cluster_type = ClusterType(name=name, slug=slug, description=description)
+                cluster_type.full_clean()  # Trigger validation
+                cluster_type.save()
+                print(f"ClusterType saved with ID: {cluster_type.pk}")
+                #cluster
+                cluster = Cluster(name=cluster_name,type=cluster_type,status=cluster_status,description=cluster_description)
+                cluster.full_clean()
+                cluster.save()
+                print(f"ClusterType saved with ID: {cluster_type.pk}")
+                print(f"Cluster saved with ID: {cluster.pk}")
+                #vm
+                vm = VirtualMachine(name=vm_name,status=vm_status, description=vm_description,cluster=cluster)
+                vm.full_clean()
+                vm.save()
+                print(f"ClusterType saved with ID: {cluster_type.pk}")
+                print(f"Cluster saved with ID: {cluster.pk}")
+                print(f"Virtual Machine saved with ID: {vm.pk}")
+                return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form})
+            except Exception as e:
+                print(f"Error saving Something: {str(e)}")
+                return JsonResponse({"status": "error", "message": str(e)})
+            
-- 
GitLab


From 523a551b2f0cce7f41a72aef53e13bfe035ed747 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Wed, 27 Nov 2024 14:20:40 +0000
Subject: [PATCH 06/28] =?UTF-8?q?=F0=9F=91=94=20Add=20new=20fields=20-=20v?=
 =?UTF-8?q?m=20interface=20and=20services?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py           |  42 +++-
 netbox_sys_plugin/navigation.py               |   4 +-
 ...cluster_management.html => create_vm.html} |  33 +--
 netbox_sys_plugin/urls.py                     |   2 +-
 netbox_sys_plugin/views.py                    | 211 ++++++++++++++----
 5 files changed, 223 insertions(+), 69 deletions(-)
 rename netbox_sys_plugin/templates/netbox_sys_plugin/{cluster_management.html => create_vm.html} (68%)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 7c063c4..6d54cbd 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -1,7 +1,9 @@
 from django import forms
 from django.forms import inlineformset_factory
 from netbox.forms import NetBoxModelForm
-from virtualization.models import ClusterType, Cluster, VirtualMachine
+from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterface
+from dcim.models import Site, Platform
+from ipam.models import IPAddress, Service
 
 class ClusterForm(NetBoxModelForm):
     """Form for Clusters."""
@@ -13,7 +15,20 @@ class VirtualMachineForm(NetBoxModelForm):
     """Form for Virtual Machines."""
     class Meta:
         model = VirtualMachine
-        fields = ('name', 'cluster', 'status', 'role', 'description','tags')
+        fields = ('name', 'cluster', 'status', 'role','platform','site', 'description','tags')
+
+class VirtualMachineInterfaceForm(NetBoxModelForm):
+    """Form for Virtual Machine Interfaces."""
+    class Meta:
+        model = VMInterface
+        fields = ('name','tags')
+
+class ServiceForm(NetBoxModelForm):
+    """Form for Virtual Machine Interfaces."""
+    class Meta:
+        model = Service
+        fields = ('name','protocol','ports','tags')
+
 
 # Inline formsets for managing relationships
 ClusterFormSet = inlineformset_factory(
@@ -24,6 +39,13 @@ VirtualMachineFormSet = inlineformset_factory(
     Cluster, VirtualMachine, form=VirtualMachineForm, extra=1, can_delete=False
 )
 
+VirtualMachineInterfaceFormSet = inlineformset_factory(
+    VirtualMachine, VMInterface, form=VirtualMachineInterfaceForm, extra=1, can_delete=False
+)
+
+ServiceFormSet = inlineformset_factory(
+    VirtualMachine, Service, form=ServiceForm, extra=3, can_delete=False
+)
 # Combined form
 class ClusterManagementForm(NetBoxModelForm):
     """Combined form for managing ClusterType, Cluster, and VirtualMachine."""
@@ -43,11 +65,11 @@ class ClusterManagementForm(NetBoxModelForm):
         self.virtual_machine_formsets = []
         empty_vm_formset = VirtualMachineFormSet(data=data, prefix='vms_new')
         self.virtual_machine_formsets.append(('new', empty_vm_formset))
-
-    def is_valid(self):
-        """Validate the main form only."""
-        main_form_valid = super().is_valid()
-        print("Main form valid:", main_form_valid)
-        if not main_form_valid:
-            print("Main form errors:", self.errors)
-        return main_form_valid
+        #Virtual Machine Interfaces
+        self.virtual_machine_interface_formsets = []
+        empty_vmi_formset = VirtualMachineInterfaceFormSet(data=data, prefix='vmis_new')
+        self.virtual_machine_interface_formsets.append(('new', empty_vmi_formset))
+        #Service
+        self.service_formsets = []
+        empty_service_formset = ServiceFormSet(data=data, prefix='service_new')
+        self.service_formsets.append(('new', empty_service_formset))
diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py
index 69afd5a..a418b4a 100644
--- a/netbox_sys_plugin/navigation.py
+++ b/netbox_sys_plugin/navigation.py
@@ -49,7 +49,7 @@ vm_machine_type_buttons = [
 
 create_vm_buttons = [
     PluginMenuButton(
-        link="plugins:netbox_sys_plugin:clustermanagement_list",
+        link="plugins:netbox_sys_plugin:creatvm_add",
         title="Add",
         icon_class="mdi mdi-plus-thick",
         color=ButtonColorChoices.GREEN,
@@ -91,7 +91,7 @@ operItem =[
 
 createVMItem =[
     PluginMenuItem(
-        link="plugins:netbox_sys_plugin:clustermanagement_list",
+        link="plugins:netbox_sys_plugin:creatvm_add",
         link_text="Create Virtual Machine ",
         buttons=create_vm_buttons
     ),
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
similarity index 68%
rename from netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html
rename to netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
index 115e891..91a7e60 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_management.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
@@ -28,30 +28,39 @@ SYS - Virtual Machine
     <h5>Provider Type</h5>
     {% csrf_token %}
     {{ form.as_p }}
-
     <h5>Provider Credentials</h5>
-    {{ form.cluster_formset.management_form }}
     {% for cluster_form in form.cluster_formset %}
         <div class="form-group" class="field-group mb-5">
             {{ cluster_form.as_p }}
-            {% for error in cluster_form.errors %}
-                <p class="text-danger">{{ error }}</p>
-            {% endfor %}
         </div>
     {% endfor %}
 
-    <h5>Virtual Machine Info</h5>
-    {% for cluster_id, vm_formset in form.virtual_machine_formsets %}
-        {{ vm_formset.management_form }}
+    <h5>Virtual Machine</h5>
+    {% for vm_formset in form.virtual_machine_formsets %}
         {% for vm_form in vm_formset %}
             <div class="form-group" class="field-group mb-5">
                 {{ vm_form.as_p }}
-                {% for error in vm_form.errors %}
-                    <p class="text-danger">{{ error }}</p>
-                {% endfor %}
             </div>
         {% endfor %}
     {% endfor %}
+    <h5>Network Name</h5>
+    {% for vmi_formset in form.virtual_machine_interface_formsets %}
+        {% for vmi_form in vmi_formset %}
+            <div class="form-group" class="field-group mb-5">
+                {{ vmi_form.as_p }}
+            </div>
+        {% endfor %}
+    {% endfor %}
+    <h5>Dependencies</h5>
+    <div class="row">
+    {% for service_formset in form.service_formsets %}
+        {% for service_form in service_formset %}
+            <div class="form-group" class="field-group mb-5">
+                {{ service_form.as_p }}
+            </div>
+        {% endfor %}
+    {% endfor %}
+    </div>
     <div class="text-end my-3">
         {% block buttons %}
     <button type="submit" class="btn btn-primary">Save</button>
@@ -63,5 +72,3 @@ SYS - Virtual Machine
 {% endblock %}
 {% endblock content-wrapper %}
 
-{# User messages #}
-{% include 'inc/messages.html' %}
\ No newline at end of file
diff --git a/netbox_sys_plugin/urls.py b/netbox_sys_plugin/urls.py
index 4421c8b..c3ece9c 100644
--- a/netbox_sys_plugin/urls.py
+++ b/netbox_sys_plugin/urls.py
@@ -41,7 +41,7 @@ urlpatterns = (
 
     #CreateVM
     
-    path('cluster-management/', ClusterManagementView.as_view(), name='clustermanagement_list'),
+    path('create-vm/', ClusterManagementView.as_view(), name='creatvm_add'),
 
 
 )
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index fb28a5e..0948049 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -3,8 +3,12 @@
 from django.shortcuts import render, redirect
 from netbox.views import generic
 from . import forms, models, tables, filtersets
-from virtualization.models import ClusterType, Cluster, VirtualMachine
-from django.http import JsonResponse
+from virtualization.models import ClusterType, Cluster, VirtualMachine,VMInterface
+from dcim.models import DeviceRole,Site, Platform
+from ipam.models import Service
+from django.db import transaction
+from django.contrib import messages
+from taggit.models import Tag
 
 
 
@@ -121,6 +125,128 @@ class VmTypeEditView(generic.ObjectEditView):
     queryset = models.VirtualMachineType.objects.all()
     form = forms.VmTypeForm
 
+
+#Create VM
+# Helper functions for object creation
+def create_cluster_type(data):
+    """Creates and saves a ClusterType object."""
+    cluster_type = ClusterType(
+        name=data.get('name', ''),
+        slug=data.get('slug', ''),
+        description=data.get('description', '')
+        #tag=tag,
+    )
+    cluster_type.full_clean()  # Validate the data
+    cluster_type.save()  # Save to the database
+    return cluster_type
+
+
+def create_cluster(data, cluster_type):
+    """Creates and saves a Cluster object linked to a ClusterType."""
+    site_id = data.get('vms_new-0-site', '')
+    try:
+        site=Site.objects.get(pk=site_id)
+    except Site.DoesNotExist:
+        raise ValueError(f"Invalid Site ID: {site_id}")
+
+    cluster = Cluster(
+        name=data.get('clusters-0-name', ''),
+        type=cluster_type,  # Link to the saved ClusterType
+        status=data.get('clusters-0-status', ''),
+        site=site,
+        description=data.get('clusters-0-description', '')
+    )
+    cluster.full_clean()  # Validate the data
+    cluster.save()  # Save to the database
+    return cluster
+
+
+def create_virtual_machine(data, cluster):
+    """Creates and saves a VirtualMachine object linked to a Cluster."""
+    role_id = data.get('vms_new-0-role', '')
+    try:
+        role=DeviceRole.objects.get(pk=role_id)
+    except DeviceRole.DoesNotExist:
+        raise ValueError(f"Invalid Device Role ID: {role_id}")
+    
+    site_id = data.get('vms_new-0-site', '')
+    try:
+        site=Site.objects.get(pk=site_id)
+    except Site.DoesNotExist:
+        raise ValueError(f"Invalid Site ID: {site_id}")
+    
+    platform_id = data.get('vms_new-0-platform', '')
+    try:
+        platform=Platform.objects.get(pk=platform_id)
+    except Platform.DoesNotExist:
+        raise ValueError(f"Invalid Platform ID: {platform_id}")
+    
+    vm = VirtualMachine(
+        name=data.get('vms_new-0-name', ''),
+        status=data.get('vms_new-0-status', ''),
+        role=role,
+        site=site,
+        platform=platform,
+        description=data.get('vms_new-0-description', ''),
+        cluster=cluster  # Link to the saved Cluster
+    )
+    vm.full_clean()  # Validate the data
+    vm.save()  # Save to the database
+    return vm
+
+def create_vm_interface(data, vm):
+    """Creates and saves a Vm Interface object linked to a Virtual Machine."""
+    vmi = VMInterface(
+        name=data.get('vmis_new-0-name', ''),
+        virtual_machine=vm  # Link to the saved Virtual Machine
+    )
+    vmi.full_clean()  # Validate the data
+    vmi.save()  # Save to the database
+    return vmi
+
+def create_ntp_services(data, vm):
+    """Creates and saves a Vm Interface object linked to a Virtual Machine."""
+    ntp_s = Service(
+        name=data.get('service_new-0-name', ''),
+        protocol=data.get('service_new-0-protocol', ''),
+        ports=data.get('service_new-0-ports', ''),
+        virtual_machine=vm  # Link to the saved Virtual Machine
+    )
+    
+    ntp_s.full_clean()  # Validate the data
+    ntp_s.save()  # Save to the database
+    return ntp_s
+
+def create_dns_services(data, vm):
+    """Creates and saves a Vm Interface object linked to a Virtual Machine."""
+    dns_s = Service(
+        name=data.get('service_new-1-name', ''),
+        protocol=data.get('service_new-1-protocol', ''),
+        ports=data.get('service_new-1-ports', ''),
+        virtual_machine=vm  # Link to the saved Virtual Machine
+    )
+    
+    dns_s.full_clean()  # Validate the data
+    dns_s.save()  # Save to the database
+    return dns_s
+
+
+def create_syslog_services(data, vm):
+    """Creates and saves a Vm Interface object linked to a Virtual Machine."""
+    syslog_s = Service(
+        name=data.get('service_new-2-name', ''),
+        protocol=data.get('service_new-2-protocol', ''),
+        ports=data.get('service_new-2-ports', ''),
+        virtual_machine=vm  # Link to the saved Virtual Machine
+    )
+    syslog_s.full_clean()  # Validate the data
+    syslog_s.save()  # Save to the database
+    return syslog_s
+
+
+
+
+# Main view for handling ClusterManagement
 class ClusterManagementView(generic.ObjectEditView):
     """View for managing ClusterType, Cluster, and VirtualMachine in one form."""
     queryset = ClusterType.objects.all()
@@ -129,51 +255,50 @@ class ClusterManagementView(generic.ObjectEditView):
     def get(self, request, *args, **kwargs):
         instance = self.get_object()
         form = self.form(instance=instance)
-        return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form})
+        return render(request, 'netbox_sys_plugin/create_vm.html', {'form': form})
 
     def post(self, request, *args, **kwargs):
-        form = forms.ClusterManagementForm(data=request.POST)
+        """Handles POST requests for objects."""
+        form = self.form(data=request.POST)
+
         if request.method == 'POST':
             print("POST DATA:", request.POST)
 
-            #cluster type
-            name = request.POST.get('name', '')
-            slug = request.POST.get('slug', '')
-            description = request.POST.get('description', '')
-
-            #cluster
-            cluster_name = request.POST.get('clusters-0-name', '')
-            cluster_description = request.POST.get('clusters-0-description', '')
-            cluster_status = request.POST.get('clusters-0-status', '')
-            
-            #vm
-            vm_name = request.POST.get('vms_new-0-name', '')
-            vm_status = request.POST.get('vms_new-0-status', '')
-            vm_role = request.POST.get('vms_new-0-role', '')
-            vm_description = request.POST.get('vms_new-0-description', '')
-
-
             try:
-                #cluster type
-                cluster_type = ClusterType(name=name, slug=slug, description=description)
-                cluster_type.full_clean()  # Trigger validation
-                cluster_type.save()
-                print(f"ClusterType saved with ID: {cluster_type.pk}")
-                #cluster
-                cluster = Cluster(name=cluster_name,type=cluster_type,status=cluster_status,description=cluster_description)
-                cluster.full_clean()
-                cluster.save()
-                print(f"ClusterType saved with ID: {cluster_type.pk}")
-                print(f"Cluster saved with ID: {cluster.pk}")
-                #vm
-                vm = VirtualMachine(name=vm_name,status=vm_status, description=vm_description,cluster=cluster)
-                vm.full_clean()
-                vm.save()
-                print(f"ClusterType saved with ID: {cluster_type.pk}")
-                print(f"Cluster saved with ID: {cluster.pk}")
-                print(f"Virtual Machine saved with ID: {vm.pk}")
-                return render(request, 'netbox_sys_plugin/cluster_management.html', {'form': form})
+                # Use a database transaction to ensure all-or-nothing behavior
+                with transaction.atomic():
+                    # Create the ClusterType
+                    cluster_type = create_cluster_type(request.POST)
+                    print(f"ClusterType saved with ID: {cluster_type.pk}")
+
+                    # Create the Cluster
+                    cluster = create_cluster(request.POST, cluster_type)
+                    print(f"Cluster saved with ID: {cluster.pk}")
+
+                    # Create the Virtual Machine
+                    vm = create_virtual_machine(request.POST, cluster)
+                    print(f"Virtual Machine saved with ID: {vm.pk}")
+
+                    #Create the Vm Interface
+                    vmi = create_vm_interface(request.POST, vm)
+                    print(f"Virtual Machine Interface saved with ID: {vmi.pk}")
+
+                    #Create the Services
+                    ntps = create_ntp_services(request.POST, vm)
+                    print(f"NTP Server service saved with ID: {ntps.pk}")
+                    dnsss = create_dns_services(request.POST, vm)
+                    print(f"DNS Server service saved with ID: {dnsss.pk}")
+                    syslogs = create_syslog_services(request.POST, vm)
+                    print(f"SYSLOG Server service saved with ID: {syslogs.pk}")
+
+                # On successful save, provide feedback to the user
+                messages.success(request,'ClusterType, Cluster, and VirtualMachine created successfully!')
+                return render(request,'netbox_sys_plugin/create_vm.html',{'form': form,})
+
             except Exception as e:
-                print(f"Error saving Something: {str(e)}")
-                return JsonResponse({"status": "error", "message": str(e)})
-            
+                # Log the error and display feedback
+                print(f"Error saving objects: {str(e)}")
+                messages.error(request, f"Error: {str(e)}. Please correct the form and try again.")
+                return render(request,'netbox_sys_plugin/create_vm.html',{'form': form,})
+
+        return render(request,'netbox_sys_plugin/create_vm.html',{'form': form})
-- 
GitLab


From 916d20ea45ff4552369d86ab3ac6de4d318b735d Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Wed, 27 Nov 2024 15:24:32 +0000
Subject: [PATCH 07/28] =?UTF-8?q?=F0=9F=90=9B=20Fix=20issue=20in=20service?=
 =?UTF-8?q?s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py |  4 +--
 netbox_sys_plugin/views.py          | 42 ++++++++++++++++++++++-------
 2 files changed, 34 insertions(+), 12 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 6d54cbd..d75a85e 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -9,13 +9,13 @@ class ClusterForm(NetBoxModelForm):
     """Form for Clusters."""
     class Meta:
         model = Cluster
-        fields = ('name', 'type', 'description','status','tags')
+        fields = ('name', 'type', 'description','status','site','tags')
 
 class VirtualMachineForm(NetBoxModelForm):
     """Form for Virtual Machines."""
     class Meta:
         model = VirtualMachine
-        fields = ('name', 'cluster', 'status', 'role','platform','site', 'description','tags')
+        fields = ('name', 'cluster', 'status', 'role','platform', 'description','tags')
 
 class VirtualMachineInterfaceForm(NetBoxModelForm):
     """Form for Virtual Machine Interfaces."""
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index 0948049..aac0fc6 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -127,7 +127,22 @@ class VmTypeEditView(generic.ObjectEditView):
 
 
 #Create VM
+# Main view for handling ClusterManagement
+class ClusterManagementView(generic.ObjectEditView):
+    """View for managing ClusterType, Cluster, and VirtualMachine in one form."""
+    queryset = ClusterType.objects.all()
+    form = forms.ClusterManagementForm
+
 # Helper functions for object creation
+def parse_ports(ports_field):
+    """Parse the value for ports"""
+    try:
+        ports=[int(port.strip())for port in ports_field.split(',') if port.strip().isdigit()]
+        return ports
+    except Exception as e:
+        raise ValueError(f"Invalid ports value:{ports_field}. Error: {str(e)}")
+
+
 def create_cluster_type(data):
     """Creates and saves a ClusterType object."""
     cluster_type = ClusterType(
@@ -143,7 +158,7 @@ def create_cluster_type(data):
 
 def create_cluster(data, cluster_type):
     """Creates and saves a Cluster object linked to a ClusterType."""
-    site_id = data.get('vms_new-0-site', '')
+    site_id = data.get('clusters-0-site', '')
     try:
         site=Site.objects.get(pk=site_id)
     except Site.DoesNotExist:
@@ -169,7 +184,7 @@ def create_virtual_machine(data, cluster):
     except DeviceRole.DoesNotExist:
         raise ValueError(f"Invalid Device Role ID: {role_id}")
     
-    site_id = data.get('vms_new-0-site', '')
+    site_id = data.get('clusters-0-site', '')
     try:
         site=Site.objects.get(pk=site_id)
     except Site.DoesNotExist:
@@ -206,11 +221,15 @@ def create_vm_interface(data, vm):
 
 def create_ntp_services(data, vm):
     """Creates and saves a Vm Interface object linked to a Virtual Machine."""
+    ports_field=data.get('service_new-0-ports', '')
+    ports=parse_ports(ports_field)
     ntp_s = Service(
         name=data.get('service_new-0-name', ''),
         protocol=data.get('service_new-0-protocol', ''),
-        ports=data.get('service_new-0-ports', ''),
+        ports=ports,
+        description='NTP Server',
         virtual_machine=vm  # Link to the saved Virtual Machine
+
     )
     
     ntp_s.full_clean()  # Validate the data
@@ -219,11 +238,15 @@ def create_ntp_services(data, vm):
 
 def create_dns_services(data, vm):
     """Creates and saves a Vm Interface object linked to a Virtual Machine."""
+    ports_field=data.get('service_new-1-ports', '')
+    ports=parse_ports(ports_field)
     dns_s = Service(
         name=data.get('service_new-1-name', ''),
         protocol=data.get('service_new-1-protocol', ''),
-        ports=data.get('service_new-1-ports', ''),
+        ports=ports,
+        description='DNS Server',
         virtual_machine=vm  # Link to the saved Virtual Machine
+
     )
     
     dns_s.full_clean()  # Validate the data
@@ -233,10 +256,13 @@ def create_dns_services(data, vm):
 
 def create_syslog_services(data, vm):
     """Creates and saves a Vm Interface object linked to a Virtual Machine."""
+    ports_field=data.get('service_new-2-ports', '')
+    ports=parse_ports(ports_field)
     syslog_s = Service(
         name=data.get('service_new-2-name', ''),
         protocol=data.get('service_new-2-protocol', ''),
-        ports=data.get('service_new-2-ports', ''),
+        ports=ports,
+        description='SYSLOG Server',
         virtual_machine=vm  # Link to the saved Virtual Machine
     )
     syslog_s.full_clean()  # Validate the data
@@ -246,11 +272,7 @@ def create_syslog_services(data, vm):
 
 
 
-# Main view for handling ClusterManagement
-class ClusterManagementView(generic.ObjectEditView):
-    """View for managing ClusterType, Cluster, and VirtualMachine in one form."""
-    queryset = ClusterType.objects.all()
-    form = forms.ClusterManagementForm
+
 
     def get(self, request, *args, **kwargs):
         instance = self.get_object()
-- 
GitLab


From 349110c1de36a052aead629c33adda818bdb4008 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Wed, 27 Nov 2024 16:15:47 +0000
Subject: [PATCH 08/28] =?UTF-8?q?=F0=9F=92=84=20Add=20UI=20seperation=20fo?=
 =?UTF-8?q?r=20services?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py           | 25 ++++++++++++---
 .../netbox_sys_plugin/create_vm.html          | 27 ++++++++++++++--
 netbox_sys_plugin/views.py                    | 32 +++++++++----------
 3 files changed, 59 insertions(+), 25 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index d75a85e..97aa082 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -43,8 +43,14 @@ VirtualMachineInterfaceFormSet = inlineformset_factory(
     VirtualMachine, VMInterface, form=VirtualMachineInterfaceForm, extra=1, can_delete=False
 )
 
-ServiceFormSet = inlineformset_factory(
-    VirtualMachine, Service, form=ServiceForm, extra=3, can_delete=False
+ServiceNtpFormSet = inlineformset_factory(
+    VirtualMachine, Service, form=ServiceForm, extra=1, can_delete=False
+)
+ServiceDnsFormSet = inlineformset_factory(
+    VirtualMachine, Service, form=ServiceForm, extra=1, can_delete=False
+)
+ServiceSyslogFormSet = inlineformset_factory(
+    VirtualMachine, Service, form=ServiceForm, extra=1, can_delete=False
 )
 # Combined form
 class ClusterManagementForm(NetBoxModelForm):
@@ -70,6 +76,15 @@ class ClusterManagementForm(NetBoxModelForm):
         empty_vmi_formset = VirtualMachineInterfaceFormSet(data=data, prefix='vmis_new')
         self.virtual_machine_interface_formsets.append(('new', empty_vmi_formset))
         #Service
-        self.service_formsets = []
-        empty_service_formset = ServiceFormSet(data=data, prefix='service_new')
-        self.service_formsets.append(('new', empty_service_formset))
+        self.service_ntp_formsets = []
+        empty_ntp_service_formset = ServiceNtpFormSet(data=data, prefix='service_ntp')
+        self.service_ntp_formsets.append(('new', empty_ntp_service_formset))
+        #Service
+        self.service_dns_formsets = []
+        empty_dns_service_formset = ServiceDnsFormSet(data=data, prefix='service_dns')
+        self.service_dns_formsets.append(('new', empty_dns_service_formset))
+        #Service
+        self.service_syslog_formsets = []
+        empty_syslog_service_formset = ServiceSyslogFormSet(data=data, prefix='service_syslog')
+        self.service_syslog_formsets.append(('new', empty_syslog_service_formset))
+
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
index 91a7e60..82c25e7 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
@@ -53,13 +53,34 @@ SYS - Virtual Machine
     {% endfor %}
     <h5>Dependencies</h5>
     <div class="row">
-    {% for service_formset in form.service_formsets %}
-        {% for service_form in service_formset %}
-            <div class="form-group" class="field-group mb-5">
+    <h6>NTP Server</h6>
+    {% for service_ntp_formset in form.service_ntp_formsets %}
+    <div class="form-group" class="field-group mb-5">
+        {% for service_form in service_ntp_formset %}
+        <div class="form-group" class="field-group mb-5">           
+                {{ service_form.as_p }}
+            </div>
+        {% endfor %}
+    {% endfor %}
+    <h6>DNS Server</h6>
+    {% for service_dns_formset in form.service_dns_formsets %}
+    <div class="form-group" class="field-group mb-5">
+        {% for service_form in service_dns_formset %}
+        <div class="form-group" class="field-group mb-5">           
                 {{ service_form.as_p }}
             </div>
         {% endfor %}
     {% endfor %}
+    <h6>SYSLOG Server</h6>
+    {% for service_syslog_formset in form.service_syslog_formsets %}
+    <div class="form-group" class="field-group mb-5">
+        {% for service_form in service_syslog_formset %}
+        <div class="form-group" class="field-group mb-5">           
+                {{ service_form.as_p }}
+            </div>
+        {% endfor %}
+    {% endfor %}
+
     </div>
     <div class="text-end my-3">
         {% block buttons %}
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index aac0fc6..9ca68fa 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -127,11 +127,6 @@ class VmTypeEditView(generic.ObjectEditView):
 
 
 #Create VM
-# Main view for handling ClusterManagement
-class ClusterManagementView(generic.ObjectEditView):
-    """View for managing ClusterType, Cluster, and VirtualMachine in one form."""
-    queryset = ClusterType.objects.all()
-    form = forms.ClusterManagementForm
 
 # Helper functions for object creation
 def parse_ports(ports_field):
@@ -221,11 +216,11 @@ def create_vm_interface(data, vm):
 
 def create_ntp_services(data, vm):
     """Creates and saves a Vm Interface object linked to a Virtual Machine."""
-    ports_field=data.get('service_new-0-ports', '')
+    ports_field=data.get('service_syslog-0-ports', '')
     ports=parse_ports(ports_field)
     ntp_s = Service(
-        name=data.get('service_new-0-name', ''),
-        protocol=data.get('service_new-0-protocol', ''),
+        name=data.get('service_ntp-0-name', ''),
+        protocol=data.get('service_ntp-0-protocol', ''),
         ports=ports,
         description='NTP Server',
         virtual_machine=vm  # Link to the saved Virtual Machine
@@ -238,11 +233,11 @@ def create_ntp_services(data, vm):
 
 def create_dns_services(data, vm):
     """Creates and saves a Vm Interface object linked to a Virtual Machine."""
-    ports_field=data.get('service_new-1-ports', '')
+    ports_field=data.get('service_syslog-0-ports', '')
     ports=parse_ports(ports_field)
     dns_s = Service(
-        name=data.get('service_new-1-name', ''),
-        protocol=data.get('service_new-1-protocol', ''),
+        name=data.get('service_dns-0-name', ''),
+        protocol=data.get('service_dns-0-protocol', ''),
         ports=ports,
         description='DNS Server',
         virtual_machine=vm  # Link to the saved Virtual Machine
@@ -256,11 +251,11 @@ def create_dns_services(data, vm):
 
 def create_syslog_services(data, vm):
     """Creates and saves a Vm Interface object linked to a Virtual Machine."""
-    ports_field=data.get('service_new-2-ports', '')
+    ports_field=data.get('service_syslog-0-ports', '')
     ports=parse_ports(ports_field)
     syslog_s = Service(
-        name=data.get('service_new-2-name', ''),
-        protocol=data.get('service_new-2-protocol', ''),
+        name=data.get('service_syslog-0-name', ''),
+        protocol=data.get('service_syslog-0-protocol', ''),
         ports=ports,
         description='SYSLOG Server',
         virtual_machine=vm  # Link to the saved Virtual Machine
@@ -270,9 +265,12 @@ def create_syslog_services(data, vm):
     return syslog_s
 
 
-
-
-
+#Create VM
+# Main view for handling ClusterManagement
+class ClusterManagementView(generic.ObjectEditView):
+    """View for managing ClusterType, Cluster, and VirtualMachine in one form."""
+    queryset = ClusterType.objects.all()
+    form = forms.ClusterManagementForm
 
     def get(self, request, *args, **kwargs):
         instance = self.get_object()
-- 
GitLab


From baa2231d564bc36a683b2c19302e3836126987d2 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Wed, 27 Nov 2024 16:45:18 +0000
Subject: [PATCH 09/28] =?UTF-8?q?=F0=9F=9A=9A=20Rename=20main=20class?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/machine.py | 4 ++++
 netbox_sys_plugin/urls.py          | 4 ++--
 netbox_sys_plugin/views.py         | 2 +-
 3 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/netbox_sys_plugin/forms/machine.py b/netbox_sys_plugin/forms/machine.py
index 7dbb437..4396562 100644
--- a/netbox_sys_plugin/forms/machine.py
+++ b/netbox_sys_plugin/forms/machine.py
@@ -22,6 +22,10 @@ class VmAssignedVmTypeForm(NetBoxModelForm):
         queryset=VirtualMachine.objects.all(), required=True, label="Virtual Machine"
     )
 
+    virtual_machine_type_assignment_desc = forms.CharField(
+        max_length=50, min_length=1, required=False, label="Assignment Description"
+    )
+
 
     def __init__(self, *args, **kwargs):
         # Initialize helper selectors
diff --git a/netbox_sys_plugin/urls.py b/netbox_sys_plugin/urls.py
index c3ece9c..299078a 100644
--- a/netbox_sys_plugin/urls.py
+++ b/netbox_sys_plugin/urls.py
@@ -3,7 +3,7 @@
 from django.urls import path
 from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
 from netbox_sys_plugin import models, views
-from .views import ClusterManagementView
+from .views import CreateVmView
 
 urlpatterns = (
     #maintenance
@@ -41,7 +41,7 @@ urlpatterns = (
 
     #CreateVM
     
-    path('create-vm/', ClusterManagementView.as_view(), name='creatvm_add'),
+    path('create-vm/', CreateVmView.as_view(), name='creatvm_add'),
 
 
 )
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index 9ca68fa..85ecf0d 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -267,7 +267,7 @@ def create_syslog_services(data, vm):
 
 #Create VM
 # Main view for handling ClusterManagement
-class ClusterManagementView(generic.ObjectEditView):
+class CreateVmView(generic.ObjectEditView):
     """View for managing ClusterType, Cluster, and VirtualMachine in one form."""
     queryset = ClusterType.objects.all()
     form = forms.ClusterManagementForm
-- 
GitLab


From b729ebf10420eb20cbb9b3f36b6a1eb2cc5218e6 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Wed, 27 Nov 2024 17:57:12 +0000
Subject: [PATCH 10/28] =?UTF-8?q?=F0=9F=9A=A7=20Add=20IP=20Addresses?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py           | 20 ++++++++++++++--
 .../netbox_sys_plugin/create_vm.html          | 10 ++++++++
 netbox_sys_plugin/views.py                    | 23 +++++++++++++++++--
 3 files changed, 49 insertions(+), 4 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 97aa082..1b1f11b 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -1,5 +1,5 @@
 from django import forms
-from django.forms import inlineformset_factory
+from django.forms import inlineformset_factory, BaseModelFormSet
 from netbox.forms import NetBoxModelForm
 from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterface
 from dcim.models import Site, Platform
@@ -29,6 +29,12 @@ class ServiceForm(NetBoxModelForm):
         model = Service
         fields = ('name','protocol','ports','tags')
 
+class IPAddressForm(NetBoxModelForm):
+    """Form for IP Addresses."""
+    class Meta:
+        model = IPAddress
+        fields = ('address', 'status', 'role', 'tags')
+
 
 # Inline formsets for managing relationships
 ClusterFormSet = inlineformset_factory(
@@ -52,8 +58,14 @@ ServiceDnsFormSet = inlineformset_factory(
 ServiceSyslogFormSet = inlineformset_factory(
     VirtualMachine, Service, form=ServiceForm, extra=1, can_delete=False
 )
+
+IPAddressFormSet = forms.modelformset_factory(
+    IPAddress, form=IPAddressForm, formset=IPAddressForm, extra=1, can_delete=False
+)
+
+
 # Combined form
-class ClusterManagementForm(NetBoxModelForm):
+class CreateVmForm(NetBoxModelForm):
     """Combined form for managing ClusterType, Cluster, and VirtualMachine."""
 
     class Meta:
@@ -87,4 +99,8 @@ class ClusterManagementForm(NetBoxModelForm):
         self.service_syslog_formsets = []
         empty_syslog_service_formset = ServiceSyslogFormSet(data=data, prefix='service_syslog')
         self.service_syslog_formsets.append(('new', empty_syslog_service_formset))
+        # IP Address
+        self.ip_formsets = []
+        empty_ip_formset = IPAddressFormSet(data=data, prefix='ip_new')
+        self.ip_formsets.append(('new', empty_ip_formset))
 
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
index 82c25e7..3ce0938 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
@@ -80,6 +80,16 @@ SYS - Virtual Machine
             </div>
         {% endfor %}
     {% endfor %}
+    
+    <h6>IP Address</h6>
+    {% for ip_formset in form.ip_formsets %}
+    <div class="form-group" class="field-group mb-5">
+        {% for ip_form in ip_formset %}
+        <div class="form-group" class="field-group mb-5">           
+                {{ ip_form.as_p }}
+            </div>
+        {% endfor %}
+    {% endfor %}
 
     </div>
     <div class="text-end my-3">
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index 85ecf0d..de10bb9 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -5,7 +5,7 @@ from netbox.views import generic
 from . import forms, models, tables, filtersets
 from virtualization.models import ClusterType, Cluster, VirtualMachine,VMInterface
 from dcim.models import DeviceRole,Site, Platform
-from ipam.models import Service
+from ipam.models import Service, IPAddress
 from django.db import transaction
 from django.contrib import messages
 from taggit.models import Tag
@@ -265,12 +265,27 @@ def create_syslog_services(data, vm):
     return syslog_s
 
 
+def create_ip_address(data, vm_interface):
+    """
+    Creates and saves an IPAddress object linked to a specific VMInterface.
+    """
+    ip_address = IPAddress(
+        address=data.get('ip_new-address', ''),
+        status=data.get('ip_new-status', ''),
+        role=data.get('ip_new-role', ''),
+        assigned_object=vm_interface  # Link to the saved VMInterface
+    )
+    ip_address.full_clean()  # Validate the data
+    ip_address.save()  # Save to the database
+    return ip_address
+
+
 #Create VM
 # Main view for handling ClusterManagement
 class CreateVmView(generic.ObjectEditView):
     """View for managing ClusterType, Cluster, and VirtualMachine in one form."""
     queryset = ClusterType.objects.all()
-    form = forms.ClusterManagementForm
+    form = forms.CreateVmForm
 
     def get(self, request, *args, **kwargs):
         instance = self.get_object()
@@ -311,6 +326,10 @@ class CreateVmView(generic.ObjectEditView):
                     syslogs = create_syslog_services(request.POST, vm)
                     print(f"SYSLOG Server service saved with ID: {syslogs.pk}")
 
+                    # Create the IP Address
+                    ip_address = create_ip_address(request.POST, vmi)
+                    print(f"IP Address saved with ID: {ip_address.pk}")
+
                 # On successful save, provide feedback to the user
                 messages.success(request,'ClusterType, Cluster, and VirtualMachine created successfully!')
                 return render(request,'netbox_sys_plugin/create_vm.html',{'form': form,})
-- 
GitLab


From 9334af1bd7785977bf4cd338c4822bbd7fe18000 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Thu, 28 Nov 2024 12:23:44 +0000
Subject: [PATCH 11/28] =?UTF-8?q?=F0=9F=92=84=20Fix=20UI=20vanishing=20ele?=
 =?UTF-8?q?ments?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 1b1f11b..5f26f41 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -78,7 +78,9 @@ class CreateVmForm(NetBoxModelForm):
 
         # Initialize formsets
         #Cluster + Cluster type
-        self.cluster_formset = ClusterFormSet(data=data, prefix='clusters')
+        self.cluster_formsets = []
+        empty_cluster_formset = ClusterFormSet(data=data, prefix='clusters')
+        self.cluster_formsets.append(('new', empty_cluster_formset))
         #Virtual Machine 
         self.virtual_machine_formsets = []
         empty_vm_formset = VirtualMachineFormSet(data=data, prefix='vms_new')
-- 
GitLab


From 976e34b84e848416914cdecbc1154e34f3ed89bc Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Thu, 28 Nov 2024 14:28:47 +0000
Subject: [PATCH 12/28] =?UTF-8?q?=F0=9F=92=84=20Add=20Ui=20tab=20to=20mana?=
 =?UTF-8?q?ge=20providers?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../netbox_sys_plugin/create_vm.html          | 45 +++++++++++++++----
 1 file changed, 36 insertions(+), 9 deletions(-)

diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
index 3ce0938..88836d0 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
@@ -25,16 +25,43 @@ SYS - Virtual Machine
     <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab">
 {% block content %}
 <form method="post" enctype="multipart/form-data" class="form-object-edit mt-5">
-    <h5>Provider Type</h5>
-    {% csrf_token %}
-    {{ form.as_p }}
-    <h5>Provider Credentials</h5>
-    {% for cluster_form in form.cluster_formset %}
-        <div class="form-group" class="field-group mb-5">
-            {{ cluster_form.as_p }}
+    <div class="field-group my-5">
+        <div class="row mb-2">
         </div>
-    {% endfor %}
-
+        <div class="row mb-2">
+            <ul class="nav nav-pills" role="tablist">
+              <li role="presentation" class="nav-item">
+                <button role="tab" type="button" id="device_tab" data-bs-toggle="tab" aria-controls="device" data-bs-target="#device" class="nav-link {% if not form.initial.vminterface and not form.initial.fhrpgroup %}active{% endif %}">
+                New Provider
+                </button>
+              </li>
+              <li role="presentation" class="nav-item">
+                <button role="tab" type="button" id="vm_tab" data-bs-toggle="tab" aria-controls="vm" data-bs-target="#vm" class="nav-link {% if form.initial.vminterface %}active{% endif %}">
+                  Existing Provider
+                </button>
+              </li>
+            </ul>
+        </div>
+        <div class="tab-content p-0 border-0">
+          <div class="tab-pane" id="device" role="tabpanel" aria-labeled-by="device_tab">
+            <h5>Provider Type</h5>
+            {% csrf_token %}
+            {{ form.as_p }}
+            <h5>Provider Credentials</h5>
+            {% for cluster_formset in form.cluster_formsets %}
+            {% for vl_form in cluster_formset %}
+            <div class="form-group" class="field-group mb-5">
+                {{ vl_form.as_p }}
+            </div>
+            {% endfor %}
+            {% endfor %}
+          </div>
+          <div class="tab-pane" id="vm" role="tabpanel" aria-labeled-by="vm_tab">
+            <p>Tab2</p>
+          </div>
+        </div>
+      </div>
+    
     <h5>Virtual Machine</h5>
     {% for vm_formset in form.virtual_machine_formsets %}
         {% for vm_form in vm_formset %}
-- 
GitLab


From f3bd19dcdd7a39f35bedfd7e6d26f1cd56bcb04a Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Thu, 28 Nov 2024 15:52:50 +0000
Subject: [PATCH 13/28] =?UTF-8?q?=F0=9F=92=84=20Add=20elements=20to=20the?=
 =?UTF-8?q?=20specific=20tabs?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py           | 50 ++++++++++++++++---
 .../netbox_sys_plugin/create_vm.html          | 24 +++++++--
 2 files changed, 62 insertions(+), 12 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 5f26f41..68c2e0b 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -2,8 +2,8 @@ from django import forms
 from django.forms import inlineformset_factory, BaseModelFormSet
 from netbox.forms import NetBoxModelForm
 from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterface
-from dcim.models import Site, Platform
 from ipam.models import IPAddress, Service
+from utilities.forms.fields import DynamicModelChoiceField
 
 class ClusterForm(NetBoxModelForm):
     """Form for Clusters."""
@@ -11,6 +11,24 @@ class ClusterForm(NetBoxModelForm):
         model = Cluster
         fields = ('name', 'type', 'description','status','site','tags')
 
+class ClusterFormList(NetBoxModelForm):
+    """Form for Cluster List."""
+    provider = DynamicModelChoiceField(
+        queryset=Cluster.objects.all(), required=False, label="Provider"
+    )
+    class Meta:
+        model = Cluster
+        fields = ('provider',)
+
+class ClusterTypeFormList(NetBoxModelForm):
+    """Form for Cluster Types List."""
+    provider_type = DynamicModelChoiceField(
+        queryset=ClusterType.objects.all(), required=False, label="Provider Type"
+    )
+    class Meta:
+        model = ClusterType
+        fields = ('provider_type',)
+
 class VirtualMachineForm(NetBoxModelForm):
     """Form for Virtual Machines."""
     class Meta:
@@ -63,6 +81,14 @@ IPAddressFormSet = forms.modelformset_factory(
     IPAddress, form=IPAddressForm, formset=IPAddressForm, extra=1, can_delete=False
 )
 
+ClusterTypeListFormSet = forms.modelformset_factory(
+    ClusterType, form=ClusterTypeFormList, formset=ClusterTypeFormList, extra=1, can_delete=False
+)
+
+ClusterListFormSet = forms.modelformset_factory(
+    Cluster, form=ClusterFormList, formset=ClusterFormList, extra=1, can_delete=False
+)
+
 
 # Combined form
 class CreateVmForm(NetBoxModelForm):
@@ -77,27 +103,27 @@ class CreateVmForm(NetBoxModelForm):
         super().__init__(*args, **kwargs)
 
         # Initialize formsets
-        #Cluster + Cluster type
+        # Cluster + Cluster type
         self.cluster_formsets = []
         empty_cluster_formset = ClusterFormSet(data=data, prefix='clusters')
         self.cluster_formsets.append(('new', empty_cluster_formset))
-        #Virtual Machine 
+        # Virtual Machine 
         self.virtual_machine_formsets = []
         empty_vm_formset = VirtualMachineFormSet(data=data, prefix='vms_new')
         self.virtual_machine_formsets.append(('new', empty_vm_formset))
-        #Virtual Machine Interfaces
+        # Virtual Machine Interfaces
         self.virtual_machine_interface_formsets = []
         empty_vmi_formset = VirtualMachineInterfaceFormSet(data=data, prefix='vmis_new')
         self.virtual_machine_interface_formsets.append(('new', empty_vmi_formset))
-        #Service
+        # Service
         self.service_ntp_formsets = []
         empty_ntp_service_formset = ServiceNtpFormSet(data=data, prefix='service_ntp')
         self.service_ntp_formsets.append(('new', empty_ntp_service_formset))
-        #Service
+        # Service
         self.service_dns_formsets = []
         empty_dns_service_formset = ServiceDnsFormSet(data=data, prefix='service_dns')
         self.service_dns_formsets.append(('new', empty_dns_service_formset))
-        #Service
+        # Service
         self.service_syslog_formsets = []
         empty_syslog_service_formset = ServiceSyslogFormSet(data=data, prefix='service_syslog')
         self.service_syslog_formsets.append(('new', empty_syslog_service_formset))
@@ -105,4 +131,14 @@ class CreateVmForm(NetBoxModelForm):
         self.ip_formsets = []
         empty_ip_formset = IPAddressFormSet(data=data, prefix='ip_new')
         self.ip_formsets.append(('new', empty_ip_formset))
+        # List Cluster Type
+        self.clt_list_formsets = []
+        empty_clt_list_formset = ClusterTypeListFormSet(data=data, prefix='clt_list_new')
+        self.clt_list_formsets.append(('new', empty_clt_list_formset))
+        # List Cluster
+        self.cl_list_formsets = []
+        empty_cl_list_formset = ClusterListFormSet(data=data, prefix='cl_list_new')
+        self.cl_list_formsets.append(('new', empty_cl_list_formset))
+
+
 
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
index 88836d0..d482803 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
@@ -25,27 +25,27 @@ SYS - Virtual Machine
     <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab">
 {% block content %}
 <form method="post" enctype="multipart/form-data" class="form-object-edit mt-5">
+    {% csrf_token %}
     <div class="field-group my-5">
         <div class="row mb-2">
         </div>
         <div class="row mb-2">
             <ul class="nav nav-pills" role="tablist">
               <li role="presentation" class="nav-item">
-                <button role="tab" type="button" id="device_tab" data-bs-toggle="tab" aria-controls="device" data-bs-target="#device" class="nav-link {% if not form.initial.vminterface and not form.initial.fhrpgroup %}active{% endif %}">
+                <button role="tab" type="button" id="device_tab" data-bs-toggle="tab" aria-controls="device" data-bs-target="#device" class="nav-link active">
                 New Provider
                 </button>
               </li>
               <li role="presentation" class="nav-item">
-                <button role="tab" type="button" id="vm_tab" data-bs-toggle="tab" aria-controls="vm" data-bs-target="#vm" class="nav-link {% if form.initial.vminterface %}active{% endif %}">
+                <button role="tab" type="button" id="vm_tab" data-bs-toggle="tab" aria-controls="vm" data-bs-target="#vm" class="nav-link">
                   Existing Provider
                 </button>
               </li>
             </ul>
         </div>
         <div class="tab-content p-0 border-0">
-          <div class="tab-pane" id="device" role="tabpanel" aria-labeled-by="device_tab">
+          <div class="tab-pane active" id="device" role="tabpanel" aria-labeled-by="device_tab">
             <h5>Provider Type</h5>
-            {% csrf_token %}
             {{ form.as_p }}
             <h5>Provider Credentials</h5>
             {% for cluster_formset in form.cluster_formsets %}
@@ -57,7 +57,21 @@ SYS - Virtual Machine
             {% endfor %}
           </div>
           <div class="tab-pane" id="vm" role="tabpanel" aria-labeled-by="vm_tab">
-            <p>Tab2</p>
+            <h5>Provider</h5>
+            {% for clt_list_formset in form.clt_list_formsets %}
+            {% for ctl_form in clt_list_formset %}
+            <div class="form-group" class="field-group mb-5">
+                {{ ctl_form.as_p }}
+            </div>
+            {% endfor %}
+            {% endfor %}
+            {% for cl_list_formset in form.cl_list_formsets %}
+            {% for ct_form in cl_list_formset %}
+            <div class="form-group" class="field-group mb-5">
+                {{ ct_form.as_p }}
+            </div>
+            {% endfor %}
+            {% endfor %}
           </div>
         </div>
       </div>
-- 
GitLab


From 412e87f31e156e2ccd0cca5c3a78cf26e7363522 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Thu, 28 Nov 2024 16:05:56 +0000
Subject: [PATCH 14/28] =?UTF-8?q?=F0=9F=94=A5=20Remove=20unnecessary=20cod?=
 =?UTF-8?q?e?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py            | 18 ++----------------
 .../templates/netbox_sys_plugin/create_vm.html |  7 -------
 2 files changed, 2 insertions(+), 23 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 68c2e0b..6fc14d6 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -16,18 +16,12 @@ class ClusterFormList(NetBoxModelForm):
     provider = DynamicModelChoiceField(
         queryset=Cluster.objects.all(), required=False, label="Provider"
     )
-    class Meta:
-        model = Cluster
-        fields = ('provider',)
-
-class ClusterTypeFormList(NetBoxModelForm):
-    """Form for Cluster Types List."""
     provider_type = DynamicModelChoiceField(
         queryset=ClusterType.objects.all(), required=False, label="Provider Type"
     )
     class Meta:
-        model = ClusterType
-        fields = ('provider_type',)
+        model = Cluster
+        fields = ('provider_type','provider')
 
 class VirtualMachineForm(NetBoxModelForm):
     """Form for Virtual Machines."""
@@ -81,10 +75,6 @@ IPAddressFormSet = forms.modelformset_factory(
     IPAddress, form=IPAddressForm, formset=IPAddressForm, extra=1, can_delete=False
 )
 
-ClusterTypeListFormSet = forms.modelformset_factory(
-    ClusterType, form=ClusterTypeFormList, formset=ClusterTypeFormList, extra=1, can_delete=False
-)
-
 ClusterListFormSet = forms.modelformset_factory(
     Cluster, form=ClusterFormList, formset=ClusterFormList, extra=1, can_delete=False
 )
@@ -131,10 +121,6 @@ class CreateVmForm(NetBoxModelForm):
         self.ip_formsets = []
         empty_ip_formset = IPAddressFormSet(data=data, prefix='ip_new')
         self.ip_formsets.append(('new', empty_ip_formset))
-        # List Cluster Type
-        self.clt_list_formsets = []
-        empty_clt_list_formset = ClusterTypeListFormSet(data=data, prefix='clt_list_new')
-        self.clt_list_formsets.append(('new', empty_clt_list_formset))
         # List Cluster
         self.cl_list_formsets = []
         empty_cl_list_formset = ClusterListFormSet(data=data, prefix='cl_list_new')
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
index d482803..a990f45 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
@@ -58,13 +58,6 @@ SYS - Virtual Machine
           </div>
           <div class="tab-pane" id="vm" role="tabpanel" aria-labeled-by="vm_tab">
             <h5>Provider</h5>
-            {% for clt_list_formset in form.clt_list_formsets %}
-            {% for ctl_form in clt_list_formset %}
-            <div class="form-group" class="field-group mb-5">
-                {{ ctl_form.as_p }}
-            </div>
-            {% endfor %}
-            {% endfor %}
             {% for cl_list_formset in form.cl_list_formsets %}
             {% for ct_form in cl_list_formset %}
             <div class="form-group" class="field-group mb-5">
-- 
GitLab


From b42fc4ade4c758cef5c73ac28ad22e36d92a7918 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Thu, 28 Nov 2024 18:30:52 +0000
Subject: [PATCH 15/28] =?UTF-8?q?=F0=9F=9A=A7=20Validation=20between=20new?=
 =?UTF-8?q?=20and=20existing=20clusters?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py           | 45 +++++++++++++-
 .../netbox_sys_plugin/create_vm.html          |  1 -
 netbox_sys_plugin/views.py                    | 62 ++++++++++++++++---
 3 files changed, 96 insertions(+), 12 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 6fc14d6..6756d88 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -7,46 +7,76 @@ from utilities.forms.fields import DynamicModelChoiceField
 
 class ClusterForm(NetBoxModelForm):
     """Form for Clusters."""
+
+    name = forms.CharField(
+        max_length=50, min_length=1, required=False, label="Name"
+    )
+
     class Meta:
         model = Cluster
         fields = ('name', 'type', 'description','status','site','tags')
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields.pop('tags',None)
+
 class ClusterFormList(NetBoxModelForm):
     """Form for Cluster List."""
-    provider = DynamicModelChoiceField(
+    cluster = DynamicModelChoiceField(
         queryset=Cluster.objects.all(), required=False, label="Provider"
     )
-    provider_type = DynamicModelChoiceField(
+    cluster_type = DynamicModelChoiceField(
         queryset=ClusterType.objects.all(), required=False, label="Provider Type"
     )
     class Meta:
         model = Cluster
-        fields = ('provider_type','provider')
+        fields = ('cluster_type','cluster')
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields.pop('tags',None)
 
 class VirtualMachineForm(NetBoxModelForm):
     """Form for Virtual Machines."""
+
     class Meta:
         model = VirtualMachine
         fields = ('name', 'cluster', 'status', 'role','platform', 'description','tags')
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields.pop('tags',None)
+
 class VirtualMachineInterfaceForm(NetBoxModelForm):
     """Form for Virtual Machine Interfaces."""
     class Meta:
         model = VMInterface
         fields = ('name','tags')
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields.pop('tags',None)
+
 class ServiceForm(NetBoxModelForm):
     """Form for Virtual Machine Interfaces."""
     class Meta:
         model = Service
         fields = ('name','protocol','ports','tags')
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields.pop('tags',None)
+
 class IPAddressForm(NetBoxModelForm):
     """Form for IP Addresses."""
     class Meta:
         model = IPAddress
         fields = ('address', 'status', 'role', 'tags')
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields.pop('tags',None)
+
 
 # Inline formsets for managing relationships
 ClusterFormSet = inlineformset_factory(
@@ -84,6 +114,14 @@ ClusterListFormSet = forms.modelformset_factory(
 class CreateVmForm(NetBoxModelForm):
     """Combined form for managing ClusterType, Cluster, and VirtualMachine."""
 
+    name = forms.CharField(
+        max_length=50, min_length=1, required=False, label="Name"
+    )
+
+    slug = forms.CharField(
+        max_length=100, min_length=1, required=False, label="Slug"
+    )
+
     class Meta:
         model = ClusterType
         fields = ('name', 'slug','description','tags')
@@ -91,6 +129,7 @@ class CreateVmForm(NetBoxModelForm):
     def __init__(self, *args, **kwargs):
         data = kwargs.pop('data', None)
         super().__init__(*args, **kwargs)
+        self.fields.pop('tags',None)
 
         # Initialize formsets
         # Cluster + Cluster type
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
index a990f45..e39703f 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
@@ -1,7 +1,6 @@
 {% extends 'base/layout.html' %}
 {% load buttons %}
 {% load custom_links %}
-{% load helpers %}
 {% load perms %}
 {% load plugins %}
 {% load tabs %}
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index de10bb9..a0e3a14 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -129,6 +129,33 @@ class VmTypeEditView(generic.ObjectEditView):
 #Create VM
 
 # Helper functions for object creation
+def check_cluster_existence(data):
+
+    # Get Value from request
+    new_clustertype=data.get('name', ''),
+    new_cluster=data.get('clusters-0-name', ''),
+    existing_cluster_type=data.get('cl_list_new-cluster_type', ''),
+    existing_cluster=data.get('cl_list_new-cluster', ''),
+
+    # Clean values
+    new_clustertype=new_clustertype[0].strip()
+    new_cluster=new_cluster[0].strip()
+    existing_cluster_type=existing_cluster_type[0].strip()
+    existing_cluster=existing_cluster[0].strip()
+
+    # Check assignment
+    # Validation for both cases selected
+    if (new_clustertype and new_cluster and existing_cluster_type and existing_cluster):        
+        raise ValueError(f"Cant create a provider and choose a existing one at the same time")
+    
+    # Choose outcome
+    if ((not new_clustertype and not new_clustertype) and (existing_cluster_type and existing_cluster)):
+        result=True
+    else:
+        result=False
+
+    return result
+    
 def parse_ports(ports_field):
     """Parse the value for ports"""
     try:
@@ -136,6 +163,16 @@ def parse_ports(ports_field):
         return ports
     except Exception as e:
         raise ValueError(f"Invalid ports value:{ports_field}. Error: {str(e)}")
+    
+def get_cluster(data):
+    cluster_id = data.get('cl_list_new-cluster', '')
+    print("cluster id: ",cluster_id)
+    try:
+        cluster=Cluster.objects.get(pk=cluster_id)
+        print("cluster obj: ",cluster)
+    except Cluster.DoesNotExist:
+        raise ValueError(f"Invalid Platform ID: {cluster_id}")
+    return cluster
 
 
 def create_cluster_type(data):
@@ -144,7 +181,6 @@ def create_cluster_type(data):
         name=data.get('name', ''),
         slug=data.get('slug', ''),
         description=data.get('description', '')
-        #tag=tag,
     )
     cluster_type.full_clean()  # Validate the data
     cluster_type.save()  # Save to the database
@@ -302,13 +338,23 @@ class CreateVmView(generic.ObjectEditView):
             try:
                 # Use a database transaction to ensure all-or-nothing behavior
                 with transaction.atomic():
-                    # Create the ClusterType
-                    cluster_type = create_cluster_type(request.POST)
-                    print(f"ClusterType saved with ID: {cluster_type.pk}")
-
-                    # Create the Cluster
-                    cluster = create_cluster(request.POST, cluster_type)
-                    print(f"Cluster saved with ID: {cluster.pk}")
+                    
+                    if check_cluster_existence(request.POST) == False:
+                        print('HERE NEW CLUSTER TYPE')
+                        # Create the ClusterType
+                        cluster_type = create_cluster_type(request.POST)
+                        print(f"ClusterType saved with ID: {cluster_type.pk}")
+                        # Create the Cluster
+                        print('HERE NEW CLUSTER')
+                        cluster = create_cluster(request.POST, cluster_type)
+                        print(f"Cluster saved with ID: {cluster.pk}")
+                        print("cluster new: ",cluster)
+                    else:
+                        # Get existint Cluster
+                        print('HERE EXISTING CLUSTER')
+                        cluster = get_cluster(request.POST)
+                        print("cluster existing: ",cluster)
+                        
 
                     # Create the Virtual Machine
                     vm = create_virtual_machine(request.POST, cluster)
-- 
GitLab


From 7d882c8d6092edb05fa1feb7ad5f13f16973417c Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Thu, 28 Nov 2024 20:02:18 +0000
Subject: [PATCH 16/28] =?UTF-8?q?=F0=9F=9A=A7=20Fixed=20create=20vm=20for?=
 =?UTF-8?q?=20new=20and=20existing=20cluster?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py | 11 ++++----
 netbox_sys_plugin/navigation.py     |  6 ----
 netbox_sys_plugin/views.py          | 43 ++++++++++++++++-------------
 3 files changed, 30 insertions(+), 30 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 6756d88..da25cb4 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -14,7 +14,7 @@ class ClusterForm(NetBoxModelForm):
 
     class Meta:
         model = Cluster
-        fields = ('name', 'type', 'description','status','site','tags')
+        fields = ('name', 'type', 'description','status','tags')
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -22,12 +22,13 @@ class ClusterForm(NetBoxModelForm):
 
 class ClusterFormList(NetBoxModelForm):
     """Form for Cluster List."""
-    cluster = DynamicModelChoiceField(
-        queryset=Cluster.objects.all(), required=False, label="Provider"
-    )
     cluster_type = DynamicModelChoiceField(
         queryset=ClusterType.objects.all(), required=False, label="Provider Type"
     )
+    cluster = DynamicModelChoiceField(
+        queryset=Cluster.objects.all(), required=False, label="Provider",
+        query_params={'type': '$cluster_type',},
+    )
     class Meta:
         model = Cluster
         fields = ('cluster_type','cluster')
@@ -41,7 +42,7 @@ class VirtualMachineForm(NetBoxModelForm):
 
     class Meta:
         model = VirtualMachine
-        fields = ('name', 'cluster', 'status', 'role','platform', 'description','tags')
+        fields = ('name', 'cluster', 'status','site','role','platform', 'description','tags')
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py
index a418b4a..2391977 100644
--- a/netbox_sys_plugin/navigation.py
+++ b/netbox_sys_plugin/navigation.py
@@ -86,16 +86,11 @@ operItem =[
         link_text="Virtual Machine Types",
         buttons=vm_machine_type_buttons
     ),
-]
-
-
-createVMItem =[
     PluginMenuItem(
         link="plugins:netbox_sys_plugin:creatvm_add",
         link_text="Create Virtual Machine ",
         buttons=create_vm_buttons
     ),
-
 ]
 #Menu
 menu = PluginMenu(
@@ -104,7 +99,6 @@ menu = PluginMenu(
         ("Virtual Machine", (vmMachineItem)),
         ("Provider", clusterProviderCredentialsItem),
         ("Operation", (operItem)),
-        ("SYS", (createVMItem)),
             ),
     icon_class="mdi mdi-all-inclusive-box-outline",
 )
\ No newline at end of file
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index a0e3a14..6cce239 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -129,7 +129,7 @@ class VmTypeEditView(generic.ObjectEditView):
 #Create VM
 
 # Helper functions for object creation
-def check_cluster_existence(data):
+def check_cluster_exist(data):
 
     # Get Value from request
     new_clustertype=data.get('name', ''),
@@ -166,12 +166,10 @@ def parse_ports(ports_field):
     
 def get_cluster(data):
     cluster_id = data.get('cl_list_new-cluster', '')
-    print("cluster id: ",cluster_id)
     try:
         cluster=Cluster.objects.get(pk=cluster_id)
-        print("cluster obj: ",cluster)
     except Cluster.DoesNotExist:
-        raise ValueError(f"Invalid Platform ID: {cluster_id}")
+        raise ValueError(f"Invalid Cluster ID: {cluster_id}")
     return cluster
 
 
@@ -189,7 +187,7 @@ def create_cluster_type(data):
 
 def create_cluster(data, cluster_type):
     """Creates and saves a Cluster object linked to a ClusterType."""
-    site_id = data.get('clusters-0-site', '')
+    site_id = data.get('vms_new-0-role', '')
     try:
         site=Site.objects.get(pk=site_id)
     except Site.DoesNotExist:
@@ -207,20 +205,30 @@ def create_cluster(data, cluster_type):
     return cluster
 
 
-def create_virtual_machine(data, cluster):
+def create_virtual_machine(data, cluster, cluster_existence):
     """Creates and saves a VirtualMachine object linked to a Cluster."""
     role_id = data.get('vms_new-0-role', '')
     try:
         role=DeviceRole.objects.get(pk=role_id)
     except DeviceRole.DoesNotExist:
         raise ValueError(f"Invalid Device Role ID: {role_id}")
-    
-    site_id = data.get('clusters-0-site', '')
-    try:
-        site=Site.objects.get(pk=site_id)
-    except Site.DoesNotExist:
-        raise ValueError(f"Invalid Site ID: {site_id}")
-    
+    #Site logic for existing clusters
+    if cluster_existence == False:
+        site_id = data.get('vms_new-0-site', '')
+        try:
+            site=Site.objects.get(pk=site_id)
+        except Site.DoesNotExist:
+            raise ValueError(f"Invalid Site ID: {site_id}")
+        
+    else:
+        cluster_id = data.get('cl_list_new-cluster', '')
+        cluster=Cluster.objects.get(pk=cluster_id)
+        site_id = cluster.site_id
+        try:
+            site=Site.objects.get(pk=site_id)
+        except Site.DoesNotExist:
+            raise ValueError(f"Invalid Site ID: {site_id}")
+
     platform_id = data.get('vms_new-0-platform', '')
     try:
         platform=Platform.objects.get(pk=platform_id)
@@ -338,26 +346,23 @@ class CreateVmView(generic.ObjectEditView):
             try:
                 # Use a database transaction to ensure all-or-nothing behavior
                 with transaction.atomic():
+                    cl_exist = check_cluster_exist(request.POST)
                     
-                    if check_cluster_existence(request.POST) == False:
+                    if cl_exist == False:
                         print('HERE NEW CLUSTER TYPE')
                         # Create the ClusterType
                         cluster_type = create_cluster_type(request.POST)
                         print(f"ClusterType saved with ID: {cluster_type.pk}")
                         # Create the Cluster
-                        print('HERE NEW CLUSTER')
                         cluster = create_cluster(request.POST, cluster_type)
                         print(f"Cluster saved with ID: {cluster.pk}")
-                        print("cluster new: ",cluster)
                     else:
                         # Get existint Cluster
-                        print('HERE EXISTING CLUSTER')
                         cluster = get_cluster(request.POST)
-                        print("cluster existing: ",cluster)
                         
 
                     # Create the Virtual Machine
-                    vm = create_virtual_machine(request.POST, cluster)
+                    vm = create_virtual_machine(request.POST, cluster,cl_exist)
                     print(f"Virtual Machine saved with ID: {vm.pk}")
 
                     #Create the Vm Interface
-- 
GitLab


From e319587ca5f181d1d1907ec93eea960d0ffdeb65 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Fri, 29 Nov 2024 09:52:09 +0000
Subject: [PATCH 17/28] =?UTF-8?q?=F0=9F=8E=A8=20Improve=20template=20struc?=
 =?UTF-8?q?ture?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py           |  7 ++++
 .../netbox_sys_plugin/create_vm.html          | 41 +++++++++----------
 2 files changed, 26 insertions(+), 22 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index da25cb4..4ab1da3 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -25,10 +25,12 @@ class ClusterFormList(NetBoxModelForm):
     cluster_type = DynamicModelChoiceField(
         queryset=ClusterType.objects.all(), required=False, label="Provider Type"
     )
+
     cluster = DynamicModelChoiceField(
         queryset=Cluster.objects.all(), required=False, label="Provider",
         query_params={'type': '$cluster_type',},
     )
+    
     class Meta:
         model = Cluster
         fields = ('cluster_type','cluster')
@@ -50,6 +52,11 @@ class VirtualMachineForm(NetBoxModelForm):
 
 class VirtualMachineInterfaceForm(NetBoxModelForm):
     """Form for Virtual Machine Interfaces."""
+
+    name = forms.CharField(
+        max_length=50, min_length=1, required=True, label="Interface Name"
+    )
+
     class Meta:
         model = VMInterface
         fields = ('name','tags')
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
index e39703f..120b947 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
@@ -26,8 +26,6 @@ SYS - Virtual Machine
 <form method="post" enctype="multipart/form-data" class="form-object-edit mt-5">
     {% csrf_token %}
     <div class="field-group my-5">
-        <div class="row mb-2">
-        </div>
         <div class="row mb-2">
             <ul class="nav nav-pills" role="tablist">
               <li role="presentation" class="nav-item">
@@ -45,8 +43,10 @@ SYS - Virtual Machine
         <div class="tab-content p-0 border-0">
           <div class="tab-pane active" id="device" role="tabpanel" aria-labeled-by="device_tab">
             <h5>Provider Type</h5>
+            <div class="form-group" class="field-group mb-5">
             {{ form.as_p }}
-            <h5>Provider Credentials</h5>
+          </div>
+            <h5>Provider</h5>
             {% for cluster_formset in form.cluster_formsets %}
             {% for vl_form in cluster_formset %}
             <div class="form-group" class="field-group mb-5">
@@ -67,15 +67,17 @@ SYS - Virtual Machine
           </div>
         </div>
       </div>
-    
+      <div class="field-group my-5">
     <h5>Virtual Machine</h5>
     {% for vm_formset in form.virtual_machine_formsets %}
         {% for vm_form in vm_formset %}
-            <div class="form-group" class="field-group mb-5">
+        <div class="form-group" class="field-group mb-5">
                 {{ vm_form.as_p }}
-            </div>
+              </div>
         {% endfor %}
     {% endfor %}
+    </div>
+    <div class="field-group my-5">
     <h5>Network Name</h5>
     {% for vmi_formset in form.virtual_machine_interface_formsets %}
         {% for vmi_form in vmi_formset %}
@@ -84,11 +86,19 @@ SYS - Virtual Machine
             </div>
         {% endfor %}
     {% endfor %}
+    <h5>IP Address</h5>
+    {% for ip_formset in form.ip_formsets %}
+        {% for ip_form in ip_formset %}
+        <div class="form-group" class="field-group mb-5">           
+                {{ ip_form.as_p }}
+            </div>
+        {% endfor %}
+    {% endfor %}
+          </div>
+    <div class="field-group my-5">
     <h5>Dependencies</h5>
-    <div class="row">
     <h6>NTP Server</h6>
     {% for service_ntp_formset in form.service_ntp_formsets %}
-    <div class="form-group" class="field-group mb-5">
         {% for service_form in service_ntp_formset %}
         <div class="form-group" class="field-group mb-5">           
                 {{ service_form.as_p }}
@@ -97,7 +107,6 @@ SYS - Virtual Machine
     {% endfor %}
     <h6>DNS Server</h6>
     {% for service_dns_formset in form.service_dns_formsets %}
-    <div class="form-group" class="field-group mb-5">
         {% for service_form in service_dns_formset %}
         <div class="form-group" class="field-group mb-5">           
                 {{ service_form.as_p }}
@@ -106,30 +115,18 @@ SYS - Virtual Machine
     {% endfor %}
     <h6>SYSLOG Server</h6>
     {% for service_syslog_formset in form.service_syslog_formsets %}
-    <div class="form-group" class="field-group mb-5">
         {% for service_form in service_syslog_formset %}
         <div class="form-group" class="field-group mb-5">           
                 {{ service_form.as_p }}
             </div>
         {% endfor %}
     {% endfor %}
-    
-    <h6>IP Address</h6>
-    {% for ip_formset in form.ip_formsets %}
-    <div class="form-group" class="field-group mb-5">
-        {% for ip_form in ip_formset %}
-        <div class="form-group" class="field-group mb-5">           
-                {{ ip_form.as_p }}
-            </div>
-        {% endfor %}
-    {% endfor %}
-
     </div>
+  </div>
     <div class="text-end my-3">
         {% block buttons %}
     <button type="submit" class="btn btn-primary">Save</button>
     {% endblock buttons %}
-</div>
 </form>
 </div>
 </div>
-- 
GitLab


From e96fde511e14bb7510ce164b042a1cd25c6d2ff0 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Fri, 29 Nov 2024 16:54:11 +0000
Subject: [PATCH 18/28] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20views=20a?=
 =?UTF-8?q?nd=20forms=20code?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py           | 170 ++++++++++-
 .../netbox_sys_plugin/create_vm.html          | 196 ++++++------
 netbox_sys_plugin/views.py                    | 286 ++----------------
 3 files changed, 275 insertions(+), 377 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 4ab1da3..e8c94bf 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -1,9 +1,14 @@
 from django import forms
-from django.forms import inlineformset_factory, BaseModelFormSet
+from django.forms import inlineformset_factory
 from netbox.forms import NetBoxModelForm
+
 from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterface
 from ipam.models import IPAddress, Service
+from dcim.models import DeviceRole,Site, Platform
 from utilities.forms.fields import DynamicModelChoiceField
+from django.db import transaction
+
+
 
 class ClusterForm(NetBoxModelForm):
     """Form for Clusters."""
@@ -122,22 +127,17 @@ ClusterListFormSet = forms.modelformset_factory(
 class CreateVmForm(NetBoxModelForm):
     """Combined form for managing ClusterType, Cluster, and VirtualMachine."""
 
-    name = forms.CharField(
-        max_length=50, min_length=1, required=False, label="Name"
-    )
-
-    slug = forms.CharField(
-        max_length=100, min_length=1, required=False, label="Slug"
-    )
+    name = forms.CharField(max_length=50, min_length=1, required=False, label="Name")
+    slug = forms.CharField(max_length=100, min_length=1, required=False, label="Slug")
 
     class Meta:
         model = ClusterType
-        fields = ('name', 'slug','description','tags')
+        fields = ('name', 'slug', 'description', 'tags')
 
     def __init__(self, *args, **kwargs):
         data = kwargs.pop('data', None)
         super().__init__(*args, **kwargs)
-        self.fields.pop('tags',None)
+        self.fields.pop('tags', None)
 
         # Initialize formsets
         # Cluster + Cluster type
@@ -173,5 +173,151 @@ class CreateVmForm(NetBoxModelForm):
         empty_cl_list_formset = ClusterListFormSet(data=data, prefix='cl_list_new')
         self.cl_list_formsets.append(('new', empty_cl_list_formset))
 
-
-
+    @staticmethod
+    def check_cluster_exist(data):
+        """Validate whether a cluster already exists based on the input."""
+        new_clustertype = data.get('name', '').strip()
+        new_cluster = data.get('clusters-0-name', '').strip()
+        existing_cluster_type = data.get('cl_list_new-cluster_type', '').strip()
+        existing_cluster = data.get('cl_list_new-cluster', '').strip()
+
+        if new_clustertype and new_cluster and existing_cluster_type and existing_cluster:
+            raise ValueError("Cannot create a provider and choose an existing one at the same time.")
+
+        return bool(existing_cluster_type and existing_cluster)
+
+    @staticmethod
+    def parse_ports(ports_field):
+        """Parse the port numbers."""
+        try:
+            ports = [int(port.strip()) for port in ports_field.split(',') if port.strip().isdigit()]
+            return ports
+        except ValueError:
+            raise ValueError(f"Invalid ports value: {ports_field}")
+
+    @staticmethod
+    def create_cluster_type(data):
+        """Create and save a ClusterType object."""
+        cluster_type = ClusterType(
+            name=data.get('name', ''),
+            slug=data.get('slug', ''),
+            description=data.get('description', '')
+        )
+        cluster_type.full_clean()
+        cluster_type.save()
+        return cluster_type
+
+    @staticmethod
+    def create_cluster(data, cluster_type):
+        """Create and save a Cluster object."""
+        site_id = data.get('vms_new-0-role', '')
+        try:
+            site = Site.objects.get(pk=site_id)
+        except Site.DoesNotExist:
+            raise ValueError(f"Invalid Site ID: {site_id}")
+
+        cluster = Cluster(
+            name=data.get('clusters-0-name', ''),
+            type=cluster_type,
+            status=data.get('clusters-0-status', ''),
+            site=site,
+            description=data.get('clusters-0-description', '')
+        )
+        cluster.full_clean()
+        cluster.save()
+        return cluster
+
+    @staticmethod
+    def create_virtual_machine(data, cluster, cluster_existence):
+        """Create and save a VirtualMachine object."""
+        role_id = data.get('vms_new-0-role', '')
+        try:
+            role = DeviceRole.objects.get(pk=role_id)
+        except DeviceRole.DoesNotExist:
+            raise ValueError(f"Invalid Device Role ID: {role_id}")
+
+        platform_id = data.get('vms_new-0-platform', '')
+        try:
+            platform = Platform.objects.get(pk=platform_id)
+        except Platform.DoesNotExist:
+            raise ValueError(f"Invalid Platform ID: {platform_id}")
+
+        vm = VirtualMachine(
+            name=data.get('vms_new-0-name', ''),
+            status=data.get('vms_new-0-status', ''),
+            role=role,
+            site=cluster.site,
+            platform=platform,
+            description=data.get('vms_new-0-description', ''),
+            cluster=cluster
+        )
+        vm.full_clean()
+        vm.save()
+        return vm
+
+    @staticmethod
+    def create_vm_interface(data, vm):
+        """Create and save a VMInterface object."""
+        vmi = VMInterface(
+            name=data.get('vmis_new-0-name', ''),
+            virtual_machine=vm
+        )
+        vmi.full_clean()
+        vmi.save()
+        return vmi
+    
+    @staticmethod
+    def create_service(data, vm, prefix, description):
+        """Create and save a Service object."""
+        ports_field = data.get(f'{prefix}-0-ports', '')
+        ports = CreateVmForm.parse_ports(ports_field)
+
+        service = Service(
+            name=data.get(f'{prefix}-0-name', ''),
+            protocol=data.get(f'{prefix}-0-protocol', ''),
+            ports=ports,
+            description=description,
+            virtual_machine=vm
+        )
+        service.full_clean()
+        service.save()
+        return service
+
+    def create_all_services(self, data, vm):
+        """Create NTP, DNS, and SYSLOG services."""
+        self.create_service(data, vm, 'service_ntp', 'NTP Server')
+        self.create_service(data, vm, 'service_dns', 'DNS Server')
+        self.create_service(data, vm, 'service_syslog', 'SYSLOG Server')
+
+    @staticmethod
+    def create_ip_address(data, vm_interface):
+        """Create and save an IPAddress object."""
+        ip_address = IPAddress(
+            address=data.get('ip_new-address', ''),
+            status=data.get('ip_new-status', ''),
+            role=data.get('ip_new-role', ''),
+            assigned_object=vm_interface
+        )
+        ip_address.full_clean()
+        ip_address.save()
+        return ip_address
+
+    def process_creation(self, data):
+        """Process creation of ClusterType, Cluster, VM, and related objects."""
+        try:
+            with transaction.atomic():
+                cluster_exists = self.check_cluster_exist(data)
+                if not cluster_exists:
+                    cluster_type = self.create_cluster_type(data)
+                    cluster = self.create_cluster(data, cluster_type)
+                else:
+                    cluster = Cluster.objects.get(pk=data.get('cl_list_new-cluster', ''))
+
+                vm = self.create_virtual_machine(data, cluster, cluster_exists)
+                vmi = self.create_vm_interface(data, vm)
+                self.create_all_services(data, vm)
+                self.create_ip_address(data, vmi)
+                return vm
+
+        except ValueError as e:
+            raise ValueError(f"Error during creation: {str(e)}")
\ No newline at end of file
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
index 120b947..d267c46 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
@@ -12,25 +12,25 @@
 SYS - Virtual Machine
 {% endblock title %}
 {% block tabs %}
-  <ul class="nav nav-tabs px-3">
-    <li class="nav-item" role="presentation">
-      <button class="nav-link active" id="edit-form-tab" data-bs-toggle="tab" data-bs-target="#edit-form" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
-        Create      </button>
+<ul class="nav nav-tabs px-3">
+  <li class="nav-item" role="presentation">
+    <button class="nav-link active" id="edit-form-tab" data-bs-toggle="tab" data-bs-target="#edit-form" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
+      Create      </button>
     </li>
   </ul>
-{% endblock tabs %}
-{% block content-wrapper %}
-<div class="tab-content">
+  {% endblock tabs %}
+  {% block content-wrapper %}
+  <div class="tab-content">
     <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab">
-{% block content %}
-<form method="post" enctype="multipart/form-data" class="form-object-edit mt-5">
-    {% csrf_token %}
-    <div class="field-group my-5">
-        <div class="row mb-2">
+      {% block content %}
+      <form method="post" enctype="multipart/form-data" class="form-object-edit mt-5">
+        {% csrf_token %}
+        <div class="field-group my-5">
+          <div class="row mb-2">
             <ul class="nav nav-pills" role="tablist">
               <li role="presentation" class="nav-item">
                 <button role="tab" type="button" id="device_tab" data-bs-toggle="tab" aria-controls="device" data-bs-target="#device" class="nav-link active">
-                New Provider
+                  New Provider
                 </button>
               </li>
               <li role="presentation" class="nav-item">
@@ -39,97 +39,99 @@ SYS - Virtual Machine
                 </button>
               </li>
             </ul>
-        </div>
-        <div class="tab-content p-0 border-0">
-          <div class="tab-pane active" id="device" role="tabpanel" aria-labeled-by="device_tab">
-            <h5>Provider Type</h5>
-            <div class="form-group" class="field-group mb-5">
-            {{ form.as_p }}
           </div>
-            <h5>Provider</h5>
-            {% for cluster_formset in form.cluster_formsets %}
-            {% for vl_form in cluster_formset %}
-            <div class="form-group" class="field-group mb-5">
+          <div class="tab-content p-0 border-0">
+            <div class="tab-pane active" id="device" role="tabpanel" aria-labeled-by="device_tab">
+              <h5>Provider Type</h5>
+              <div class="form-group" class="field-group mb-5">
+                {{ form.as_p }}
+              </div>
+              <h5>Provider</h5>
+              {% for cluster_formset in form.cluster_formsets %}
+              {% for vl_form in cluster_formset %}
+              <div class="form-group" class="field-group mb-5">
                 {{ vl_form.as_p }}
+              </div>
+              {% endfor %}
+              {% endfor %}
             </div>
-            {% endfor %}
-            {% endfor %}
-          </div>
-          <div class="tab-pane" id="vm" role="tabpanel" aria-labeled-by="vm_tab">
-            <h5>Provider</h5>
-            {% for cl_list_formset in form.cl_list_formsets %}
-            {% for ct_form in cl_list_formset %}
-            <div class="form-group" class="field-group mb-5">
+            <div class="tab-pane" id="vm" role="tabpanel" aria-labeled-by="vm_tab">
+              <h5>Provider</h5>
+              {% for cl_list_formset in form.cl_list_formsets %}
+              {% for ct_form in cl_list_formset %}
+              <div class="form-group" class="field-group mb-5">
                 {{ ct_form.as_p }}
+              </div>
+              {% endfor %}
+              {% endfor %}
             </div>
-            {% endfor %}
-            {% endfor %}
           </div>
         </div>
-      </div>
-      <div class="field-group my-5">
-    <h5>Virtual Machine</h5>
-    {% for vm_formset in form.virtual_machine_formsets %}
-        {% for vm_form in vm_formset %}
-        <div class="form-group" class="field-group mb-5">
-                {{ vm_form.as_p }}
-              </div>
-        {% endfor %}
-    {% endfor %}
-    </div>
-    <div class="field-group my-5">
-    <h5>Network Name</h5>
-    {% for vmi_formset in form.virtual_machine_interface_formsets %}
-        {% for vmi_form in vmi_formset %}
-            <div class="form-group" class="field-group mb-5">
-                {{ vmi_form.as_p }}
-            </div>
-        {% endfor %}
-    {% endfor %}
-    <h5>IP Address</h5>
-    {% for ip_formset in form.ip_formsets %}
-        {% for ip_form in ip_formset %}
-        <div class="form-group" class="field-group mb-5">           
-                {{ ip_form.as_p }}
-            </div>
-        {% endfor %}
-    {% endfor %}
+        <div class="field-group my-5">
+          <h5>Virtual Machine</h5>
+          {% for vm_formset in form.virtual_machine_formsets %}
+          {% for vm_form in vm_formset %}
+          <div class="form-group" class="field-group mb-5">
+            {{ vm_form.as_p }}
           </div>
-    <div class="field-group my-5">
-    <h5>Dependencies</h5>
-    <h6>NTP Server</h6>
-    {% for service_ntp_formset in form.service_ntp_formsets %}
-        {% for service_form in service_ntp_formset %}
-        <div class="form-group" class="field-group mb-5">           
-                {{ service_form.as_p }}
-            </div>
-        {% endfor %}
-    {% endfor %}
-    <h6>DNS Server</h6>
-    {% for service_dns_formset in form.service_dns_formsets %}
-        {% for service_form in service_dns_formset %}
-        <div class="form-group" class="field-group mb-5">           
-                {{ service_form.as_p }}
-            </div>
-        {% endfor %}
-    {% endfor %}
-    <h6>SYSLOG Server</h6>
-    {% for service_syslog_formset in form.service_syslog_formsets %}
-        {% for service_form in service_syslog_formset %}
-        <div class="form-group" class="field-group mb-5">           
-                {{ service_form.as_p }}
-            </div>
-        {% endfor %}
-    {% endfor %}
+          {% endfor %}
+          {% endfor %}
+        </div>
+        <div class="field-group my-5">
+          <h5>Network Name</h5>
+          {% for vmi_formset in form.virtual_machine_interface_formsets %}
+          {% for vmi_form in vmi_formset %}
+          <div class="form-group" class="field-group mb-5">
+            {{ vmi_form.as_p }}
+          </div>
+          {% endfor %}
+          {% endfor %}
+          <h5>IP Address</h5>
+          {% for ip_formset in form.ip_formsets %}
+          {% for ip_form in ip_formset %}
+          <div class="form-group" class="field-group mb-5">           
+            {{ ip_form.as_p }}
+          </div>
+          {% endfor %}
+          {% endfor %}
+        </div>
+        <div class="field-group my-5">
+          <h5>Dependencies</h5>
+          <h6>NTP Server</h6>
+          {% for service_ntp_formset in form.service_ntp_formsets %}
+          {% for service_form in service_ntp_formset %}
+          <div class="form-group" class="field-group mb-5">           
+            {{ service_form.as_p }}
+          </div>
+          {% endfor %}
+          {% endfor %}
+          <h6>DNS Server</h6>
+          {% for service_dns_formset in form.service_dns_formsets %}
+          {% for service_form in service_dns_formset %}
+          <div class="form-group" class="field-group mb-5">           
+            {{ service_form.as_p }}
+          </div>
+          {% endfor %}
+          {% endfor %}
+          <h6>SYSLOG Server</h6>
+          {% for service_syslog_formset in form.service_syslog_formsets %}
+          {% for service_form in service_syslog_formset %}
+          <div class="form-group" class="field-group mb-5">           
+            {{ service_form.as_p }}
+          </div>
+          {% endfor %}
+          {% endfor %}
+        </div>
+        
+        <div class="text-end my-3">
+          {% block buttons %}
+          <button type="submit" class="btn btn-primary">Save</button>
+          {% endblock buttons %}
+        </div>
+      </form>
     </div>
   </div>
-    <div class="text-end my-3">
-        {% block buttons %}
-    <button type="submit" class="btn btn-primary">Save</button>
-    {% endblock buttons %}
-</form>
-</div>
-</div>
-{% endblock %}
-{% endblock content-wrapper %}
-
+  {% endblock %}
+  {% endblock content-wrapper %}
+  
+  
\ No newline at end of file
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index 6cce239..fd8bbc9 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -3,12 +3,8 @@
 from django.shortcuts import render, redirect
 from netbox.views import generic
 from . import forms, models, tables, filtersets
-from virtualization.models import ClusterType, Cluster, VirtualMachine,VMInterface
-from dcim.models import DeviceRole,Site, Platform
-from ipam.models import Service, IPAddress
-from django.db import transaction
 from django.contrib import messages
-from taggit.models import Tag
+from django.urls import reverse
 
 
 
@@ -96,7 +92,6 @@ class VmAssignedVmTypeEditView(generic.ObjectEditView):
     queryset = models.VmAssignedVirtualMachineType.objects.all()
     form = forms.VmAssignedVmTypeForm
 
-
 #VmType
 class  VmTypeView(generic.ObjectView):
     """ Virtual Machine Type view definition"""
@@ -125,270 +120,25 @@ class VmTypeEditView(generic.ObjectEditView):
     queryset = models.VirtualMachineType.objects.all()
     form = forms.VmTypeForm
 
-
-#Create VM
-
-# Helper functions for object creation
-def check_cluster_exist(data):
-
-    # Get Value from request
-    new_clustertype=data.get('name', ''),
-    new_cluster=data.get('clusters-0-name', ''),
-    existing_cluster_type=data.get('cl_list_new-cluster_type', ''),
-    existing_cluster=data.get('cl_list_new-cluster', ''),
-
-    # Clean values
-    new_clustertype=new_clustertype[0].strip()
-    new_cluster=new_cluster[0].strip()
-    existing_cluster_type=existing_cluster_type[0].strip()
-    existing_cluster=existing_cluster[0].strip()
-
-    # Check assignment
-    # Validation for both cases selected
-    if (new_clustertype and new_cluster and existing_cluster_type and existing_cluster):        
-        raise ValueError(f"Cant create a provider and choose a existing one at the same time")
-    
-    # Choose outcome
-    if ((not new_clustertype and not new_clustertype) and (existing_cluster_type and existing_cluster)):
-        result=True
-    else:
-        result=False
-
-    return result
-    
-def parse_ports(ports_field):
-    """Parse the value for ports"""
-    try:
-        ports=[int(port.strip())for port in ports_field.split(',') if port.strip().isdigit()]
-        return ports
-    except Exception as e:
-        raise ValueError(f"Invalid ports value:{ports_field}. Error: {str(e)}")
-    
-def get_cluster(data):
-    cluster_id = data.get('cl_list_new-cluster', '')
-    try:
-        cluster=Cluster.objects.get(pk=cluster_id)
-    except Cluster.DoesNotExist:
-        raise ValueError(f"Invalid Cluster ID: {cluster_id}")
-    return cluster
-
-
-def create_cluster_type(data):
-    """Creates and saves a ClusterType object."""
-    cluster_type = ClusterType(
-        name=data.get('name', ''),
-        slug=data.get('slug', ''),
-        description=data.get('description', '')
-    )
-    cluster_type.full_clean()  # Validate the data
-    cluster_type.save()  # Save to the database
-    return cluster_type
-
-
-def create_cluster(data, cluster_type):
-    """Creates and saves a Cluster object linked to a ClusterType."""
-    site_id = data.get('vms_new-0-role', '')
-    try:
-        site=Site.objects.get(pk=site_id)
-    except Site.DoesNotExist:
-        raise ValueError(f"Invalid Site ID: {site_id}")
-
-    cluster = Cluster(
-        name=data.get('clusters-0-name', ''),
-        type=cluster_type,  # Link to the saved ClusterType
-        status=data.get('clusters-0-status', ''),
-        site=site,
-        description=data.get('clusters-0-description', '')
-    )
-    cluster.full_clean()  # Validate the data
-    cluster.save()  # Save to the database
-    return cluster
-
-
-def create_virtual_machine(data, cluster, cluster_existence):
-    """Creates and saves a VirtualMachine object linked to a Cluster."""
-    role_id = data.get('vms_new-0-role', '')
-    try:
-        role=DeviceRole.objects.get(pk=role_id)
-    except DeviceRole.DoesNotExist:
-        raise ValueError(f"Invalid Device Role ID: {role_id}")
-    #Site logic for existing clusters
-    if cluster_existence == False:
-        site_id = data.get('vms_new-0-site', '')
-        try:
-            site=Site.objects.get(pk=site_id)
-        except Site.DoesNotExist:
-            raise ValueError(f"Invalid Site ID: {site_id}")
-        
-    else:
-        cluster_id = data.get('cl_list_new-cluster', '')
-        cluster=Cluster.objects.get(pk=cluster_id)
-        site_id = cluster.site_id
-        try:
-            site=Site.objects.get(pk=site_id)
-        except Site.DoesNotExist:
-            raise ValueError(f"Invalid Site ID: {site_id}")
-
-    platform_id = data.get('vms_new-0-platform', '')
-    try:
-        platform=Platform.objects.get(pk=platform_id)
-    except Platform.DoesNotExist:
-        raise ValueError(f"Invalid Platform ID: {platform_id}")
-    
-    vm = VirtualMachine(
-        name=data.get('vms_new-0-name', ''),
-        status=data.get('vms_new-0-status', ''),
-        role=role,
-        site=site,
-        platform=platform,
-        description=data.get('vms_new-0-description', ''),
-        cluster=cluster  # Link to the saved Cluster
-    )
-    vm.full_clean()  # Validate the data
-    vm.save()  # Save to the database
-    return vm
-
-def create_vm_interface(data, vm):
-    """Creates and saves a Vm Interface object linked to a Virtual Machine."""
-    vmi = VMInterface(
-        name=data.get('vmis_new-0-name', ''),
-        virtual_machine=vm  # Link to the saved Virtual Machine
-    )
-    vmi.full_clean()  # Validate the data
-    vmi.save()  # Save to the database
-    return vmi
-
-def create_ntp_services(data, vm):
-    """Creates and saves a Vm Interface object linked to a Virtual Machine."""
-    ports_field=data.get('service_syslog-0-ports', '')
-    ports=parse_ports(ports_field)
-    ntp_s = Service(
-        name=data.get('service_ntp-0-name', ''),
-        protocol=data.get('service_ntp-0-protocol', ''),
-        ports=ports,
-        description='NTP Server',
-        virtual_machine=vm  # Link to the saved Virtual Machine
-
-    )
-    
-    ntp_s.full_clean()  # Validate the data
-    ntp_s.save()  # Save to the database
-    return ntp_s
-
-def create_dns_services(data, vm):
-    """Creates and saves a Vm Interface object linked to a Virtual Machine."""
-    ports_field=data.get('service_syslog-0-ports', '')
-    ports=parse_ports(ports_field)
-    dns_s = Service(
-        name=data.get('service_dns-0-name', ''),
-        protocol=data.get('service_dns-0-protocol', ''),
-        ports=ports,
-        description='DNS Server',
-        virtual_machine=vm  # Link to the saved Virtual Machine
-
-    )
-    
-    dns_s.full_clean()  # Validate the data
-    dns_s.save()  # Save to the database
-    return dns_s
-
-
-def create_syslog_services(data, vm):
-    """Creates and saves a Vm Interface object linked to a Virtual Machine."""
-    ports_field=data.get('service_syslog-0-ports', '')
-    ports=parse_ports(ports_field)
-    syslog_s = Service(
-        name=data.get('service_syslog-0-name', ''),
-        protocol=data.get('service_syslog-0-protocol', ''),
-        ports=ports,
-        description='SYSLOG Server',
-        virtual_machine=vm  # Link to the saved Virtual Machine
-    )
-    syslog_s.full_clean()  # Validate the data
-    syslog_s.save()  # Save to the database
-    return syslog_s
-
-
-def create_ip_address(data, vm_interface):
-    """
-    Creates and saves an IPAddress object linked to a specific VMInterface.
-    """
-    ip_address = IPAddress(
-        address=data.get('ip_new-address', ''),
-        status=data.get('ip_new-status', ''),
-        role=data.get('ip_new-role', ''),
-        assigned_object=vm_interface  # Link to the saved VMInterface
-    )
-    ip_address.full_clean()  # Validate the data
-    ip_address.save()  # Save to the database
-    return ip_address
-
-
 #Create VM
-# Main view for handling ClusterManagement
-class CreateVmView(generic.ObjectEditView):
-    """View for managing ClusterType, Cluster, and VirtualMachine in one form."""
-    queryset = ClusterType.objects.all()
-    form = forms.CreateVmForm
+class CreateVmView(generic.ObjectView):
+    """View for creating a ClusterType, Cluster, and VirtualMachine."""
+    form_class = forms.CreateVmForm
+    template_name = 'netbox_sys_plugin/create_vm.html'
+    queryset = models.Cluster.objects.all()
 
     def get(self, request, *args, **kwargs):
-        instance = self.get_object()
-        form = self.form(instance=instance)
-        return render(request, 'netbox_sys_plugin/create_vm.html', {'form': form})
+        form = self.form_class()
+        return render(request, self.template_name, {'form': form})
 
     def post(self, request, *args, **kwargs):
-        """Handles POST requests for objects."""
-        form = self.form(data=request.POST)
-
-        if request.method == 'POST':
-            print("POST DATA:", request.POST)
-
-            try:
-                # Use a database transaction to ensure all-or-nothing behavior
-                with transaction.atomic():
-                    cl_exist = check_cluster_exist(request.POST)
-                    
-                    if cl_exist == False:
-                        print('HERE NEW CLUSTER TYPE')
-                        # Create the ClusterType
-                        cluster_type = create_cluster_type(request.POST)
-                        print(f"ClusterType saved with ID: {cluster_type.pk}")
-                        # Create the Cluster
-                        cluster = create_cluster(request.POST, cluster_type)
-                        print(f"Cluster saved with ID: {cluster.pk}")
-                    else:
-                        # Get existint Cluster
-                        cluster = get_cluster(request.POST)
-                        
-
-                    # Create the Virtual Machine
-                    vm = create_virtual_machine(request.POST, cluster,cl_exist)
-                    print(f"Virtual Machine saved with ID: {vm.pk}")
-
-                    #Create the Vm Interface
-                    vmi = create_vm_interface(request.POST, vm)
-                    print(f"Virtual Machine Interface saved with ID: {vmi.pk}")
-
-                    #Create the Services
-                    ntps = create_ntp_services(request.POST, vm)
-                    print(f"NTP Server service saved with ID: {ntps.pk}")
-                    dnsss = create_dns_services(request.POST, vm)
-                    print(f"DNS Server service saved with ID: {dnsss.pk}")
-                    syslogs = create_syslog_services(request.POST, vm)
-                    print(f"SYSLOG Server service saved with ID: {syslogs.pk}")
-
-                    # Create the IP Address
-                    ip_address = create_ip_address(request.POST, vmi)
-                    print(f"IP Address saved with ID: {ip_address.pk}")
-
-                # On successful save, provide feedback to the user
-                messages.success(request,'ClusterType, Cluster, and VirtualMachine created successfully!')
-                return render(request,'netbox_sys_plugin/create_vm.html',{'form': form,})
-
-            except Exception as e:
-                # Log the error and display feedback
-                print(f"Error saving objects: {str(e)}")
-                messages.error(request, f"Error: {str(e)}. Please correct the form and try again.")
-                return render(request,'netbox_sys_plugin/create_vm.html',{'form': form,})
-
-        return render(request,'netbox_sys_plugin/create_vm.html',{'form': form})
+        form = self.form_class(data=request.POST)
+
+        try:
+            form.process_creation(request.POST)
+            messages.success(request, "ClusterType, Cluster, and VirtualMachine created successfully!")
+            #return render(request, self.template_name, {'form': form})
+            return redirect(reverse('virtualization:virtualmachine_list'))
+        except ValueError as e:
+                messages.error(request, f"Error: {str(e)}")
+        return render(request, self.template_name, {'form': form})
\ No newline at end of file
-- 
GitLab


From 8bf2a46e778841a9dc5dd02cad70b0770b8211ba Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Mon, 2 Dec 2024 09:59:57 +0000
Subject: [PATCH 19/28] =?UTF-8?q?=F0=9F=94=A5=20Remove=20url=20column=20an?=
 =?UTF-8?q?d=20change=20validator?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/filtersets.py               |  4 ++--
 netbox_sys_plugin/forms/provider.py           | 11 ++++------
 ...redentials_provider_password_vault_path.py | 18 +++++++++++++++
 ...ovidercredentials_provider_url_and_more.py | 22 +++++++++++++++++++
 netbox_sys_plugin/models/provider.py          | 11 ++--------
 netbox_sys_plugin/tables.py                   | 15 +++++--------
 .../cluster_providercredentials.html          | 11 ++--------
 .../providercredentials.html                  |  6 +----
 8 files changed, 56 insertions(+), 42 deletions(-)
 create mode 100644 netbox_sys_plugin/migrations/0004_rename_provider_password_vault_url_providercredentials_provider_password_vault_path.py
 create mode 100644 netbox_sys_plugin/migrations/0005_remove_providercredentials_provider_url_and_more.py

diff --git a/netbox_sys_plugin/filtersets.py b/netbox_sys_plugin/filtersets.py
index 3c031b6..a934a31 100644
--- a/netbox_sys_plugin/filtersets.py
+++ b/netbox_sys_plugin/filtersets.py
@@ -87,11 +87,11 @@ class ProviderCredentialsFilterSet(NetBoxModelFilterSet):
     class Meta:
         """Meta class"""     
         model = ProviderCredentials
-        fields = ('provider_url','provider_username','provider_password_vault_url','cluster')
+        fields = ('provider_username','provider_password_vault_path','cluster')
 
     # pylint: disable=W0613
     def search(self, queryset, name, value):
         """override"""
         if not value.strip():
             return queryset
-        return queryset.filter(Q(provider_username__icontains=value)|Q(provider_url__icontains=value)|Q(provider_password_vault_url__icontains=value))
\ No newline at end of file
+        return queryset.filter(Q(provider_username__icontains=value)|Q(provider_password_vault_url__icontains=value))
\ No newline at end of file
diff --git a/netbox_sys_plugin/forms/provider.py b/netbox_sys_plugin/forms/provider.py
index ffd747c..1823cb1 100644
--- a/netbox_sys_plugin/forms/provider.py
+++ b/netbox_sys_plugin/forms/provider.py
@@ -20,14 +20,11 @@ class ProviderCredentialsForm(NetBoxModelForm):
     cluster = DynamicModelChoiceField(
         queryset=Cluster.objects.all(), required=True, label="Cluster"
     )
-    provider_url = forms.CharField(
-        max_length=200, min_length=1, required=True, label="URL"
-    )
     provider_username = forms.CharField(
         max_length=50, min_length=1, required=True, label="Username"
     )
-    provider_password_vault_url = forms.CharField(
-        max_length=200, min_length=1, required=True, label="Password Vault URL"
+    provider_password_vault_path = forms.CharField(
+        max_length=200, min_length=1, required=True, label="Password Vault Path"
     )
 
     def __init__(self, *args, **kwargs):
@@ -53,7 +50,7 @@ class ProviderCredentialsForm(NetBoxModelForm):
     class Meta:
         """Meta class"""
         model = ProviderCredentials
-        fields = ('cluster','provider_url', 'provider_username', 'provider_password_vault_url','tags')
+        fields = ('cluster', 'provider_username', 'provider_password_vault_path','tags')
 
 
     def clean(self):
@@ -72,7 +69,7 @@ class ProviderCredentialsForm(NetBoxModelForm):
                 {"__all__": "Can't assign more than one Credential to the same Cluster"},
             )
         
-        if self.errors.get(f"provider_url"):
+        if self.errors.get(f"provider_username"):
             return
 
     def save(self, *args, **kwargs):
diff --git a/netbox_sys_plugin/migrations/0004_rename_provider_password_vault_url_providercredentials_provider_password_vault_path.py b/netbox_sys_plugin/migrations/0004_rename_provider_password_vault_url_providercredentials_provider_password_vault_path.py
new file mode 100644
index 0000000..2f21dc6
--- /dev/null
+++ b/netbox_sys_plugin/migrations/0004_rename_provider_password_vault_url_providercredentials_provider_password_vault_path.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.16 on 2024-12-02 09:44
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('netbox_sys_plugin', '0003_virtualmachinetype_vmassignedvirtualmachinetype'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='providercredentials',
+            old_name='provider_password_vault_url',
+            new_name='provider_password_vault_path',
+        ),
+    ]
diff --git a/netbox_sys_plugin/migrations/0005_remove_providercredentials_provider_url_and_more.py b/netbox_sys_plugin/migrations/0005_remove_providercredentials_provider_url_and_more.py
new file mode 100644
index 0000000..17ee435
--- /dev/null
+++ b/netbox_sys_plugin/migrations/0005_remove_providercredentials_provider_url_and_more.py
@@ -0,0 +1,22 @@
+# Generated by Django 4.2.16 on 2024-12-02 09:47
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('netbox_sys_plugin', '0004_rename_provider_password_vault_url_providercredentials_provider_password_vault_path'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='providercredentials',
+            name='provider_url',
+        ),
+        migrations.AlterField(
+            model_name='providercredentials',
+            name='provider_password_vault_path',
+            field=models.CharField(blank=True, default=None, max_length=200, null=True),
+        ),
+    ]
diff --git a/netbox_sys_plugin/models/provider.py b/netbox_sys_plugin/models/provider.py
index eb1cc24..61a33cb 100644
--- a/netbox_sys_plugin/models/provider.py
+++ b/netbox_sys_plugin/models/provider.py
@@ -20,21 +20,14 @@ CLUSTER_ASSIGNMENT_MODELS = models.Q(models.Q(app_label="virtualization", model=
 
 class ProviderCredentials(NetBoxModel):
     """Cluster ProviderInfo definition class"""
-
-    provider_url = models.CharField(
-        default=None, blank=True, null=True,
-        max_length=100,
-        validators=[URLValidator(schemes=["http", "https"])],
-        )
-    
+  
     provider_username = models.CharField(
         default=None, blank=True, null=True,
         max_length=50)
 
-    provider_password_vault_url = models.CharField(
+    provider_password_vault_path = models.CharField(
         default=None, blank=True, null=True,
         max_length=200,
-        validators=[URLValidator(schemes=["http", "https"])],
         )
 
     assigned_object_type = models.ForeignKey(
diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py
index 001f223..8366169 100644
--- a/netbox_sys_plugin/tables.py
+++ b/netbox_sys_plugin/tables.py
@@ -47,19 +47,15 @@ class ProviderCredentialsTable(NetBoxTable):
     assigned_object = tables.Column(
         linkify=True,
         orderable=False,
-        verbose_name="Cluster",
+        verbose_name="Provider",
     )
 
-    provider_url = tables.Column(
-        linkify=True,
-        verbose_name="URL",
-    )
     provider_username = tables.Column(
-        linkify=False,
+        linkify=True,
         verbose_name="Username",
 
     )
-    provider_password_vault_url = tables.Column(
+    provider_password_vault_path = tables.Column(
         linkify=False,
         verbose_name="Password Vault URL",
     )
@@ -70,15 +66,14 @@ class ProviderCredentialsTable(NetBoxTable):
         fields = (
             "pk",
             "id",
-            "provider_url",
             "provider_username", 
-            "provider_password_vault_url",
+            "provider_password_vault_path",
             "assigned_object",
             "created",
             "last_updated",
         )
 
-        default_columns = ('id','provider_url','provider_username','provider_password_vault_url','assigned_object')
+        default_columns = ('id','assigned_object','provider_username','provider_password_vault_path',)
 
 #VmAssignedType
 class VmAssignedVmTypeTable(NetBoxTable):
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
index 71616af..5bf84db 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
@@ -10,13 +10,6 @@
               <th scope="row">Passworl Vault URL</th>
               {% for providercredentials in providercredentialsinfo %}
         <tr>
-            <td>
-              {% if providercredentials.provider_url %}
-                <a href="{{ providercredentials.get_absolute_url }}">{{ providercredentials.provider_url }}</a>
-              {% else %}
-                {{ ''|placeholder }}
-              {% endif %}
-            </td>
             <td>
               {% if providercredentials.provider_username %}
                 {{ providercredentials.provider_username }}
@@ -25,8 +18,8 @@
               {% endif %}
             </td>
             <td>
-              {% if providercredentials.provider_password_vault_url %}
-                {{ providercredentials.provider_password_vault_url }}
+              {% if providercredentials.provider_password_vault_path %}
+                {{ providercredentials.provider_password_vault_path }}
               {% else %}
                 {{ ''|placeholder }}
               {% endif %}
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
index d18f35b..a8a289d 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
@@ -11,17 +11,13 @@ Provider Credentials for {{ object.assigned_object }}
       <h5 class="card-header">Provider Credentials</h5>
       <div class="card-body">
         <table class="table table-hover attr-table">
-          <tr>
-            <th scope="row">URL</th>
-            <td>{{ object.provider_url }}</td>
-          </tr>
           <tr>
             <th scope="row">Username</th>
             <td>{{ object.provider_username }}</td>
           </tr>
           <tr>
             <th scope="row">Password vault URL</th>
-            <td>{{ object.provider_password_vault_url }}</td>
+            <td>{{ object.provider_password_vault_path }}</td>
           </tr>
         </table>
       </div>
-- 
GitLab


From 68c5fa11801292d0284a7247aaacd95b0b0b4551 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Mon, 2 Dec 2024 10:04:55 +0000
Subject: [PATCH 20/28] =?UTF-8?q?=F0=9F=92=84=20Fix=20UI=20elements?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../cluster_providercredentials.html            | 17 ++++++++---------
 .../netbox_sys_plugin/providercredentials.html  |  2 +-
 2 files changed, 9 insertions(+), 10 deletions(-)

diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
index 5bf84db..c40dba5 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/cluster_providercredentials.html
@@ -5,18 +5,17 @@
         <h5 class="card-header">Provider Credentials</h5>
         <div class="card-body">
           <table class="table table-responsive">
-              <th scope="row">URL</th>
               <th scope="row">Username</th>
-              <th scope="row">Passworl Vault URL</th>
+              <th scope="row">Passworl Vault path</th>
               {% for providercredentials in providercredentialsinfo %}
         <tr>
-            <td>
-              {% if providercredentials.provider_username %}
-                {{ providercredentials.provider_username }}
-              {% else %}
-                {{ ''|placeholder }}
-              {% endif %}
-            </td>
+          <td>
+            {% if providercredentials.provider_username %}
+              <a href="{{ providercredentials.get_absolute_url }}">{{ providercredentials.provider_username }}</a>
+            {% else %}
+              {{ ''|placeholder }}
+            {% endif %}
+          </td>
             <td>
               {% if providercredentials.provider_password_vault_path %}
                 {{ providercredentials.provider_password_vault_path }}
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
index a8a289d..cb91198 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
@@ -27,7 +27,7 @@ Provider Credentials for {{ object.assigned_object }}
   </div>
   <div class="col col-md-6">
     <div class="card">
-      <h5 class="card-header">Assigned Object</h5>
+      <h5 class="card-header">Provider</h5>
       <div class="card-body">
         <table class="table table-hover attr-table">
           <tr>
-- 
GitLab


From ea3b9d68ce32553b647ee8e235fd91e696a6c450 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Mon, 2 Dec 2024 10:10:26 +0000
Subject: [PATCH 21/28] =?UTF-8?q?=F0=9F=92=84=20More=20UI=20fixes?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/tables.py                                 | 6 +++---
 .../templates/netbox_sys_plugin/virtualmachinetype.html     | 2 +-
 .../netbox_sys_plugin/vmassignedvirtualmachinetype.html     | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py
index 8366169..b7de3e4 100644
--- a/netbox_sys_plugin/tables.py
+++ b/netbox_sys_plugin/tables.py
@@ -10,7 +10,7 @@ class VmMaintenanceTable(NetBoxTable):
 
     pk = columns.ToggleColumn()
     id = tables.Column(
-        linkify=False,
+        linkify=True,
     )
     
     assigned_object = tables.Column(
@@ -41,7 +41,7 @@ class ProviderCredentialsTable(NetBoxTable):
 
     pk = columns.ToggleColumn()
     id = tables.Column(
-        linkify=False,
+        linkify=True,
     )
     
     assigned_object = tables.Column(
@@ -122,7 +122,7 @@ class VmTypeTable(NetBoxTable):
     assigned_object = tables.Column(
         linkify=True,
         orderable=False,
-        verbose_name="Cluster Type",
+        verbose_name="Provider Type",
     )
 
     virtual_machine_type_name = tables.Column(
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/virtualmachinetype.html b/netbox_sys_plugin/templates/netbox_sys_plugin/virtualmachinetype.html
index c33372a..1bdb7fc 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/virtualmachinetype.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/virtualmachinetype.html
@@ -27,7 +27,7 @@ Virtual Machine Type for {{ object.assigned_object }}
   </div>
   <div class="col col-md-6">
     <div class="card">
-      <h5 class="card-header">Assigned Object</h5>
+      <h5 class="card-header">Provider Type</h5>
       <div class="card-body">
         <table class="table table-hover attr-table">
           <tr>
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedvirtualmachinetype.html b/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedvirtualmachinetype.html
index 5fa41ae..a51c986 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedvirtualmachinetype.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/vmassignedvirtualmachinetype.html
@@ -34,7 +34,7 @@ Virtual Machine Type for {{ object.assigned_object }}
   </div>
   <div class="col col-md-6">
     <div class="card">
-      <h5 class="card-header">Assigned Object</h5>
+      <h5 class="card-header">Virtual Machine</h5>
       <div class="card-body">
         <table class="table table-hover attr-table">
           <tr>
-- 
GitLab


From 9dbce602be17e5884f4b04d5695a14fbce1ac066 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Mon, 2 Dec 2024 15:09:33 +0000
Subject: [PATCH 22/28] =?UTF-8?q?=E2=9C=A8=20Add=20tags=20to=20form?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py           | 57 +++++++++++++++++--
 .../netbox_sys_plugin/create_vm.html          | 10 +++-
 2 files changed, 60 insertions(+), 7 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index e8c94bf..740f5ce 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -5,6 +5,7 @@ from netbox.forms import NetBoxModelForm
 from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterface
 from ipam.models import IPAddress, Service
 from dcim.models import DeviceRole,Site, Platform
+from extras.models import Tag
 from utilities.forms.fields import DynamicModelChoiceField
 from django.db import transaction
 
@@ -38,7 +39,7 @@ class ClusterFormList(NetBoxModelForm):
     
     class Meta:
         model = Cluster
-        fields = ('cluster_type','cluster')
+        fields = ('cluster_type','cluster','tags')
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -90,6 +91,21 @@ class IPAddressForm(NetBoxModelForm):
         super().__init__(*args, **kwargs)
         self.fields.pop('tags',None)
 
+class TagsForm(NetBoxModelForm):
+    """Form for IP Addresses."""
+    tag = DynamicModelChoiceField(
+        queryset=Tag.objects.all(), required=False, label="Tag"
+    )
+    class Meta:
+        model = Tag
+        fields = ('tag',)
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields.pop('tags',None)
+    
+
+
 
 # Inline formsets for managing relationships
 ClusterFormSet = inlineformset_factory(
@@ -122,6 +138,10 @@ ClusterListFormSet = forms.modelformset_factory(
     Cluster, form=ClusterFormList, formset=ClusterFormList, extra=1, can_delete=False
 )
 
+TagFormSet = forms.modelformset_factory(
+    Tag, form=TagsForm, formset=TagsForm, extra=1, can_delete=False
+)
+
 
 # Combined form
 class CreateVmForm(NetBoxModelForm):
@@ -172,6 +192,10 @@ class CreateVmForm(NetBoxModelForm):
         self.cl_list_formsets = []
         empty_cl_list_formset = ClusterListFormSet(data=data, prefix='cl_list_new')
         self.cl_list_formsets.append(('new', empty_cl_list_formset))
+        # Tags
+        self.tagformsets = []
+        empty_tagformset = TagFormSet(data=data, prefix='tag_new')
+        self.tagformsets.append(('new', empty_tagformset))
 
     @staticmethod
     def check_cluster_exist(data):
@@ -194,14 +218,28 @@ class CreateVmForm(NetBoxModelForm):
             return ports
         except ValueError:
             raise ValueError(f"Invalid ports value: {ports_field}")
+   
+    @staticmethod
+    def get_parse_tags(data):
+        tag_id = data.get('tag_new-tag', '')
+        tags_field = Tag.objects.get(pk=tag_id)
+        #try:
+        #    tags = [int(tag.strip()) for tag in tags_field.split(',') if tag.strip().isdigit()]
+        #    return tags 
+        #except ValueError:
+        #    raise ValueError(f"Invalid ports value: {tags_field}")
+        
+        return tags_field 
 
     @staticmethod
     def create_cluster_type(data):
         """Create and save a ClusterType object."""
+        tags = CreateVmForm.get_parse_tags(data)
         cluster_type = ClusterType(
             name=data.get('name', ''),
             slug=data.get('slug', ''),
-            description=data.get('description', '')
+            description=data.get('description', ''),
+            tags=tags
         )
         cluster_type.full_clean()
         cluster_type.save()
@@ -215,13 +253,15 @@ class CreateVmForm(NetBoxModelForm):
             site = Site.objects.get(pk=site_id)
         except Site.DoesNotExist:
             raise ValueError(f"Invalid Site ID: {site_id}")
+        tags = CreateVmForm.get_parse_tags(data)
 
         cluster = Cluster(
             name=data.get('clusters-0-name', ''),
             type=cluster_type,
             status=data.get('clusters-0-status', ''),
             site=site,
-            description=data.get('clusters-0-description', '')
+            description=data.get('clusters-0-description', ''),
+            tags=tags
         )
         cluster.full_clean()
         cluster.save()
@@ -241,6 +281,7 @@ class CreateVmForm(NetBoxModelForm):
             platform = Platform.objects.get(pk=platform_id)
         except Platform.DoesNotExist:
             raise ValueError(f"Invalid Platform ID: {platform_id}")
+        tags = CreateVmForm.get_parse_tags(data)
 
         vm = VirtualMachine(
             name=data.get('vms_new-0-name', ''),
@@ -249,7 +290,8 @@ class CreateVmForm(NetBoxModelForm):
             site=cluster.site,
             platform=platform,
             description=data.get('vms_new-0-description', ''),
-            cluster=cluster
+            cluster=cluster,
+            tags=tags
         )
         vm.full_clean()
         vm.save()
@@ -271,13 +313,15 @@ class CreateVmForm(NetBoxModelForm):
         """Create and save a Service object."""
         ports_field = data.get(f'{prefix}-0-ports', '')
         ports = CreateVmForm.parse_ports(ports_field)
+        tags = CreateVmForm.get_parse_tags(data)
 
         service = Service(
             name=data.get(f'{prefix}-0-name', ''),
             protocol=data.get(f'{prefix}-0-protocol', ''),
             ports=ports,
             description=description,
-            virtual_machine=vm
+            virtual_machine=vm,
+            tags=tags
         )
         service.full_clean()
         service.save()
@@ -296,7 +340,8 @@ class CreateVmForm(NetBoxModelForm):
             address=data.get('ip_new-address', ''),
             status=data.get('ip_new-status', ''),
             role=data.get('ip_new-role', ''),
-            assigned_object=vm_interface
+            assigned_object=vm_interface,
+            tags=data.get('tag_new-tag', '')
         )
         ip_address.full_clean()
         ip_address.save()
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
index d267c46..ad1def9 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
@@ -122,7 +122,15 @@ SYS - Virtual Machine
           {% endfor %}
           {% endfor %}
         </div>
-        
+        <div class="field-group my-5">
+          <h5>Tag</h5>
+          {% for tagformset in form.tagformsets %}
+          {% for tag_form in tagformset %}
+          <div class="form-group" class="field-group mb-5">
+            {{ tag_form.as_p }}
+          </div>
+        {% endfor %}
+        {% endfor %}
         <div class="text-end my-3">
           {% block buttons %}
           <button type="submit" class="btn btn-primary">Save</button>
-- 
GitLab


From 922197b939724ee459ef1c30cdb2c1293f0c132e Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Mon, 2 Dec 2024 15:58:49 +0000
Subject: [PATCH 23/28] =?UTF-8?q?=F0=9F=90=9B=20Fix=20not=20assigned=20tag?=
 =?UTF-8?q?s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py | 52 ++++++++++++++++++-----------
 1 file changed, 32 insertions(+), 20 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 740f5ce..f0483a8 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -1,13 +1,15 @@
 from django import forms
 from django.forms import inlineformset_factory
+from django.contrib.contenttypes.models import ContentType
+from django.db import transaction
+from utilities.forms.fields import DynamicModelChoiceField
 from netbox.forms import NetBoxModelForm
-
 from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterface
 from ipam.models import IPAddress, Service
 from dcim.models import DeviceRole,Site, Platform
-from extras.models import Tag
-from utilities.forms.fields import DynamicModelChoiceField
-from django.db import transaction
+from extras.models import Tag, TaggedItem
+
+
 
 
 
@@ -222,27 +224,39 @@ class CreateVmForm(NetBoxModelForm):
     @staticmethod
     def get_parse_tags(data):
         tag_id = data.get('tag_new-tag', '')
-        tags_field = Tag.objects.get(pk=tag_id)
-        #try:
-        #    tags = [int(tag.strip()) for tag in tags_field.split(',') if tag.strip().isdigit()]
-        #    return tags 
-        #except ValueError:
-        #    raise ValueError(f"Invalid ports value: {tags_field}")
         
-        return tags_field 
+        if not tag_id:
+            return []
+        try:
+            tags_field = Tag.objects.get(pk=tag_id)
+            return [tags_field]
+        except Tag.DoesNotExist:
+            raise ValueError (f"Invalid tag: {tags_field}")
+    
+    @staticmethod
+    def assign_tags(obj,data):
+        """Assign Tags to the created objects"""
+
+        tags = CreateVmForm.get_parse_tags(data)
+        content_type = ContentType.objects.get_for_model(obj)
+        for tag in tags:
+            TaggedItem.objects.create(
+                tag=tag,
+                content_type=content_type,
+                object_id=obj.pk
+            )
 
     @staticmethod
     def create_cluster_type(data):
         """Create and save a ClusterType object."""
-        tags = CreateVmForm.get_parse_tags(data)
         cluster_type = ClusterType(
             name=data.get('name', ''),
             slug=data.get('slug', ''),
             description=data.get('description', ''),
-            tags=tags
         )
         cluster_type.full_clean()
         cluster_type.save()
+        CreateVmForm.assign_tags(cluster_type,data)
         return cluster_type
 
     @staticmethod
@@ -253,7 +267,6 @@ class CreateVmForm(NetBoxModelForm):
             site = Site.objects.get(pk=site_id)
         except Site.DoesNotExist:
             raise ValueError(f"Invalid Site ID: {site_id}")
-        tags = CreateVmForm.get_parse_tags(data)
 
         cluster = Cluster(
             name=data.get('clusters-0-name', ''),
@@ -261,10 +274,10 @@ class CreateVmForm(NetBoxModelForm):
             status=data.get('clusters-0-status', ''),
             site=site,
             description=data.get('clusters-0-description', ''),
-            tags=tags
         )
         cluster.full_clean()
         cluster.save()
+        CreateVmForm.assign_tags(cluster,data)
         return cluster
 
     @staticmethod
@@ -281,7 +294,6 @@ class CreateVmForm(NetBoxModelForm):
             platform = Platform.objects.get(pk=platform_id)
         except Platform.DoesNotExist:
             raise ValueError(f"Invalid Platform ID: {platform_id}")
-        tags = CreateVmForm.get_parse_tags(data)
 
         vm = VirtualMachine(
             name=data.get('vms_new-0-name', ''),
@@ -291,10 +303,10 @@ class CreateVmForm(NetBoxModelForm):
             platform=platform,
             description=data.get('vms_new-0-description', ''),
             cluster=cluster,
-            tags=tags
         )
         vm.full_clean()
         vm.save()
+        CreateVmForm.assign_tags(vm,data)
         return vm
 
     @staticmethod
@@ -306,6 +318,7 @@ class CreateVmForm(NetBoxModelForm):
         )
         vmi.full_clean()
         vmi.save()
+        CreateVmForm.assign_tags(vmi,data)
         return vmi
     
     @staticmethod
@@ -313,7 +326,6 @@ class CreateVmForm(NetBoxModelForm):
         """Create and save a Service object."""
         ports_field = data.get(f'{prefix}-0-ports', '')
         ports = CreateVmForm.parse_ports(ports_field)
-        tags = CreateVmForm.get_parse_tags(data)
 
         service = Service(
             name=data.get(f'{prefix}-0-name', ''),
@@ -321,10 +333,10 @@ class CreateVmForm(NetBoxModelForm):
             ports=ports,
             description=description,
             virtual_machine=vm,
-            tags=tags
         )
         service.full_clean()
         service.save()
+        CreateVmForm.assign_tags(service,data)
         return service
 
     def create_all_services(self, data, vm):
@@ -341,10 +353,10 @@ class CreateVmForm(NetBoxModelForm):
             status=data.get('ip_new-status', ''),
             role=data.get('ip_new-role', ''),
             assigned_object=vm_interface,
-            tags=data.get('tag_new-tag', '')
         )
         ip_address.full_clean()
         ip_address.save()
+        CreateVmForm.assign_tags(ip_address,data)
         return ip_address
 
     def process_creation(self, data):
-- 
GitLab


From bd367838bf3881d6a15f393f185fabbcb22c73ce Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Tue, 3 Dec 2024 11:31:47 +0000
Subject: [PATCH 24/28] =?UTF-8?q?=F0=9F=90=9B=20Fix=20several=20issues?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py           |  6 ++---
 netbox_sys_plugin/navigation.py               | 27 ++++++++++---------
 netbox_sys_plugin/tables.py                   |  2 +-
 .../providercredentials.html                  |  2 +-
 4 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index f0483a8..9bbfbb4 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -28,7 +28,7 @@ class ClusterForm(NetBoxModelForm):
         super().__init__(*args, **kwargs)
         self.fields.pop('tags',None)
 
-class ClusterFormList(NetBoxModelForm):
+class ClusterFormList(NetBoxModelForm, forms.Form):
     """Form for Cluster List."""
     cluster_type = DynamicModelChoiceField(
         queryset=ClusterType.objects.all(), required=False, label="Provider Type"
@@ -147,7 +147,7 @@ TagFormSet = forms.modelformset_factory(
 
 # Combined form
 class CreateVmForm(NetBoxModelForm):
-    """Combined form for managing ClusterType, Cluster, and VirtualMachine."""
+    """Combined form for managing ClusterType, Cluster, and VirtualMachine, etc"""
 
     name = forms.CharField(max_length=50, min_length=1, required=False, label="Name")
     slug = forms.CharField(max_length=100, min_length=1, required=False, label="Slug")
@@ -360,7 +360,7 @@ class CreateVmForm(NetBoxModelForm):
         return ip_address
 
     def process_creation(self, data):
-        """Process creation of ClusterType, Cluster, VM, and related objects."""
+        """Object creation"""
         try:
             with transaction.atomic():
                 cluster_exists = self.check_cluster_exist(data)
diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py
index 2391977..8a4fa9b 100644
--- a/netbox_sys_plugin/navigation.py
+++ b/netbox_sys_plugin/navigation.py
@@ -68,37 +68,38 @@ clusterProviderCredentialsItem = [
 ]
 
 vmMachineItem = [
+    PluginMenuItem(
+        link="plugins:netbox_sys_plugin:virtualmachinetype_list",
+        link_text="VM Type",
+        buttons=vm_machine_type_buttons
+    ),
     PluginMenuItem(
         link="plugins:netbox_sys_plugin:vmassignedvirtualmachinetype_list",
-        link_text="Type Assignment",
+        link_text="VM Type Assignment",
         buttons=vm_assigned_machine_type_buttons,
     ),
     PluginMenuItem(
         link="plugins:netbox_sys_plugin:virtualmachinemaintenance_list",
-        link_text="Maintenance",
+        link_text="VM Maintenance",
         buttons=vm_maintenance_buttons,
     ),
-]
-
-operItem =[
-    PluginMenuItem(
-        link="plugins:netbox_sys_plugin:virtualmachinetype_list",
-        link_text="Virtual Machine Types",
-        buttons=vm_machine_type_buttons
-    ),
     PluginMenuItem(
         link="plugins:netbox_sys_plugin:creatvm_add",
-        link_text="Create Virtual Machine ",
+        link_text="Create VM",
         buttons=create_vm_buttons
     ),
 ]
+
+operItem =[
+
+]
 #Menu
 menu = PluginMenu(
     label="Sys",
     groups=(
-        ("Virtual Machine", (vmMachineItem)),
+        
         ("Provider", clusterProviderCredentialsItem),
-        ("Operation", (operItem)),
+        ("Virtual Machine", (vmMachineItem)),
             ),
     icon_class="mdi mdi-all-inclusive-box-outline",
 )
\ No newline at end of file
diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py
index b7de3e4..374b9b2 100644
--- a/netbox_sys_plugin/tables.py
+++ b/netbox_sys_plugin/tables.py
@@ -57,7 +57,7 @@ class ProviderCredentialsTable(NetBoxTable):
     )
     provider_password_vault_path = tables.Column(
         linkify=False,
-        verbose_name="Password Vault URL",
+        verbose_name="Password Vault Path",
     )
 
     class Meta(NetBoxTable.Meta):
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
index cb91198..f6c63b9 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/providercredentials.html
@@ -16,7 +16,7 @@ Provider Credentials for {{ object.assigned_object }}
             <td>{{ object.provider_username }}</td>
           </tr>
           <tr>
-            <th scope="row">Password vault URL</th>
+            <th scope="row">Password vault Path</th>
             <td>{{ object.provider_password_vault_path }}</td>
           </tr>
         </table>
-- 
GitLab


From 5b91279442d3ca48717d45a953bf8ebf437ce7a7 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Tue, 3 Dec 2024 16:23:06 +0000
Subject: [PATCH 25/28] =?UTF-8?q?=F0=9F=A6=BA=20Add=20validation=20to=20IP?=
 =?UTF-8?q?=20address?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py              | 15 +++++++++++++--
 .../templates/netbox_sys_plugin/create_vm.html   | 16 +++++++++++++---
 2 files changed, 26 insertions(+), 5 deletions(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 9bbfbb4..89293df 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -8,6 +8,7 @@ from virtualization.models import ClusterType, Cluster, VirtualMachine, VMInterf
 from ipam.models import IPAddress, Service
 from dcim.models import DeviceRole,Site, Platform
 from extras.models import Tag, TaggedItem
+import netaddr
 
 
 
@@ -57,7 +58,7 @@ class VirtualMachineForm(NetBoxModelForm):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.fields.pop('tags',None)
-
+    
 class VirtualMachineInterfaceForm(NetBoxModelForm):
     """Form for Virtual Machine Interfaces."""
 
@@ -125,9 +126,11 @@ VirtualMachineInterfaceFormSet = inlineformset_factory(
 ServiceNtpFormSet = inlineformset_factory(
     VirtualMachine, Service, form=ServiceForm, extra=1, can_delete=False
 )
+
 ServiceDnsFormSet = inlineformset_factory(
     VirtualMachine, Service, form=ServiceForm, extra=1, can_delete=False
 )
+
 ServiceSyslogFormSet = inlineformset_factory(
     VirtualMachine, Service, form=ServiceForm, extra=1, can_delete=False
 )
@@ -348,8 +351,16 @@ class CreateVmForm(NetBoxModelForm):
     @staticmethod
     def create_ip_address(data, vm_interface):
         """Create and save an IPAddress object."""
+        ipaddress_raw=data.get('ip_new-address', '').strip()
+        if not ipaddress_raw:
+            raise ValueError("IP Address cannot be empty")
+        try:
+            ipaddress_raw = netaddr.IPNetwork(ipaddress_raw)
+        except:
+            raise ValueError("Invalid IP Address")
+            
         ip_address = IPAddress(
-            address=data.get('ip_new-address', ''),
+            address=ipaddress_raw,
             status=data.get('ip_new-status', ''),
             role=data.get('ip_new-role', ''),
             assigned_object=vm_interface,
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
index ad1def9..2c04cd5 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
@@ -1,25 +1,31 @@
 {% extends 'base/layout.html' %}
 {% load buttons %}
 {% load custom_links %}
+{% load form_helpers %}
 {% load perms %}
 {% load plugins %}
 {% load tabs %}
+{% load static %}
 {% load i18n %}
 
-
-
 {% block title %}
 SYS - Virtual Machine
 {% endblock title %}
+
 {% block tabs %}
 <ul class="nav nav-tabs px-3">
   <li class="nav-item" role="presentation">
     <button class="nav-link active" id="edit-form-tab" data-bs-toggle="tab" data-bs-target="#edit-form" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
-      Create      </button>
+      Create</button>
     </li>
   </ul>
   {% endblock tabs %}
   {% block content-wrapper %}
+  <style>
+    .errorlist.nonfield {
+      display: none;
+    }
+  </style>
   <div class="tab-content">
     <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab">
       {% block content %}
@@ -73,6 +79,10 @@ SYS - Virtual Machine
           {% for vm_form in vm_formset %}
           <div class="form-group" class="field-group mb-5">
             {{ vm_form.as_p }}
+            {% if vm_form.non_field_errors %}
+              <h1> {{ vm_form.non_field_errors }}</h1>
+            {% endif %}
+
           </div>
           {% endfor %}
           {% endfor %}
-- 
GitLab


From 8d031f65d1cb3f091021becdfe0de1adc9835a74 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Wed, 4 Dec 2024 10:15:06 +0000
Subject: [PATCH 26/28] =?UTF-8?q?=F0=9F=90=9B=20Fix=20bug=20on=20filter=20?=
 =?UTF-8?q?for=20dropbox?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/forms/createvm.py                      | 2 +-
 .../templates/netbox_sys_plugin/create_vm.html           | 9 +++++++++
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 89293df..557806e 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -37,7 +37,7 @@ class ClusterFormList(NetBoxModelForm, forms.Form):
 
     cluster = DynamicModelChoiceField(
         queryset=Cluster.objects.all(), required=False, label="Provider",
-        query_params={'type': '$cluster_type',},
+        query_params={'type_id': '$cl_list_new-cluster_type',},
     )
     
     class Meta:
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
index 2c04cd5..d236c57 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
@@ -26,6 +26,11 @@ SYS - Virtual Machine
       display: none;
     }
   </style>
+      <script
+      type="text/javascript"
+      src="{% static 'netbox.js' %}?v={{ settings.VERSION }}"
+      onerror="window.location='{% url 'media_failure' %}?filename=netbox.js'">
+    </script>
   <div class="tab-content">
     <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab">
       {% block content %}
@@ -151,5 +156,9 @@ SYS - Virtual Machine
   </div>
   {% endblock %}
   {% endblock content-wrapper %}
+{% block modals %}
+  {% include 'inc/htmx_modal.html' %}
+{% endblock modals %}
+{% block javascript %}{% endblock %}
   
   
\ No newline at end of file
-- 
GitLab


From 14828e9d5b521eb6a65750a57d989ba873f31422 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Wed, 4 Dec 2024 17:29:56 +0000
Subject: [PATCH 27/28] =?UTF-8?q?=F0=9F=92=84=20removed=20site=20from=20VM?=
 =?UTF-8?q?,=20added=20gatewat=20IP?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md                                     |  4 +--
 netbox_sys_plugin/forms/createvm.py           | 36 +++++++++++++------
 .../netbox_sys_plugin/create_vm.html          |  8 +++++
 3 files changed, 35 insertions(+), 13 deletions(-)

diff --git a/README.md b/README.md
index 316aac8..9551765 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# SYS Address Plugin for Netbox
+# SYS Plugin for Netbox
 
-A Netbox plugin for Certificate management.
+A Netbox plugin for Create Virtual Machine Process.
 
 ## Installation
 
diff --git a/netbox_sys_plugin/forms/createvm.py b/netbox_sys_plugin/forms/createvm.py
index 557806e..6f96ce9 100644
--- a/netbox_sys_plugin/forms/createvm.py
+++ b/netbox_sys_plugin/forms/createvm.py
@@ -23,7 +23,7 @@ class ClusterForm(NetBoxModelForm):
 
     class Meta:
         model = Cluster
-        fields = ('name', 'type', 'description','status','tags')
+        fields = ('name', 'type', 'description','site','status','tags')
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -53,7 +53,7 @@ class VirtualMachineForm(NetBoxModelForm):
 
     class Meta:
         model = VirtualMachine
-        fields = ('name', 'cluster', 'status','site','role','platform', 'description','tags')
+        fields = ('name', 'cluster', 'status','role','platform', 'description','tags')
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -88,7 +88,7 @@ class IPAddressForm(NetBoxModelForm):
     """Form for IP Addresses."""
     class Meta:
         model = IPAddress
-        fields = ('address', 'status', 'role', 'tags')
+        fields = ('address', 'status', 'tags')
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -193,6 +193,10 @@ class CreateVmForm(NetBoxModelForm):
         self.ip_formsets = []
         empty_ip_formset = IPAddressFormSet(data=data, prefix='ip_new')
         self.ip_formsets.append(('new', empty_ip_formset))
+        # IP Address Gateway
+        self.gateway_formsets = []
+        empty_gateway_formset = IPAddressFormSet(data=data, prefix='gateway_new')
+        self.gateway_formsets.append(('new', empty_gateway_formset))
         # List Cluster
         self.cl_list_formsets = []
         empty_cl_list_formset = ClusterListFormSet(data=data, prefix='cl_list_new')
@@ -256,6 +260,7 @@ class CreateVmForm(NetBoxModelForm):
             name=data.get('name', ''),
             slug=data.get('slug', ''),
             description=data.get('description', ''),
+            tags = CreateVmForm.get_parse_tags(data)
         )
         cluster_type.full_clean()
         cluster_type.save()
@@ -265,7 +270,7 @@ class CreateVmForm(NetBoxModelForm):
     @staticmethod
     def create_cluster(data, cluster_type):
         """Create and save a Cluster object."""
-        site_id = data.get('vms_new-0-role', '')
+        site_id = data.get('clusters-0-site', '')
         try:
             site = Site.objects.get(pk=site_id)
         except Site.DoesNotExist:
@@ -277,6 +282,7 @@ class CreateVmForm(NetBoxModelForm):
             status=data.get('clusters-0-status', ''),
             site=site,
             description=data.get('clusters-0-description', ''),
+            tags = CreateVmForm.get_parse_tags(data)
         )
         cluster.full_clean()
         cluster.save()
@@ -302,10 +308,10 @@ class CreateVmForm(NetBoxModelForm):
             name=data.get('vms_new-0-name', ''),
             status=data.get('vms_new-0-status', ''),
             role=role,
-            site=cluster.site,
             platform=platform,
             description=data.get('vms_new-0-description', ''),
             cluster=cluster,
+            tags = CreateVmForm.get_parse_tags(data)
         )
         vm.full_clean()
         vm.save()
@@ -317,7 +323,8 @@ class CreateVmForm(NetBoxModelForm):
         """Create and save a VMInterface object."""
         vmi = VMInterface(
             name=data.get('vmis_new-0-name', ''),
-            virtual_machine=vm
+            virtual_machine=vm,
+            tags = CreateVmForm.get_parse_tags(data)
         )
         vmi.full_clean()
         vmi.save()
@@ -336,6 +343,7 @@ class CreateVmForm(NetBoxModelForm):
             ports=ports,
             description=description,
             virtual_machine=vm,
+            tags = CreateVmForm.get_parse_tags(data)
         )
         service.full_clean()
         service.save()
@@ -349,9 +357,9 @@ class CreateVmForm(NetBoxModelForm):
         self.create_service(data, vm, 'service_syslog', 'SYSLOG Server')
 
     @staticmethod
-    def create_ip_address(data, vm_interface):
+    def create_ip_address(data, vm_interface, prefix, description):
         """Create and save an IPAddress object."""
-        ipaddress_raw=data.get('ip_new-address', '').strip()
+        ipaddress_raw=data.get(f'{prefix}-address', '').strip()
         if not ipaddress_raw:
             raise ValueError("IP Address cannot be empty")
         try:
@@ -361,14 +369,20 @@ class CreateVmForm(NetBoxModelForm):
             
         ip_address = IPAddress(
             address=ipaddress_raw,
-            status=data.get('ip_new-status', ''),
-            role=data.get('ip_new-role', ''),
+            status=data.get(f'{prefix}-status', ''),
             assigned_object=vm_interface,
+            tags = CreateVmForm.get_parse_tags(data),
+            description=description
         )
         ip_address.full_clean()
         ip_address.save()
         CreateVmForm.assign_tags(ip_address,data)
         return ip_address
+    
+
+    def create_all_ip_adresses(self, data, vm_interface,):
+        self.create_ip_address(data,vm_interface,'ip_new','IP Address')
+        self.create_ip_address(data,vm_interface,'gateway_new','Gateway')
 
     def process_creation(self, data):
         """Object creation"""
@@ -384,7 +398,7 @@ class CreateVmForm(NetBoxModelForm):
                 vm = self.create_virtual_machine(data, cluster, cluster_exists)
                 vmi = self.create_vm_interface(data, vm)
                 self.create_all_services(data, vm)
-                self.create_ip_address(data, vmi)
+                self.create_all_ip_adresses(data, vmi)
                 return vm
 
         except ValueError as e:
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
index d236c57..d1d14b2 100644
--- a/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/create_vm.html
@@ -109,6 +109,14 @@ SYS - Virtual Machine
           </div>
           {% endfor %}
           {% endfor %}
+          <h5>Gateway</h5>
+          {% for gateway_formset in form.gateway_formsets %}
+          {% for gateway_form in gateway_formset %}
+          <div class="form-group" class="field-group mb-5">           
+            {{ gateway_form.as_p }}
+          </div>
+          {% endfor %}
+          {% endfor %}
         </div>
         <div class="field-group my-5">
           <h5>Dependencies</h5>
-- 
GitLab


From 3ce1fab46c893b2d396987a6218cd56abee47c56 Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Thu, 5 Dec 2024 09:17:08 +0000
Subject: [PATCH 28/28] =?UTF-8?q?=F0=9F=9B=82=20Fix=20permissions=20for=20?=
 =?UTF-8?q?menu=20items?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 netbox_sys_plugin/navigation.py | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py
index 8a4fa9b..bd6fefc 100644
--- a/netbox_sys_plugin/navigation.py
+++ b/netbox_sys_plugin/navigation.py
@@ -64,6 +64,7 @@ clusterProviderCredentialsItem = [
         link="plugins:netbox_sys_plugin:providercredentials_list",
         link_text="Credentials",
         buttons=cluster_provider_credentials_buttons,
+        permissions=["netbox_sys_plugin.list_providercredentials"],
     ),
 ]
 
@@ -71,22 +72,26 @@ vmMachineItem = [
     PluginMenuItem(
         link="plugins:netbox_sys_plugin:virtualmachinetype_list",
         link_text="VM Type",
-        buttons=vm_machine_type_buttons
+        buttons=vm_machine_type_buttons,
+        permissions=["netbox_sys_plugin.list_vmmachinetype"],
     ),
     PluginMenuItem(
         link="plugins:netbox_sys_plugin:vmassignedvirtualmachinetype_list",
         link_text="VM Type Assignment",
         buttons=vm_assigned_machine_type_buttons,
+        permissions=["netbox_sys_plugin.list_vmassignedmachinetype"],
     ),
     PluginMenuItem(
         link="plugins:netbox_sys_plugin:virtualmachinemaintenance_list",
         link_text="VM Maintenance",
         buttons=vm_maintenance_buttons,
+        permissions=["netbox_sys_plugin.list_maintenance"],
     ),
     PluginMenuItem(
         link="plugins:netbox_sys_plugin:creatvm_add",
         link_text="Create VM",
-        buttons=create_vm_buttons
+        buttons=create_vm_buttons,
+        permissions=["netbox_sys_plugin.add_createvm"],
     ),
 ]
 
-- 
GitLab