From ea20983b923ff4f58be5609a3bde18dd67bb98e2 Mon Sep 17 00:00:00 2001 From: Nico Felbinger Date: Thu, 18 Apr 2024 07:56:39 -0500 Subject: [PATCH 1/3] Add documents for power panels --- netbox_documents/__init__.py | 2 + netbox_documents/api/serializers.py | 35 +++++- netbox_documents/api/urls.py | 1 + netbox_documents/api/views.py | 9 +- netbox_documents/filtersets.py | 20 ++- netbox_documents/forms.py | 37 +++++- .../0007_circuitproviderdocument.py | 1 - .../migrations/0008_powerpaneldocument.py | 40 ++++++ netbox_documents/models.py | 117 +++++++++++++++++- netbox_documents/navigation.py | 15 +++ netbox_documents/search.py | 12 +- netbox_documents/tables.py | 40 ++++-- netbox_documents/template_content.py | 28 ++++- .../netbox_documents/powerpaneldocument.html | 56 +++++++++ .../powerpaneldocument_edit.html | 49 ++++++++ .../powerpaneldocument_include.html | 49 ++++++++ netbox_documents/urls.py | 15 ++- netbox_documents/utils.py | 2 + netbox_documents/views.py | 21 +++- 19 files changed, 520 insertions(+), 29 deletions(-) create mode 100644 netbox_documents/migrations/0008_powerpaneldocument.py create mode 100644 netbox_documents/templates/netbox_documents/powerpaneldocument.html create mode 100644 netbox_documents/templates/netbox_documents/powerpaneldocument_edit.html create mode 100644 netbox_documents/templates/netbox_documents/powerpaneldocument_include.html diff --git a/netbox_documents/__init__.py b/netbox_documents/__init__.py index ba1d1c4..65a5602 100644 --- a/netbox_documents/__init__.py +++ b/netbox_documents/__init__.py @@ -17,6 +17,7 @@ class NetboxDocuments(PluginConfig): "enable_device_type_documents": True, "enable_vm_documents": True, "enable_circuit_provider_documents": True, + "enable_power_panel_documents": True, "enable_navigation_menu": True, "site_documents_location": "left", "location_documents_location": "left", @@ -25,6 +26,7 @@ class NetboxDocuments(PluginConfig): "device_type_documents_location": "left", "vm_documents_location": "left", "circuit_provider_documents_location": "left", + "power_panel_documents_location": "left", } config = NetboxDocuments diff --git a/netbox_documents/api/serializers.py b/netbox_documents/api/serializers.py index 69489e1..5c4aacd 100644 --- a/netbox_documents/api/serializers.py +++ b/netbox_documents/api/serializers.py @@ -1,8 +1,8 @@ from rest_framework import serializers from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer -from ..models import SiteDocument, LocationDocument, DeviceDocument, DeviceTypeDocument, CircuitDocument, VMDocument, CircuitProviderDocument -from dcim.api.nested_serializers import NestedSiteSerializer, NestedLocationSerializer, NestedDeviceSerializer, NestedDeviceTypeSerializer +from ..models import SiteDocument, LocationDocument, DeviceDocument, DeviceTypeDocument, CircuitDocument, VMDocument, CircuitProviderDocument, PowerPanelDocument +from dcim.api.nested_serializers import NestedSiteSerializer, NestedLocationSerializer, NestedDeviceSerializer, NestedDeviceTypeSerializer from circuits.api.nested_serializers import NestedCircuitSerializer, NestedProviderSerializer from virtualization.api.nested_serializers import NestedVirtualMachineSerializer from .fields import UploadableBase64FileField @@ -199,4 +199,33 @@ class Meta: model = CircuitProviderDocument fields = ( 'id', 'url', 'display', 'name', 'document', 'external_url', 'document_type', 'filename', - ) \ No newline at end of file + ) + +# Power Panel Document Serializer +class PowerPanelDocumentSerializer(NetBoxModelSerializer): + + url = serializers.HyperlinkedIdentityField( + view_name='plugins-api:netbox_documents-api:powerpaneldocument-detail' + ) + + provider = NestedProviderSerializer() + document = UploadableBase64FileField(required=False) + + class Meta: + model = PowerPanelDocument + fields = ( + 'id', 'url', 'display', 'name', 'document', 'external_url', 'document_type', 'filename', 'powerpanel', 'comments', 'tags', 'custom_fields', 'created', + 'last_updated', + ) + +class NestedPowerPanelDocumentSerializer(WritableNestedSerializer): + + url = serializers.HyperlinkedIdentityField( + view_name='plugins-api:netbox_documents-api:powerpanel-detail' + ) + + class Meta: + model = PowerPanelDocument + fields = ( + 'id', 'url', 'display', 'name', 'document', 'external_url', 'document_type', 'filename', + ) diff --git a/netbox_documents/api/urls.py b/netbox_documents/api/urls.py index 4034248..7dedabd 100644 --- a/netbox_documents/api/urls.py +++ b/netbox_documents/api/urls.py @@ -11,5 +11,6 @@ router.register('circuit-documents', views.CircuitDocumentViewSet) router.register('vm-documents', views.VMDocumentViewSet) router.register('circuitprovider-documents', views.CircuitProviderDocumentViewSet) +router.register('powerpanel-documents', views.PowerPanelDocumentViewSet) urlpatterns = router.urls \ No newline at end of file diff --git a/netbox_documents/api/views.py b/netbox_documents/api/views.py index 6de627c..7e80380 100644 --- a/netbox_documents/api/views.py +++ b/netbox_documents/api/views.py @@ -1,7 +1,7 @@ from netbox.api.viewsets import NetBoxModelViewSet from .. import models, filtersets -from .serializers import SiteDocumentSerializer, LocationDocumentSerializer, DeviceDocumentSerializer, DeviceTypeDocumentSerializer, CircuitDocumentSerializer, VMDocumentSerializer, CircuitProviderDocumentSerializer +from .serializers import SiteDocumentSerializer, LocationDocumentSerializer, DeviceDocumentSerializer, DeviceTypeDocumentSerializer, CircuitDocumentSerializer, VMDocumentSerializer, CircuitProviderDocumentSerializer, PowerPanelDocumentSerializer class SiteDocumentViewSet(NetBoxModelViewSet): queryset = models.SiteDocument.objects.prefetch_related('tags') @@ -36,4 +36,9 @@ class VMDocumentViewSet(NetBoxModelViewSet): class CircuitProviderDocumentViewSet(NetBoxModelViewSet): queryset = models.CircuitProviderDocument.objects.prefetch_related('tags') serializer_class = CircuitProviderDocumentSerializer - filterset_class = filtersets.CircuitProviderDocumentFilterSet \ No newline at end of file + filterset_class = filtersets.CircuitProviderDocumentFilterSet + +class PowerPanelDocumentViewSet(NetBoxModelViewSet): + queryset = models.PowerPanelDocument.objects.prefetch_related('tags') + serializer_class = PowerPanelDocumentSerializer + filterset_class = filtersets.PowerPanelDocumentFilterSet diff --git a/netbox_documents/filtersets.py b/netbox_documents/filtersets.py index 7284127..1c64b40 100644 --- a/netbox_documents/filtersets.py +++ b/netbox_documents/filtersets.py @@ -1,5 +1,5 @@ from netbox.filtersets import NetBoxModelFilterSet -from .models import SiteDocument, LocationDocument, DeviceDocument, DeviceTypeDocument, CircuitDocument, VMDocument, CircuitProviderDocument +from .models import SiteDocument, LocationDocument, DeviceDocument, DeviceTypeDocument, CircuitDocument, VMDocument, CircuitProviderDocument, PowerPanelDocument from django.db.models import Q class SiteDocumentFilterSet(NetBoxModelFilterSet): @@ -86,7 +86,7 @@ def search(self, queryset, name, value): Q(name__icontains=value) | Q(document__icontains=value) ) - + class CircuitProviderDocumentFilterSet(NetBoxModelFilterSet): class Meta: @@ -99,4 +99,18 @@ def search(self, queryset, name, value): return queryset.filter( Q(name__icontains=value) | Q(document__icontains=value) - ) \ No newline at end of file + ) + +class PowerPanelDocumentFilterSet(NetBoxModelFilterSet): + + class Meta: + model = PowerPanelDocument + fields = ('id', 'name', 'document_type', 'powerpanel') + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + return queryset.filter( + Q(name__icontains=value) | + Q(document__icontains=value) + ) diff --git a/netbox_documents/forms.py b/netbox_documents/forms.py index a29eee8..9c84ed5 100644 --- a/netbox_documents/forms.py +++ b/netbox_documents/forms.py @@ -1,10 +1,10 @@ from django import forms from netbox.forms import NetBoxModelForm, NetBoxModelFilterSetForm -from dcim.models import Site, Location, Device, DeviceType +from dcim.models import Site, Location, Device, DeviceType, PowerPanel from virtualization.models import VirtualMachine from circuits.models import Circuit, Provider from utilities.forms.fields import TagFilterField, CommentField, DynamicModelChoiceField -from .models import SiteDocument, LocationDocument, DeviceDocument, DeviceTypeDocument, CircuitDocument, CircuitDocTypeChoices, SiteDocTypeChoices, LocationDocTypeChoices, DeviceDocTypeChoices, DeviceTypeDocTypeChoices, VMDocument, VMDocTypeChoices, CircuitProviderDocument, CircuitProviderDocTypeChoices +from .models import SiteDocument, LocationDocument, DeviceDocument, DeviceTypeDocument, CircuitDocument, CircuitDocTypeChoices, SiteDocTypeChoices, LocationDocTypeChoices, DeviceDocTypeChoices, DeviceTypeDocTypeChoices, VMDocument, VMDocTypeChoices, CircuitProviderDocument, CircuitProviderDocTypeChoices, PowerPanelDocument, PowerPanelDocTypeChoices #### Site Document Form & Filter Form @@ -241,4 +241,35 @@ class CircuitProviderDocumentFilterForm(NetBoxModelFilterSetForm): required=False ) - tag = TagFilterField(model) \ No newline at end of file + tag = TagFilterField(model) + +#### Power Panel Document Form & Filter Form +class PowerPanelDocumentForm(NetBoxModelForm): + comments = CommentField() + + provider = DynamicModelChoiceField( + queryset=Provider.objects.all() + ) + + class Meta: + model = PowerPanelDocument + fields = ('name', 'document', 'external_url', 'document_type', 'powerpanel', 'comments', 'tags') + +class PowerPanelDocumentFilterForm(NetBoxModelFilterSetForm): + model = PowerPanelDocument + + name = forms.CharField( + required=False + ) + + powerpanel = forms.ModelMultipleChoiceField( + queryset=PowerPanel.objects.all(), + required=False + ) + + document_type = forms.MultipleChoiceField( + choices=PowerPanelDocTypeChoices, + required=False + ) + + tag = TagFilterField(model) diff --git a/netbox_documents/migrations/0007_circuitproviderdocument.py b/netbox_documents/migrations/0007_circuitproviderdocument.py index afa4961..bc99507 100644 --- a/netbox_documents/migrations/0007_circuitproviderdocument.py +++ b/netbox_documents/migrations/0007_circuitproviderdocument.py @@ -10,7 +10,6 @@ class Migration(migrations.Migration): dependencies = [ - ('extras', '0107_cachedvalue_extras_cachedvalue_object'), ('circuits', '0043_circuittype_color'), ('netbox_documents', '0006_vmdocument'), ] diff --git a/netbox_documents/migrations/0008_powerpaneldocument.py b/netbox_documents/migrations/0008_powerpaneldocument.py new file mode 100644 index 0000000..b480d7c --- /dev/null +++ b/netbox_documents/migrations/0008_powerpaneldocument.py @@ -0,0 +1,40 @@ +# Generated by Django 4.2.9 on 2024-04-18 13:16 + +from django.db import migrations, models +import django.db.models.deletion +import netbox_documents.utils +import taggit.managers +import utilities.json + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0185_gfk_indexes'), + ('extras', '0106_bookmark_user_cascade_deletion'), + ('netbox_documents', '0007_circuitproviderdocument'), + ] + + operations = [ + migrations.CreateModel( + name='PowerPanelDocument', + 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)), + ('name', models.CharField(blank=True, max_length=100)), + ('document', models.FileField(blank=True, upload_to=netbox_documents.utils.file_upload)), + ('external_url', models.URLField(blank=True, max_length=255)), + ('document_type', models.CharField(max_length=30)), + ('comments', models.TextField(blank=True)), + ('powerpanel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='dcim.powerpanel')), + ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), + ], + options={ + 'verbose_name': 'Power Panel Document', + 'verbose_name_plural': 'Power Panel Documents', + 'ordering': ('name',), + }, + ), + ] diff --git a/netbox_documents/models.py b/netbox_documents/models.py index 5fc5348..6d980cf 100644 --- a/netbox_documents/models.py +++ b/netbox_documents/models.py @@ -98,6 +98,15 @@ class VMDocTypeChoices(ChoiceSet): ('other', 'Other', 'gray'), ] +class PowerPanelDocTypeChoices(ChoiceSet): + + key = 'DocTypeChoices.circuit' + + CHOICES = [ + ('schematic', 'Electrical Schematic', 'red'), + ('other', 'Other', 'gray'), + ] + class SiteDocument(NetBoxModel): name = models.CharField( max_length=100, @@ -855,4 +864,110 @@ def delete(self, *args, **kwargs): self.document.name = _name else: # Straight delete of external URL - super().delete(*args, **kwargs) \ No newline at end of file + super().delete(*args, **kwargs) + + +class PowerPanelDocument(NetBoxModel): + name = models.CharField( + max_length=100, + blank=True, + help_text='(Optional) Specify a name to display for this document. If no name is specified, the filename will be used.' + ) + + document = models.FileField( + upload_to=file_upload, + blank=True + ) + + external_url = models.URLField( + blank=True, + max_length=255 + ) + + document_type = models.CharField( + max_length=30, + choices=PowerPanelDocTypeChoices + ) + + powerpanel = models.ForeignKey( + to='dcim.PowerPanel', + on_delete=models.CASCADE, + related_name='documents' + ) + + comments = models.TextField( + blank=True + ) + + def get_document_type_color(self): + return PowerPanelDocTypeChoices.colors.get(self.document_type) + + class Meta: + ordering = ('name',) + verbose_name_plural = "Power Panel Documents" + verbose_name = "Power Panel Document" + + @property + def size(self): + """ + Wrapper around `document.size` to suppress an OSError in case the file is inaccessible. Also opportunistically + catch other exceptions that we know other storage back-ends to throw. + """ + expected_exceptions = [OSError] + + try: + from botocore.exceptions import ClientError + expected_exceptions.append(ClientError) + except ImportError: + pass + + try: + return self.document.size + except: + return None + + @property + def filename(self): + if self.external_url: + return self.external_url + filename = self.document.name.rsplit('/', 1)[-1] + return filename.split('_', 1)[1] + + def __str__(self): + if self.name: + return self.name + + if self.external_url: + return self.external_url + + filename = self.document.name.rsplit('/', 1)[-1] + return filename.split('_', 1)[1] + + def get_absolute_url(self): + return reverse('plugins:netbox_documents:powerpaneldocument', args=[self.pk]) + + def clean(self): + super().clean() + + # Must have an uploaded document or an external URL. cannot have both + if not self.document and self.external_url == '': + raise ValidationError("A document must contain an uploaded file or an external URL.") + if self.document and self.external_url: + raise ValidationError("A document cannot contain both an uploaded file and an external URL.") + + def delete(self, *args, **kwargs): + + # Check if its a document or a URL + if self.external_url == '': + + _name = self.document.name + + # Delete file from disk + super().delete(*args, **kwargs) + self.document.delete(save=False) + + # Restore the name of the document as it's re-used in the notifications later + self.document.name = _name + else: + # Straight delete of external URL + super().delete(*args, **kwargs) diff --git a/netbox_documents/navigation.py b/netbox_documents/navigation.py index 2c56576..f25dd02 100644 --- a/netbox_documents/navigation.py +++ b/netbox_documents/navigation.py @@ -124,6 +124,21 @@ ) ) + # Add a menu item for Power Panel Documents if enabled + if plugin_settings.get('enable_power_panel_documents'): + menuitem.append( + PluginMenuItem( + link='plugins:netbox_documents:powerpaneldocument_list', + link_text='Power Panel Documents', + buttons=[PluginMenuButton( + link='plugins:netbox_documents:powerpaneldocument_add', + title='Add', + icon_class='mdi mdi-plus-thick', + color=ButtonColorChoices.GREEN + )] + ) + ) + else: # Fall back to pre 3.4 navigation option diff --git a/netbox_documents/search.py b/netbox_documents/search.py index 22e81f6..1e29b86 100644 --- a/netbox_documents/search.py +++ b/netbox_documents/search.py @@ -1,5 +1,5 @@ from netbox.search import SearchIndex -from .models import SiteDocument, LocationDocument, DeviceDocument, DeviceTypeDocument, CircuitDocument, VMDocument, CircuitProviderDocument +from .models import SiteDocument, LocationDocument, DeviceDocument, DeviceTypeDocument, CircuitDocument, VMDocument, CircuitProviderDocument, PowerPanelDocument from django.conf import settings # If we run NB 3.4+ register search indexes @@ -60,5 +60,13 @@ class CircuitProviderDocumentIndex(SearchIndex): ("comments", 5000), ) + class PowerPanelDocumentIndex(SearchIndex): + model = PowerPanelDocument + fields = ( + ("name", 100), + ("document", 500), + ("comments", 5000), + ) + # Register indexes - indexes = [SiteDocumentIndex, LocationDocumentIndex, CircuitDocumentIndex, DeviceTypeDocumentIndex, DeviceDocumentIndex, VMDocumentIndex, CircuitProviderDocumentIndex] \ No newline at end of file + indexes = [SiteDocumentIndex, LocationDocumentIndex, CircuitDocumentIndex, DeviceTypeDocumentIndex, DeviceDocumentIndex, VMDocumentIndex, CircuitProviderDocumentIndex, PowerPanelDocumentIndex] \ No newline at end of file diff --git a/netbox_documents/tables.py b/netbox_documents/tables.py index a2aba3a..75f718d 100644 --- a/netbox_documents/tables.py +++ b/netbox_documents/tables.py @@ -1,7 +1,7 @@ import django_tables2 as tables from netbox.tables import NetBoxTable, columns -from .models import SiteDocument, LocationDocument, DeviceDocument, DeviceTypeDocument, CircuitDocument, VMDocument +from .models import SiteDocument, LocationDocument, DeviceDocument, DeviceTypeDocument, CircuitDocument, VMDocument, PowerPanelDocument SITE_DOCUMENT_LINK = """ {% if record.size %} @@ -59,6 +59,14 @@ {% endif %} """ +POWER_PANEL_DOCUMENT_LINK = """ +{% if record.size %} + {% firstof record.name record.filename %} (View Document) +{% else %} + {% firstof record.name record.filename %} (View External Document) +{% endif %} +""" + class SiteDocumentTable(NetBoxTable): name = tables.TemplateColumn(template_code=SITE_DOCUMENT_LINK) document_type = columns.ChoiceFieldColumn() @@ -91,7 +99,7 @@ class LocationDocumentTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = LocationDocument - fields = ('pk', 'id', 'name', 'document_type', 'size', 'filename', 'site', 'location', 'comments', 'actions', 'created', 'last_updated', 'tags') + fields = ('pk', 'id', 'name', 'document_type', 'size', 'filename', 'site', 'location', 'comments', 'actions', 'created', 'last_updated', 'tags') default_columns = ('name', 'document_type', 'site', 'location', 'tags') class DeviceDocumentTable(NetBoxTable): @@ -107,7 +115,7 @@ class DeviceDocumentTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = DeviceDocument - fields = ('pk', 'id', 'name', 'document_type', 'size', 'filename', 'device', 'comments', 'actions', 'created', 'last_updated', 'tags') + fields = ('pk', 'id', 'name', 'document_type', 'size', 'filename', 'device', 'comments', 'actions', 'created', 'last_updated', 'tags') default_columns = ('name', 'document_type', 'device', 'tags') @@ -124,7 +132,7 @@ class DeviceTypeDocumentTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = DeviceTypeDocument - fields = ('pk', 'id', 'name', 'document_type', 'size', 'filename', 'device_type', 'comments', 'actions', 'created', 'last_updated', 'tags') + fields = ('pk', 'id', 'name', 'document_type', 'size', 'filename', 'device_type', 'comments', 'actions', 'created', 'last_updated', 'tags') default_columns = ('name', 'document_type', 'device_type', 'tags') class CircuitDocumentTable(NetBoxTable): @@ -140,7 +148,7 @@ class CircuitDocumentTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = CircuitDocument - fields = ('pk', 'id', 'name', 'document_type', 'size', 'filename', 'circuit', 'comments', 'actions', 'created', 'last_updated', 'tags') + fields = ('pk', 'id', 'name', 'document_type', 'size', 'filename', 'circuit', 'comments', 'actions', 'created', 'last_updated', 'tags') default_columns = ('name', 'document_type', 'circuit', 'tags') class VMDocumentTable(NetBoxTable): @@ -156,7 +164,7 @@ class VMDocumentTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = VMDocument - fields = ('pk', 'id', 'name', 'document_type', 'size', 'filename', 'vm', 'comments', 'actions', 'created', 'last_updated', 'tags') + fields = ('pk', 'id', 'name', 'document_type', 'size', 'filename', 'vm', 'comments', 'actions', 'created', 'last_updated', 'tags') default_columns = ('name', 'document_type', 'vm', 'tags') class CircuitProviderDocumentTable(NetBoxTable): @@ -172,5 +180,21 @@ class CircuitProviderDocumentTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = CircuitDocument - fields = ('pk', 'id', 'name', 'document_type', 'size', 'filename', 'provider', 'comments', 'actions', 'created', 'last_updated', 'tags') - default_columns = ('name', 'document_type', 'provider', 'tags') \ No newline at end of file + fields = ('pk', 'id', 'name', 'document_type', 'size', 'filename', 'provider', 'comments', 'actions', 'created', 'last_updated', 'tags') + default_columns = ('name', 'document_type', 'provider', 'tags') + +class PowerPanelDocumentTable(NetBoxTable): + name = tables.TemplateColumn(template_code=POWER_PANEL_DOCUMENT_LINK) + document_type = columns.ChoiceFieldColumn() + powerpanel = tables.Column( + linkify=True + ) + + tags = columns.TagColumn( + url_name='dcim:sitegroup_list' + ) + + class Meta(NetBoxTable.Meta): + model = CircuitDocument + fields = ('pk', 'id', 'name', 'document_type', 'size', 'filename', 'powerpanel', 'comments', 'actions', 'created', 'last_updated', 'tags') + default_columns = ('name', 'document_type', 'powerpanel', 'tags') \ No newline at end of file diff --git a/netbox_documents/template_content.py b/netbox_documents/template_content.py index a632685..feb3e86 100644 --- a/netbox_documents/template_content.py +++ b/netbox_documents/template_content.py @@ -1,6 +1,6 @@ from extras.plugins import PluginTemplateExtension from django.conf import settings -from .models import SiteDocument, LocationDocument, DeviceDocument, DeviceTypeDocument, CircuitDocument, VMDocument, CircuitProviderDocument +from .models import SiteDocument, LocationDocument, DeviceDocument, DeviceTypeDocument, CircuitDocument, VMDocument, CircuitProviderDocument, PowerPanelDocument plugin_settings = settings.PLUGINS_CONFIG.get('netbox_documents', {}) @@ -182,5 +182,29 @@ def right_page(self): else: return "" +class PowerPanelDocumentList(PluginTemplateExtension): + model = 'dcim.powerpanel' -template_extensions = [SiteDocumentList, LocationDocumentList, DeviceDocumentList, DeviceTypeDocumentList, CircuitDocumentList, VMDocumentList, CircuitProviderDocumentList] + def left_page(self): + + if plugin_settings.get('enable_power_panel_documents') and plugin_settings.get('power_panel_documents_location') == 'left': + + return self.render('netbox_documents/powerpaneldocument_include.html', extra_context={ + 'power_panel_documents': PowerPanelDocument.objects.filter(powerpanel=self.context['object']), + }) + + else: + return "" + + def right_page(self): + + if plugin_settings.get('enable_power_panel_documents') and plugin_settings.get('power_panel_documents_location') == 'right': + + return self.render('netbox_documents/powerpaneldocument_include.html', extra_context={ + 'power_panel_documents': PowerPanelDocument.objects.filter(powerpanel=self.context['object']), + }) + + else: + return "" + +template_extensions = [SiteDocumentList, LocationDocumentList, DeviceDocumentList, DeviceTypeDocumentList, CircuitDocumentList, VMDocumentList, CircuitProviderDocumentList, PowerPanelDocumentList] diff --git a/netbox_documents/templates/netbox_documents/powerpaneldocument.html b/netbox_documents/templates/netbox_documents/powerpaneldocument.html new file mode 100644 index 0000000..72e2b6c --- /dev/null +++ b/netbox_documents/templates/netbox_documents/powerpaneldocument.html @@ -0,0 +1,56 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block content %} +
+
+
+
VM Document
+
+ + + + + + + + + + + + + + {% if object.external_url %} + + + + + {% else %} + + + + + + + + + {% endif %} +
Name{{ object.name|placeholder }}
Power Panel{{ object.powerpanel }}
Document Type{% badge object.get_document_type_display bg_color=object.get_document_type_color %}
External URL{{ object.external_url }}
Filename{{ object.filename }}
Size{{ object.size|filesizeformat }}
+
+
+ {% include 'inc/panels/custom_fields.html' %} + {% plugin_left_page object %} +
+
+ {% include 'inc/panels/tags.html' %} + {% include 'inc/panels/comments.html' %} + {% plugin_right_page object %} +
+
+
+
+ {% plugin_full_width_page object %} +
+
+{% endblock %} \ No newline at end of file diff --git a/netbox_documents/templates/netbox_documents/powerpaneldocument_edit.html b/netbox_documents/templates/netbox_documents/powerpaneldocument_edit.html new file mode 100644 index 0000000..ad8fbcc --- /dev/null +++ b/netbox_documents/templates/netbox_documents/powerpaneldocument_edit.html @@ -0,0 +1,49 @@ +{% extends 'generic/object_edit.html' %} +{% load static %} +{% load form_helpers %} +{% load helpers %} + +{% block form %} +
+ {% render_field form.name %} + +
+
+
+ +
+
+
+
+ {% render_field form.document %} +
+
+ {% render_field form.external_url %} +
+
+ + {% render_field form.document_type %} + {% render_field form.powerpanel %} + +
+
+
Comments
+
+ {% render_field form.comments %} +
+ + {% render_field form.tags %} +
+
+{% endblock %} diff --git a/netbox_documents/templates/netbox_documents/powerpaneldocument_include.html b/netbox_documents/templates/netbox_documents/powerpaneldocument_include.html new file mode 100644 index 0000000..7066b04 --- /dev/null +++ b/netbox_documents/templates/netbox_documents/powerpaneldocument_include.html @@ -0,0 +1,49 @@ +{% load helpers %} + +
+
+ Documents +
+
+{% if powerpanel_documents %} + + + + + + + + {% for document in powerpanel_documents %} + + + + + + + {% endfor %} +
NameSizeType
+ {% firstof document.name document.filename document.external_url %} + {% if document.size %}{{ document.size|filesizeformat }}{% else %}{% endif %}{% badge document.get_document_type_display bg_color=document.get_document_type_color %} + + + + + + + + + +
+ {% else %} +
+ None +
+ {% endif %} +
+ +
\ No newline at end of file diff --git a/netbox_documents/urls.py b/netbox_documents/urls.py index dd35681..03cf2c6 100644 --- a/netbox_documents/urls.py +++ b/netbox_documents/urls.py @@ -63,7 +63,7 @@ path('vm-document//delete/', views.VMDocumentDeleteView.as_view(), name='vmdocument_delete'), path('vm-document//changelog/', ObjectChangeLogView.as_view(), name='vmdocument_changelog', kwargs={ 'model': models.VMDocument - }), + }), # CircuitProviderDocument path('circuitprovider-document/', views.CircuitProviderDocumentListView.as_view(), name='circuitproviderdocument_list'), @@ -73,7 +73,16 @@ path('circuitprovider-document//delete/', views.CircuitProviderDocumentDeleteView.as_view(), name='circuitproviderdocument_delete'), path('circuitprovider-document//changelog/', ObjectChangeLogView.as_view(), name='circuitproviderdocument_changelog', kwargs={ 'model': models.CircuitProviderDocument - }), - + }), + + # PowerPanelDocument + path('powerpanel-document/', views.PowerPanelDocumentListView.as_view(), name='powerpaneldocument_list'), + path('powerpanel-document/add/', views.PowerPanelDocumentEditView.as_view(), name='powerpaneldocument_add'), + path('powerpanel-document//', views.PowerPanelDocumentView.as_view(), name='powerpaneldocument'), + path('powerpanel-document//edit/', views.PowerPanelDocumentEditView.as_view(), name='powerpaneldocument_edit'), + path('powerpanel-document//delete/', views.PowerPanelDocumentDeleteView.as_view(), name='powerpaneldocument_delete'), + path('powerpanel-document//changelog/', ObjectChangeLogView.as_view(), name='powerpaneldocument_changelog', kwargs={ + 'model': models.PowerPanelDocument + }), ) \ No newline at end of file diff --git a/netbox_documents/utils.py b/netbox_documents/utils.py index fcf9f14..41faeb5 100644 --- a/netbox_documents/utils.py +++ b/netbox_documents/utils.py @@ -22,6 +22,8 @@ def file_upload(instance, filename): path_prepend = instance.vm.id if hasattr(instance, 'provider'): path_prepend = instance.provider.id + if hasattr(instance, 'powerpanel'): + path_prepend = instance.powerpanel.id # Rename the file to the provided name, if any. Attempt to preserve the file extension. extension = filename.rsplit('.')[-1].lower() diff --git a/netbox_documents/views.py b/netbox_documents/views.py index bcf383a..0fbe7c7 100644 --- a/netbox_documents/views.py +++ b/netbox_documents/views.py @@ -137,4 +137,23 @@ class CircuitProviderDocumentEditView(generic.ObjectEditView): template_name = 'netbox_documents/circuitproviderdocument_edit.html' class CircuitProviderDocumentDeleteView(generic.ObjectDeleteView): - queryset = models.CircuitProviderDocument.objects.all() \ No newline at end of file + queryset = models.CircuitProviderDocument.objects.all() + +### PowerPanelDocument +class PowerPanelDocumentView(generic.ObjectView): + queryset = models.PowerPanelDocument.objects.all() + +class PowerPanelDocumentListView(generic.ObjectListView): + queryset = models.PowerPanelDocument.objects.all() + table = tables.PowerPanelDocumentTable + filterset = filtersets.PowerPanelDocumentFilterSet + filterset_form = forms.PowerPanelDocumentFilterForm + +class PowerPanelDocumentEditView(generic.ObjectEditView): + queryset = models.PowerPanelDocument.objects.all() + form = forms.PowerPanelDocumentForm + + template_name = 'netbox_documents/powerpaneldocument_edit.html' + +class PowerPanelDocumentDeleteView(generic.ObjectDeleteView): + queryset = models.PowerPanelDocument.objects.all() \ No newline at end of file From f68c219b8ca0803196eabbc4db49a821c095ef7c Mon Sep 17 00:00:00 2001 From: Nico Felbinger Date: Thu, 18 Apr 2024 08:33:42 -0500 Subject: [PATCH 2/3] Adjust README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d44afe1..6e3f71c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ A plugin designed to faciliate the storage of site, circuit, device type and dev - Locations - Virtual Machines - Circuit Providers + - Power Panels * Upload documents to your NetBox media/ folder or other Django supported storage method e.g. S3 * Supports a wide array of common file types (bmp, gif, jpeg, jpg, png, pdf, txt, doc, docx, xls, xlsx, xlsm) From 30ca538fb9ead7a6c4a9751b8a00b6ec3f87f6a1 Mon Sep 17 00:00:00 2001 From: Nico Felbinger Date: Fri, 19 Apr 2024 01:10:01 -0500 Subject: [PATCH 3/3] Fix typos --- netbox_documents/api/serializers.py | 4 ++-- netbox_documents/forms.py | 4 ++-- netbox_documents/template_content.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/netbox_documents/api/serializers.py b/netbox_documents/api/serializers.py index 5c4aacd..8c6346e 100644 --- a/netbox_documents/api/serializers.py +++ b/netbox_documents/api/serializers.py @@ -2,7 +2,7 @@ from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer from ..models import SiteDocument, LocationDocument, DeviceDocument, DeviceTypeDocument, CircuitDocument, VMDocument, CircuitProviderDocument, PowerPanelDocument -from dcim.api.nested_serializers import NestedSiteSerializer, NestedLocationSerializer, NestedDeviceSerializer, NestedDeviceTypeSerializer +from dcim.api.nested_serializers import NestedSiteSerializer, NestedLocationSerializer, NestedDeviceSerializer, NestedDeviceTypeSerializer, NestedPowerPanelSerializer from circuits.api.nested_serializers import NestedCircuitSerializer, NestedProviderSerializer from virtualization.api.nested_serializers import NestedVirtualMachineSerializer from .fields import UploadableBase64FileField @@ -208,7 +208,7 @@ class PowerPanelDocumentSerializer(NetBoxModelSerializer): view_name='plugins-api:netbox_documents-api:powerpaneldocument-detail' ) - provider = NestedProviderSerializer() + powerpanel = NestedPowerPanelSerializer() document = UploadableBase64FileField(required=False) class Meta: diff --git a/netbox_documents/forms.py b/netbox_documents/forms.py index 9c84ed5..25c28d8 100644 --- a/netbox_documents/forms.py +++ b/netbox_documents/forms.py @@ -247,8 +247,8 @@ class CircuitProviderDocumentFilterForm(NetBoxModelFilterSetForm): class PowerPanelDocumentForm(NetBoxModelForm): comments = CommentField() - provider = DynamicModelChoiceField( - queryset=Provider.objects.all() + powerpanel = DynamicModelChoiceField( + queryset=PowerPanel.objects.all() ) class Meta: diff --git a/netbox_documents/template_content.py b/netbox_documents/template_content.py index feb3e86..3dbd8a2 100644 --- a/netbox_documents/template_content.py +++ b/netbox_documents/template_content.py @@ -190,7 +190,7 @@ def left_page(self): if plugin_settings.get('enable_power_panel_documents') and plugin_settings.get('power_panel_documents_location') == 'left': return self.render('netbox_documents/powerpaneldocument_include.html', extra_context={ - 'power_panel_documents': PowerPanelDocument.objects.filter(powerpanel=self.context['object']), + 'powerpanel_documents': PowerPanelDocument.objects.filter(powerpanel=self.context['object']), }) else: @@ -201,7 +201,7 @@ def right_page(self): if plugin_settings.get('enable_power_panel_documents') and plugin_settings.get('power_panel_documents_location') == 'right': return self.render('netbox_documents/powerpaneldocument_include.html', extra_context={ - 'power_panel_documents': PowerPanelDocument.objects.filter(powerpanel=self.context['object']), + 'powerpanel_documents': PowerPanelDocument.objects.filter(powerpanel=self.context['object']), }) else: