diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index 5b98dabb0..39d763bb5 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -14,6 +14,7 @@ NetBox v2.9 replaces Django's built-in permissions framework with one that suppo * [#3703](https://github.com/netbox-community/netbox/issues/3703) - Tags must be created administratively before being assigned to an object * [#4615](https://github.com/netbox-community/netbox/issues/4615) - Add `label` field for all device components * [#4742](https://github.com/netbox-community/netbox/issues/4742) - Add tagging for cables, power panels, and rack reservations +* [#4788](https://github.com/netbox-community/netbox/issues/4788) - Add dedicated views for all device components ### Configuration Changes diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 30a276c7d..d94e2484f 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -268,7 +268,7 @@ class ConsolePort(CableTermination, ComponentModel): unique_together = ('device', 'name') def get_absolute_url(self): - return self.device.get_absolute_url() + return reverse('dcim:consoleport', kwargs={'pk': self.pk}) def to_csv(self): return ( @@ -325,7 +325,7 @@ class ConsoleServerPort(CableTermination, ComponentModel): unique_together = ('device', 'name') def get_absolute_url(self): - return self.device.get_absolute_url() + return reverse('dcim:consoleserverport', kwargs={'pk': self.pk}) def to_csv(self): return ( @@ -408,7 +408,7 @@ class PowerPort(CableTermination, ComponentModel): unique_together = ('device', 'name') def get_absolute_url(self): - return self.device.get_absolute_url() + return reverse('dcim:powerport', kwargs={'pk': self.pk}) def to_csv(self): return ( @@ -560,7 +560,7 @@ class PowerOutlet(CableTermination, ComponentModel): unique_together = ('device', 'name') def get_absolute_url(self): - return self.device.get_absolute_url() + return reverse('dcim:poweroutlet', kwargs={'pk': self.pk}) def to_csv(self): return ( @@ -881,6 +881,9 @@ class FrontPort(CableTermination, ComponentModel): def __str__(self): return self.name + def get_absolute_url(self): + return reverse('dcim:frontport', kwargs={'pk': self.pk}) + def to_csv(self): return ( self.device.identifier, @@ -946,6 +949,9 @@ class RearPort(CableTermination, ComponentModel): def __str__(self): return self.name + def get_absolute_url(self): + return reverse('dcim:rearport', kwargs={'pk': self.pk}) + def to_csv(self): return ( self.device.identifier, @@ -1005,7 +1011,7 @@ class DeviceBay(ComponentModel): return '{} - {}'.format(self.device.name, self.name) def get_absolute_url(self): - return self.device.get_absolute_url() + return reverse('dcim:devicebay', kwargs={'pk': self.pk}) def to_csv(self): return ( diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 6aa41ab44..9979bea1a 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -490,18 +490,6 @@ class ConsolePortTemplateTable(ComponentTemplateTable): empty_text = "None" -class ConsolePortImportTable(BaseTable): - device = tables.LinkColumn( - viewname='dcim:device', - args=[Accessor('device.pk')] - ) - - class Meta(BaseTable.Meta): - model = ConsolePort - fields = ('device', 'name', 'description') - empty_text = False - - class ConsoleServerPortTemplateTable(ComponentTemplateTable): actions = tables.TemplateColumn( template_code=get_component_template_actions('consoleserverporttemplate'), @@ -515,18 +503,6 @@ class ConsoleServerPortTemplateTable(ComponentTemplateTable): empty_text = "None" -class ConsoleServerPortImportTable(BaseTable): - device = tables.LinkColumn( - viewname='dcim:device', - args=[Accessor('device.pk')] - ) - - class Meta(BaseTable.Meta): - model = ConsoleServerPort - fields = ('device', 'name', 'description') - empty_text = False - - class PowerPortTemplateTable(ComponentTemplateTable): actions = tables.TemplateColumn( template_code=get_component_template_actions('powerporttemplate'), @@ -540,18 +516,6 @@ class PowerPortTemplateTable(ComponentTemplateTable): empty_text = "None" -class PowerPortImportTable(BaseTable): - device = tables.LinkColumn( - viewname='dcim:device', - args=[Accessor('device.pk')] - ) - - class Meta(BaseTable.Meta): - model = PowerPort - fields = ('device', 'name', 'description', 'maximum_draw', 'allocated_draw') - empty_text = False - - class PowerOutletTemplateTable(ComponentTemplateTable): actions = tables.TemplateColumn( template_code=get_component_template_actions('poweroutlettemplate'), @@ -565,18 +529,6 @@ class PowerOutletTemplateTable(ComponentTemplateTable): empty_text = "None" -class PowerOutletImportTable(BaseTable): - device = tables.LinkColumn( - viewname='dcim:device', - args=[Accessor('device.pk')] - ) - - class Meta(BaseTable.Meta): - model = PowerOutlet - fields = ('device', 'name', 'description', 'power_port', 'feed_leg') - empty_text = False - - class InterfaceTemplateTable(ComponentTemplateTable): mgmt_only = BooleanColumn( verbose_name='Management Only' @@ -593,20 +545,6 @@ class InterfaceTemplateTable(ComponentTemplateTable): empty_text = "None" -class InterfaceImportTable(BaseTable): - device = tables.LinkColumn( - viewname='dcim:device', - args=[Accessor('device.pk')] - ) - - class Meta(BaseTable.Meta): - model = Interface - fields = ( - 'device', 'name', 'description', 'lag', 'type', 'enabled', 'mac_address', 'mtu', 'mgmt_only', 'mode', - ) - empty_text = False - - class FrontPortTemplateTable(ComponentTemplateTable): rear_port_position = tables.Column( verbose_name='Position' @@ -623,18 +561,6 @@ class FrontPortTemplateTable(ComponentTemplateTable): empty_text = "None" -class FrontPortImportTable(BaseTable): - device = tables.LinkColumn( - viewname='dcim:device', - args=[Accessor('device.pk')] - ) - - class Meta(BaseTable.Meta): - model = FrontPort - fields = ('device', 'name', 'description', 'type', 'rear_port', 'rear_port_position') - empty_text = False - - class RearPortTemplateTable(ComponentTemplateTable): actions = tables.TemplateColumn( template_code=get_component_template_actions('rearporttemplate'), @@ -648,18 +574,6 @@ class RearPortTemplateTable(ComponentTemplateTable): empty_text = "None" -class RearPortImportTable(BaseTable): - device = tables.LinkColumn( - viewname='dcim:device', - args=[Accessor('device.pk')] - ) - - class Meta(BaseTable.Meta): - model = RearPort - fields = ('device', 'name', 'description', 'type', 'position') - empty_text = False - - class DeviceBayTemplateTable(ComponentTemplateTable): actions = tables.TemplateColumn( template_code=get_component_template_actions('devicebaytemplate'), @@ -855,144 +769,94 @@ class DeviceImportTable(BaseTable): # Device components # -class DeviceComponentDetailTable(BaseTable): +class DeviceComponentTable(BaseTable): pk = ToggleColumn() - device = tables.LinkColumn() - name = tables.Column(order_by=('_name',)) - cable = tables.LinkColumn() + device = tables.Column( + linkify=True + ) + name = tables.Column( + linkify=True, + order_by=('_name',) + ) + cable = tables.Column( + linkify=True + ) class Meta(BaseTable.Meta): order_by = ('device', 'name') - fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'cable') - sequence = ('pk', 'device', 'name', 'label', 'type', 'description', 'cable') -class ConsolePortTable(BaseTable): - name = tables.Column(order_by=('_name',)) +class ConsolePortTable(DeviceComponentTable): - class Meta(BaseTable.Meta): + class Meta(DeviceComponentTable.Meta): model = ConsolePort - fields = ('name', 'label', 'type') + fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'cable') + default_columns = ('pk', 'device', 'name', 'label', 'type', 'description') -class ConsolePortDetailTable(DeviceComponentDetailTable): +class ConsoleServerPortTable(DeviceComponentTable): - class Meta(DeviceComponentDetailTable.Meta, ConsolePortTable.Meta): - pass - - -class ConsoleServerPortTable(BaseTable): - name = tables.Column(order_by=('_name',)) - - class Meta(BaseTable.Meta): + class Meta(DeviceComponentTable.Meta): model = ConsoleServerPort - fields = ('name', 'label', 'description') + fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'cable') + default_columns = ('pk', 'device', 'name', 'label', 'type', 'description') -class ConsoleServerPortDetailTable(DeviceComponentDetailTable): +class PowerPortTable(DeviceComponentTable): - class Meta(DeviceComponentDetailTable.Meta, ConsoleServerPortTable.Meta): - pass - - -class PowerPortTable(BaseTable): - name = tables.Column(order_by=('_name',)) - - class Meta(BaseTable.Meta): + class Meta(DeviceComponentTable.Meta): model = PowerPort - fields = ('name', 'label', 'type') + fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'maximum_draw', 'allocated_draw', 'cable') + default_columns = ('pk', 'device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description') -class PowerPortDetailTable(DeviceComponentDetailTable): +class PowerOutletTable(DeviceComponentTable): - class Meta(DeviceComponentDetailTable.Meta, PowerPortTable.Meta): - pass - - -class PowerOutletTable(BaseTable): - name = tables.Column(order_by=('_name',)) - - class Meta(BaseTable.Meta): + class Meta(DeviceComponentTable.Meta): model = PowerOutlet - fields = ('name', 'label', 'type', 'description') + fields = ('pk', 'device', 'name', 'label', 'type', 'description', 'power_port', 'feed_leg', 'cable') + default_columns = ('pk', 'device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description') -class PowerOutletDetailTable(DeviceComponentDetailTable): - - class Meta(DeviceComponentDetailTable.Meta, PowerOutletTable.Meta): - pass - - -class InterfaceTable(BaseTable): - - class Meta(BaseTable.Meta): - model = Interface - fields = ('name', 'label', 'type', 'lag', 'enabled', 'mgmt_only', 'description') - - -class InterfaceDetailTable(DeviceComponentDetailTable): +class InterfaceTable(DeviceComponentTable): enabled = BooleanColumn() - class Meta(DeviceComponentDetailTable.Meta, InterfaceTable.Meta): - fields = ('pk', 'device', 'name', 'label', 'enabled', 'type', 'description', 'cable') - sequence = ('pk', 'device', 'name', 'label', 'enabled', 'type', 'description', 'cable') + class Meta(DeviceComponentTable.Meta): + model = Interface + fields = ( + 'pk', 'device', 'name', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'description', 'cable', + ) + default_columns = ('pk', 'device', 'name', 'label', 'enabled', 'type', 'description') -class FrontPortTable(BaseTable): - name = tables.Column(order_by=('_name',)) +class FrontPortTable(DeviceComponentTable): + rear_port_position = tables.Column( + verbose_name='Position' + ) - class Meta(BaseTable.Meta): + class Meta(DeviceComponentTable.Meta): model = FrontPort - fields = ('name', 'label', 'type', 'rear_port', 'rear_port_position', 'description') - empty_text = "None" + fields = ('pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable') + default_columns = ('pk', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description') -class FrontPortDetailTable(DeviceComponentDetailTable): +class RearPortTable(DeviceComponentTable): - class Meta(DeviceComponentDetailTable.Meta, FrontPortTable.Meta): - pass - - -class RearPortTable(BaseTable): - name = tables.Column(order_by=('_name',)) - - class Meta(BaseTable.Meta): + class Meta(DeviceComponentTable.Meta): model = RearPort - fields = ('name', 'label', 'type', 'positions', 'description') - empty_text = "None" + fields = ('pk', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable') + default_columns = ('pk', 'device', 'name', 'label', 'type', 'description') -class RearPortDetailTable(DeviceComponentDetailTable): +class DeviceBayTable(DeviceComponentTable): + installed_device = tables.Column( + linkify=True + ) - class Meta(DeviceComponentDetailTable.Meta, RearPortTable.Meta): - pass - - -class DeviceBayTable(BaseTable): - name = tables.Column(order_by=('_name',)) - - class Meta(BaseTable.Meta): + class Meta(DeviceComponentTable.Meta): model = DeviceBay - fields = ('name', 'label', 'description') - - -class DeviceBayDetailTable(DeviceComponentDetailTable): - installed_device = tables.LinkColumn() - - class Meta(DeviceBayTable.Meta): fields = ('pk', 'device', 'name', 'label', 'installed_device', 'description') - sequence = ('pk', 'device', 'name', 'label', 'installed_device', 'description') - exclude = ('cable',) - - -class DeviceBayImportTable(BaseTable): - device = tables.LinkColumn('dcim:device', args=[Accessor('device.pk')], verbose_name='Device') - installed_device = tables.LinkColumn('dcim:device', args=[Accessor('installed_device.pk')], verbose_name='Installed Device') - - class Meta(BaseTable.Meta): - model = DeviceBay - fields = ('device', 'name', 'installed_device', 'description') - empty_text = False + default_columns = ('pk', 'device', 'name', 'label', 'installed_device', 'description') # diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 5c5e46853..079f26fdf 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -1194,10 +1194,7 @@ class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase): ) -class InterfaceTestCase( - ViewTestCases.GetObjectViewTestCase, - ViewTestCases.DeviceComponentViewTestCase, -): +class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): model = Interface @classmethod @@ -1425,7 +1422,16 @@ class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase): ) -class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase): +# TODO: Convert to DeviceComponentViewTestCase? +class InventoryItemTestCase( + ViewTestCases.EditObjectViewTestCase, + ViewTestCases.DeleteObjectViewTestCase, + ViewTestCases.ListObjectsViewTestCase, + ViewTestCases.BulkCreateObjectsViewTestCase, + ViewTestCases.BulkImportObjectsViewTestCase, + ViewTestCases.BulkEditObjectsViewTestCase, + ViewTestCases.BulkDeleteObjectsViewTestCase +): model = InventoryItem @classmethod diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 347ac7064..2014427b7 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -4,9 +4,9 @@ from extras.views import ObjectChangeLogView, ImageAttachmentEditView from ipam.views import ServiceEditView from . import views from .models import ( - Cable, ConsolePort, ConsoleServerPort, Device, DeviceRole, DeviceType, FrontPort, Interface, Manufacturer, Platform, - PowerFeed, PowerPanel, PowerPort, PowerOutlet, Rack, RackGroup, RackReservation, RackRole, RearPort, Region, Site, - VirtualChassis, + Cable, ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceRole, DeviceType, FrontPort, Interface, + Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerOutlet, Rack, RackGroup, RackReservation, RackRole, + RearPort, Region, Site, VirtualChassis, ) app_name = 'dcim' @@ -189,10 +189,12 @@ urlpatterns = [ path('console-ports/edit/', views.ConsolePortBulkEditView.as_view(), name='consoleport_bulk_edit'), # TODO: Bulk rename, disconnect views for ConsolePorts path('console-ports/delete/', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'), - path('console-ports//connect//', views.CableCreateView.as_view(), name='consoleport_connect', kwargs={'termination_a_type': ConsolePort}), + path('console-ports//', views.ConsolePortView.as_view(), name='consoleport'), path('console-ports//edit/', views.ConsolePortEditView.as_view(), name='consoleport_edit'), path('console-ports//delete/', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'), + path('console-ports//changelog/', ObjectChangeLogView.as_view(), name='consoleport_changelog', kwargs={'model': ConsolePort}), path('console-ports//trace/', views.CableTraceView.as_view(), name='consoleport_trace', kwargs={'model': ConsolePort}), + path('console-ports//connect//', views.CableCreateView.as_view(), name='consoleport_connect', kwargs={'termination_a_type': ConsolePort}), path('devices/console-ports/add/', views.DeviceBulkAddConsolePortView.as_view(), name='device_bulk_add_consoleport'), # Console server ports @@ -203,10 +205,12 @@ urlpatterns = [ path('console-server-ports/rename/', views.ConsoleServerPortBulkRenameView.as_view(), name='consoleserverport_bulk_rename'), path('console-server-ports/disconnect/', views.ConsoleServerPortBulkDisconnectView.as_view(), name='consoleserverport_bulk_disconnect'), path('console-server-ports/delete/', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'), - path('console-server-ports//connect//', views.CableCreateView.as_view(), name='consoleserverport_connect', kwargs={'termination_a_type': ConsoleServerPort}), + path('console-server-ports//', views.ConsoleServerPortView.as_view(), name='consoleserverport'), path('console-server-ports//edit/', views.ConsoleServerPortEditView.as_view(), name='consoleserverport_edit'), path('console-server-ports//delete/', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'), + path('console-server-ports//changelog/', ObjectChangeLogView.as_view(), name='consoleserverport_changelog', kwargs={'model': ConsoleServerPort}), path('console-server-ports//trace/', views.CableTraceView.as_view(), name='consoleserverport_trace', kwargs={'model': ConsoleServerPort}), + path('console-server-ports//connect//', views.CableCreateView.as_view(), name='consoleserverport_connect', kwargs={'termination_a_type': ConsoleServerPort}), path('devices/console-server-ports/add/', views.DeviceBulkAddConsoleServerPortView.as_view(), name='device_bulk_add_consoleserverport'), # Power ports @@ -216,10 +220,12 @@ urlpatterns = [ path('power-ports/edit/', views.PowerPortBulkEditView.as_view(), name='powerport_bulk_edit'), # TODO: Bulk rename, disconnect views for PowerPorts path('power-ports/delete/', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'), - path('power-ports//connect//', views.CableCreateView.as_view(), name='powerport_connect', kwargs={'termination_a_type': PowerPort}), + path('power-ports//', views.PowerPortView.as_view(), name='powerport'), path('power-ports//edit/', views.PowerPortEditView.as_view(), name='powerport_edit'), path('power-ports//delete/', views.PowerPortDeleteView.as_view(), name='powerport_delete'), + path('power-ports//changelog/', ObjectChangeLogView.as_view(), name='powerport_changelog', kwargs={'model': PowerPort}), path('power-ports//trace/', views.CableTraceView.as_view(), name='powerport_trace', kwargs={'model': PowerPort}), + path('power-ports//connect//', views.CableCreateView.as_view(), name='powerport_connect', kwargs={'termination_a_type': PowerPort}), path('devices/power-ports/add/', views.DeviceBulkAddPowerPortView.as_view(), name='device_bulk_add_powerport'), # Power outlets @@ -230,10 +236,12 @@ urlpatterns = [ path('power-outlets/rename/', views.PowerOutletBulkRenameView.as_view(), name='poweroutlet_bulk_rename'), path('power-outlets/disconnect/', views.PowerOutletBulkDisconnectView.as_view(), name='poweroutlet_bulk_disconnect'), path('power-outlets/delete/', views.PowerOutletBulkDeleteView.as_view(), name='poweroutlet_bulk_delete'), - path('power-outlets//connect//', views.CableCreateView.as_view(), name='poweroutlet_connect', kwargs={'termination_a_type': PowerOutlet}), + path('power-outlets//', views.PowerOutletView.as_view(), name='poweroutlet'), path('power-outlets//edit/', views.PowerOutletEditView.as_view(), name='poweroutlet_edit'), path('power-outlets//delete/', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'), + path('power-outlets//changelog/', ObjectChangeLogView.as_view(), name='poweroutlet_changelog', kwargs={'model': PowerOutlet}), path('power-outlets//trace/', views.CableTraceView.as_view(), name='poweroutlet_trace', kwargs={'model': PowerOutlet}), + path('power-outlets//connect//', views.CableCreateView.as_view(), name='poweroutlet_connect', kwargs={'termination_a_type': PowerOutlet}), path('devices/power-outlets/add/', views.DeviceBulkAddPowerOutletView.as_view(), name='device_bulk_add_poweroutlet'), # Interfaces @@ -244,12 +252,12 @@ urlpatterns = [ path('interfaces/rename/', views.InterfaceBulkRenameView.as_view(), name='interface_bulk_rename'), path('interfaces/disconnect/', views.InterfaceBulkDisconnectView.as_view(), name='interface_bulk_disconnect'), path('interfaces/delete/', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'), - path('interfaces//connect//', views.CableCreateView.as_view(), name='interface_connect', kwargs={'termination_a_type': Interface}), path('interfaces//', views.InterfaceView.as_view(), name='interface'), path('interfaces//edit/', views.InterfaceEditView.as_view(), name='interface_edit'), path('interfaces//delete/', views.InterfaceDeleteView.as_view(), name='interface_delete'), path('interfaces//changelog/', ObjectChangeLogView.as_view(), name='interface_changelog', kwargs={'model': Interface}), path('interfaces//trace/', views.CableTraceView.as_view(), name='interface_trace', kwargs={'model': Interface}), + path('interfaces//connect//', views.CableCreateView.as_view(), name='interface_connect', kwargs={'termination_a_type': Interface}), path('devices/interfaces/add/', views.DeviceBulkAddInterfaceView.as_view(), name='device_bulk_add_interface'), # Front ports @@ -260,10 +268,12 @@ urlpatterns = [ path('front-ports/rename/', views.FrontPortBulkRenameView.as_view(), name='frontport_bulk_rename'), path('front-ports/disconnect/', views.FrontPortBulkDisconnectView.as_view(), name='frontport_bulk_disconnect'), path('front-ports/delete/', views.FrontPortBulkDeleteView.as_view(), name='frontport_bulk_delete'), - path('front-ports//connect//', views.CableCreateView.as_view(), name='frontport_connect', kwargs={'termination_a_type': FrontPort}), + path('front-ports//', views.FrontPortView.as_view(), name='frontport'), path('front-ports//edit/', views.FrontPortEditView.as_view(), name='frontport_edit'), path('front-ports//delete/', views.FrontPortDeleteView.as_view(), name='frontport_delete'), + path('front-ports//changelog/', ObjectChangeLogView.as_view(), name='frontport_changelog', kwargs={'model': FrontPort}), path('front-ports//trace/', views.CableTraceView.as_view(), name='frontport_trace', kwargs={'model': FrontPort}), + path('front-ports//connect//', views.CableCreateView.as_view(), name='frontport_connect', kwargs={'termination_a_type': FrontPort}), # path('devices/front-ports/add/', views.DeviceBulkAddFrontPortView.as_view(), name='device_bulk_add_frontport'), # Rear ports @@ -274,10 +284,12 @@ urlpatterns = [ path('rear-ports/rename/', views.RearPortBulkRenameView.as_view(), name='rearport_bulk_rename'), path('rear-ports/disconnect/', views.RearPortBulkDisconnectView.as_view(), name='rearport_bulk_disconnect'), path('rear-ports/delete/', views.RearPortBulkDeleteView.as_view(), name='rearport_bulk_delete'), - path('rear-ports//connect//', views.CableCreateView.as_view(), name='rearport_connect', kwargs={'termination_a_type': RearPort}), + path('rear-ports//', views.RearPortView.as_view(), name='rearport'), path('rear-ports//edit/', views.RearPortEditView.as_view(), name='rearport_edit'), path('rear-ports//delete/', views.RearPortDeleteView.as_view(), name='rearport_delete'), + path('rear-ports//changelog/', ObjectChangeLogView.as_view(), name='rearport_changelog', kwargs={'model': RearPort}), path('rear-ports//trace/', views.CableTraceView.as_view(), name='rearport_trace', kwargs={'model': RearPort}), + path('rear-ports//connect//', views.CableCreateView.as_view(), name='rearport_connect', kwargs={'termination_a_type': RearPort}), path('devices/rear-ports/add/', views.DeviceBulkAddRearPortView.as_view(), name='device_bulk_add_rearport'), # Device bays @@ -287,8 +299,10 @@ urlpatterns = [ path('device-bays/edit/', views.DeviceBayBulkEditView.as_view(), name='devicebay_bulk_edit'), path('device-bays/rename/', views.DeviceBayBulkRenameView.as_view(), name='devicebay_bulk_rename'), path('device-bays/delete/', views.DeviceBayBulkDeleteView.as_view(), name='devicebay_bulk_delete'), + path('device-bays//', views.DeviceBayView.as_view(), name='devicebay'), path('device-bays//edit/', views.DeviceBayEditView.as_view(), name='devicebay_edit'), path('device-bays//delete/', views.DeviceBayDeleteView.as_view(), name='devicebay_delete'), + path('device-bays//changelog/', ObjectChangeLogView.as_view(), name='devicebay_changelog', kwargs={'model': DeviceBay}), path('device-bays//populate/', views.DeviceBayPopulateView.as_view(), name='devicebay_populate'), path('device-bays//depopulate/', views.DeviceBayDepopulateView.as_view(), name='devicebay_depopulate'), path('devices/device-bays/add/', views.DeviceBulkAddDeviceBayView.as_view(), name='device_bulk_add_devicebay'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 840b9890f..1f3b61a42 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1158,13 +1158,17 @@ class DeviceBulkDeleteView(BulkDeleteView): # class ConsolePortListView(ObjectListView): - queryset = ConsolePort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable') + queryset = ConsolePort.objects.prefetch_related('device', 'cable') filterset = filters.ConsolePortFilterSet filterset_form = forms.ConsolePortFilterForm - table = tables.ConsolePortDetailTable + table = tables.ConsolePortTable action_buttons = ('import', 'export') +class ConsolePortView(ObjectView): + queryset = ConsolePort.objects.all() + + class ConsolePortCreateView(ComponentCreateView): queryset = ConsolePort.objects.all() form = forms.ConsolePortCreateForm @@ -1184,7 +1188,7 @@ class ConsolePortDeleteView(ObjectDeleteView): class ConsolePortBulkImportView(BulkImportView): queryset = ConsolePort.objects.all() model_form = forms.ConsolePortCSVForm - table = tables.ConsolePortImportTable + table = tables.ConsolePortTable default_return_url = 'dcim:consoleport_list' @@ -1207,13 +1211,17 @@ class ConsolePortBulkDeleteView(BulkDeleteView): # class ConsoleServerPortListView(ObjectListView): - queryset = ConsoleServerPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable') + queryset = ConsoleServerPort.objects.prefetch_related('device', 'cable') filterset = filters.ConsoleServerPortFilterSet filterset_form = forms.ConsoleServerPortFilterForm - table = tables.ConsoleServerPortDetailTable + table = tables.ConsoleServerPortTable action_buttons = ('import', 'export') +class ConsoleServerPortView(ObjectView): + queryset = ConsoleServerPort.objects.all() + + class ConsoleServerPortCreateView(ComponentCreateView): queryset = ConsoleServerPort.objects.all() form = forms.ConsoleServerPortCreateForm @@ -1233,7 +1241,7 @@ class ConsoleServerPortDeleteView(ObjectDeleteView): class ConsoleServerPortBulkImportView(BulkImportView): queryset = ConsoleServerPort.objects.all() model_form = forms.ConsoleServerPortCSVForm - table = tables.ConsoleServerPortImportTable + table = tables.ConsoleServerPortTable default_return_url = 'dcim:consoleserverport_list' @@ -1266,13 +1274,17 @@ class ConsoleServerPortBulkDeleteView(BulkDeleteView): # class PowerPortListView(ObjectListView): - queryset = PowerPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable') + queryset = PowerPort.objects.prefetch_related('device', 'cable') filterset = filters.PowerPortFilterSet filterset_form = forms.PowerPortFilterForm - table = tables.PowerPortDetailTable + table = tables.PowerPortTable action_buttons = ('import', 'export') +class PowerPortView(ObjectView): + queryset = PowerPort.objects.all() + + class PowerPortCreateView(ComponentCreateView): queryset = PowerPort.objects.all() form = forms.PowerPortCreateForm @@ -1292,7 +1304,7 @@ class PowerPortDeleteView(ObjectDeleteView): class PowerPortBulkImportView(BulkImportView): queryset = PowerPort.objects.all() model_form = forms.PowerPortCSVForm - table = tables.PowerPortImportTable + table = tables.PowerPortTable default_return_url = 'dcim:powerport_list' @@ -1315,13 +1327,17 @@ class PowerPortBulkDeleteView(BulkDeleteView): # class PowerOutletListView(ObjectListView): - queryset = PowerOutlet.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable') + queryset = PowerOutlet.objects.prefetch_related('device', 'cable') filterset = filters.PowerOutletFilterSet filterset_form = forms.PowerOutletFilterForm - table = tables.PowerOutletDetailTable + table = tables.PowerOutletTable action_buttons = ('import', 'export') +class PowerOutletView(ObjectView): + queryset = PowerOutlet.objects.all() + + class PowerOutletCreateView(ComponentCreateView): queryset = PowerOutlet.objects.all() form = forms.PowerOutletCreateForm @@ -1341,7 +1357,7 @@ class PowerOutletDeleteView(ObjectDeleteView): class PowerOutletBulkImportView(BulkImportView): queryset = PowerOutlet.objects.all() model_form = forms.PowerOutletCSVForm - table = tables.PowerOutletImportTable + table = tables.PowerOutletTable default_return_url = 'dcim:poweroutlet_list' @@ -1374,10 +1390,10 @@ class PowerOutletBulkDeleteView(BulkDeleteView): # class InterfaceListView(ObjectListView): - queryset = Interface.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable') + queryset = Interface.objects.prefetch_related('device', 'cable') filterset = filters.InterfaceFilterSet filterset_form = forms.InterfaceFilterForm - table = tables.InterfaceDetailTable + table = tables.InterfaceTable action_buttons = ('import', 'export') @@ -1409,7 +1425,7 @@ class InterfaceView(ObjectView): ) return render(request, 'dcim/interface.html', { - 'interface': interface, + 'instance': interface, 'connected_interface': interface._connected_interface, 'connected_circuittermination': interface._connected_circuittermination, 'ipaddress_table': ipaddress_table, @@ -1437,7 +1453,7 @@ class InterfaceDeleteView(ObjectDeleteView): class InterfaceBulkImportView(BulkImportView): queryset = Interface.objects.all() model_form = forms.InterfaceCSVForm - table = tables.InterfaceImportTable + table = tables.InterfaceTable default_return_url = 'dcim:interface_list' @@ -1470,13 +1486,17 @@ class InterfaceBulkDeleteView(BulkDeleteView): # class FrontPortListView(ObjectListView): - queryset = FrontPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable') + queryset = FrontPort.objects.prefetch_related('device', 'cable') filterset = filters.FrontPortFilterSet filterset_form = forms.FrontPortFilterForm - table = tables.FrontPortDetailTable + table = tables.FrontPortTable action_buttons = ('import', 'export') +class FrontPortView(ObjectView): + queryset = FrontPort.objects.all() + + class FrontPortCreateView(ComponentCreateView): queryset = FrontPort.objects.all() form = forms.FrontPortCreateForm @@ -1496,7 +1516,7 @@ class FrontPortDeleteView(ObjectDeleteView): class FrontPortBulkImportView(BulkImportView): queryset = FrontPort.objects.all() model_form = forms.FrontPortCSVForm - table = tables.FrontPortImportTable + table = tables.FrontPortTable default_return_url = 'dcim:frontport_list' @@ -1529,13 +1549,17 @@ class FrontPortBulkDeleteView(BulkDeleteView): # class RearPortListView(ObjectListView): - queryset = RearPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable') + queryset = RearPort.objects.prefetch_related('device', 'cable') filterset = filters.RearPortFilterSet filterset_form = forms.RearPortFilterForm - table = tables.RearPortDetailTable + table = tables.RearPortTable action_buttons = ('import', 'export') +class RearPortView(ObjectView): + queryset = RearPort.objects.all() + + class RearPortCreateView(ComponentCreateView): queryset = RearPort.objects.all() form = forms.RearPortCreateForm @@ -1555,7 +1579,7 @@ class RearPortDeleteView(ObjectDeleteView): class RearPortBulkImportView(BulkImportView): queryset = RearPort.objects.all() model_form = forms.RearPortCSVForm - table = tables.RearPortImportTable + table = tables.RearPortTable default_return_url = 'dcim:rearport_list' @@ -1588,15 +1612,17 @@ class RearPortBulkDeleteView(BulkDeleteView): # class DeviceBayListView(ObjectListView): - queryset = DeviceBay.objects.prefetch_related( - 'device', 'device__site', 'installed_device', 'installed_device__site' - ) + queryset = DeviceBay.objects.prefetch_related('device', 'installed_device') filterset = filters.DeviceBayFilterSet filterset_form = forms.DeviceBayFilterForm - table = tables.DeviceBayDetailTable + table = tables.DeviceBayTable action_buttons = ('import', 'export') +class DeviceBayView(ObjectView): + queryset = DeviceBay.objects.all() + + class DeviceBayCreateView(ComponentCreateView): queryset = DeviceBay.objects.all() form = forms.DeviceBayCreateForm @@ -1683,7 +1709,7 @@ class DeviceBayDepopulateView(ObjectEditView): class DeviceBayBulkImportView(BulkImportView): queryset = DeviceBay.objects.all() model_form = forms.DeviceBayCSVForm - table = tables.DeviceBayImportTable + table = tables.DeviceBayTable default_return_url = 'dcim:devicebay_list' diff --git a/netbox/extras/views.py b/netbox/extras/views.py index ff8ce75e0..af5106a33 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -296,6 +296,7 @@ class ObjectChangeLogView(View): return render(request, 'extras/object_changelog.html', { object_var: obj, + 'instance': obj, # We'll eventually standardize on 'instance` for the object variable name 'table': objectchanges_table, 'base_template': base_template, 'active_tab': 'changelog', diff --git a/netbox/templates/dcim/consoleport.html b/netbox/templates/dcim/consoleport.html new file mode 100644 index 000000000..63916bcc5 --- /dev/null +++ b/netbox/templates/dcim/consoleport.html @@ -0,0 +1,103 @@ +{% extends 'dcim/device_component.html' %} +{% load helpers %} +{% load plugins %} + +{% block content %} +
+
+
+
+ Console Port +
+ + + + + + + + + + + + + + + + + + + + + +
Device + {{ instance.device }} +
Name{{ instance.name }}
Label{{ instance.label|placeholder }}
Type{{ instance.get_type_display }}
Description{{ instance.description|placeholder }}
+
+ {% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %} + {% plugin_left_page instance %} +
+
+
+
+ Connection +
+ {% if instance.cable %} + + {% if instance.connected_endpoint %} + + + + + + + + + + + + + + + + + {% endif %} + + + + + + + + +
Device + {{ instance.connected_endpoint.device }} +
Name + {{ instance.connected_endpoint.name }} +
Type{{ instance.connected_endpoint.get_type_display|placeholder }}
Description{{ instance.connected_endpoint.description|placeholder }}
Cable + {{ instance.cable }} + + + +
Connection Status + {% if instance.connection_status %} + {{ instance.get_connection_status_display }} + {% else %} + {{ instance.get_connection_status_display }} + {% endif %} +
+ {% else %} +
+ Not connected +
+ {% endif %} +
+ {% plugin_right_page instance %} +
+
+
+
+ {% plugin_full_width_page instance %} +
+
+{% endblock %} diff --git a/netbox/templates/dcim/consoleserverport.html b/netbox/templates/dcim/consoleserverport.html new file mode 100644 index 000000000..cdc43142e --- /dev/null +++ b/netbox/templates/dcim/consoleserverport.html @@ -0,0 +1,103 @@ +{% extends 'dcim/device_component.html' %} +{% load helpers %} +{% load plugins %} + +{% block content %} +
+
+
+
+ Console Server Port +
+ + + + + + + + + + + + + + + + + + + + + +
Device + {{ instance.device }} +
Name{{ instance.name }}
Label{{ instance.label|placeholder }}
Type{{ instance.get_type_display }}
Description{{ instance.description|placeholder }}
+
+ {% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %} + {% plugin_left_page instance %} +
+
+
+
+ Connection +
+ {% if instance.cable %} + + {% if instance.connected_endpoint %} + + + + + + + + + + + + + + + + + {% endif %} + + + + + + + + +
Device + {{ instance.connected_endpoint.device }} +
Name + {{ instance.connected_endpoint.name }} +
Type{{ instance.connected_endpoint.get_type_display|placeholder }}
Description{{ instance.connected_endpoint.description|placeholder }}
Cable + {{ instance.cable }} + + + +
Connection Status + {% if instance.connection_status %} + {{ instance.get_connection_status_display }} + {% else %} + {{ instance.get_connection_status_display }} + {% endif %} +
+ {% else %} +
+ Not connected +
+ {% endif %} +
+ {% plugin_right_page instance %} +
+
+
+
+ {% plugin_full_width_page instance %} +
+
+{% endblock %} diff --git a/netbox/templates/dcim/device_component.html b/netbox/templates/dcim/device_component.html new file mode 100644 index 000000000..616655066 --- /dev/null +++ b/netbox/templates/dcim/device_component.html @@ -0,0 +1,41 @@ +{% extends 'base.html' %} +{% load helpers %} +{% load perms %} +{% load plugins %} + +{% block header %} + +
+ {% plugin_buttons instance %} + {% if request.user|can_change:instance %} + + Edit + + {% endif %} + {% if request.user|can_delete:instance %} + + Delete + + {% endif %} +
+

{% block title %}{{ instance.device }} / {{ instance }}{% endblock %}

+ +{% endblock %} diff --git a/netbox/templates/dcim/devicebay.html b/netbox/templates/dcim/devicebay.html new file mode 100644 index 000000000..3d65e8bde --- /dev/null +++ b/netbox/templates/dcim/devicebay.html @@ -0,0 +1,70 @@ +{% extends 'dcim/device_component.html' %} +{% load helpers %} +{% load plugins %} + +{% block content %} +
+
+
+
+ Device Bay +
+ + + + + + + + + + + + + + + + + +
Device + {{ instance.device }} +
Name{{ instance.name }}
Label{{ instance.label|placeholder }}
Description{{ instance.description|placeholder }}
+
+ {% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %} + {% plugin_left_page instance %} +
+
+
+
+ Installed Device +
+ {% if instance.installed_device %} + {% with device=instance.installed_device %} + + + + + + + + + +
Device + {{ device }} +
Device Type{{ device.device_type }}
+ {% endwith %} + {% else %} +
+ None +
+ {% endif %} +
+ {% plugin_right_page instance %} +
+
+
+
+ {% plugin_full_width_page instance %} +
+
+{% endblock %} diff --git a/netbox/templates/dcim/frontport.html b/netbox/templates/dcim/frontport.html new file mode 100644 index 000000000..8ab51cb30 --- /dev/null +++ b/netbox/templates/dcim/frontport.html @@ -0,0 +1,91 @@ +{% extends 'dcim/device_component.html' %} +{% load helpers %} +{% load plugins %} + +{% block content %} +
+
+
+
+ Front Port +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Device + {{ instance.device }} +
Name{{ instance.name }}
Label{{ instance.label|placeholder }}
Type{{ instance.get_type_display }}
Rear Port + {{ instance.rear_port }} +
Rear Port Position{{ instance.rear_port_position }}
Description{{ instance.description|placeholder }}
+
+ {% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %} + {% plugin_left_page instance %} +
+
+
+
+ Connection +
+ {% if instance.cable %} + + + + + + + + + +
Cable + {{ instance.cable }} + + + +
Connection Status + {% if instance.cable.status %} + {{ instance.cable.get_status_display }} + {% else %} + {{ instance.cable.get_status_display }} + {% endif %} +
+ {% else %} +
+ Not connected +
+ {% endif %} +
+ {% plugin_right_page instance %} +
+
+
+
+ {% plugin_full_width_page instance %} +
+
+{% endblock %} diff --git a/netbox/templates/dcim/inc/consoleport.html b/netbox/templates/dcim/inc/consoleport.html index 9089f19b4..02afd8f99 100644 --- a/netbox/templates/dcim/inc/consoleport.html +++ b/netbox/templates/dcim/inc/consoleport.html @@ -2,7 +2,8 @@ {# Name #} - {{ cp }} + + {{ cp }} {# Type #} diff --git a/netbox/templates/dcim/inc/consoleserverport.html b/netbox/templates/dcim/inc/consoleserverport.html index 0d649f812..dcf168ae7 100644 --- a/netbox/templates/dcim/inc/consoleserverport.html +++ b/netbox/templates/dcim/inc/consoleserverport.html @@ -11,7 +11,8 @@ {# Name #} - {{ csp }} + + {{ csp }} {# Type #} diff --git a/netbox/templates/dcim/inc/devicebay.html b/netbox/templates/dcim/inc/devicebay.html index 70ce7e8df..ee6a66d8f 100644 --- a/netbox/templates/dcim/inc/devicebay.html +++ b/netbox/templates/dcim/inc/devicebay.html @@ -9,7 +9,8 @@ {# Name #} - {{ devicebay.name }} + + {{ devicebay.name }} {# Status #} diff --git a/netbox/templates/dcim/inc/frontport.html b/netbox/templates/dcim/inc/frontport.html index 12915f64d..f267479f3 100644 --- a/netbox/templates/dcim/inc/frontport.html +++ b/netbox/templates/dcim/inc/frontport.html @@ -10,7 +10,8 @@ {# Name #} - {{ frontport }} + + {{ frontport }} {# Type #} diff --git a/netbox/templates/dcim/inc/poweroutlet.html b/netbox/templates/dcim/inc/poweroutlet.html index 1c0630310..d9a77d647 100644 --- a/netbox/templates/dcim/inc/poweroutlet.html +++ b/netbox/templates/dcim/inc/poweroutlet.html @@ -11,7 +11,8 @@ {# Name #} - {{ po }} + + {{ po }} {# Type #} diff --git a/netbox/templates/dcim/inc/powerport.html b/netbox/templates/dcim/inc/powerport.html index 045b25dfd..c3293e959 100644 --- a/netbox/templates/dcim/inc/powerport.html +++ b/netbox/templates/dcim/inc/powerport.html @@ -2,7 +2,8 @@ {# Name #} - {{ pp }} + + {{ pp }} {# Type #} diff --git a/netbox/templates/dcim/inc/rearport.html b/netbox/templates/dcim/inc/rearport.html index 73ccd6b70..c1e5482d0 100644 --- a/netbox/templates/dcim/inc/rearport.html +++ b/netbox/templates/dcim/inc/rearport.html @@ -10,7 +10,8 @@ {# Name #} - {{ rearport }} + + {{ rearport }} {# Type #} diff --git a/netbox/templates/dcim/interface.html b/netbox/templates/dcim/interface.html index 5165169ff..e3d67eb2c 100644 --- a/netbox/templates/dcim/interface.html +++ b/netbox/templates/dcim/interface.html @@ -1,254 +1,227 @@ -{% extends 'base.html' %} +{% extends 'dcim/device_component.html' %} {% load helpers %} - -{% block header %} -
-
- -
-
-
- {% if perms.dcim.change_interface %} - - Edit - - {% endif %} - {% if perms.dcim.delete_interface %} - - Delete - - {% endif %} -
-

{% block title %}{{ interface.device }} / {{ interface.name }}{% endblock %}

- -{% endblock %} +{% load plugins %} {% block content %} -
-
-
-
- Interface -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Device - {{ interface.device }} -
Name{{ interface.name }}
Label{{ interface.label|placeholder }}
Type{{ interface.get_type_display }}
Enabled - {% if interface.enabled %} - - {% else %} - - {% endif %} -
LAG - {% if interface.lag%} - {{ interface.lag }} - {% else %} - None - {% endif %} -
Description{{ interface.description|placeholder }}
MTU{{ interface.mtu|placeholder }}
MAC Address{{ interface.mac_address|placeholder }}
802.1Q Mode{{ interface.get_mode_display }}
-
- {% include 'extras/inc/tags_panel.html' with tags=interface.tags.all %} -
-
- {% if interface.is_connectable %} +
+
- Connection + Interface
- {% if interface.cable %} - - {% if connected_interface %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {% elif connected_circuittermination %} - {% with ct=connected_circuittermination %} - - - - - - - - - - - - - {% endwith %} - {% endif %} - - - - - - - - -
Device - {{ connected_interface.device }} -
Name - {{ connected_interface.name }} -
Type{{ connected_interface.get_type_display }}
Enabled - {% if connected_interface.enabled %} - - {% else %} - - {% endif %} -
LAG - {% if connected_interface.lag%} - {{ connected_interface.lag }} - {% else %} - None - {% endif %} -
Description{{ connected_interface.description|placeholder }}
MTU{{ connected_interface.mtu|placeholder }}
MAC Address{{ connected_interface.mac_address|placeholder }}
802.1Q Mode{{ connected_interface.get_mode_display }}
Provider{{ ct.circuit.provider }}
Circuit{{ ct.circuit }}
Side{{ ct.term_side }}
Cable - {{ interface.cable }} - - - -
Connection Status - {% if interface.connection_status %} - {{ interface.get_connection_status_display }} - {% else %} - {{ interface.get_connection_status_display }} - {% endif %} -
- {% else %} -
- Not connected -
- {% endif %} -
- {% endif %} - {% if interface.is_lag %} -
-
LAG Members
- - - - - - - - - - {% for member in interface.member_interfaces.all %} - - - - - - {% empty %} - - - - {% endfor %} - +
ParentInterfaceType
- {{ member.device }} - - {{ member }} - - {{ member.get_type_display }} -
No member interfaces
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Device + {{ instance.device }} +
Name{{ instance.name }}
Label{{ instance.label|placeholder }}
Type{{ instance.get_type_display }}
Enabled + {% if instance.enabled %} + + {% else %} + + {% endif %} +
LAG + {% if instance.lag%} + {{ instance.lag }} + {% else %} + None + {% endif %} +
Description{{ instance.description|placeholder }}
MTU{{ instance.mtu|placeholder }}
MAC Address{{ instance.mac_address|placeholder }}
802.1Q Mode{{ instance.get_mode_display }}
- {% endif %} + {% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %} + {% plugin_left_page instance %} +
+
+ {% if instance.is_connectable %} +
+
+ Connection +
+ {% if instance.cable %} + + {% if connected_interface %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% elif connected_circuittermination %} + {% with ct=connected_circuittermination %} + + + + + + + + + + + + + {% endwith %} + {% endif %} + + + + + + + + +
Device + {{ connected_interface.device }} +
Name + {{ connected_interface.name }} +
Type{{ connected_interface.get_type_display }}
Enabled + {% if connected_interface.enabled %} + + {% else %} + + {% endif %} +
LAG + {% if connected_interface.lag%} + {{ connected_interface.lag }} + {% else %} + None + {% endif %} +
Description{{ connected_interface.description|placeholder }}
MTU{{ connected_interface.mtu|placeholder }}
MAC Address{{ connected_interface.mac_address|placeholder }}
802.1Q Mode{{ connected_interface.get_mode_display }}
Provider{{ ct.circuit.provider }}
Circuit{{ ct.circuit }}
Side{{ ct.term_side }}
Cable + {{ instance.cable }} + + + +
Connection Status + {% if instance.connection_status %} + {{ instance.get_connection_status_display }} + {% else %} + {{ instance.get_connection_status_display }} + {% endif %} +
+ {% else %} +
+ Not connected +
+ {% endif %} +
+ {% endif %} + {% if instance.is_lag %} +
+
LAG Members
+ + + + + + + + + + {% for member in instance.member_interfaces.all %} + + + + + + {% empty %} + + + + {% endfor %} + +
ParentInterfaceType
+ {{ member.device }} + + {{ member }} + + {{ member.get_type_display }} +
No member interfaces
+
+ {% endif %} + {% plugin_right_page instance %} +
-
-
-
- {% include 'panel_table.html' with table=ipaddress_table heading="IP Addresses" %} +
+
+ {% include 'panel_table.html' with table=ipaddress_table heading="IP Addresses" %} +
-
-
-
- {% include 'panel_table.html' with table=vlan_table heading="VLANs" %} +
+
+ {% include 'panel_table.html' with table=vlan_table heading="VLANs" %} +
+
+
+
+ {% plugin_full_width_page instance %} +
-
{% endblock %} diff --git a/netbox/templates/dcim/poweroutlet.html b/netbox/templates/dcim/poweroutlet.html new file mode 100644 index 000000000..cddcffd6f --- /dev/null +++ b/netbox/templates/dcim/poweroutlet.html @@ -0,0 +1,111 @@ +{% extends 'dcim/device_component.html' %} +{% load helpers %} +{% load plugins %} + +{% block content %} +
+
+
+
+ Power Outlet +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Device + {{ instance.device }} +
Name{{ instance.name }}
Label{{ instance.label|placeholder }}
Type{{ instance.get_type_display }}
Description{{ instance.description|placeholder }}
Power Port{{ instance.power_port }}
Feed Leg{{ instance.get_feed_leg_display }}
+
+ {% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %} + {% plugin_left_page instance %} +
+
+
+
+ Connection +
+ {% if instance.cable %} + + {% if instance.connected_endpoint %} + + + + + + + + + + + + + + + + + {% endif %} + + + + + + + + +
Device + {{ instance.connected_endpoint.device }} +
Name + {{ instance.connected_endpoint.name }} +
Type{{ instance.connected_endpoint.get_type_display|placeholder }}
Description{{ instance.connected_endpoint.description|placeholder }}
Cable + {{ instance.cable }} + + + +
Connection Status + {% if instance.connection_status %} + {{ instance.get_connection_status_display }} + {% else %} + {{ instance.get_connection_status_display }} + {% endif %} +
+ {% else %} +
+ Not connected +
+ {% endif %} +
+ {% plugin_right_page instance %} +
+
+
+
+ {% plugin_full_width_page instance %} +
+
+{% endblock %} diff --git a/netbox/templates/dcim/powerport.html b/netbox/templates/dcim/powerport.html new file mode 100644 index 000000000..8642bd8fb --- /dev/null +++ b/netbox/templates/dcim/powerport.html @@ -0,0 +1,111 @@ +{% extends 'dcim/device_component.html' %} +{% load helpers %} +{% load plugins %} + +{% block content %} +
+
+
+
+ Power Port +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Device + {{ instance.device }} +
Name{{ instance.name }}
Label{{ instance.label|placeholder }}
Type{{ instance.get_type_display }}
Description{{ instance.description|placeholder }}
Maximum Draw{{ instance.maximum_draw|placeholder }}
Allocated Draw{{ instance.allocated_draw|placeholder }}
+
+ {% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %} + {% plugin_left_page instance %} +
+
+
+
+ Connection +
+ {% if instance.cable %} + + {% if instance.connected_endpoint %} + + + + + + + + + + + + + + + + + {% endif %} + + + + + + + + +
Device + {{ instance.connected_endpoint.device }} +
Name + {{ instance.connected_endpoint.name }} +
Type{{ instance.connected_endpoint.get_type_display|placeholder }}
Description{{ instance.connected_endpoint.description|placeholder }}
Cable + {{ instance.cable }} + + + +
Connection Status + {% if instance.connection_status %} + {{ instance.get_connection_status_display }} + {% else %} + {{ instance.get_connection_status_display }} + {% endif %} +
+ {% else %} +
+ Not connected +
+ {% endif %} +
+ {% plugin_right_page instance %} +
+
+
+
+ {% plugin_full_width_page instance %} +
+
+{% endblock %} diff --git a/netbox/templates/dcim/rearport.html b/netbox/templates/dcim/rearport.html new file mode 100644 index 000000000..982d53eaa --- /dev/null +++ b/netbox/templates/dcim/rearport.html @@ -0,0 +1,85 @@ +{% extends 'dcim/device_component.html' %} +{% load helpers %} +{% load plugins %} + +{% block content %} +
+
+
+
+ Rear Port +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Device + {{ instance.device }} +
Name{{ instance.name }}
Label{{ instance.label|placeholder }}
Type{{ instance.get_type_display }}
Positions{{ instance.positions }}
Description{{ instance.description|placeholder }}
+
+ {% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %} + {% plugin_left_page instance %} +
+
+
+
+ Connection +
+ {% if instance.cable %} + + + + + + + + + +
Cable + {{ instance.cable }} + + + +
Connection Status + {% if instance.cable.status %} + {{ instance.cable.get_status_display }} + {% else %} + {{ instance.cable.get_status_display }} + {% endif %} +
+ {% else %} +
+ Not connected +
+ {% endif %} +
+ {% plugin_right_page instance %} +
+
+
+
+ {% plugin_full_width_page instance %} +
+
+{% endblock %} diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index a70e917d8..425a2fca2 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -5,7 +5,6 @@ import re import yaml from django import template from django.conf import settings -from django.urls import NoReverseMatch, reverse from django.utils.html import strip_tags from django.utils.safestring import mark_safe from markdown import markdown @@ -79,14 +78,7 @@ def url_name(model, action): """ Return the URL name for the given model and action, or None if invalid. """ - url_name = '{}:{}_{}'.format(model._meta.app_label, model._meta.model_name, action) - try: - # Validate and return the URL name. We don't return the actual URL yet because many of the templates - # are written to pass a name to {% url %}. - reverse(url_name) - return url_name - except NoReverseMatch: - return None + return '{}:{}_{}'.format(model._meta.app_label, model._meta.model_name, action) @register.filter() diff --git a/netbox/utilities/templatetags/perms.py b/netbox/utilities/templatetags/perms.py new file mode 100644 index 000000000..f1bbf7549 --- /dev/null +++ b/netbox/utilities/templatetags/perms.py @@ -0,0 +1,30 @@ +from django import template + +register = template.Library() + + +def _check_permission(user, instance, action): + return user.has_perm( + perm=f'{instance._meta.app_label}.{action}_{instance._meta.model_name}', + obj=instance + ) + + +@register.filter() +def can_view(user, instance): + return _check_permission(user, instance, 'view') + + +@register.filter() +def can_add(user, instance): + return _check_permission(user, instance, 'add') + + +@register.filter() +def can_change(user, instance): + return _check_permission(user, instance, 'change') + + +@register.filter() +def can_delete(user, instance): + return _check_permission(user, instance, 'delete') diff --git a/netbox/utilities/testing/views.py b/netbox/utilities/testing/views.py index 774ceac85..2cf32616c 100644 --- a/netbox/utilities/testing/views.py +++ b/netbox/utilities/testing/views.py @@ -917,6 +917,7 @@ class ViewTestCases: maxDiff = None class DeviceComponentViewTestCase( + GetObjectViewTestCase, EditObjectViewTestCase, DeleteObjectViewTestCase, ListObjectsViewTestCase, diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index cf282a8c0..0fdb2f89e 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -127,6 +127,25 @@ class ObjectView(ObjectPermissionRequiredMixin, View): def get_required_permission(self): return get_permission_for_model(self.queryset.model, 'view') + def get_template_name(self): + """ + Return self.template_name if set. Otherwise, resolve the template path by model app_label and name. + """ + if hasattr(self, 'template_name'): + return self.template_name + model_opts = self.queryset.model._meta + return f'{model_opts.app_label}/{model_opts.model_name}.html' + + def get(self, request, pk): + """ + Generic GET handler for accessing an object by PK + """ + instance = get_object_or_404(self.queryset, pk=pk) + + return render(request, self.get_template_name(), { + 'instance': instance, + }) + class ObjectListView(ObjectPermissionRequiredMixin, View): """ diff --git a/netbox/virtualization/tests/test_views.py b/netbox/virtualization/tests/test_views.py index 408558779..ec4159dd4 100644 --- a/netbox/virtualization/tests/test_views.py +++ b/netbox/virtualization/tests/test_views.py @@ -189,10 +189,7 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase): } -class VMInterfaceTestCase( - ViewTestCases.GetObjectViewTestCase, - ViewTestCases.DeviceComponentViewTestCase, -): +class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase): model = VMInterface @classmethod