Merge pull request #4790 from netbox-community/4788-component-views

#4788: Add individual views for device components
This commit is contained in:
Jeremy Stretch 2020-06-25 13:38:09 -04:00 committed by GitHub
commit 6d23d9ebb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1148 additions and 496 deletions

View File

@ -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

View File

@ -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 (

View File

@ -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')
#

View File

@ -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

View File

@ -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/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='consoleport_connect', kwargs={'termination_a_type': ConsolePort}),
path('console-ports/<int:pk>/', views.ConsolePortView.as_view(), name='consoleport'),
path('console-ports/<int:pk>/edit/', views.ConsolePortEditView.as_view(), name='consoleport_edit'),
path('console-ports/<int:pk>/delete/', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'),
path('console-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='consoleport_changelog', kwargs={'model': ConsolePort}),
path('console-ports/<int:pk>/trace/', views.CableTraceView.as_view(), name='consoleport_trace', kwargs={'model': ConsolePort}),
path('console-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', 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/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='consoleserverport_connect', kwargs={'termination_a_type': ConsoleServerPort}),
path('console-server-ports/<int:pk>/', views.ConsoleServerPortView.as_view(), name='consoleserverport'),
path('console-server-ports/<int:pk>/edit/', views.ConsoleServerPortEditView.as_view(), name='consoleserverport_edit'),
path('console-server-ports/<int:pk>/delete/', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'),
path('console-server-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='consoleserverport_changelog', kwargs={'model': ConsoleServerPort}),
path('console-server-ports/<int:pk>/trace/', views.CableTraceView.as_view(), name='consoleserverport_trace', kwargs={'model': ConsoleServerPort}),
path('console-server-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', 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/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='powerport_connect', kwargs={'termination_a_type': PowerPort}),
path('power-ports/<int:pk>/', views.PowerPortView.as_view(), name='powerport'),
path('power-ports/<int:pk>/edit/', views.PowerPortEditView.as_view(), name='powerport_edit'),
path('power-ports/<int:pk>/delete/', views.PowerPortDeleteView.as_view(), name='powerport_delete'),
path('power-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='powerport_changelog', kwargs={'model': PowerPort}),
path('power-ports/<int:pk>/trace/', views.CableTraceView.as_view(), name='powerport_trace', kwargs={'model': PowerPort}),
path('power-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', 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/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='poweroutlet_connect', kwargs={'termination_a_type': PowerOutlet}),
path('power-outlets/<int:pk>/', views.PowerOutletView.as_view(), name='poweroutlet'),
path('power-outlets/<int:pk>/edit/', views.PowerOutletEditView.as_view(), name='poweroutlet_edit'),
path('power-outlets/<int:pk>/delete/', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'),
path('power-outlets/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='poweroutlet_changelog', kwargs={'model': PowerOutlet}),
path('power-outlets/<int:pk>/trace/', views.CableTraceView.as_view(), name='poweroutlet_trace', kwargs={'model': PowerOutlet}),
path('power-outlets/<int:termination_a_id>/connect/<str:termination_b_type>/', 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/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='interface_connect', kwargs={'termination_a_type': Interface}),
path('interfaces/<int:pk>/', views.InterfaceView.as_view(), name='interface'),
path('interfaces/<int:pk>/edit/', views.InterfaceEditView.as_view(), name='interface_edit'),
path('interfaces/<int:pk>/delete/', views.InterfaceDeleteView.as_view(), name='interface_delete'),
path('interfaces/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='interface_changelog', kwargs={'model': Interface}),
path('interfaces/<int:pk>/trace/', views.CableTraceView.as_view(), name='interface_trace', kwargs={'model': Interface}),
path('interfaces/<int:termination_a_id>/connect/<str:termination_b_type>/', 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/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='frontport_connect', kwargs={'termination_a_type': FrontPort}),
path('front-ports/<int:pk>/', views.FrontPortView.as_view(), name='frontport'),
path('front-ports/<int:pk>/edit/', views.FrontPortEditView.as_view(), name='frontport_edit'),
path('front-ports/<int:pk>/delete/', views.FrontPortDeleteView.as_view(), name='frontport_delete'),
path('front-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='frontport_changelog', kwargs={'model': FrontPort}),
path('front-ports/<int:pk>/trace/', views.CableTraceView.as_view(), name='frontport_trace', kwargs={'model': FrontPort}),
path('front-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', 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/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='rearport_connect', kwargs={'termination_a_type': RearPort}),
path('rear-ports/<int:pk>/', views.RearPortView.as_view(), name='rearport'),
path('rear-ports/<int:pk>/edit/', views.RearPortEditView.as_view(), name='rearport_edit'),
path('rear-ports/<int:pk>/delete/', views.RearPortDeleteView.as_view(), name='rearport_delete'),
path('rear-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='rearport_changelog', kwargs={'model': RearPort}),
path('rear-ports/<int:pk>/trace/', views.CableTraceView.as_view(), name='rearport_trace', kwargs={'model': RearPort}),
path('rear-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', 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/<int:pk>/', views.DeviceBayView.as_view(), name='devicebay'),
path('device-bays/<int:pk>/edit/', views.DeviceBayEditView.as_view(), name='devicebay_edit'),
path('device-bays/<int:pk>/delete/', views.DeviceBayDeleteView.as_view(), name='devicebay_delete'),
path('device-bays/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='devicebay_changelog', kwargs={'model': DeviceBay}),
path('device-bays/<int:pk>/populate/', views.DeviceBayPopulateView.as_view(), name='devicebay_populate'),
path('device-bays/<int:pk>/depopulate/', views.DeviceBayDepopulateView.as_view(), name='devicebay_depopulate'),
path('devices/device-bays/add/', views.DeviceBulkAddDeviceBayView.as_view(), name='device_bulk_add_devicebay'),

View File

@ -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'

View File

@ -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',

View File

@ -0,0 +1,103 @@
{% extends 'dcim/device_component.html' %}
{% load helpers %}
{% load plugins %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Console Port</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Device</td>
<td>
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
</td>
</tr>
<tr>
<td>Name</td>
<td>{{ instance.name }}</td>
</tr>
<tr>
<td>Label</td>
<td>{{ instance.label|placeholder }}</td>
</tr>
<tr>
<td>Type</td>
<td>{{ instance.get_type_display }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ instance.description|placeholder }}</td>
</tr>
</table>
</div>
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
{% plugin_left_page instance %}
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if instance.cable %}
<table class="table table-hover panel-body attr-table">
{% if instance.connected_endpoint %}
<tr>
<td>Device</td>
<td>
<a href="{{ instance.connected_endpoint.device.get_absolute_url }}">{{ instance.connected_endpoint.device }}</a>
</td>
</tr>
<tr>
<td>Name</td>
<td>
<a href="{{ instance.connected_endpoint.get_absolute_url }}">{{ instance.connected_endpoint.name }}</a>
</td>
</tr>
<tr>
<td>Type</td>
<td>{{ instance.connected_endpoint.get_type_display|placeholder }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ instance.connected_endpoint.description|placeholder }}</td>
</tr>
{% endif %}
<tr>
<td>Cable</td>
<td>
<a href="{{ instance.cable.get_absolute_url }}">{{ instance.cable }}</a>
<a href="{% url 'dcim:consoleport_trace' pk=instance.pk %}" class="btn btn-primary btn-xs" title="Trace">
<i class="fa fa-share-alt" aria-hidden="true"></i>
</a>
</td>
</tr>
<tr>
<td>Connection Status</td>
<td>
{% if instance.connection_status %}
<span class="label label-success">{{ instance.get_connection_status_display }}</span>
{% else %}
<span class="label label-info">{{ instance.get_connection_status_display }}</span>
{% endif %}
</td>
</tr>
</table>
{% else %}
<div class="panel-body text-muted">
Not connected
</div>
{% endif %}
</div>
{% plugin_right_page instance %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page instance %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,103 @@
{% extends 'dcim/device_component.html' %}
{% load helpers %}
{% load plugins %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Console Server Port</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Device</td>
<td>
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
</td>
</tr>
<tr>
<td>Name</td>
<td>{{ instance.name }}</td>
</tr>
<tr>
<td>Label</td>
<td>{{ instance.label|placeholder }}</td>
</tr>
<tr>
<td>Type</td>
<td>{{ instance.get_type_display }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ instance.description|placeholder }}</td>
</tr>
</table>
</div>
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
{% plugin_left_page instance %}
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if instance.cable %}
<table class="table table-hover panel-body attr-table">
{% if instance.connected_endpoint %}
<tr>
<td>Device</td>
<td>
<a href="{{ instance.connected_endpoint.device.get_absolute_url }}">{{ instance.connected_endpoint.device }}</a>
</td>
</tr>
<tr>
<td>Name</td>
<td>
<a href="{{ instance.connected_endpoint.get_absolute_url }}">{{ instance.connected_endpoint.name }}</a>
</td>
</tr>
<tr>
<td>Type</td>
<td>{{ instance.connected_endpoint.get_type_display|placeholder }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ instance.connected_endpoint.description|placeholder }}</td>
</tr>
{% endif %}
<tr>
<td>Cable</td>
<td>
<a href="{{ instance.cable.get_absolute_url }}">{{ instance.cable }}</a>
<a href="{% url 'dcim:consoleserverport_trace' pk=instance.pk %}" class="btn btn-primary btn-xs" title="Trace">
<i class="fa fa-share-alt" aria-hidden="true"></i>
</a>
</td>
</tr>
<tr>
<td>Connection Status</td>
<td>
{% if instance.connection_status %}
<span class="label label-success">{{ instance.get_connection_status_display }}</span>
{% else %}
<span class="label label-info">{{ instance.get_connection_status_display }}</span>
{% endif %}
</td>
</tr>
</table>
{% else %}
<div class="panel-body text-muted">
Not connected
</div>
{% endif %}
</div>
{% plugin_right_page instance %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page instance %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,41 @@
{% extends 'base.html' %}
{% load helpers %}
{% load perms %}
{% load plugins %}
{% block header %}
<div class="row noprint">
<div class="col-md-12">
<ol class="breadcrumb">
<li><a href="{% url 'dcim:device_list' %}">Devices</a></li>
<li><a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a></li>
<li><a href="{% url instance|url_name:"list" %}?device_id={{ instance.device.pk }}">{{ instance|meta:"verbose_name_plural"|bettertitle }}</a></li>
<li>{{ instance }}</li>
</ol>
</div>
</div>
<div class="pull-right noprint">
{% plugin_buttons instance %}
{% if request.user|can_change:instance %}
<a href="{% url instance|url_name:"edit" pk=instance.pk %}" class="btn btn-warning">
<span class="fa fa-pencil" aria-hidden="true"></span> Edit
</a>
{% endif %}
{% if request.user|can_delete:instance %}
<a href="{% url instance|url_name:"delete" pk=instance.pk %}" class="btn btn-danger">
<span class="fa fa-trash" aria-hidden="true"></span> Delete
</a>
{% endif %}
</div>
<h1>{% block title %}{{ instance.device }} / {{ instance }}{% endblock %}</h1>
<ul class="nav nav-tabs">
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
<a href="{{ instance.get_absolute_url }}">{{ instance|meta:"verbose_name"|bettertitle }}</a>
</li>
{% if perms.extras.view_objectchange %}
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
<a href="{% url instance|url_name:"changelog" pk=instance.pk %}">Change Log</a>
</li>
{% endif %}
</ul>
{% endblock %}

View File

@ -0,0 +1,70 @@
{% extends 'dcim/device_component.html' %}
{% load helpers %}
{% load plugins %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Device Bay</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Device</td>
<td>
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
</td>
</tr>
<tr>
<td>Name</td>
<td>{{ instance.name }}</td>
</tr>
<tr>
<td>Label</td>
<td>{{ instance.label|placeholder }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ instance.description|placeholder }}</td>
</tr>
</table>
</div>
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
{% plugin_left_page instance %}
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Installed Device</strong>
</div>
{% if instance.installed_device %}
{% with device=instance.installed_device %}
<table class="table table-hover panel-body attr-table">
<tr>
<td>Device</td>
<td>
<a href="{{ device.get_absolute_url }}">{{ device }}</a>
</td>
</tr>
<tr>
<td>Device Type</td>
<td>{{ device.device_type }}</td>
</tr>
</table>
{% endwith %}
{% else %}
<div class="panel-body text-muted">
None
</div>
{% endif %}
</div>
{% plugin_right_page instance %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page instance %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,91 @@
{% extends 'dcim/device_component.html' %}
{% load helpers %}
{% load plugins %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Front Port</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Device</td>
<td>
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
</td>
</tr>
<tr>
<td>Name</td>
<td>{{ instance.name }}</td>
</tr>
<tr>
<td>Label</td>
<td>{{ instance.label|placeholder }}</td>
</tr>
<tr>
<td>Type</td>
<td>{{ instance.get_type_display }}</td>
</tr>
<tr>
<td>Rear Port</td>
<td>
<a href="{{ instance.rear_port.get_absolute_url }}">{{ instance.rear_port }}</a>
</td>
</tr>
<tr>
<td>Rear Port Position</td>
<td>{{ instance.rear_port_position }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ instance.description|placeholder }}</td>
</tr>
</table>
</div>
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
{% plugin_left_page instance %}
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if instance.cable %}
<table class="table table-hover panel-body attr-table">
<tr>
<td>Cable</td>
<td>
<a href="{{ instance.cable.get_absolute_url }}">{{ instance.cable }}</a>
<a href="{% url 'dcim:frontport_trace' pk=instance.pk %}" class="btn btn-primary btn-xs" title="Trace">
<i class="fa fa-share-alt" aria-hidden="true"></i>
</a>
</td>
</tr>
<tr>
<td>Connection Status</td>
<td>
{% if instance.cable.status %}
<span class="label label-success">{{ instance.cable.get_status_display }}</span>
{% else %}
<span class="label label-info">{{ instance.cable.get_status_display }}</span>
{% endif %}
</td>
</tr>
</table>
{% else %}
<div class="panel-body text-muted">
Not connected
</div>
{% endif %}
</div>
{% plugin_right_page instance %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page instance %}
</div>
</div>
{% endblock %}

View File

@ -2,7 +2,8 @@
{# Name #}
<td>
<i class="fa fa-fw fa-keyboard-o"></i> {{ cp }}
<i class="fa fa-fw fa-keyboard-o"></i>
<a href="{{ cp.get_absolute_url }}">{{ cp }}</a>
</td>
{# Type #}

View File

@ -11,7 +11,8 @@
{# Name #}
<td>
<i class="fa fa-fw fa-keyboard-o"></i> {{ csp }}
<i class="fa fa-fw fa-keyboard-o"></i>
<a href="{{ csp.get_absolute_url }}">{{ csp }}</a>
</td>
{# Type #}

View File

@ -9,7 +9,8 @@
{# Name #}
<td>
<i class="fa fa-fw fa-{% if devicebay.installed_device %}dot-circle-o{% else %}circle-o{% endif %}"></i> {{ devicebay.name }}
<i class="fa fa-fw fa-{% if devicebay.installed_device %}dot-circle-o{% else %}circle-o{% endif %}"></i>
<a href="{{ devicebay.get_absolute_url }}">{{ devicebay.name }}</a>
</td>
{# Status #}

View File

@ -10,7 +10,8 @@
{# Name #}
<td>
<i class="fa fa-fw fa-square{% if not frontport.cable %}-o{% endif %}"></i> {{ frontport }}
<i class="fa fa-fw fa-square{% if not frontport.cable %}-o{% endif %}"></i>
<a href="{{ frontport.get_absolute_url }}">{{ frontport }}</a>
</td>
{# Type #}

View File

@ -11,7 +11,8 @@
{# Name #}
<td>
<i class="fa fa-fw fa-bolt"></i> {{ po }}
<i class="fa fa-fw fa-bolt"></i>
<a href="{{ po.get_absolute_url }}">{{ po }}</a>
</td>
{# Type #}

View File

@ -2,7 +2,8 @@
{# Name #}
<td>
<i class="fa fa-fw fa-bolt"></i> {{ pp }}
<i class="fa fa-fw fa-bolt"></i>
<a href="{{ pp.get_absolute_url }}">{{ pp }}</a>
</td>
{# Type #}

View File

@ -10,7 +10,8 @@
{# Name #}
<td>
<i class="fa fa-fw fa-square{% if not rearport.cable %}-o{% endif %}"></i> {{ rearport }}
<i class="fa fa-fw fa-square{% if not rearport.cable %}-o{% endif %}"></i>
<a href="{{ rearport.get_absolute_url }}">{{ rearport }}</a>
</td>
{# Type #}

View File

@ -1,40 +1,6 @@
{% extends 'base.html' %}
{% extends 'dcim/device_component.html' %}
{% load helpers %}
{% block header %}
<div class="row noprint">
<div class="col-md-12">
<ol class="breadcrumb">
<li><a href="{% url 'dcim:device_list' %}">Devices</a></li>
<li><a href="{{ interface.device.get_absolute_url }}">{{ interface.device }}</a></li>
<li>{{ interface }}</li>
</ol>
</div>
</div>
<div class="pull-right noprint">
{% if perms.dcim.change_interface %}
<a href="{% url 'dcim:interface_edit' pk=interface.pk %}" class="btn btn-warning">
<span class="fa fa-pencil" aria-hidden="true"></span> Edit
</a>
{% endif %}
{% if perms.dcim.delete_interface %}
<a href="{% url 'dcim:interface_delete' pk=interface.pk %}" class="btn btn-danger">
<span class="fa fa-trash" aria-hidden="true"></span> Delete
</a>
{% endif %}
</div>
<h1>{% block title %}{{ interface.device }} / {{ interface.name }}{% endblock %}</h1>
<ul class="nav nav-tabs">
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
<a href="{{ interface.get_absolute_url }}">Interface</a>
</li>
{% if perms.extras.view_objectchange %}
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
<a href="{% url 'dcim:interface_changelog' pk=interface.pk %}">Change Log</a>
</li>
{% endif %}
</ul>
{% endblock %}
{% load plugins %}
{% block content %}
<div class="row">
@ -47,25 +13,25 @@
<tr>
<td>Device</td>
<td>
<a href="{{ interface.device.get_absolute_url }}">{{ interface.device }}</a>
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
</td>
</tr>
<tr>
<td>Name</td>
<td>{{ interface.name }}</td>
<td>{{ instance.name }}</td>
</tr>
<tr>
<td>Label</td>
<td>{{ interface.label|placeholder }}</td>
<td>{{ instance.label|placeholder }}</td>
</tr>
<tr>
<td>Type</td>
<td>{{ interface.get_type_display }}</td>
<td>{{ instance.get_type_display }}</td>
</tr>
<tr>
<td>Enabled</td>
<td>
{% if interface.enabled %}
{% if instance.enabled %}
<span class="text-success"><i class="fa fa-check"></i></span>
{% else %}
<span class="text-danger"><i class="fa fa-close"></i></span>
@ -75,8 +41,8 @@
<tr>
<td>LAG</td>
<td>
{% if interface.lag%}
<a href="{{ interface.lag.get_absolute_url }}">{{ interface.lag }}</a>
{% if instance.lag%}
<a href="{{ instance.lag.get_absolute_url }}">{{ instance.lag }}</a>
{% else %}
<span class="text-muted">None</span>
{% endif %}
@ -84,31 +50,32 @@
</tr>
<tr>
<td>Description</td>
<td>{{ interface.description|placeholder }} </td>
<td>{{ instance.description|placeholder }} </td>
</tr>
<tr>
<td>MTU</td>
<td>{{ interface.mtu|placeholder }}</td>
<td>{{ instance.mtu|placeholder }}</td>
</tr>
<tr>
<td>MAC Address</td>
<td><span class="text-monospace">{{ interface.mac_address|placeholder }}</span></td>
<td><span class="text-monospace">{{ instance.mac_address|placeholder }}</span></td>
</tr>
<tr>
<td>802.1Q Mode</td>
<td>{{ interface.get_mode_display }}</td>
<td>{{ instance.get_mode_display }}</td>
</tr>
</table>
</div>
{% include 'extras/inc/tags_panel.html' with tags=interface.tags.all %}
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
{% plugin_left_page instance %}
</div>
<div class="col-md-6">
{% if interface.is_connectable %}
{% if instance.is_connectable %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if interface.cable %}
{% if instance.cable %}
<table class="table table-hover panel-body attr-table">
{% if connected_interface %}
<tr>
@ -182,8 +149,8 @@
<tr>
<td>Cable</td>
<td>
<a href="{{ interface.cable.get_absolute_url }}">{{ interface.cable }}</a>
<a href="{% url 'dcim:interface_trace' pk=interface.pk %}" class="btn btn-primary btn-xs" title="Trace">
<a href="{{ instance.cable.get_absolute_url }}">{{ instance.cable }}</a>
<a href="{% url 'dcim:interface_trace' pk=instance.pk %}" class="btn btn-primary btn-xs" title="Trace">
<i class="fa fa-share-alt" aria-hidden="true"></i>
</a>
</td>
@ -191,10 +158,10 @@
<tr>
<td>Connection Status</td>
<td>
{% if interface.connection_status %}
<span class="label label-success">{{ interface.get_connection_status_display }}</span>
{% if instance.connection_status %}
<span class="label label-success">{{ instance.get_connection_status_display }}</span>
{% else %}
<span class="label label-info">{{ interface.get_connection_status_display }}</span>
<span class="label label-info">{{ instance.get_connection_status_display }}</span>
{% endif %}
</td>
</tr>
@ -206,7 +173,7 @@
{% endif %}
</div>
{% endif %}
{% if interface.is_lag %}
{% if instance.is_lag %}
<div class="panel panel-default">
<div class="panel-heading"><strong>LAG Members</strong></div>
<table class="table table-hover table-headings panel-body">
@ -218,7 +185,7 @@
</tr>
</thead>
<tbody>
{% for member in interface.member_interfaces.all %}
{% for member in instance.member_interfaces.all %}
<tr>
<td>
<a href="{{ member.device.get_absolute_url }}">{{ member.device }}</a>
@ -239,6 +206,7 @@
</table>
</div>
{% endif %}
{% plugin_right_page instance %}
</div>
</div>
<div class="row">
@ -251,4 +219,9 @@
{% include 'panel_table.html' with table=vlan_table heading="VLANs" %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page instance %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,111 @@
{% extends 'dcim/device_component.html' %}
{% load helpers %}
{% load plugins %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Power Outlet</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Device</td>
<td>
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
</td>
</tr>
<tr>
<td>Name</td>
<td>{{ instance.name }}</td>
</tr>
<tr>
<td>Label</td>
<td>{{ instance.label|placeholder }}</td>
</tr>
<tr>
<td>Type</td>
<td>{{ instance.get_type_display }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ instance.description|placeholder }}</td>
</tr>
<tr>
<td>Power Port</td>
<td>{{ instance.power_port }}</td>
</tr>
<tr>
<td>Feed Leg</td>
<td>{{ instance.get_feed_leg_display }}</td>
</tr>
</table>
</div>
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
{% plugin_left_page instance %}
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if instance.cable %}
<table class="table table-hover panel-body attr-table">
{% if instance.connected_endpoint %}
<tr>
<td>Device</td>
<td>
<a href="{{ instance.connected_endpoint.device.get_absolute_url }}">{{ instance.connected_endpoint.device }}</a>
</td>
</tr>
<tr>
<td>Name</td>
<td>
<a href="{{ instance.connected_endpoint.get_absolute_url }}">{{ instance.connected_endpoint.name }}</a>
</td>
</tr>
<tr>
<td>Type</td>
<td>{{ instance.connected_endpoint.get_type_display|placeholder }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ instance.connected_endpoint.description|placeholder }}</td>
</tr>
{% endif %}
<tr>
<td>Cable</td>
<td>
<a href="{{ instance.cable.get_absolute_url }}">{{ instance.cable }}</a>
<a href="{% url 'dcim:poweroutlet_trace' pk=instance.pk %}" class="btn btn-primary btn-xs" title="Trace">
<i class="fa fa-share-alt" aria-hidden="true"></i>
</a>
</td>
</tr>
<tr>
<td>Connection Status</td>
<td>
{% if instance.connection_status %}
<span class="label label-success">{{ instance.get_connection_status_display }}</span>
{% else %}
<span class="label label-info">{{ instance.get_connection_status_display }}</span>
{% endif %}
</td>
</tr>
</table>
{% else %}
<div class="panel-body text-muted">
Not connected
</div>
{% endif %}
</div>
{% plugin_right_page instance %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page instance %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,111 @@
{% extends 'dcim/device_component.html' %}
{% load helpers %}
{% load plugins %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Power Port</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Device</td>
<td>
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
</td>
</tr>
<tr>
<td>Name</td>
<td>{{ instance.name }}</td>
</tr>
<tr>
<td>Label</td>
<td>{{ instance.label|placeholder }}</td>
</tr>
<tr>
<td>Type</td>
<td>{{ instance.get_type_display }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ instance.description|placeholder }}</td>
</tr>
<tr>
<td>Maximum Draw</td>
<td>{{ instance.maximum_draw|placeholder }}</td>
</tr>
<tr>
<td>Allocated Draw</td>
<td>{{ instance.allocated_draw|placeholder }}</td>
</tr>
</table>
</div>
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
{% plugin_left_page instance %}
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if instance.cable %}
<table class="table table-hover panel-body attr-table">
{% if instance.connected_endpoint %}
<tr>
<td>Device</td>
<td>
<a href="{{ instance.connected_endpoint.device.get_absolute_url }}">{{ instance.connected_endpoint.device }}</a>
</td>
</tr>
<tr>
<td>Name</td>
<td>
<a href="{{ instance.connected_endpoint.get_absolute_url }}">{{ instance.connected_endpoint.name }}</a>
</td>
</tr>
<tr>
<td>Type</td>
<td>{{ instance.connected_endpoint.get_type_display|placeholder }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ instance.connected_endpoint.description|placeholder }}</td>
</tr>
{% endif %}
<tr>
<td>Cable</td>
<td>
<a href="{{ instance.cable.get_absolute_url }}">{{ instance.cable }}</a>
<a href="{% url 'dcim:powerport_trace' pk=instance.pk %}" class="btn btn-primary btn-xs" title="Trace">
<i class="fa fa-share-alt" aria-hidden="true"></i>
</a>
</td>
</tr>
<tr>
<td>Connection Status</td>
<td>
{% if instance.connection_status %}
<span class="label label-success">{{ instance.get_connection_status_display }}</span>
{% else %}
<span class="label label-info">{{ instance.get_connection_status_display }}</span>
{% endif %}
</td>
</tr>
</table>
{% else %}
<div class="panel-body text-muted">
Not connected
</div>
{% endif %}
</div>
{% plugin_right_page instance %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page instance %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,85 @@
{% extends 'dcim/device_component.html' %}
{% load helpers %}
{% load plugins %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Rear Port</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Device</td>
<td>
<a href="{{ instance.device.get_absolute_url }}">{{ instance.device }}</a>
</td>
</tr>
<tr>
<td>Name</td>
<td>{{ instance.name }}</td>
</tr>
<tr>
<td>Label</td>
<td>{{ instance.label|placeholder }}</td>
</tr>
<tr>
<td>Type</td>
<td>{{ instance.get_type_display }}</td>
</tr>
<tr>
<td>Positions</td>
<td>{{ instance.positions }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ instance.description|placeholder }}</td>
</tr>
</table>
</div>
{% include 'extras/inc/tags_panel.html' with tags=instance.tags.all %}
{% plugin_left_page instance %}
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Connection</strong>
</div>
{% if instance.cable %}
<table class="table table-hover panel-body attr-table">
<tr>
<td>Cable</td>
<td>
<a href="{{ instance.cable.get_absolute_url }}">{{ instance.cable }}</a>
<a href="{% url 'dcim:rearport_trace' pk=instance.pk %}" class="btn btn-primary btn-xs" title="Trace">
<i class="fa fa-share-alt" aria-hidden="true"></i>
</a>
</td>
</tr>
<tr>
<td>Connection Status</td>
<td>
{% if instance.cable.status %}
<span class="label label-success">{{ instance.cable.get_status_display }}</span>
{% else %}
<span class="label label-info">{{ instance.cable.get_status_display }}</span>
{% endif %}
</td>
</tr>
</table>
{% else %}
<div class="panel-body text-muted">
Not connected
</div>
{% endif %}
</div>
{% plugin_right_page instance %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page instance %}
</div>
</div>
{% endblock %}

View File

@ -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()

View File

@ -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')

View File

@ -917,6 +917,7 @@ class ViewTestCases:
maxDiff = None
class DeviceComponentViewTestCase(
GetObjectViewTestCase,
EditObjectViewTestCase,
DeleteObjectViewTestCase,
ListObjectsViewTestCase,

View File

@ -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):
"""

View File

@ -189,10 +189,7 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
}
class VMInterfaceTestCase(
ViewTestCases.GetObjectViewTestCase,
ViewTestCases.DeviceComponentViewTestCase,
):
class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
model = VMInterface
@classmethod