11 Commits
24 changed files with 271 additions and 741 deletions
+7 -15
View File
@@ -9,24 +9,26 @@ Software Lifecycle Management
Installation Guide
~~~~~~~~~~~~~~~~~~
WARNING: This plugin requires Netbox 3.1.X or higher!
When using the Docker version of Netbox, first follow the `netbox-docker
quickstart <https://github.com/netbox-community/netbox-docker#quickstart>`__
instructions to clone the netbox-docker repo and set up the
``docker-compose.override.yml``.
Next, follow these instructions (based on the generic `Netbox plugin
instructions <https://github.com/netbox-community/netbox-docker/wiki/Using-Netbox-Plugins>`__)
Next, follow these instructions (based on the `Netbox docker variant
instructions <https://github.com/netbox-community/netbox-docker/wiki/Configuration#custom-configuration-files>`__)
to install the Netbox SLM plugin:
1. Add ``netbox_slm`` to the ``PLUGINS`` list in
``configuration/configuration.py``.
2. Create a ``plugin_requirements.txt`` with ``netbox-slm==0.93`` as
``configuration/extra.py``.
2. Create a ``plugin_requirements.txt`` with ``netbox-slm==0.95`` as
contents.
3. Create a ``Dockerfile-SLM`` with contents:
.. code:: dockerfile
FROM netboxcommunity/netbox:v3.0.9 # The Netbox SLM plugin currently only works with v3.0.9 of Netbox due to backwards incompatible changes in newer version of Netbox
FROM netboxcommunity/netbox:v3.X.X
COPY ./plugin_requirements.txt /
RUN /opt/netbox/venv/bin/pip install --no-warn-script-location -r /plugin_requirements.txt
@@ -87,15 +89,6 @@ Netbox Django copy with auto-reload to develop the plugin pointing to
the Dockerized postgres and redis instances, basically ignoring the
netbox docker runtime server.
Gotchas
~~~~~~~~
*Netboxs exception handler seems to supress initial Exceptions,
obfuscating missing imports or other Django related issues by re-raising
an opague error, e.g. netbox_slm plugin namespace doesnotexist. Its
recommended to often check the runtime server or patch Netboxs global
exception handler.*
Steps
~~~~~
@@ -105,7 +98,6 @@ Steps
$ git clone https://github.com/netbox-community/netbox
$ cd netbox
$ git checkout f5356b8 (to pin 3.0.9)
install the virtual environment
+6 -16
View File
@@ -14,19 +14,19 @@ quickstart <https://github.com/netbox-community/netbox-docker#quickstart>`__
instructions to clone the netbox-docker repo and set up the
``docker-compose.override.yml``.
Next, follow these instructions (based on the generic `Netbox plugin
instructions <https://github.com/netbox-community/netbox-docker/wiki/Using-Netbox-Plugins>`__)
Next, follow these instructions (based on the `Netbox docker variant
instructions <https://github.com/netbox-community/netbox-docker/wiki/Configuration#custom-configuration-files>`__)
to install the Netbox SLM plugin:
1. Add ``netbox_slm`` to the ``PLUGINS`` list in
``configuration/configuration.py``.
2. Create a ``plugin_requirements.txt`` with ``netbox-slm==0.93`` as
``configuration/extra.py``.
2. Create a ``plugin_requirements.txt`` with ``netbox-slm==0.94`` as
contents.
3. Create a ``Dockerfile-SLM`` with contents:
.. code:: dockerfile
FROM netboxcommunity/netbox:v3.0.9 # The Netbox SLM plugin currently only works with v3.0.9 of Netbox due to backwards incompatible changes in newer version of Netbox
FROM netboxcommunity/netbox:v3.X.X
COPY ./plugin_requirements.txt /
RUN /opt/netbox/venv/bin/pip install --no-warn-script-location -r /plugin_requirements.txt
@@ -63,7 +63,7 @@ directory run
::
# make sure to update the version in netbox_slm/__init__.py and setup.cfg
# make sure to update the version in netbox_slm/__init__.py
$ python setup.py sdist
$ twine upload dist/*
@@ -87,15 +87,6 @@ Netbox Django copy with auto-reload to develop the plugin pointing to
the Dockerized postgres and redis instances, basically ignoring the
netbox docker runtime server.
Gotchas
~~~~~~~~
*Netboxs exception handler seems to supress initial Exceptions,
obfuscating missing imports or other Django related issues by re-raising
an opague error, e.g. netbox_slm plugin namespace doesnotexist. Its
recommended to often check the runtime server or patch Netboxs global
exception handler.*
Steps
~~~~~
@@ -105,7 +96,6 @@ Steps
$ git clone https://github.com/netbox-community/netbox
$ cd netbox
$ git checkout f5356b8 (to pin 3.0.9)
install the virtual environment
+1 -1
View File
@@ -5,7 +5,7 @@ class SLMConfig(PluginConfig):
name = 'netbox_slm'
verbose_name = 'Software Lifecycle Management'
description = 'Software Lifecycle Management'
version = '0.94'
version = '0.96'
author = 'Hedde van der Heide'
author_email = 'hedde.vanderheide@ictu.nl'
base_url = 'slm'
+23 -8
View File
@@ -1,14 +1,12 @@
from rest_framework import serializers
from netbox.api.serializers import PrimaryModelSerializer, OrganizationalModelSerializer
# from netbox_slm.api.nested_serializers import (
# NestedRecordSerializer,
# NestedNameServerSerializer,
# )
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion
from netbox.api.serializers import NetBoxModelSerializer
from netbox_slm.models import (
SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation,
)
class SoftwareProductSerializer(PrimaryModelSerializer):
class SoftwareProductSerializer(NetBoxModelSerializer):
display = serializers.SerializerMethodField()
url = serializers.HyperlinkedIdentityField(
view_name="plugins-api:netbox_slm-api:softwareproduct-detail"
@@ -24,7 +22,7 @@ class SoftwareProductSerializer(PrimaryModelSerializer):
return f"{obj.manufacturer.name} - {obj.name}"
class SoftwareProductVersionSerializer(PrimaryModelSerializer):
class SoftwareProductVersionSerializer(NetBoxModelSerializer):
display = serializers.SerializerMethodField()
url = serializers.HyperlinkedIdentityField(
view_name="plugins-api:netbox_slm-api:softwareproductversion-detail"
@@ -38,3 +36,20 @@ class SoftwareProductVersionSerializer(PrimaryModelSerializer):
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
+4 -2
View File
@@ -1,13 +1,15 @@
from netbox.api import OrderedDefaultRouter
from netbox.api.routers import NetBoxRouter
from netbox_slm.api.views import (
NetboxSLMRootView,
SoftwareProductViewSet,
SoftwareProductVersionViewSet,
SoftwareProductInstallationViewSet,
)
router = OrderedDefaultRouter()
router = NetBoxRouter()
router.APIRootView = NetboxSLMRootView
router.register("softwareproducts", SoftwareProductViewSet)
router.register("softwareproductversions", SoftwareProductVersionViewSet)
router.register("softwareproductinstallations", SoftwareProductInstallationViewSet)
urlpatterns = router.urls
+16 -18
View File
@@ -3,10 +3,16 @@ from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.routers import APIRootView
from extras.api.views import CustomFieldModelViewSet
from netbox_slm.api.serializers import SoftwareProductSerializer, SoftwareProductVersionSerializer
from netbox_slm.filters import SoftwareProductFilter, SoftwareProductVersionFilter
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion
from netbox.api.viewsets import NetBoxModelViewSet
from netbox_slm.api.serializers import (
SoftwareProductSerializer, SoftwareProductVersionSerializer, SoftwareProductInstallationSerializer,
)
from netbox_slm.filters import (
SoftwareProductFilter, SoftwareProductVersionFilter, SoftwareProductInstallationFilter,
)
from netbox_slm.models import (
SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation,
)
class NetboxSLMRootView(APIRootView):
@@ -18,27 +24,19 @@ class NetboxSLMRootView(APIRootView):
return "NetboxSLM"
class SoftwareProductViewSet(CustomFieldModelViewSet):
class SoftwareProductViewSet(NetBoxModelViewSet):
queryset = SoftwareProduct.objects.all()
serializer_class = SoftwareProductSerializer
filterset_class = SoftwareProductFilter
# @action(detail=True, methods=["get"])
# def records(self, request, pk=None):
# records = Record.objects.filter(zone=pk)
# serializer = RecordSerializer(records, many=True, context={"request": request})
# return Response(serializer.data)
class SoftwareProductVersionViewSet(CustomFieldModelViewSet):
class SoftwareProductVersionViewSet(NetBoxModelViewSet):
queryset = SoftwareProductVersion.objects.all()
serializer_class = SoftwareProductVersionSerializer
filterset_class = SoftwareProductVersionFilter
# @action(detail=True, methods=["get"])
# def records(self, request, pk=None):
# records = Record.objects.filter(zone=pk)
# serializer = RecordSerializer(records, many=True, context={"request": request})
# return Response(serializer.data)
# for reference: https://github.com/auroraresearchlab/netbox-dns/blob/main/netbox_dns/api/views.py
class SoftwareProductInstallationViewSet(NetBoxModelViewSet):
queryset = SoftwareProductInstallation.objects.all()
serializer_class = SoftwareProductInstallationSerializer
filterset_class = SoftwareProductInstallationFilter
+2 -2
View File
@@ -2,12 +2,12 @@ import django_filters
from django.db.models import Q
from django.utils.translation import gettext as _
from extras.filters import TagFilter
from netbox.filtersets import PrimaryModelFilterSet
from netbox.filtersets import NetBoxModelFilterSet
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation
from utilities.forms import DynamicModelMultipleChoiceField
class BaseFilter(PrimaryModelFilterSet):
class BaseFilter(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method="search",
label="Search",
+34 -44
View File
@@ -1,12 +1,11 @@
from django import forms
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from extras.forms import (
CustomFieldModelForm,
CustomFieldModelCSVForm,
AddRemoveTagsForm,
CustomFieldModelBulkEditForm,
CustomFieldModelFilterForm,
from netbox.forms import (
NetBoxModelForm,
NetBoxModelCSVForm,
NetBoxModelBulkEditForm,
NetBoxModelFilterSetForm,
)
from dcim.models import Manufacturer, Device
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation
@@ -16,7 +15,7 @@ from utilities.forms import (
from virtualization.models import VirtualMachine
class SoftwareProductForm(BootstrapMixin, CustomFieldModelForm):
class SoftwareProductForm(NetBoxModelForm):
"""Form for creating a new SoftwareProduct object."""
manufacturer = DynamicModelChoiceField(
@@ -27,17 +26,12 @@ class SoftwareProductForm(BootstrapMixin, CustomFieldModelForm):
# }
)
# tags = DynamicModelMultipleChoiceField(
# queryset=Tag.objects.all(),
# required=False,
# )
class Meta:
model = SoftwareProduct
fields = ("name", "manufacturer", "description",) # "tags")
fields = ("name", "manufacturer", "description", "tags")
class SoftwareProductFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
class SoftwareProductFilterForm(NetBoxModelFilterSetForm):
"""Form for filtering SoftwareProduct instances."""
model = SoftwareProduct
@@ -52,23 +46,24 @@ class SoftwareProductFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
# tag = TagFilterField(SoftwareProduct)
class SoftwareProductCSVForm(CustomFieldModelCSVForm):
class SoftwareProductCSVForm(NetBoxModelCSVForm):
class Meta:
model = SoftwareProduct
fields = ("name",)
class SoftwareProductBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
class SoftwareProductBulkEditForm(NetBoxModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=SoftwareProduct.objects.all(),
widget=forms.MultipleHiddenInput(),
)
class Meta:
model = SoftwareProduct
nullable_fields = []
class SoftwareProductVersionForm(BootstrapMixin, CustomFieldModelForm):
class SoftwareProductVersionForm(NetBoxModelForm):
"""Form for creating a new SoftwareProductVersion object."""
name = forms.CharField(label=_("Version"))
@@ -79,21 +74,16 @@ class SoftwareProductVersionForm(BootstrapMixin, CustomFieldModelForm):
),
)
# tags = DynamicModelMultipleChoiceField(
# queryset=Tag.objects.all(),
# required=False,
# )
class Meta:
model = SoftwareProductVersion
fields = ("name", "software_product",) # "tags")
fields = ("name", "software_product", "tags")
# def clean(self):
# import pdb;pdb.set_trace()
# return super(SoftwareProductVersionForm, self).clean()
class SoftwareProductVersionFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
class SoftwareProductVersionFilterForm(NetBoxModelFilterSetForm):
"""Form for filtering SoftwareProductVersion instances."""
model = SoftwareProductVersion
@@ -108,23 +98,24 @@ class SoftwareProductVersionFilterForm(BootstrapMixin, CustomFieldModelFilterFor
# tag = TagFilterField(SoftwareProduct)
class SoftwareProductVersionCSVForm(CustomFieldModelCSVForm):
class SoftwareProductVersionCSVForm(NetBoxModelCSVForm):
class Meta:
model = SoftwareProductVersion
fields = ("name",)
class SoftwareProductVersionBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
class SoftwareProductVersionBulkEditForm(NetBoxModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=SoftwareProduct.objects.all(),
widget=forms.MultipleHiddenInput(),
)
class Meta:
model = SoftwareProductVersion
nullable_fields = []
class SoftwareProductInstallationForm(BootstrapMixin, CustomFieldModelForm):
class SoftwareProductInstallationForm(NetBoxModelForm):
"""Form for creating a new SoftwareProductInstallation object."""
device = DynamicModelChoiceField(
@@ -159,16 +150,9 @@ class SoftwareProductInstallationForm(BootstrapMixin, CustomFieldModelForm):
}
)
# todo need version and device ?
# tags = DynamicModelMultipleChoiceField(
# queryset=Tag.objects.all(),
# required=False,
# )
class Meta:
model = SoftwareProductInstallation
fields = ("device", "virtualmachine", "software_product", "version",) # "tags")
fields = ("device", "virtualmachine", "software_product", "version", "tags")
def clean(self):
if not any([self.cleaned_data['device'], self.cleaned_data['virtualmachine']]):
@@ -176,7 +160,7 @@ class SoftwareProductInstallationForm(BootstrapMixin, CustomFieldModelForm):
return super(SoftwareProductInstallationForm, self).clean()
class SoftwareProductInstallationFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
class SoftwareProductInstallationFilterForm(NetBoxModelFilterSetForm):
"""Form for filtering SoftwareProductInstallation instances."""
model = SoftwareProductInstallation
@@ -186,17 +170,23 @@ class SoftwareProductInstallationFilterForm(BootstrapMixin, CustomFieldModelFilt
# tag = TagFilterField(SoftwareProduct)
class SoftwareProductInstallationCSVForm(CustomFieldModelCSVForm):
class SoftwareProductInstallationCSVForm(NetBoxModelCSVForm):
class Meta:
model = SoftwareProductInstallation
fields = tuple()
class SoftwareProductInstallationBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=SoftwareProductInstallation.objects.all(),
widget=forms.MultipleHiddenInput(),
class SoftwareProductInstallationBulkEditForm(NetBoxModelBulkEditForm):
software_product = DynamicModelChoiceField(
queryset=SoftwareProduct.objects.all(),
required=False
)
class Meta:
nullable_fields = []
version = DynamicModelChoiceField(
queryset=SoftwareProductVersion.objects.all(),
required=False
)
model = SoftwareProductInstallation
fieldsets = (
(None, ('software_product', 'version',)),
)
# nullable_fields = ('',)
View File
@@ -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),
),
]
+5 -5
View File
@@ -1,13 +1,13 @@
from django.db import models
from django.urls import reverse, reverse_lazy
from django.utils import safestring
from extras.utils import extras_features
from netbox.models import PrimaryModel
# from extras.utils import extras_features # todo this changed in netbox 3.1.X and higher, currently not used but checkout why
from netbox.models import NetBoxModel
from utilities.querysets import RestrictedQuerySet
# @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class SoftwareProduct(PrimaryModel):
class SoftwareProduct(NetBoxModel):
"""
A SoftwareProduct represents ...
"""
@@ -38,7 +38,7 @@ class SoftwareProduct(PrimaryModel):
# @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class SoftwareProductVersion(PrimaryModel):
class SoftwareProductVersion(NetBoxModel):
software_product = models.ForeignKey(
to='netbox_slm.SoftwareProduct',
on_delete=models.PROTECT,
@@ -63,7 +63,7 @@ class SoftwareProductVersion(PrimaryModel):
# @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class SoftwareProductInstallation(PrimaryModel):
class SoftwareProductInstallation(NetBoxModel):
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.PROTECT,
View File
+31 -22
View File
@@ -1,10 +1,11 @@
import django_tables2 as tables
from django_tables2.utils import Accessor
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation
from utilities.tables import BaseTable, ChoiceFieldColumn, ToggleColumn
from netbox.tables import NetBoxTable, ChoiceFieldColumn, ToggleColumn, columns
class SoftwareProductTable(BaseTable):
class SoftwareProductTable(NetBoxTable):
"""Table for displaying SoftwareProduct objects."""
pk = ToggleColumn()
@@ -15,11 +16,11 @@ class SoftwareProductTable(BaseTable):
)
installations = tables.Column(accessor='get_installation_count', verbose_name='Installations')
# tags = TagColumn(
# url_name="plugins:netbox_dns:zone_list",
# )
tags = columns.TagColumn(
url_name="plugins:netbox_slm:softwareproduct_list",
)
class Meta(BaseTable.Meta):
class Meta(NetBoxTable.Meta):
model = SoftwareProduct
fields = (
"pk",
@@ -27,7 +28,7 @@ class SoftwareProductTable(BaseTable):
"manufacturer",
"description",
"installations",
# "tags",
"tags",
)
default_columns = (
"pk",
@@ -35,7 +36,7 @@ class SoftwareProductTable(BaseTable):
"manufacturer",
"description",
"installations",
# "tags",
"tags",
)
sequence = (
"manufacturer",
@@ -45,7 +46,7 @@ class SoftwareProductTable(BaseTable):
)
class SoftwareProductVersionTable(BaseTable):
class SoftwareProductVersionTable(NetBoxTable):
"""Table for displaying SoftwareProductVersion objects."""
pk = ToggleColumn()
@@ -60,11 +61,11 @@ class SoftwareProductVersionTable(BaseTable):
)
installations = tables.Column(accessor='get_installation_count', verbose_name='Installations')
# tags = TagColumn(
# url_name="plugins:netbox_dns:zone_list",
# )
tags = columns.TagColumn(
url_name="plugins:netbox_slm:softwareproductversion_list",
)
class Meta(BaseTable.Meta):
class Meta(NetBoxTable.Meta):
model = SoftwareProductVersion
fields = (
"pk",
@@ -72,7 +73,7 @@ class SoftwareProductVersionTable(BaseTable):
"software_product",
"manufacturer",
"installations",
# "tags",
"tags",
)
default_columns = (
"pk",
@@ -80,7 +81,7 @@ class SoftwareProductVersionTable(BaseTable):
"software_product",
"manufacturer",
"installations",
# "tags",
"tags",
)
sequence = (
"manufacturer",
@@ -90,7 +91,7 @@ class SoftwareProductVersionTable(BaseTable):
)
class SoftwareProductInstallationTable(BaseTable):
class SoftwareProductInstallationTable(NetBoxTable):
"""Table for displaying SoftwareProductInstallation objects."""
pk = ToggleColumn()
@@ -117,11 +118,11 @@ class SoftwareProductInstallationTable(BaseTable):
linkify=True
)
# tags = TagColumn(
# url_name="plugins:netbox_dns:zone_list",
# )
tags = columns.TagColumn(
url_name="plugins:netbox_slm:softwareproductinstallation_list",
)
class Meta(BaseTable.Meta):
class Meta(NetBoxTable.Meta):
model = SoftwareProductInstallation
fields = (
"pk",
@@ -130,7 +131,7 @@ class SoftwareProductInstallationTable(BaseTable):
"type",
"software_product",
"version",
# "tags",
"tags",
)
default_columns = (
"pk",
@@ -138,8 +139,16 @@ class SoftwareProductInstallationTable(BaseTable):
"type",
"software_product",
"version",
# "tags",
"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}"
View File
@@ -1,93 +0,0 @@
{% extends 'base/layout.html' %}
{% load buttons %}
{% load custom_links %}
{% load helpers %}
{% load perms %}
{% load plugins %}
{% load view_helpers %}
{% block header %}
{# Breadcrumbs #}
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
<ol class="breadcrumb">
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url object|plugin_viewname:'list' %}">{{ object|meta:'verbose_name_plural'|bettertitle }}</a></li>
{% endblock %}
</ol>
</nav>
{{ block.super }}
{% endblock %}
{% block title %}{{ object }}{% endblock %}
{% block subtitle %}
<div class="object-subtitle">
<span>Created {{ object.created|annotated_date }}</span>
<span class="separator">&middot;</span>
<span>Updated <span title="{{ object.last_updated }}">{{ object.last_updated|timesince }}</span> ago</span>
</div>
{% endblock %}
{% block controls %}
{# Clone/Edit/Delete Buttons #}
<div class="controls">
<div class="control-group">
{% plugin_buttons object %}
{# Extra buttons #}
{% block extra_controls %}{% endblock %}
{% if object.clone_fields and request.user|can_add:object %}
{% plugin_clone_button object %}
{% endif %}
{% if request.user|can_change:object %}
{% plugin_edit_button object %}
{% endif %}
{% if request.user|can_delete:object %}
{% plugin_delete_button object %}
{% endif %}
</div>
<div class="control-group">
{% custom_links object %}
</div>
</div>
{% endblock controls %}
{% block tabs %}
<ul class="nav nav-tabs px-3">
{% block tab_items %}
<li class="nav-item" role="presentation">
<a class="nav-link{% if not active_tab %} active{% endif %}" href="{{ object.get_absolute_url }}">{{ object|meta:"verbose_name"|bettertitle }}</a>
</li>
{% endblock tab_items %}
{% if perms.extras.view_journalentry %}
{% with journal_viewname=object|plugin_viewname:'journal' %}
{% url journal_viewname pk=object.pk as journal_url %}
{% if journal_url %}
<li role="presentation" class="nav-item">
<a href="{{ journal_url }}" class="nav-link{% if active_tab == 'journal'%} active{% endif %}">
Journal {% badge object.journal_entries.count %}
</a>
</li>
{% endif %}
{% endwith %}
{% endif %}
{% if perms.extras.view_objectchange %}
{% with changelog_viewname=object|plugin_viewname:'changelog' %}
{% url changelog_viewname pk=object.pk as changelog_url %}
{% if changelog_url %}
<li role="presentation" class="nav-item">
<a href="{{ changelog_url }}" class="nav-link{% if active_tab == 'changelog'%} active{% endif %}">Change Log</a>
</li>
{% endif %}
{% endwith %}
{% endif %}
</ul>
{% endblock tabs %}
{% block content-wrapper %}
<div class="tab-content">
{% block content %}{% endblock %}
</div>
{% endblock %}
@@ -1,141 +0,0 @@
{% extends 'base/layout.html' %}
{% load buttons %}
{% load helpers %}
{% load render_table from django_tables2 %}
{% load static %}
{% load view_helpers %}
{% block controls %}
<div class="controls">
<div class="control-group">
{% block extra_controls %}{% endblock %}
{% if permissions.add and 'add' in action_buttons %}
{% add_button content_type.model_class|plugin_viewname:"add" %}
{% endif %}
{% if permissions.add and 'import' in action_buttons %}
{% import_button content_type.model_class|plugin_viewname:"import" %}
{% endif %}
{% if 'export' in action_buttons %}
{% export_button content_type %}
{% endif %}
</div>
</div>
{% endblock controls %}
{% block tabs %}
<ul class="nav nav-tabs px-3">
{% block tab_items %}
<li class="nav-item" role="presentation">
<button class="nav-link active" id="object-list-tab" data-bs-toggle="tab" data-bs-target="#object-list" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
{% block title %}{{ content_type.model_class|meta:"verbose_name_plural"|bettertitle }}{% endblock %}
{% badge table.page.paginator.count %}
</button>
</li>
{% if filter_form %}
<li class="nav-item" role="presentation">
<button class="nav-link" id="filters-form-tab" data-bs-toggle="tab" data-bs-target="#filters-form" type="button" role="tab" aria-controls="object-list" aria-selected="false">
Filters
{% if filter_form %}{% badge filter_form.changed_data|length bg_class="primary" %}{% endif %}
</button>
</li>
{% endif %}
{% endblock tab_items %}
</ul>
{% endblock tabs %}
{% block content-wrapper %}
<div class="tab-content">
{# Object list #}
<div class="tab-pane show active" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">
{# Applied filters #}
{% if filter_form %}
{% applied_filters filter_form request.GET %}
{% endif %}
{# "Select all" form #}
{% if table.paginator.num_pages > 1 %}
{% with bulk_edit_url=content_type.model_class|validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|validated_viewname:"bulk_delete" %}
<div id="select-all-box" class="d-none card noprint">
<form method="post" class="form col-md-12">
{% csrf_token %}
<div class="card-body">
<div class="float-end">
{% if bulk_edit_url and permissions.change %}
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm" disabled>
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit All
</button>
{% endif %}
{% if bulk_delete_url and permissions.delete %}
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm" disabled>
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete All
</button>
{% endif %}
</div>
<div class="form-check">
<input type="checkbox" id="select-all" name="_all" class="form-check-input" />
<label for="select-all" class="form-check-label">
Select <strong>all {{ table.rows|length }} {{ table.data.verbose_name_plural }}</strong> matching query
</label>
</div>
</div>
</form>
</div>
{% endwith %}
{% endif %}
{# Object table controls #}
{% include 'inc/table_controls.html' with table_modal="ObjectTable_config" %}
<form method="post" class="form form-horizontal">
{% csrf_token %}
<input type="hidden" name="return_url" value="{% if return_url %}{{ return_url }}{% else %}{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}{% endif %}" />
{# Object table #}
<div class="card">
<div class="card-body">
<div class="table-responsive">
{% render_table table 'inc/table.html' %}
</div>
</div>
</div>
{# Form buttons #}
{% if permissions.change or permissions.delete %}
{% with bulk_edit_url=content_type.model_class|plugin_validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|plugin_validated_viewname:"bulk_delete" %}
<div class="noprint bulk-buttons">
<div class="bulk-button-group">
{% block bulk_buttons %}{% endblock %}
{% if bulk_edit_url and permissions.change %}
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
</button>
{% endif %}
{% if bulk_delete_url and permissions.delete %}
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete Selected
</button>
{% endif %}
</div>
</div>
{% endwith %}
{% endif %}
</form>
{# Paginator #}
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
</div>
{# Filter form #}
{% if filter_form %}
<div class="tab-pane show" id="filters-form" role="tabpanel" aria-labelledby="filters-form-tab">
{% include 'inc/filter_list.html' %}
</div>
{% endif %}
</div>
{# Table config form #}
{% table_config_form table table_name="ObjectTable" %}
{% endblock content-wrapper %}
@@ -1,26 +1,21 @@
{% extends 'netbox_slm/object.html' %}
{% extends 'generic/object.html' %}
{% load buttons %}
{% load static %}
{% load helpers %}
{% load plugins %}
{% load render_table from django_tables2 %}
{% load view_helpers %}
{% load perms %}
{% block extra_controls %}
{{ block.super }}
{% endblock %}
{% block content %}
<div class="row">
<div class="col col-md-6">
<div class="row my-3">
<div class="col col-md-6">
<div class="card">
<div class="card-header">
<h5 class="card-header">
Software Product
</div>
</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">Name</th>
<td>{{ object.name }}</td>
<td>{{ object }}</td>
</tr>
<tr>
<th scope="row">Versions</th>
@@ -35,42 +30,15 @@
</table>
</div>
</div>
{% include 'inc/custom_fields_panel.html' %}
</div>
<div class="col col-md-6">
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='plugins:netbox_slm:softwareproduct_list' %}
{% 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">
<div class="card">
<h5 class="card-header">Versions</h5>
<div class="card-body">
<table class="table table-hover attr-table table-striped">
<thead>
<tr>
<th scope="col">NAME</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{% for version in versions %}
<tr>
<td>{{ version.name|truncatechars:32 }}</td>
<td class="noprint text-end text-nowrap">
{% if request.user|can_change:object %}
{% plugin_edit_button version %}
{% endif %}
{% if request.user|can_delete:object %}
{% plugin_delete_button version %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}
{% endblock %}
@@ -1,82 +1,49 @@
{% extends 'netbox_slm/object.html' %}
{% extends 'generic/object.html' %}
{% load buttons %}
{% load static %}
{% load helpers %}
{% load plugins %}
{% load render_table from django_tables2 %}
{% load view_helpers %}
{% load perms %}
{% block extra_controls %}
{{ block.super }}
{% endblock %}
{% block content %}
<div class="row">
<div class="col col-md-6">
<div class="row my-3">
<div class="col col-md-6">
<div class="card">
<div class="card-header">
Software Product
</div>
<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">Installation</th>
<td>{{ object }}</td>
<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">bla</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>-->
<!-- {% endfor %}-->
</td>
<th scope="row">Version</th>
<td>{{ object.version }}</td>
</tr>
</table>
</div>
</div>
{% include 'inc/custom_fields_panel.html' %}
</div>
<div class="col col-md-6">
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='plugins:netbox_slm:softwareproduct_list' %}
{% 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">-->
<!-- <div class="card">-->
<!-- <h5 class="card-header">Records</h5>-->
<!-- <div class="card-body">-->
<!-- <table class="table table-hover attr-table table-striped">-->
<!-- <thead>-->
<!-- <tr>-->
<!-- <th scope="col">TYPE</th>-->
<!-- <th scope="col">NAME</th>-->
<!-- <th scope="col">VALUE</th>-->
<!-- <th scope="col">TTL</th>-->
<!-- <th scope="col">Actions</th>-->
<!-- </tr>-->
<!-- </thead>-->
<!-- <tbody>-->
<!-- {% for record in records %}-->
<!-- <tr>-->
<!-- <td>{{ record.type }}</td>-->
<!-- <td>{{ record.name|truncatechars:32 }}</td>-->
<!-- <td>{{ record.value|truncatechars:64 }}</td>-->
<!-- <td>{{ record.ttl }}</td>-->
<!-- <td class="noprint text-end text-nowrap">-->
<!-- {% if request.user|can_change:object %}-->
<!-- {% plugin_edit_button record %}-->
<!-- {% endif %}-->
<!-- {% if request.user|can_delete:object %}-->
<!-- {% plugin_delete_button record %}-->
<!-- {% endif %}-->
<!-- </td>-->
<!-- </tr>-->
<!-- {% endfor %}-->
<!-- </tbody>-->
<!-- </table>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!--</div>-->
{% endblock %}
<div class="row">
<div class="col col-md-12">
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}
@@ -1,21 +1,16 @@
{% extends 'netbox_slm/object.html' %}
{% extends 'generic/object.html' %}
{% load buttons %}
{% load static %}
{% load helpers %}
{% load plugins %}
{% load render_table from django_tables2 %}
{% load view_helpers %}
{% load perms %}
{% block extra_controls %}
{{ block.super }}
{% endblock %}
{% block content %}
<div class="row">
<div class="col col-md-6">
<div class="row my-3">
<div class="col col-md-6">
<div class="card">
<div class="card-header">
Software Product
</div>
<h5 class="card-header">
Software Product Version
</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
@@ -31,48 +26,15 @@
</table>
</div>
</div>
{% include 'inc/custom_fields_panel.html' %}
</div>
<div class="col col-md-6">
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='plugins:netbox_slm:softwareproduct_list' %}
{% 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">-->
<!-- <div class="card">-->
<!-- <h5 class="card-header">Records</h5>-->
<!-- <div class="card-body">-->
<!-- <table class="table table-hover attr-table table-striped">-->
<!-- <thead>-->
<!-- <tr>-->
<!-- <th scope="col">TYPE</th>-->
<!-- <th scope="col">NAME</th>-->
<!-- <th scope="col">VALUE</th>-->
<!-- <th scope="col">TTL</th>-->
<!-- <th scope="col">Actions</th>-->
<!-- </tr>-->
<!-- </thead>-->
<!-- <tbody>-->
<!-- {% for record in records %}-->
<!-- <tr>-->
<!-- <td>{{ record.type }}</td>-->
<!-- <td>{{ record.name|truncatechars:32 }}</td>-->
<!-- <td>{{ record.value|truncatechars:64 }}</td>-->
<!-- <td>{{ record.ttl }}</td>-->
<!-- <td class="noprint text-end text-nowrap">-->
<!-- {% if request.user|can_change:object %}-->
<!-- {% plugin_edit_button record %}-->
<!-- {% endif %}-->
<!-- {% if request.user|can_delete:object %}-->
<!-- {% plugin_delete_button record %}-->
<!-- {% endif %}-->
<!-- </td>-->
<!-- </tr>-->
<!-- {% endfor %}-->
<!-- </tbody>-->
<!-- </table>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!--</div>-->
{% endblock %}
<div class="row">
<div class="col col-md-12">
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}
@@ -1,91 +0,0 @@
{% extends 'netbox_slm/object.html' %}
{% load helpers %}
{% load plugins %}
{% load render_table from django_tables2 %}
{% load view_helpers %}
{% load perms %}
{% block extra_controls %}
{% comment %}
<a href="{% url 'plugins:netbox_slm:record_add' %}?zone={{ object.id }}" class="btn btn-sm btn-primary" role="button">
<i class="mdi mdi-plus-thick"></i> Add Record
</a>
{% endcomment %}
{{ block.super }}
{% endblock %}
{% block content %}
<div class="row">
<div class="col col-md-6">
<div class="card">
<div class="card-header">
Zone
</div>
<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">Versions</th>
<td>
<ul>
<!-- {% for nameserver in object.nameservers.all %}-->
<!-- <li>-->
<!-- <a href="{% url 'plugins:netbox_slm:nameserver' pk=nameserver.pk %}">-->
<!-- {{ nameserver }}-->
<!-- </a>-->
<!-- </li>-->
<!-- {% endfor %}-->
</ul>
</td>
</tr>
</table>
</div>
</div>
{% include 'inc/custom_fields_panel.html' %}
</div>
<div class="col col-md-6">
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='plugins:netbox_slm:softwareproduct_list' %}
</div>
</div>
<!--<div class="row">-->
<!-- <div class="col col-md-12">-->
<!-- <div class="card">-->
<!-- <h5 class="card-header">Records</h5>-->
<!-- <div class="card-body">-->
<!-- <table class="table table-hover attr-table table-striped">-->
<!-- <thead>-->
<!-- <tr>-->
<!-- <th scope="col">TYPE</th>-->
<!-- <th scope="col">NAME</th>-->
<!-- <th scope="col">VALUE</th>-->
<!-- <th scope="col">TTL</th>-->
<!-- <th scope="col">Actions</th>-->
<!-- </tr>-->
<!-- </thead>-->
<!-- <tbody>-->
<!-- {% for record in records %}-->
<!-- <tr>-->
<!-- <td>{{ record.type }}</td>-->
<!-- <td>{{ record.name|truncatechars:32 }}</td>-->
<!-- <td>{{ record.value|truncatechars:64 }}</td>-->
<!-- <td>{{ record.ttl }}</td>-->
<!-- <td class="noprint text-end text-nowrap">-->
<!-- {% if request.user|can_change:object %}-->
<!-- {% plugin_edit_button record %}-->
<!-- {% endif %}-->
<!-- {% if request.user|can_delete:object %}-->
<!-- {% plugin_delete_button record %}-->
<!-- {% endif %}-->
<!-- </td>-->
<!-- </tr>-->
<!-- {% endfor %}-->
<!-- </tbody>-->
<!-- </table>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!--</div>-->
{% endblock %}
@@ -1,94 +0,0 @@
from django import template
from django.urls import NoReverseMatch, reverse
from django import template
from utilities.templatetags.buttons import _get_viewname
from utilities.utils import prepare_cloned_fields
register = template.Library()
@register.filter()
def plugin_validated_viewname(model, action):
"""Return the view name for :
* the given model and action if valid
* or the given model and action if valid inside a plugin
* or None if invalid.
"""
core_viewname = f"{model._meta.app_label}:{model._meta.model_name}_{action}"
plugin_viewname = (
f"plugins:{model._meta.app_label}:{model._meta.model_name}_{action}"
)
try:
# Validate and return the view name. We don't return the actual URL yet because many of the templates
# are written to pass a name to {% url %}.
reverse(core_viewname)
return core_viewname
except NoReverseMatch:
try:
reverse(plugin_viewname)
return plugin_viewname
except NoReverseMatch:
return None
@register.filter()
def plugin_viewname(model, action):
"""
Return the view name for the given model and action. Does not perform any validation.
"""
return f"plugins:{model._meta.app_label}:{model._meta.model_name}_{action}"
#
# Instance buttons
#
@register.inclusion_tag("buttons/clone.html")
def plugin_clone_button(instance):
viewname = _get_viewname(instance, "add")
url = reverse(f"plugins:{viewname}")
# Populate cloned field values
param_string = prepare_cloned_fields(instance)
if param_string:
url = f"{url}?{param_string}"
return {
"url": url,
}
@register.inclusion_tag("buttons/edit.html")
def plugin_edit_button(instance, **kwargs):
viewname = _get_viewname(instance, "edit")
url = reverse(f"plugins:{viewname}", kwargs={"pk": instance.pk, **kwargs})
return {
"url": url,
}
@register.inclusion_tag("buttons/delete.html")
def plugin_delete_button(instance, **kwargs):
viewname = _get_viewname(instance, "delete")
url = reverse(f"plugins:{viewname}", kwargs={"pk": instance.pk, **kwargs})
return {
"url": url,
}
#
# List buttons
#
@register.inclusion_tag("buttons/add.html")
def plugin_add_button(url):
url = reverse(f"plugins:{url}")
return {
"add_url": url,
}
+27 -14
View File
@@ -1,5 +1,5 @@
from django.urls import path
from extras.views import ObjectChangeLogView
from netbox.views.generic import ObjectChangeLogView
from netbox_slm import views
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion, SoftwareProductInstallation
@@ -9,9 +9,11 @@ urlpatterns = [
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/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>/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/",
@@ -23,11 +25,14 @@ urlpatterns = [
# 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/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/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>/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/",
@@ -37,14 +42,22 @@ urlpatterns = [
),
# 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/", 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(),
+6 -6
View File
@@ -19,7 +19,7 @@ class SoftwareProductListView(generic.ObjectListView):
filterset = SoftwareProductFilter
filterset_form = SoftwareProductFilterForm
table = SoftwareProductTable
template_name = "netbox_slm/object_list.html"
# template_name = "netbox_slm/object_list.html"
class SoftwareProductView(generic.ObjectView):
@@ -36,7 +36,7 @@ class SoftwareProductEditView(generic.ObjectEditView):
"""View for editing and creating a SoftwareProduct instance."""
queryset = SoftwareProduct.objects.all()
model_form = SoftwareProductForm
form = SoftwareProductForm
class SoftwareProductDeleteView(generic.ObjectDeleteView):
@@ -70,7 +70,7 @@ class SoftwareProductVersionListView(generic.ObjectListView):
filterset = SoftwareProductVersionFilter
filterset_form = SoftwareProductVersionFilterForm
table = SoftwareProductVersionTable
template_name = "netbox_slm/object_list.html"
# template_name = "netbox_slm/object_list.html"
class SoftwareProductVersionView(generic.ObjectView):
@@ -87,7 +87,7 @@ class SoftwareProductVersionEditView(generic.ObjectEditView):
"""View for editing and creating a SoftwareProductVersion instance."""
queryset = SoftwareProductVersion.objects.all()
model_form = SoftwareProductVersionForm
form = SoftwareProductVersionForm
class SoftwareProductVersionDeleteView(generic.ObjectDeleteView):
@@ -121,7 +121,7 @@ class SoftwareProductInstallationListView(generic.ObjectListView):
filterset = SoftwareProductInstallationFilter
filterset_form = SoftwareProductInstallationFilterForm
table = SoftwareProductInstallationTable
template_name = "netbox_slm/object_list.html"
# template_name = "netbox_slm/object_list.html"
class SoftwareProductInstallationView(generic.ObjectView):
@@ -138,7 +138,7 @@ class SoftwareProductInstallationEditView(generic.ObjectEditView):
"""View for editing and creating a SoftwareProductInstallation instance."""
queryset = SoftwareProductInstallation.objects.all()
model_form = SoftwareProductInstallationForm
form = SoftwareProductInstallationForm
class SoftwareProductInstallationDeleteView(generic.ObjectDeleteView):
+1 -1
View File
@@ -1,6 +1,6 @@
[metadata]
name = netbox-slm
version = 0.94
version = 0.96
description = Software Lifecycle Management Netbox Plugin.
# long_description = file: README.rst
# long_description_content_type='text/x-rst'