normalized project structure

This commit is contained in:
Hedde van der Heide
2022-04-22 13:52:26 +02:00
parent 7a24221444
commit 9d76bb421d
26 changed files with 0 additions and 13 deletions
+17
View File
@@ -0,0 +1,17 @@
from extras.plugins import PluginConfig
class SLMConfig(PluginConfig):
name = 'netbox_slm'
verbose_name = 'Software Lifecycle Management'
description = 'Software Lifecycle Management'
version = '0.99'
author = 'Hedde van der Heide'
author_email = 'hedde.vanderheide@ictu.nl'
base_url = 'slm'
required_settings = []
default_settings = {
'version_info': False
}
config = SLMConfig
+14
View File
@@ -0,0 +1,14 @@
from django.contrib import admin
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion
class SoftwareProductVersionInline(admin.TabularInline):
model = SoftwareProductVersion
fields = ('name',)
extra = 0
@admin.register(SoftwareProduct)
class SoftwareProductAdmin(admin.ModelAdmin):
list_display = ('name',)
inlines = (SoftwareProductVersionInline,)
View File
+55
View File
@@ -0,0 +1,55 @@
from rest_framework import serializers
from netbox.api.serializers import NetBoxModelSerializer
from netbox_slm.models import (
SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation,
)
class SoftwareProductSerializer(NetBoxModelSerializer):
display = serializers.SerializerMethodField()
url = serializers.HyperlinkedIdentityField(
view_name="plugins-api:netbox_slm-api:softwareproduct-detail"
)
class Meta:
model = SoftwareProduct
fields = [
'id', 'display', 'url', 'name', 'tags', 'custom_field_data', 'created', 'last_updated',
]
def get_display(self, obj):
return f"{obj.manufacturer.name} - {obj.name}"
class SoftwareProductVersionSerializer(NetBoxModelSerializer):
display = serializers.SerializerMethodField()
url = serializers.HyperlinkedIdentityField(
view_name="plugins-api:netbox_slm-api:softwareproductversion-detail"
)
class Meta:
model = SoftwareProductVersion
fields = [
'id', 'display', 'url', 'name', 'software_product', 'tags', 'custom_field_data', 'created', 'last_updated',
]
def get_display(self, obj):
return obj.name
class SoftwareProductInstallationSerializer(NetBoxModelSerializer):
display = serializers.SerializerMethodField()
url = serializers.HyperlinkedIdentityField(
view_name="plugins-api:netbox_slm-api:softwareproductinstallation-detail"
)
class Meta:
model = SoftwareProductInstallation
fields = [
'id', 'display', 'url', 'device', 'virtualmachine', 'software_product', 'version', 'tags',
'custom_field_data', 'created', 'last_updated',
]
def get_display(self, obj):
return obj
+15
View File
@@ -0,0 +1,15 @@
from netbox.api.routers import NetBoxRouter
from netbox_slm.api.views import (
NetboxSLMRootView,
SoftwareProductViewSet,
SoftwareProductVersionViewSet,
SoftwareProductInstallationViewSet,
)
router = NetBoxRouter()
router.APIRootView = NetboxSLMRootView
router.register("softwareproducts", SoftwareProductViewSet)
router.register("softwareproductversions", SoftwareProductVersionViewSet)
router.register("softwareproductinstallations", SoftwareProductInstallationViewSet)
urlpatterns = router.urls
+39
View File
@@ -0,0 +1,39 @@
from netbox.api.viewsets import NetBoxModelViewSet
from rest_framework.routers import APIRootView
from netbox_slm.api.serializers import (
SoftwareProductSerializer, SoftwareProductVersionSerializer, SoftwareProductInstallationSerializer,
)
from netbox_slm.filtersets import (
SoftwareProductFilterSet, SoftwareProductVersionFilterSet, SoftwareProductInstallationFilterSet,
)
from netbox_slm.models import (
SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation,
)
class NetboxSLMRootView(APIRootView):
"""
NetboxSLM API root view
"""
def get_view_name(self):
return "NetboxSLM"
class SoftwareProductViewSet(NetBoxModelViewSet):
queryset = SoftwareProduct.objects.all()
serializer_class = SoftwareProductSerializer
filterset_class = SoftwareProductFilterSet
class SoftwareProductVersionViewSet(NetBoxModelViewSet):
queryset = SoftwareProductVersion.objects.all()
serializer_class = SoftwareProductVersionSerializer
filterset_class = SoftwareProductVersionFilterSet
class SoftwareProductInstallationViewSet(NetBoxModelViewSet):
queryset = SoftwareProductInstallation.objects.all()
serializer_class = SoftwareProductInstallationSerializer
filterset_class = SoftwareProductInstallationFilterSet
+23
View File
@@ -0,0 +1,23 @@
from netbox.filtersets import NetBoxModelFilterSet
from netbox_slm.models import *
class SoftwareProductFilterSet(NetBoxModelFilterSet):
"""Filter capabilities for SoftwareProduct instances."""
class Meta:
model = SoftwareProduct
fields = tuple()
class SoftwareProductVersionFilterSet(NetBoxModelFilterSet):
"""Filter capabilities for SoftwareProductVersion instances."""
class Meta:
model = SoftwareProductVersion
fields = tuple()
class SoftwareProductInstallationFilterSet(NetBoxModelFilterSet):
"""Filter capabilities for SoftwareProductInstallation instances."""
class Meta:
model = SoftwareProductInstallation
fields = tuple()
+201
View File
@@ -0,0 +1,201 @@
from django import forms
from django.db.models import Q
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from dcim.models import Manufacturer, Device
from netbox.forms import (
NetBoxModelForm,
NetBoxModelCSVForm,
NetBoxModelBulkEditForm,
NetBoxModelFilterSetForm,
)
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation
from utilities.forms import (
DynamicModelChoiceField, APISelect, TagFilterField
)
from virtualization.models import VirtualMachine
class SoftwareProductForm(NetBoxModelForm):
"""Form for creating a new SoftwareProduct object."""
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
required=False,
)
class Meta:
model = SoftwareProduct
fields = ("name", "manufacturer", "description", "tags")
class SoftwareProductFilterForm(NetBoxModelFilterSetForm):
model = SoftwareProduct
fieldsets = (
(None, ('q', 'tag')),
)
tag = TagFilterField(model)
def search(self, queryset, name, value):
"""Perform the filtered search."""
if not value.strip():
return queryset
qs_filter = Q(name__icontains=value) | \
Q(manufacturer__name__icontains=value)
return queryset.filter(qs_filter)
class SoftwareProductCSVForm(NetBoxModelCSVForm):
class Meta:
model = SoftwareProduct
fields = ("name", "manufacturer",)
class SoftwareProductBulkEditForm(NetBoxModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=SoftwareProduct.objects.all(),
widget=forms.MultipleHiddenInput(),
)
class Meta:
model = SoftwareProduct
nullable_fields = []
class SoftwareProductVersionForm(NetBoxModelForm):
"""Form for creating a new SoftwareProductVersion object."""
name = forms.CharField(label=_("Version"))
software_product = DynamicModelChoiceField(
queryset=SoftwareProduct.objects.all(),
widget=APISelect(
attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}
),
)
class Meta:
model = SoftwareProductVersion
fields = ("name", "software_product", "tags")
class SoftwareProductVersionFilterForm(NetBoxModelFilterSetForm):
model = SoftwareProductVersion
fieldsets = (
(None, ('q', 'tag')),
)
tag = TagFilterField(model)
def search(self, queryset, name, value):
"""Perform the filtered search."""
if not value.strip():
return queryset
qs_filter = Q(name__icontains=value) | \
Q(software_product__name__icontains=value) | \
Q(software_product__manufacturer__name__icontains=value)
return queryset.filter(qs_filter)
class SoftwareProductVersionCSVForm(NetBoxModelCSVForm):
class Meta:
model = SoftwareProductVersion
fields = ("name",)
class SoftwareProductVersionBulkEditForm(NetBoxModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=SoftwareProduct.objects.all(),
widget=forms.MultipleHiddenInput(),
)
class Meta:
model = SoftwareProductVersion
nullable_fields = []
class SoftwareProductInstallationForm(NetBoxModelForm):
"""Form for creating a new SoftwareProductInstallation object."""
device = DynamicModelChoiceField(
queryset=Device.objects.all(),
required=False,
)
virtualmachine = DynamicModelChoiceField(
queryset=VirtualMachine.objects.all(),
required=False,
)
software_product = DynamicModelChoiceField(
queryset=SoftwareProduct.objects.all(),
required=True,
widget=APISelect(
attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproduct-list")}
),
)
version = DynamicModelChoiceField(
queryset=SoftwareProductVersion.objects.all(),
required=True,
widget=APISelect(
attrs={"data-url": reverse_lazy("plugins-api:netbox_slm-api:softwareproductversion-list")}
),
query_params={
'software_product': '$software_product',
}
)
class Meta:
model = SoftwareProductInstallation
fields = ("device", "virtualmachine", "software_product", "version", "tags")
def clean_version(self):
version = self.cleaned_data['version']
software_product = self.cleaned_data['software_product']
if version not in software_product.softwareproductversion_set.all():
raise forms.ValidationError(_(f"Version `{version}` doesn't exist on {software_product}, make sure you've "
f"selected a compatible version or first select the software product."))
return version
def clean(self):
if not any([self.cleaned_data['device'], self.cleaned_data['virtualmachine']]):
raise forms.ValidationError(_("Installation requires atleast one virtualmachine or device destination."))
return super(SoftwareProductInstallationForm, self).clean()
class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm):
model = SoftwareProductInstallation
fieldsets = (
(None, ('q', 'tag')),
)
tag = TagFilterField(model)
def search(self, queryset, name, value):
"""Perform the filtered search."""
if not value.strip():
return queryset
qs_filter = Q(software_product__name__icontains=value) | \
Q(software_product__manufacturer__name__icontains=value) | \
Q(version__name__icontains=value)
return queryset.filter(qs_filter)
class SoftwareProductInstallationCSVForm(NetBoxModelCSVForm):
class Meta:
model = SoftwareProductInstallation
fields = tuple()
class SoftwareProductInstallationBulkEditForm(NetBoxModelBulkEditForm):
software_product = DynamicModelChoiceField(
queryset=SoftwareProduct.objects.all(),
required=False
)
version = DynamicModelChoiceField(
queryset=SoftwareProductVersion.objects.all(),
required=False
)
model = SoftwareProductInstallation
fieldsets = (
(None, ('software_product', 'version',)),
)
+68
View File
@@ -0,0 +1,68 @@
# Generated by Django 3.2.8 on 2021-12-17 10:11
import django.core.serializers.json
from django.db import migrations, models
import django.db.models.deletion
import taggit.managers
class Migration(migrations.Migration):
initial = True
dependencies = [
('virtualization', '0023_virtualmachine_natural_ordering'),
('extras', '0062_clear_secrets_changelog'),
('dcim', '0133_port_colors'),
]
operations = [
migrations.CreateModel(
name='SoftwareProduct',
fields=[
('created', models.DateField(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=django.core.serializers.json.DjangoJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=128)),
('description', models.CharField(blank=True, max_length=255, null=True)),
('manufacturer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='software_products', to='dcim.manufacturer')),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='SoftwareProductVersion',
fields=[
('created', models.DateField(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=django.core.serializers.json.DjangoJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('software_product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='softwareproduct_versions', to='netbox_slm.softwareproduct')),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='SoftwareProductInstallation',
fields=[
('created', models.DateField(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=django.core.serializers.json.DjangoJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('device', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='softwareproduct_installations', to='dcim.device')),
('software_product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='software_products', to='netbox_slm.softwareproduct')),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
('version', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='softwareproduct_versions', to='netbox_slm.softwareproductversion')),
('virtualmachine', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='softwareproduct_installations', to='virtualization.virtualmachine')),
],
options={
'abstract': False,
},
),
]
@@ -0,0 +1,20 @@
# Generated by Django 3.2.9 on 2022-01-24 08:05
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0133_port_colors'),
('netbox_slm', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='softwareproduct',
name='manufacturer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='software_products', to='dcim.manufacturer'),
),
]
@@ -0,0 +1,43 @@
# Generated by Django 3.2.9 on 2022-04-07 12:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('netbox_slm', '0002_alter_softwareproduct_manufacturer'),
]
operations = [
migrations.AlterField(
model_name='softwareproduct',
name='created',
field=models.DateTimeField(auto_now_add=True, null=True),
),
migrations.AlterField(
model_name='softwareproduct',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='softwareproductinstallation',
name='created',
field=models.DateTimeField(auto_now_add=True, null=True),
),
migrations.AlterField(
model_name='softwareproductinstallation',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='softwareproductversion',
name='created',
field=models.DateTimeField(auto_now_add=True, null=True),
),
migrations.AlterField(
model_name='softwareproductversion',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
),
]
@@ -0,0 +1,46 @@
# Generated by Django 3.2.9 on 2022-04-15 07:05
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0153_created_datetimefield'),
('virtualization', '0029_created_datetimefield'),
('netbox_slm', '0003_auto_20220407_1209'),
]
operations = [
migrations.AlterField(
model_name='softwareproduct',
name='manufacturer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dcim.manufacturer'),
),
migrations.AlterField(
model_name='softwareproductinstallation',
name='device',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dcim.device'),
),
migrations.AlterField(
model_name='softwareproductinstallation',
name='software_product',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='netbox_slm.softwareproduct'),
),
migrations.AlterField(
model_name='softwareproductinstallation',
name='version',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='netbox_slm.softwareproductversion'),
),
migrations.AlterField(
model_name='softwareproductinstallation',
name='virtualmachine',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='virtualization.virtualmachine'),
),
migrations.AlterField(
model_name='softwareproductversion',
name='software_product',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='netbox_slm.softwareproduct'),
),
]
View File
+92
View File
@@ -0,0 +1,92 @@
from django.db import models
from django.urls import reverse, reverse_lazy
from django.utils import safestring
from netbox.models import NetBoxModel
from utilities.querysets import RestrictedQuerySet
class SoftwareProduct(NetBoxModel):
name = models.CharField(max_length=128)
description = models.CharField(max_length=255, null=True, blank=True)
manufacturer = models.ForeignKey(
to='dcim.Manufacturer',
on_delete=models.PROTECT,
null=True, blank=True
)
objects = RestrictedQuerySet.as_manager()
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("plugins:netbox_slm:softwareproduct", kwargs={"pk": self.pk})
def get_installation_count(self):
count = SoftwareProductInstallation.objects.filter(software_product_id=self.pk).count()
return safestring.mark_safe("<a href=\"{url}\">{count}</a>".format(
url=reverse_lazy("plugins:netbox_slm:softwareproductinstallation_list") + f"?q={self.name}",
count=count
)) if count else "0"
class SoftwareProductVersion(NetBoxModel):
software_product = models.ForeignKey(
to='netbox_slm.SoftwareProduct',
on_delete=models.PROTECT,
)
name = models.CharField(max_length=64)
objects = RestrictedQuerySet.as_manager()
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("plugins:netbox_slm:softwareproductversion", kwargs={"pk": self.pk})
def get_installation_count(self):
count = SoftwareProductInstallation.objects.filter(version_id=self.pk).count()
return safestring.mark_safe("<a href=\"{url}\">{count}</a>".format(
url=reverse_lazy("plugins:netbox_slm:softwareproductinstallation_list") + f"?q={self.name}",
count=count
)) if count else "0"
class SoftwareProductInstallation(NetBoxModel):
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.PROTECT,
null=True,
blank=True
)
virtualmachine = models.ForeignKey(
to='virtualization.VirtualMachine',
on_delete=models.PROTECT,
null=True,
blank=True
)
software_product = models.ForeignKey(
to='netbox_slm.SoftwareProduct',
on_delete=models.PROTECT,
)
version = models.ForeignKey(
to='netbox_slm.SoftwareProductVersion',
on_delete=models.PROTECT,
)
objects = RestrictedQuerySet.as_manager()
def __str__(self):
return f"{self.pk}"
def get_absolute_url(self):
return reverse("plugins:netbox_slm:softwareproductinstallation", kwargs={"pk": self.pk})
def get_platform(self):
return self.device or self.virtualmachine
def render_type(self):
return f"{'device' if self.device else 'virtualmachine'}"
+66
View File
@@ -0,0 +1,66 @@
from extras.plugins import PluginMenuButton, PluginMenuItem
from utilities.choices import ButtonColorChoices
menu_items = (
PluginMenuItem(
link='plugins:netbox_slm:softwareproduct_list',
link_text='Software Products',
buttons=(
PluginMenuButton(
"plugins:netbox_slm:softwareproduct_add",
"Add",
"mdi mdi-plus-thick",
ButtonColorChoices.GREEN,
permissions=["netbox_slm.add_softwareproduct"],
),
PluginMenuButton(
"plugins:netbox_slm:softwareproduct_import",
"Import",
"mdi mdi-upload",
ButtonColorChoices.CYAN,
permissions=["netbox_slm.add_softwareproduct"],
),
)
),
PluginMenuItem(
link='plugins:netbox_slm:softwareproductversion_list',
link_text='Versions',
buttons=(
PluginMenuButton(
"plugins:netbox_slm:softwareproductversion_add",
"Add",
"mdi mdi-plus-thick",
ButtonColorChoices.GREEN,
permissions=["netbox_slm.add_softwareproductversion"],
),
PluginMenuButton(
"plugins:netbox_slm:softwareproductversion_import",
"Import",
"mdi mdi-upload",
ButtonColorChoices.CYAN,
permissions=["netbox_slm.add_softwareproductversion"],
),
)
),
PluginMenuItem(
link='plugins:netbox_slm:softwareproductinstallation_list',
link_text='Installations',
buttons=(
PluginMenuButton(
"plugins:netbox_slm:softwareproductinstallation_add",
"Add",
"mdi mdi-plus-thick",
ButtonColorChoices.GREEN,
permissions=["netbox_slm.add_softwareproductinstallation"],
),
PluginMenuButton(
"plugins:netbox_slm:softwareproductinstallation_import",
"Import",
"mdi mdi-upload",
ButtonColorChoices.CYAN,
permissions=["netbox_slm.add_softwareproductinstallation"],
),
)
),
)
+167
View File
@@ -0,0 +1,167 @@
import django_tables2 as tables
from django.db.models import Count
from django_tables2.utils import Accessor
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation
from netbox.tables import NetBoxTable, ChoiceFieldColumn, ToggleColumn, columns
class SoftwareProductTable(NetBoxTable):
"""Table for displaying SoftwareProduct objects."""
pk = ToggleColumn()
name = tables.LinkColumn()
manufacturer = tables.Column(
accessor=Accessor('manufacturer'),
linkify=True
)
installations = tables.Column(accessor='get_installation_count', verbose_name='Installations')
tags = columns.TagColumn(
url_name="plugins:netbox_slm:softwareproduct_list",
)
class Meta(NetBoxTable.Meta):
model = SoftwareProduct
fields = (
"pk",
"name",
"manufacturer",
"description",
"installations",
"tags",
)
default_columns = (
"pk",
"name",
"manufacturer",
"description",
"installations",
"tags",
)
sequence = (
"manufacturer",
"name",
"description",
"installations",
)
def order_installations(self, queryset, is_descending):
queryset = queryset.annotate(
count=Count('softwareproductinstallation__id')
).order_by(("-" if is_descending else "") + "count")
return queryset, True
class SoftwareProductVersionTable(NetBoxTable):
"""Table for displaying SoftwareProductVersion objects."""
pk = ToggleColumn()
name = tables.LinkColumn(verbose_name='Version')
software_product = tables.Column(
accessor=Accessor('software_product'),
linkify=True
)
manufacturer = tables.Column(
accessor=Accessor('software_product__manufacturer'),
linkify=True
)
installations = tables.Column(accessor='get_installation_count', verbose_name='Installations')
tags = columns.TagColumn(
url_name="plugins:netbox_slm:softwareproductversion_list",
)
class Meta(NetBoxTable.Meta):
model = SoftwareProductVersion
fields = (
"pk",
"name",
"software_product",
"manufacturer",
"installations",
"tags",
)
default_columns = (
"pk",
"name",
"software_product",
"manufacturer",
"installations",
"tags",
)
sequence = (
"manufacturer",
"software_product",
"name",
"installations",
)
def order_installations(self, queryset, is_descending):
queryset = queryset.annotate(
count=Count('softwareproductinstallation__id')
).order_by(("-" if is_descending else "") + "count")
return queryset, True
class SoftwareProductInstallationTable(NetBoxTable):
"""Table for displaying SoftwareProductInstallation objects."""
pk = ToggleColumn()
name = tables.LinkColumn()
device = tables.Column(
accessor=Accessor('device'),
linkify=True
)
virtualmachine = tables.Column(
accessor=Accessor('virtualmachine'),
linkify=True
)
platform = tables.Column(
accessor='get_platform',
linkify=True
)
type = tables.Column(accessor='render_type')
software_product = tables.Column(
accessor=Accessor('software_product'),
linkify=True
)
version = tables.Column(
accessor=Accessor('version'),
linkify=True
)
tags = columns.TagColumn(
url_name="plugins:netbox_slm:softwareproductinstallation_list",
)
class Meta(NetBoxTable.Meta):
model = SoftwareProductInstallation
fields = (
"pk",
"name",
"platform",
"type",
"software_product",
"version",
"tags",
)
default_columns = (
"pk",
"platform",
"type",
"software_product",
"version",
"tags",
)
def order_platform(self, queryset, is_descending):
queryset = queryset.order_by(("device" if is_descending else "virtualmachine"))
return queryset, True
def order_type(self, queryset, is_descending):
queryset = queryset.order_by(("device" if is_descending else "virtualmachine"))
return queryset, True
def render_software_product(self, value, **kwargs):
return f"{kwargs['record'].software_product.manufacturer.name} - {value}"
@@ -0,0 +1,46 @@
{% extends 'generic/object.html' %}
{% load buttons %}
{% load static %}
{% load helpers %}
{% load plugins %}
{% block content %}
<div class="row my-3">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">
Software Product
</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">Name</th>
<td>{{ object }}</td>
</tr>
<tr>
<th scope="row">Versions</th>
<td>
{% for version in object.softwareproduct_versions.all %}
<a href="{% url 'plugins:netbox_slm:softwareproductversion' pk=version.pk %}">
<span class="badge" style="color: #ffffff; background-color: #9e9e9e">{{ version }}</span>
</a>
{% empty %}
n/a
{% endfor %}
</td>
</tr>
</table>
</div>
</div>
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{# {% include 'inc/panels/comments.html' %}#}
{% plugin_left_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}
@@ -0,0 +1,49 @@
{% extends 'generic/object.html' %}
{% load buttons %}
{% load static %}
{% load helpers %}
{% load plugins %}
{% block content %}
<div class="row my-3">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">
Software Product Installation
</h5>
<div class="card-body">
<table class="table table-hover attr-table">
{% if object.device %}
<tr>
<th scope="row">Device</th>
<td>{{ object.device }}</td>
</tr>
{% else %}
<tr>
<th scope="row">Virtualmachine</th>
<td>{{ object.virtualmachine }}</td>
</tr>
{% endif %}
<tr>
<th scope="row">Software Product</th>
<td>{{ object.software_product }}</td>
</tr>
<tr>
<th scope="row">Version</th>
<td>{{ object.version }}</td>
</tr>
</table>
</div>
</div>
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{# {% include 'inc/panels/comments.html' %}#}
{% plugin_left_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}
@@ -0,0 +1,40 @@
{% extends 'generic/object.html' %}
{% load buttons %}
{% load static %}
{% load helpers %}
{% load plugins %}
{% block content %}
<div class="row my-3">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">
Software Product Version
</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">Name</th>
<td>{{ object.name }}</td>
</tr>
<tr>
<th scope="row">Installations</th>
<td>
{{ object.get_installation_count }}
</td>
</tr>
</table>
</div>
</div>
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{# {% include 'inc/panels/comments.html' %}#}
{% plugin_left_page object %}
</div>
</div>
<div class="row">
<div class="col col-md-12">
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}
View File
+68
View File
@@ -0,0 +1,68 @@
from django.urls import path
from netbox.views.generic import ObjectChangeLogView
from netbox_slm import views
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation
urlpatterns = [
# Software Products
path("software-products/", views.SoftwareProductListView.as_view(), name='softwareproduct_list'),
path("software-products/add/", views.SoftwareProductEditView.as_view(), name='softwareproduct_add'),
path("software-products/import/", views.SoftwareProductBulkImportView.as_view(), name="softwareproduct_import"),
path("software-products/edit/", views.SoftwareProductBulkEditView.as_view(), name="softwareproduct_bulk_edit"),
path("software-products/delete/", views.SoftwareProductBulkDeleteView.as_view(),
name="softwareproduct_bulk_delete"),
path("software-products/<int:pk>/", views.SoftwareProductView.as_view(), name="softwareproduct"),
path("software-products/<int:pk>/delete/", views.SoftwareProductDeleteView.as_view(),
name="softwareproduct_delete"),
path("software-products/<int:pk>/edit/", views.SoftwareProductEditView.as_view(), name="softwareproduct_edit"),
path(
"software-products/<int:pk>/changelog/",
ObjectChangeLogView.as_view(),
name="softwareproduct_changelog",
kwargs={"model": SoftwareProduct},
),
# Software Product Versions
path("versions/", views.SoftwareProductVersionListView.as_view(), name='softwareproductversion_list'),
path("versions/add/", views.SoftwareProductVersionEditView.as_view(), name='softwareproductversion_add'),
path("versions/import/", views.SoftwareProductVersionBulkImportView.as_view(),
name="softwareproductversion_import"),
path("versions/edit/", views.SoftwareProductVersionBulkEditView.as_view(), name="softwareproductversion_bulk_edit"),
path("versions/delete/", views.SoftwareProductVersionBulkDeleteView.as_view(),
name="softwareproductversion_bulk_delete"),
path("versions/<int:pk>/", views.SoftwareProductVersionView.as_view(), name="softwareproductversion"),
path("versions/<int:pk>/delete/", views.SoftwareProductVersionDeleteView.as_view(),
name="softwareproductversion_delete"),
path("versions/<int:pk>/edit/", views.SoftwareProductVersionEditView.as_view(), name="softwareproductversion_edit"),
path(
"versions/<int:pk>/changelog/",
ObjectChangeLogView.as_view(),
name="softwareproductversion_changelog",
kwargs={"model": SoftwareProductVersion},
),
# Software Product Versions
path("installations/", views.SoftwareProductInstallationListView.as_view(),
name='softwareproductinstallation_list'),
path("installations/add/", views.SoftwareProductInstallationEditView.as_view(),
name='softwareproductinstallation_add'),
path("installations/import/", views.SoftwareProductInstallationBulkImportView.as_view(),
name="softwareproductinstallation_import"),
path("installations/edit/", views.SoftwareProductInstallationBulkEditView.as_view(),
name="softwareproductinstallation_bulk_edit"),
path("installations/delete/", views.SoftwareProductInstallationBulkDeleteView.as_view(),
name="softwareproductinstallation_bulk_delete"),
path("installations/<int:pk>/", views.SoftwareProductInstallationView.as_view(),
name="softwareproductinstallation"),
path("installations/<int:pk>/delete/", views.SoftwareProductInstallationDeleteView.as_view(),
name="softwareproductinstallation_delete"),
path("installations/<int:pk>/edit/", views.SoftwareProductInstallationEditView.as_view(),
name="softwareproductinstallation_edit"),
path(
"installations/<int:pk>/changelog/",
ObjectChangeLogView.as_view(),
name="softwareproductinstallation_changelog",
kwargs={"model": SoftwareProductInstallation},
),
]
+154
View File
@@ -0,0 +1,154 @@
from netbox.views import generic
from netbox_slm import filtersets
from netbox_slm import forms
from netbox_slm import tables
from netbox_slm.models import (
SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation
)
class SoftwareProductListView(generic.ObjectListView):
"""View for listing all existing SoftwareProducts."""
queryset = SoftwareProduct.objects.all()
filterset = filtersets.SoftwareProductFilterSet
filterset_form = forms.SoftwareProductFilterForm
table = tables.SoftwareProductTable
class SoftwareProductView(generic.ObjectView):
"""Display SoftwareProduct details"""
queryset = SoftwareProduct.objects.all()
def get_extra_context(self, request, instance):
versions = instance.softwareproductversion_set.all()
return {"versions": versions}
class SoftwareProductEditView(generic.ObjectEditView):
"""View for editing and creating a SoftwareProduct instance."""
queryset = SoftwareProduct.objects.all()
form = forms.SoftwareProductForm
class SoftwareProductDeleteView(generic.ObjectDeleteView):
"""View for deleting a SoftwareProduct instance"""
queryset = SoftwareProduct.objects.all()
class SoftwareProductBulkImportView(generic.BulkImportView):
queryset = SoftwareProduct.objects.all()
model_form = forms.SoftwareProductCSVForm
table = tables.SoftwareProductTable
class SoftwareProductBulkEditView(generic.BulkEditView):
queryset = SoftwareProduct.objects.all()
filterset = filtersets.SoftwareProductFilterSet
filterset_form = forms.SoftwareProductFilterForm
table = tables.SoftwareProductTable
form = forms.SoftwareProductBulkEditForm
class SoftwareProductBulkDeleteView(generic.BulkDeleteView):
queryset = SoftwareProduct.objects.all()
table = tables.SoftwareProductTable
class SoftwareProductVersionListView(generic.ObjectListView):
"""View for listing all existing SoftwareProductVersions."""
queryset = SoftwareProductVersion.objects.all()
filterset = filtersets.SoftwareProductVersionFilterSet
filterset_form = forms.SoftwareProductVersionFilterForm
table = tables.SoftwareProductVersionTable
class SoftwareProductVersionView(generic.ObjectView):
"""Display SoftwareProductVersion details"""
queryset = SoftwareProductVersion.objects.all()
def get_extra_context(self, request, instance):
installation_count = instance.get_installation_count()
return {"installations": installation_count}
class SoftwareProductVersionEditView(generic.ObjectEditView):
"""View for editing and creating a SoftwareProductVersion instance."""
queryset = SoftwareProductVersion.objects.all()
form = forms.SoftwareProductVersionForm
class SoftwareProductVersionDeleteView(generic.ObjectDeleteView):
"""View for deleting a SoftwareProductVersion instance"""
queryset = SoftwareProductVersion.objects.all()
class SoftwareProductVersionBulkImportView(generic.BulkImportView):
queryset = SoftwareProductVersion.objects.all()
model_form = forms.SoftwareProductVersionCSVForm
table = tables.SoftwareProductVersionTable
class SoftwareProductVersionBulkEditView(generic.BulkEditView):
queryset = SoftwareProductVersion.objects.all()
filterset = filtersets.SoftwareProductVersionFilterSet
table = tables.SoftwareProductVersionTable
form = forms.SoftwareProductVersionBulkEditForm
class SoftwareProductVersionBulkDeleteView(generic.BulkDeleteView):
queryset = SoftwareProductVersion.objects.all()
table = tables.SoftwareProductVersionTable
class SoftwareProductInstallationListView(generic.ObjectListView):
"""View for listing all existing SoftwareProductInstallations."""
queryset = SoftwareProductInstallation.objects.all()
filterset = filtersets.SoftwareProductInstallationFilterSet
filterset_form = forms.SoftwareProductInstallationFilterForm
table = tables.SoftwareProductInstallationTable
class SoftwareProductInstallationView(generic.ObjectView):
"""Display SoftwareProductInstallation details"""
queryset = SoftwareProductInstallation.objects.all()
class SoftwareProductInstallationEditView(generic.ObjectEditView):
"""View for editing and creating a SoftwareProductInstallation instance."""
queryset = SoftwareProductInstallation.objects.all()
form = forms.SoftwareProductInstallationForm
class SoftwareProductInstallationDeleteView(generic.ObjectDeleteView):
"""View for deleting a SoftwareProductInstallation instance"""
queryset = SoftwareProductInstallation.objects.all()
class SoftwareProductInstallationBulkImportView(generic.BulkImportView):
queryset = SoftwareProductInstallation.objects.all()
model_form = forms.SoftwareProductInstallationCSVForm
table = tables.SoftwareProductInstallationTable
class SoftwareProductInstallationBulkEditView(generic.BulkEditView):
queryset = SoftwareProductInstallation.objects.all()
filterset = filtersets.SoftwareProductInstallationFilterSet
table = tables.SoftwareProductInstallationTable
form = forms.SoftwareProductInstallationBulkEditForm
class SoftwareProductInstallationBulkDeleteView(generic.BulkDeleteView):
queryset = SoftwareProductInstallation.objects.all()
table = tables.SoftwareProductInstallationTable