From d2bd08a06740935b34124ca07f1e54a1245eac9b Mon Sep 17 00:00:00 2001
From: Frederico Sequeira <frederico.sequeira@ext.ec.europa.eu>
Date: Fri, 6 Dec 2024 17:57:24 +0000
Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20extention=20for=20virtual=20m?=
 =?UTF-8?q?achine=20to=20include=20a=20list=20of=20domain=20names?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 docs/model/netbox_sys_domain_names.md         | 11 +++
 .../netbox_sys_virtual_machine_maintenance.md |  2 +-
 netbox_sys_plugin/api/serializers.py          | 17 ++++-
 netbox_sys_plugin/api/urls.py                 |  1 +
 netbox_sys_plugin/api/views.py                |  6 +-
 netbox_sys_plugin/forms/machine.py            | 74 ++++++++++++++++++-
 .../migrations/0006_domainnames.py            | 37 ++++++++++
 ..._alter_domainnames_assigned_object_type.py | 20 +++++
 netbox_sys_plugin/models/machine.py           | 43 +++++++++++
 netbox_sys_plugin/navigation.py               | 17 +++++
 netbox_sys_plugin/tables.py                   | 31 ++++++++
 netbox_sys_plugin/template_content.py         | 11 ++-
 .../netbox_sys_plugin/domainnames.html        | 46 ++++++++++++
 .../netbox_sys_plugin/vm_domainnames.html     | 33 +++++++++
 netbox_sys_plugin/urls.py                     | 11 ++-
 netbox_sys_plugin/views.py                    | 31 +++++++-
 16 files changed, 378 insertions(+), 13 deletions(-)
 create mode 100644 docs/model/netbox_sys_domain_names.md
 create mode 100644 netbox_sys_plugin/migrations/0006_domainnames.py
 create mode 100644 netbox_sys_plugin/migrations/0007_alter_domainnames_assigned_object_type.py
 create mode 100644 netbox_sys_plugin/templates/netbox_sys_plugin/domainnames.html
 create mode 100644 netbox_sys_plugin/templates/netbox_sys_plugin/vm_domainnames.html

diff --git a/docs/model/netbox_sys_domain_names.md b/docs/model/netbox_sys_domain_names.md
new file mode 100644
index 0000000..9bef37a
--- /dev/null
+++ b/docs/model/netbox_sys_domain_names.md
@@ -0,0 +1,11 @@
+## netbox_sys_domain_names
+
+**Virtual Machine domain names definition class**
+
+| 	FIELD	                                         | 	TYPE	                        | 	DESCRIPTION	                                                            |
+|----------------------------------------------------|----------------------------------|---------------------------------------------------------------------------|
+| 	id	                                             | 	Big (8 byte) integer	        | 	Unique ID	                                                            |
+| 	assigned_object_type_id                          | 	Big (8 byte) integer	        | 	virtual machine Type ID                                                 |
+| 	assigned_object_id	                             | 	Big (8 byte) integer	        | 	virtual machine unique ID	                                            |
+| 	domain_names                                     | 	Json              	            | 	Information about all the domains for a Virtual Machine in a Json format|
+
diff --git a/docs/model/netbox_sys_virtual_machine_maintenance.md b/docs/model/netbox_sys_virtual_machine_maintenance.md
index c8b181d..4c5fb9d 100644
--- a/docs/model/netbox_sys_virtual_machine_maintenance.md
+++ b/docs/model/netbox_sys_virtual_machine_maintenance.md
@@ -5,7 +5,7 @@
 | 	FIELD	                                         | 	TYPE	                        | 	DESCRIPTION	                                                            |
 |----------------------------------------------------|----------------------------------|---------------------------------------------------------------------------|
 | 	id	                                             | 	Big (8 byte) integer	        | 	Unique ID	                                                            |
-| 	assigned_object_type_id                          | 	Big (8 byte) integer	        | 	virtual machine type ID	                                                |
+| 	assigned_object_type_id                          | 	Big (8 byte) integer	        | 	virtual machine Type ID	                                                |
 | 	assigned_object_id	                             | 	Big (8 byte) integer	        | 	virtual machine unique ID	                                            |
 | 	virtual_machine_maintenance_window               | 	String              	        | 	A string representing a day of the week and a time of the day, 0 = Sunday , 1 = monday |
 
diff --git a/netbox_sys_plugin/api/serializers.py b/netbox_sys_plugin/api/serializers.py
index bc249e4..afba76e 100644
--- a/netbox_sys_plugin/api/serializers.py
+++ b/netbox_sys_plugin/api/serializers.py
@@ -8,7 +8,7 @@ from dcim.api.nested_serializers import (
 from ipam.api.nested_serializers import (
     NestedIPAddressSerializer, NestedServiceSerializer
 )
-from .. models import VirtualMachineMaintenance, ProviderCredentials,VmAssignedVirtualMachineType, VirtualMachineType
+from .. models import VirtualMachineMaintenance, ProviderCredentials,VmAssignedVirtualMachineType, VirtualMachineType,DomainNames
 from django.contrib.contenttypes.models import ContentType
 from .nested_serializers import * 
 from virtualization.choices import *
@@ -34,13 +34,11 @@ class ClusterSerializer(serializers.ModelSerializer):
         )
         return NestedProviderCredentialsSerializer(provider_credentials, many=True).data
 
-
 class ClusterTypeSerializer(serializers.ModelSerializer):
     class Meta:
         model = ClusterType
         fields = ['id', 'name']
 
-
 #Plugin Data Serializer
 class VirtualMachineMaintenanceSerializer(serializers.ModelSerializer):
     id = serializers.IntegerField(read_only=True)
@@ -63,6 +61,7 @@ class ProviderCredentialsSerializer(serializers.ModelSerializer):
         model = ProviderCredentials
         #fields = ['id','maintenance_window','assigned_object_type','assigned_object_id','assigned_object_type','virtual_machine']
         fields = '__all__'
+
 class VirtualMachineTypeSerializer(serializers.ModelSerializer):
     id = serializers.IntegerField(read_only=True)
     cluster_type = ClusterTypeSerializer(source='assigned_object', read_only=True)
@@ -74,7 +73,6 @@ class VirtualMachineTypeSerializer(serializers.ModelSerializer):
         #fields = ['id','maintenance_window','assigned_object_type','assigned_object_id','assigned_object_type','virtual_machine']
         fields = '__all__'
 
-
 class VmAssignedVirtualMachineTypeSerializer(serializers.ModelSerializer):
     id = serializers.IntegerField(read_only=True)
     virtual_machine = VirtualMachineSerializer(source='assigned_object', read_only=True)
@@ -87,6 +85,17 @@ class VmAssignedVirtualMachineTypeSerializer(serializers.ModelSerializer):
         #fields = ['id','maintenance_window','assigned_object_type','assigned_object_id','assigned_object_type','virtual_machine']
         fields = '__all__'
 
+class DomainNamesSerializer(serializers.ModelSerializer):
+    id = serializers.IntegerField(read_only=True)
+    virtual_machine = VirtualMachineSerializer(source='assigned_object', read_only=True)
+    assigned_object_id = serializers.IntegerField(write_only=True)
+    assigned_object_type = serializers.CharField(write_only=True)
+
+    class Meta:
+        model = DomainNames
+        #fields = ['id','maintenance_window','assigned_object_type','assigned_object_id','assigned_object_type','virtual_machine']
+        fields = '__all__'
+
 class VirtualMachineSerializer(NetBoxModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:virtualmachine-detail')
     status = ChoiceField(choices=VirtualMachineStatusChoices, required=False)
diff --git a/netbox_sys_plugin/api/urls.py b/netbox_sys_plugin/api/urls.py
index 47d606c..51e074e 100644
--- a/netbox_sys_plugin/api/urls.py
+++ b/netbox_sys_plugin/api/urls.py
@@ -6,6 +6,7 @@ router.register(r'MaintenanceWindow', VirtualMachineMaintenanceViewSet)
 router.register(r'ProviderCredentials', ProviderCredentialsViewSet)
 router.register(r'AssignedVmType', VmAssignedVirtualMachineTypeViewSet)
 router.register(r'VmType', VirtualMachineTypeViewSet)
+router.register(r'DomainNames', VirtualMachineTypeViewSet)
 router.register(r'VirtualMachine', VirtualMachineViewSet)
 
 app_name = "netbox_sys_plugin"
diff --git a/netbox_sys_plugin/api/views.py b/netbox_sys_plugin/api/views.py
index 094873d..bc4c63e 100644
--- a/netbox_sys_plugin/api/views.py
+++ b/netbox_sys_plugin/api/views.py
@@ -4,7 +4,7 @@ from rest_framework.response import Response
 from django.db.models import Prefetch
 from netbox.api.viewsets import NetBoxModelViewSet
 from virtualization import filtersets
-from .. models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType
+from .. models import VirtualMachineMaintenance, ProviderCredentials, VmAssignedVirtualMachineType, VirtualMachineType,DomainNames
 from . serializers import *
 
 class VirtualMachineMaintenanceViewSet(viewsets.ModelViewSet):
@@ -12,6 +12,10 @@ class VirtualMachineMaintenanceViewSet(viewsets.ModelViewSet):
     serializer_class = VirtualMachineMaintenanceSerializer
     http_method_names = ["get", "post", "patch", "delete", "options"]
 
+class DomainNamesViewSet(viewsets.ModelViewSet):
+    queryset = VirtualMachineMaintenance.objects.all()
+    serializer_class = DomainNamesSerializer
+    http_method_names = ["get", "post", "patch", "delete", "options"]
 
 class ProviderCredentialsViewSet(viewsets.ModelViewSet):
     queryset = ProviderCredentials.objects.all()
diff --git a/netbox_sys_plugin/forms/machine.py b/netbox_sys_plugin/forms/machine.py
index 4396562..a217ed7 100644
--- a/netbox_sys_plugin/forms/machine.py
+++ b/netbox_sys_plugin/forms/machine.py
@@ -10,8 +10,8 @@ from netbox.forms import (
     NetBoxModelForm,
     NetBoxModelFilterSetForm,
 )
-from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField
-from ..models import VmAssignedVirtualMachineType, VirtualMachineMaintenance, VirtualMachineType
+from utilities.forms.fields import DynamicModelChoiceField, JSONField
+from ..models import VmAssignedVirtualMachineType, VirtualMachineMaintenance, VirtualMachineType, DomainNames
 
 class VmAssignedVmTypeForm(NetBoxModelForm):
     """
@@ -214,4 +214,74 @@ class VmMaitenanceForm(NetBoxModelForm):
 class VmMaintenanceFilterForm(NetBoxModelFilterSetForm):
     """MacAddress filter form definition class"""
 
+    model = VirtualMachineMaintenance
+
+class DomainNamesForm(NetBoxModelForm):
+    """
+    GUI form to add or edit a VM Maitenance.
+    """
+    virtual_machine = DynamicModelChoiceField(
+        queryset=VirtualMachine.objects.none(), required=True, label="Virtual Machine"
+    )
+    domain_names = JSONField(
+        label=_('Domain Names'),
+        required=False
+    )
+
+    def __init__(self, *args, **kwargs):
+        # Initialize helper selectors
+        instance = kwargs.get("instance")
+        initial = kwargs.get("initial", {}).copy()
+        if instance:
+            if isinstance(instance.assigned_object, VirtualMachine):
+                initial["virtual_machine"] = instance.assigned_object
+        kwargs["initial"] = initial
+        super().__init__(*args, **kwargs)
+
+        if instance:
+            current_vm_id = instance.assigned_object.id if instance.assigned_object else None
+        else:
+            current_vm_id = None
+
+        assigned_vms = DomainNames.objects.filter(
+            assigned_object_type=ContentType.objects.get_for_model(VirtualMachine)
+        ).exclude(assigned_object_id=current_vm_id).values_list('assigned_object_id', flat=True)
+        self.fields['virtual_machine'].queryset = VirtualMachine.objects.exclude(id__in=assigned_vms)
+
+    class Meta:
+        """Meta class"""
+        model = DomainNames
+        fields = ( 'virtual_machine','domain_names','tags')
+
+
+    def clean(self):
+        """
+        Validates form inputs before submitting:
+        """
+        super().clean()
+        
+        
+        vm = self.cleaned_data.get("virtual_machine")
+
+        #Check if Virtual Machine is assigned corretly
+        if (not vm):
+            raise ValidationError(
+                {"__all__": "Can't assign more than one Maintenance Window to the same Virtual Machine"},
+            )
+        
+        if self.errors.get("virtual_machine"):
+            return
+
+
+
+    def save(self, *args, **kwargs):
+        # Set assigned object
+        self.instance.assigned_object = (
+            self.cleaned_data.get("virtual_machine") 
+        )
+        return super().save(*args, **kwargs)
+
+class DomainNamesFilterForm(NetBoxModelFilterSetForm):
+    """MacAddress filter form definition class"""
+
     model = VirtualMachineMaintenance
\ No newline at end of file
diff --git a/netbox_sys_plugin/migrations/0006_domainnames.py b/netbox_sys_plugin/migrations/0006_domainnames.py
new file mode 100644
index 0000000..b0fbbf9
--- /dev/null
+++ b/netbox_sys_plugin/migrations/0006_domainnames.py
@@ -0,0 +1,37 @@
+# Generated by Django 4.2.16 on 2024-12-06 12:19
+
+from django.db import migrations, models
+import django.db.models.deletion
+import taggit.managers
+import utilities.json
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('extras', '0098_webhook_custom_field_data_webhook_tags'),
+        ('contenttypes', '0002_remove_content_type_name'),
+        ('netbox_sys_plugin', '0005_remove_providercredentials_provider_url_and_more'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='DomainNames',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
+                ('created', models.DateTimeField(auto_now_add=True, null=True)),
+                ('last_updated', models.DateTimeField(auto_now=True, null=True)),
+                ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
+                ('domain_names', models.JSONField(blank=True, null=True)),
+                ('assigned_object_id', models.PositiveBigIntegerField(blank=True, null=True)),
+                ('assigned_object_type', models.ForeignKey(blank=True, limit_choices_to=models.Q(models.Q(('app_label', 'ipam'), ('model', 'IPAddress'))), null=True, on_delete=django.db.models.deletion.PROTECT, to='contenttypes.contenttype')),
+                ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
+            ],
+            options={
+                'verbose_name': 'Domain Name',
+                'verbose_name_plural': 'Domain Names',
+                'ordering': ['assigned_object_id'],
+                'unique_together': {('assigned_object_id',)},
+            },
+        ),
+    ]
diff --git a/netbox_sys_plugin/migrations/0007_alter_domainnames_assigned_object_type.py b/netbox_sys_plugin/migrations/0007_alter_domainnames_assigned_object_type.py
new file mode 100644
index 0000000..2d74afe
--- /dev/null
+++ b/netbox_sys_plugin/migrations/0007_alter_domainnames_assigned_object_type.py
@@ -0,0 +1,20 @@
+# Generated by Django 4.2.16 on 2024-12-06 14:58
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('contenttypes', '0002_remove_content_type_name'),
+        ('netbox_sys_plugin', '0006_domainnames'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='domainnames',
+            name='assigned_object_type',
+            field=models.ForeignKey(blank=True, limit_choices_to=models.Q(models.Q(('app_label', 'virtualization'), ('model', 'VirtualMachine'))), null=True, on_delete=django.db.models.deletion.PROTECT, to='contenttypes.contenttype'),
+        ),
+    ]
diff --git a/netbox_sys_plugin/models/machine.py b/netbox_sys_plugin/models/machine.py
index 690faa7..f1e195b 100644
--- a/netbox_sys_plugin/models/machine.py
+++ b/netbox_sys_plugin/models/machine.py
@@ -165,3 +165,46 @@ GenericRelation(
     related_query_name="VirtualMachine",
 ).contribute_to_class(VirtualMachine, "VirtualMachineMaintenance")
 
+#Domain Names
+class DomainNames(NetBoxModel):
+    """Cluster ProviderInfo definition class"""
+  
+    domain_names = models.JSONField(
+        blank=True,
+        null=True,
+    )
+
+    assigned_object_type = models.ForeignKey(
+        to=ContentType,
+        limit_choices_to=VM_ASSIGNMENT_MODELS,
+        on_delete=models.PROTECT,
+        null=True,
+        blank=True,
+    )
+    assigned_object_id = models.PositiveBigIntegerField(null=True, blank=True)
+    assigned_object = GenericForeignKey(
+        ct_field="assigned_object_type",
+        fk_field="assigned_object_id",
+    )
+
+    class Meta:
+        """Meta class"""
+        unique_together = ["assigned_object_id"]
+        ordering = ["assigned_object_id"]
+        verbose_name = "Domain Name"
+        verbose_name_plural = "Domain Names"
+
+
+    def __str__(self):
+        return f"for Virtual Machine Address {self.assigned_object_id}"
+
+    def get_absolute_url(self):
+        """override"""
+        return reverse("plugins:netbox_sys_plugin:domainnames", args=[self.pk])
+
+GenericRelation(
+    to=DomainNames,
+    content_type_field="assigned_object_type",
+    object_id_field="assigned_object_id",
+    related_query_name="VirtualMachine",
+).contribute_to_class(VirtualMachine, "DomainNames")
\ No newline at end of file
diff --git a/netbox_sys_plugin/navigation.py b/netbox_sys_plugin/navigation.py
index bd6fefc..ca9245d 100644
--- a/netbox_sys_plugin/navigation.py
+++ b/netbox_sys_plugin/navigation.py
@@ -57,6 +57,17 @@ create_vm_buttons = [
     ),
 ]
 
+create_domainnames_buttons = [
+    PluginMenuButton(
+        link="plugins:netbox_sys_plugin:domainnames_add",
+        title="Add",
+        icon_class="mdi mdi-plus-thick",
+        color=ButtonColorChoices.GREEN,
+        permissions=["netbox_sys_plugin.add_domainnames"],
+    ),
+]
+
+
 #Items
 
 clusterProviderCredentialsItem = [
@@ -87,6 +98,12 @@ vmMachineItem = [
         buttons=vm_maintenance_buttons,
         permissions=["netbox_sys_plugin.list_maintenance"],
     ),
+    PluginMenuItem(
+        link="plugins:netbox_sys_plugin:domainnames_list",
+        link_text="VM Domain Names",
+        buttons=create_domainnames_buttons,
+        permissions=["netbox_sys_plugin.add_domainnames"],
+    ),
     PluginMenuItem(
         link="plugins:netbox_sys_plugin:creatvm_add",
         link_text="Create VM",
diff --git a/netbox_sys_plugin/tables.py b/netbox_sys_plugin/tables.py
index 374b9b2..a922227 100644
--- a/netbox_sys_plugin/tables.py
+++ b/netbox_sys_plugin/tables.py
@@ -145,3 +145,34 @@ class VmTypeTable(NetBoxTable):
         )
 
         default_columns = ('id','virtual_machine_type_name','assigned_object','virtual_machine_type_desc')
+
+#DomainNames
+class DomainNamesTable(NetBoxTable):
+    """VM Maintenance Table definition class"""
+
+    pk = columns.ToggleColumn()
+    id = tables.Column(
+        linkify=True,
+    )
+    
+    assigned_object = tables.Column(
+        linkify=True,
+        orderable=False,
+        verbose_name="Virtual Machine",
+    )
+
+    domain_names = tables.Column(
+        linkify=True,
+        verbose_name="Domain Names",
+    )
+
+    class Meta(NetBoxTable.Meta):
+        """Meta class"""  
+        model = VirtualMachineType
+        fields = (
+            "pk",
+            "id",
+            "domain_names",
+        )
+
+        default_columns = ('id','domain_names','assigned_object')
\ No newline at end of file
diff --git a/netbox_sys_plugin/template_content.py b/netbox_sys_plugin/template_content.py
index 4400d88..99b8625 100644
--- a/netbox_sys_plugin/template_content.py
+++ b/netbox_sys_plugin/template_content.py
@@ -15,6 +15,15 @@ class VmTable(PluginTemplateExtension):
     def right_page(self):
         return self.render('netbox_sys_plugin/vm_vmassignedvirtualmachinetype.html', extra_context={'vmassigntypeinfo': models.VmAssignedVirtualMachineType.objects.filter(VirtualMachine=self.context['object'])})
 
+class VmTableDomainNames(PluginTemplateExtension):
+    """VM Domain names object template"""
+
+    model = 'virtualization.virtualmachine'
+    
+    def left_page(self):
+        return self.render('netbox_sys_plugin/vm_domainnames.html', extra_context={'domainnamesinfo': models.DomainNames.objects.filter(VirtualMachine=self.context['object'])})
+
+
 class VmTypeTable(PluginTemplateExtension):
     """Cluster object template"""
 
@@ -33,4 +42,4 @@ class ProviderCredentialsTable(PluginTemplateExtension):
         return self.render('netbox_sys_plugin/cluster_providercredentials.html', extra_context={'providercredentialsinfo': models.ProviderCredentials.objects.filter(Cluster=self.context['object'])})
 
 
-template_extensions = [VmTable,ProviderCredentialsTable,VmTypeTable]
+template_extensions = [VmTable,ProviderCredentialsTable,VmTypeTable,VmTableDomainNames]
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/domainnames.html b/netbox_sys_plugin/templates/netbox_sys_plugin/domainnames.html
new file mode 100644
index 0000000..a203221
--- /dev/null
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/domainnames.html
@@ -0,0 +1,46 @@
+{% extends 'generic/object.html' %}
+
+{% block title%}
+{{ object.assigned_object }} Domain Names
+{% endblock title%}
+
+{% block content %}
+<div class="row mb-3">
+  <div class="col col-md-6">
+    <div class="card">
+      <h5 class="card-header">Virtual Machine Maintenance</h5>
+      
+      <div class="card-body">
+        <table class="table table-hover attr-table">
+          <tr>
+            <th scope="row">Domain Names</th>
+            <td>{% include 'extras/inc/configcontext_data.html' with data=object.domain_names format=json %}</td>
+            
+          </tr>
+        </table>
+      </div>
+    </div>
+    {% include 'inc/panels/custom_fields.html' %}
+    {% include 'inc/panels/tags.html' %}
+  </div>
+  <div class="col col-md-6">
+    <div class="card">
+      <h5 class="card-header">Details</h5>
+      <div class="card-body">
+        <table class="table table-hover attr-table">
+          <tr>
+            <th scope="row">{{ object.assigned_object_type|cut:"virtualization | " }}</th>
+            <td>
+              {% if object.assigned_object %}
+                <a href="{{ object.assigned_object.get_absolute_url }}">{{ object.assigned_object }}</a>
+              {% else %}
+                {{ ''|placeholder }}
+              {% endif %}
+            </td>
+          </tr>
+        </table>
+      </div>
+    </div>
+  </div>
+</div>
+{% endblock content %}
diff --git a/netbox_sys_plugin/templates/netbox_sys_plugin/vm_domainnames.html b/netbox_sys_plugin/templates/netbox_sys_plugin/vm_domainnames.html
new file mode 100644
index 0000000..0293a29
--- /dev/null
+++ b/netbox_sys_plugin/templates/netbox_sys_plugin/vm_domainnames.html
@@ -0,0 +1,33 @@
+{% block content %}
+  <div class="row mb-3">
+    <div class="col col-md-12">
+      <div class="card">
+        <h5 class="card-header">Domain Names Information</h5>
+        <div class="card-body">
+          <table class="table table-responsive">
+              <th scope="row">ID</th>
+              <th scope="row">Domain Names</th>
+              {% for domainnames in domainnamesinfo %}
+        <tr>
+            <td>{{ domainnames.id }}</td>
+            <td>
+              {% if domainnames.domain_names %}
+                <a href="{{ domainnames.get_absolute_url }}">{{ domainnames.domain_names }}</a>
+              {% else %}
+                {{ ''|placeholder }}
+              {% endif %}
+            </td>
+        </tr>
+        {% empty %}
+        <tr>
+            <td colspan="3">No domain names found.</td>
+        </tr>
+        {% endfor %}
+            </tr>
+          </table>
+        </div>
+      </div>
+      {% include 'inc/panels/custom_fields.html' %}
+    </div>
+  </div>
+{% endblock content %}
\ No newline at end of file
diff --git a/netbox_sys_plugin/urls.py b/netbox_sys_plugin/urls.py
index 299078a..c43d9b1 100644
--- a/netbox_sys_plugin/urls.py
+++ b/netbox_sys_plugin/urls.py
@@ -38,10 +38,15 @@ urlpatterns = (
     path('vm-type/<int:pk>/delete/', views.VmTypeDeleteView.as_view(), name='virtualmachinetype_delete'),
     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}),
-
+    #DomainNames
+    path('domain-names/<int:pk>/', views.DomainNamesView.as_view(), name='domainnames'),
+    path('domain-names/', views.DomainNamesListView.as_view(), name='domainnames_list'),
+    path('domain-names/add/', views.DomainNamesEditView.as_view(), name='domainnames_add'),
+    path('domain-names/<int:pk>/edit/', views.DomainNamesEditView.as_view(), name='domainnames_edit'),
+    path('domain-names/<int:pk>/delete/', views.DomainNamesDeleteView.as_view(), name='domainnames_delete'),
+    path('domain-names/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='domainnames_changelog', kwargs={'model': models.DomainNames}),
+    path('domain-names/<int:pk>/journal/', ObjectJournalView.as_view(), name='domainnames_journal', kwargs={'model': models.DomainNames}),
     #CreateVM
-    
     path('create-vm/', CreateVmView.as_view(), name='creatvm_add'),
 
-
 )
diff --git a/netbox_sys_plugin/views.py b/netbox_sys_plugin/views.py
index fd8bbc9..0b1f59b 100644
--- a/netbox_sys_plugin/views.py
+++ b/netbox_sys_plugin/views.py
@@ -141,4 +141,33 @@ class CreateVmView(generic.ObjectView):
             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
+        return render(request, self.template_name, {'form': form})
+    
+#Domain Names
+class  DomainNamesView(generic.ObjectView):
+
+    """ Domain names view definition"""
+
+    queryset = (
+        models.DomainNames.objects.all()
+    )
+
+class DomainNamesListView(generic.ObjectListView):
+    """Domain Names list view definition"""
+
+    queryset = models.DomainNames.objects.all()
+    table = tables.DomainNamesTable
+    #filterset = filtersets.VmTypeFilterSet
+    filterset_form = forms.DomainNamesFilterForm
+
+class DomainNamesEditView(generic.ObjectEditView):
+    """
+    Defines the edit view for the Domain Names django model.
+    """
+    queryset = models.DomainNames.objects.all()
+    form = forms.DomainNamesForm
+
+class DomainNamesDeleteView(generic.ObjectDeleteView):
+    """Domain Names delete view definition"""
+
+    queryset = models.DomainNames.objects.all()
\ No newline at end of file
-- 
GitLab