mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 04:02:52 -06:00
Merge pull request #10597 from netbox-community/9072-plugin-view-tabs
#9072: Custom model view tabs for plugins
This commit is contained in:
commit
a3dbf4023c
@ -148,6 +148,32 @@ These views are provided to enable or enhance certain NetBox model features, suc
|
||||
|
||||
## Extending Core Views
|
||||
|
||||
### Additional Tabs
|
||||
|
||||
Plugins can "attach" a custom view to a core NetBox model by registering it with `register_model_view()`. To include a tab for this view within the NetBox UI, declare a TabView instance named `tab`:
|
||||
|
||||
```python
|
||||
from dcim.models import Site
|
||||
from myplugin.models import Stuff
|
||||
from netbox.views import generic
|
||||
from utilities.views import ViewTab, register_model_view
|
||||
|
||||
@register_model_view(Site, 'mview', path='some-other-stuff')
|
||||
class MyView(generic.ObjectView):
|
||||
...
|
||||
tab = ViewTab(
|
||||
label='Other Stuff',
|
||||
badge=lambda obj: Stuff.objects.filter(site=obj).count(),
|
||||
permission='myplugin.view_stuff'
|
||||
)
|
||||
```
|
||||
|
||||
::: utilities.views.register_model_view
|
||||
|
||||
::: utilities.views.ViewTab
|
||||
|
||||
### Extra Template Content
|
||||
|
||||
Plugins can inject custom content into certain areas of the detail views of applicable models. This is accomplished by subclassing `PluginTemplateExtension`, designating a particular NetBox model, and defining the desired methods to render custom content. Four methods are available:
|
||||
|
||||
* `left_page()` - Inject content on the left side of the page
|
||||
|
@ -27,6 +27,7 @@ A new `PluginMenu` class has been introduced, which enables a plugin to inject a
|
||||
### Plugins API
|
||||
|
||||
* [#9071](https://github.com/netbox-community/netbox/issues/9071) - Introduce `PluginMenu` for top-level plugin navigation menus
|
||||
* [#9072](https://github.com/netbox-community/netbox/issues/9072) - Enable registration of tabbed plugin views for core NetBox models
|
||||
* [#9880](https://github.com/netbox-community/netbox/issues/9880) - Introduce `django_apps` plugin configuration parameter
|
||||
* [#10314](https://github.com/netbox-community/netbox/issues/10314) - Move `clone()` method from NetBoxModel to CloningMixin
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from dcim.views import PathTraceView
|
||||
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
|
||||
from utilities.urls import get_model_urls
|
||||
from . import views
|
||||
from .models import *
|
||||
from .models import CircuitTermination
|
||||
|
||||
app_name = 'circuits'
|
||||
urlpatterns = [
|
||||
@ -17,8 +17,7 @@ urlpatterns = [
|
||||
path('providers/<int:pk>/', views.ProviderView.as_view(), name='provider'),
|
||||
path('providers/<int:pk>/edit/', views.ProviderEditView.as_view(), name='provider_edit'),
|
||||
path('providers/<int:pk>/delete/', views.ProviderDeleteView.as_view(), name='provider_delete'),
|
||||
path('providers/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='provider_changelog', kwargs={'model': Provider}),
|
||||
path('providers/<int:pk>/journal/', ObjectJournalView.as_view(), name='provider_journal', kwargs={'model': Provider}),
|
||||
path('providers/<int:pk>/', include(get_model_urls('circuits', 'provider'))),
|
||||
|
||||
# Provider networks
|
||||
path('provider-networks/', views.ProviderNetworkListView.as_view(), name='providernetwork_list'),
|
||||
@ -29,8 +28,7 @@ urlpatterns = [
|
||||
path('provider-networks/<int:pk>/', views.ProviderNetworkView.as_view(), name='providernetwork'),
|
||||
path('provider-networks/<int:pk>/edit/', views.ProviderNetworkEditView.as_view(), name='providernetwork_edit'),
|
||||
path('provider-networks/<int:pk>/delete/', views.ProviderNetworkDeleteView.as_view(), name='providernetwork_delete'),
|
||||
path('provider-networks/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='providernetwork_changelog', kwargs={'model': ProviderNetwork}),
|
||||
path('provider-networks/<int:pk>/journal/', ObjectJournalView.as_view(), name='providernetwork_journal', kwargs={'model': ProviderNetwork}),
|
||||
path('provider-networks/<int:pk>/', include(get_model_urls('circuits', 'providernetwork'))),
|
||||
|
||||
# Circuit types
|
||||
path('circuit-types/', views.CircuitTypeListView.as_view(), name='circuittype_list'),
|
||||
@ -41,7 +39,7 @@ urlpatterns = [
|
||||
path('circuit-types/<int:pk>/', views.CircuitTypeView.as_view(), name='circuittype'),
|
||||
path('circuit-types/<int:pk>/edit/', views.CircuitTypeEditView.as_view(), name='circuittype_edit'),
|
||||
path('circuit-types/<int:pk>/delete/', views.CircuitTypeDeleteView.as_view(), name='circuittype_delete'),
|
||||
path('circuit-types/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='circuittype_changelog', kwargs={'model': CircuitType}),
|
||||
path('circuit-types/<int:pk>/', include(get_model_urls('circuits', 'circuittype'))),
|
||||
|
||||
# Circuits
|
||||
path('circuits/', views.CircuitListView.as_view(), name='circuit_list'),
|
||||
@ -52,9 +50,8 @@ urlpatterns = [
|
||||
path('circuits/<int:pk>/', views.CircuitView.as_view(), name='circuit'),
|
||||
path('circuits/<int:pk>/edit/', views.CircuitEditView.as_view(), name='circuit_edit'),
|
||||
path('circuits/<int:pk>/delete/', views.CircuitDeleteView.as_view(), name='circuit_delete'),
|
||||
path('circuits/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='circuit_changelog', kwargs={'model': Circuit}),
|
||||
path('circuits/<int:pk>/journal/', ObjectJournalView.as_view(), name='circuit_journal', kwargs={'model': Circuit}),
|
||||
path('circuits/<int:pk>/terminations/swap/', views.CircuitSwapTerminations.as_view(), name='circuit_terminations_swap'),
|
||||
path('circuits/<int:pk>/', include(get_model_urls('circuits', 'circuit'))),
|
||||
|
||||
# Circuit terminations
|
||||
path('circuit-terminations/add/', views.CircuitTerminationEditView.as_view(), name='circuittermination_add'),
|
||||
|
@ -1,8 +1,10 @@
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
|
||||
from utilities.urls import get_model_urls
|
||||
from . import views
|
||||
from .models import *
|
||||
from .models import (
|
||||
ConsolePort, ConsoleServerPort, FrontPort, Interface, PowerFeed, PowerPort, PowerOutlet, RearPort,
|
||||
)
|
||||
|
||||
app_name = 'dcim'
|
||||
urlpatterns = [
|
||||
@ -16,7 +18,7 @@ urlpatterns = [
|
||||
path('regions/<int:pk>/', views.RegionView.as_view(), name='region'),
|
||||
path('regions/<int:pk>/edit/', views.RegionEditView.as_view(), name='region_edit'),
|
||||
path('regions/<int:pk>/delete/', views.RegionDeleteView.as_view(), name='region_delete'),
|
||||
path('regions/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='region_changelog', kwargs={'model': Region}),
|
||||
path('regions/<int:pk>/', include(get_model_urls('dcim', 'region'))),
|
||||
|
||||
# Site groups
|
||||
path('site-groups/', views.SiteGroupListView.as_view(), name='sitegroup_list'),
|
||||
@ -27,7 +29,7 @@ urlpatterns = [
|
||||
path('site-groups/<int:pk>/', views.SiteGroupView.as_view(), name='sitegroup'),
|
||||
path('site-groups/<int:pk>/edit/', views.SiteGroupEditView.as_view(), name='sitegroup_edit'),
|
||||
path('site-groups/<int:pk>/delete/', views.SiteGroupDeleteView.as_view(), name='sitegroup_delete'),
|
||||
path('site-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='sitegroup_changelog', kwargs={'model': SiteGroup}),
|
||||
path('site-groups/<int:pk>/', include(get_model_urls('dcim', 'sitegroup'))),
|
||||
|
||||
# Sites
|
||||
path('sites/', views.SiteListView.as_view(), name='site_list'),
|
||||
@ -38,8 +40,7 @@ urlpatterns = [
|
||||
path('sites/<int:pk>/', views.SiteView.as_view(), name='site'),
|
||||
path('sites/<int:pk>/edit/', views.SiteEditView.as_view(), name='site_edit'),
|
||||
path('sites/<int:pk>/delete/', views.SiteDeleteView.as_view(), name='site_delete'),
|
||||
path('sites/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='site_changelog', kwargs={'model': Site}),
|
||||
path('sites/<int:pk>/journal/', ObjectJournalView.as_view(), name='site_journal', kwargs={'model': Site}),
|
||||
path('sites/<int:pk>/', include(get_model_urls('dcim', 'site'))),
|
||||
|
||||
# Locations
|
||||
path('locations/', views.LocationListView.as_view(), name='location_list'),
|
||||
@ -50,7 +51,7 @@ urlpatterns = [
|
||||
path('locations/<int:pk>/', views.LocationView.as_view(), name='location'),
|
||||
path('locations/<int:pk>/edit/', views.LocationEditView.as_view(), name='location_edit'),
|
||||
path('locations/<int:pk>/delete/', views.LocationDeleteView.as_view(), name='location_delete'),
|
||||
path('locations/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='location_changelog', kwargs={'model': Location}),
|
||||
path('locations/<int:pk>/', include(get_model_urls('dcim', 'location'))),
|
||||
|
||||
# Rack roles
|
||||
path('rack-roles/', views.RackRoleListView.as_view(), name='rackrole_list'),
|
||||
@ -61,7 +62,7 @@ urlpatterns = [
|
||||
path('rack-roles/<int:pk>/', views.RackRoleView.as_view(), name='rackrole'),
|
||||
path('rack-roles/<int:pk>/edit/', views.RackRoleEditView.as_view(), name='rackrole_edit'),
|
||||
path('rack-roles/<int:pk>/delete/', views.RackRoleDeleteView.as_view(), name='rackrole_delete'),
|
||||
path('rack-roles/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='rackrole_changelog', kwargs={'model': RackRole}),
|
||||
path('rack-roles/<int:pk>/', include(get_model_urls('dcim', 'rackrole'))),
|
||||
|
||||
# Rack reservations
|
||||
path('rack-reservations/', views.RackReservationListView.as_view(), name='rackreservation_list'),
|
||||
@ -72,8 +73,7 @@ urlpatterns = [
|
||||
path('rack-reservations/<int:pk>/', views.RackReservationView.as_view(), name='rackreservation'),
|
||||
path('rack-reservations/<int:pk>/edit/', views.RackReservationEditView.as_view(), name='rackreservation_edit'),
|
||||
path('rack-reservations/<int:pk>/delete/', views.RackReservationDeleteView.as_view(), name='rackreservation_delete'),
|
||||
path('rack-reservations/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='rackreservation_changelog', kwargs={'model': RackReservation}),
|
||||
path('rack-reservations/<int:pk>/journal/', ObjectJournalView.as_view(), name='rackreservation_journal', kwargs={'model': RackReservation}),
|
||||
path('rack-reservations/<int:pk>/', include(get_model_urls('dcim', 'rackreservation'))),
|
||||
|
||||
# Racks
|
||||
path('racks/', views.RackListView.as_view(), name='rack_list'),
|
||||
@ -85,8 +85,7 @@ urlpatterns = [
|
||||
path('racks/<int:pk>/', views.RackView.as_view(), name='rack'),
|
||||
path('racks/<int:pk>/edit/', views.RackEditView.as_view(), name='rack_edit'),
|
||||
path('racks/<int:pk>/delete/', views.RackDeleteView.as_view(), name='rack_delete'),
|
||||
path('racks/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='rack_changelog', kwargs={'model': Rack}),
|
||||
path('racks/<int:pk>/journal/', ObjectJournalView.as_view(), name='rack_journal', kwargs={'model': Rack}),
|
||||
path('racks/<int:pk>/', include(get_model_urls('dcim', 'rack'))),
|
||||
|
||||
# Manufacturers
|
||||
path('manufacturers/', views.ManufacturerListView.as_view(), name='manufacturer_list'),
|
||||
@ -97,7 +96,7 @@ urlpatterns = [
|
||||
path('manufacturers/<int:pk>/', views.ManufacturerView.as_view(), name='manufacturer'),
|
||||
path('manufacturers/<int:pk>/edit/', views.ManufacturerEditView.as_view(), name='manufacturer_edit'),
|
||||
path('manufacturers/<int:pk>/delete/', views.ManufacturerDeleteView.as_view(), name='manufacturer_delete'),
|
||||
path('manufacturers/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='manufacturer_changelog', kwargs={'model': Manufacturer}),
|
||||
path('manufacturers/<int:pk>/', include(get_model_urls('dcim', 'manufacturer'))),
|
||||
|
||||
# Device types
|
||||
path('device-types/', views.DeviceTypeListView.as_view(), name='devicetype_list'),
|
||||
@ -106,20 +105,9 @@ urlpatterns = [
|
||||
path('device-types/edit/', views.DeviceTypeBulkEditView.as_view(), name='devicetype_bulk_edit'),
|
||||
path('device-types/delete/', views.DeviceTypeBulkDeleteView.as_view(), name='devicetype_bulk_delete'),
|
||||
path('device-types/<int:pk>/', views.DeviceTypeView.as_view(), name='devicetype'),
|
||||
path('device-types/<int:pk>/console-ports/', views.DeviceTypeConsolePortsView.as_view(), name='devicetype_consoleports'),
|
||||
path('device-types/<int:pk>/console-server-ports/', views.DeviceTypeConsoleServerPortsView.as_view(), name='devicetype_consoleserverports'),
|
||||
path('device-types/<int:pk>/power-ports/', views.DeviceTypePowerPortsView.as_view(), name='devicetype_powerports'),
|
||||
path('device-types/<int:pk>/power-outlets/', views.DeviceTypePowerOutletsView.as_view(), name='devicetype_poweroutlets'),
|
||||
path('device-types/<int:pk>/interfaces/', views.DeviceTypeInterfacesView.as_view(), name='devicetype_interfaces'),
|
||||
path('device-types/<int:pk>/front-ports/', views.DeviceTypeFrontPortsView.as_view(), name='devicetype_frontports'),
|
||||
path('device-types/<int:pk>/rear-ports/', views.DeviceTypeRearPortsView.as_view(), name='devicetype_rearports'),
|
||||
path('device-types/<int:pk>/module-bays/', views.DeviceTypeModuleBaysView.as_view(), name='devicetype_modulebays'),
|
||||
path('device-types/<int:pk>/device-bays/', views.DeviceTypeDeviceBaysView.as_view(), name='devicetype_devicebays'),
|
||||
path('device-types/<int:pk>/inventory-items/', views.DeviceTypeInventoryItemsView.as_view(), name='devicetype_inventoryitems'),
|
||||
path('device-types/<int:pk>/edit/', views.DeviceTypeEditView.as_view(), name='devicetype_edit'),
|
||||
path('device-types/<int:pk>/delete/', views.DeviceTypeDeleteView.as_view(), name='devicetype_delete'),
|
||||
path('device-types/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='devicetype_changelog', kwargs={'model': DeviceType}),
|
||||
path('device-types/<int:pk>/journal/', ObjectJournalView.as_view(), name='devicetype_journal', kwargs={'model': DeviceType}),
|
||||
path('device-types/<int:pk>/', include(get_model_urls('dcim', 'devicetype'))),
|
||||
|
||||
# Module types
|
||||
path('module-types/', views.ModuleTypeListView.as_view(), name='moduletype_list'),
|
||||
@ -128,17 +116,9 @@ urlpatterns = [
|
||||
path('module-types/edit/', views.ModuleTypeBulkEditView.as_view(), name='moduletype_bulk_edit'),
|
||||
path('module-types/delete/', views.ModuleTypeBulkDeleteView.as_view(), name='moduletype_bulk_delete'),
|
||||
path('module-types/<int:pk>/', views.ModuleTypeView.as_view(), name='moduletype'),
|
||||
path('module-types/<int:pk>/console-ports/', views.ModuleTypeConsolePortsView.as_view(), name='moduletype_consoleports'),
|
||||
path('module-types/<int:pk>/console-server-ports/', views.ModuleTypeConsoleServerPortsView.as_view(), name='moduletype_consoleserverports'),
|
||||
path('module-types/<int:pk>/power-ports/', views.ModuleTypePowerPortsView.as_view(), name='moduletype_powerports'),
|
||||
path('module-types/<int:pk>/power-outlets/', views.ModuleTypePowerOutletsView.as_view(), name='moduletype_poweroutlets'),
|
||||
path('module-types/<int:pk>/interfaces/', views.ModuleTypeInterfacesView.as_view(), name='moduletype_interfaces'),
|
||||
path('module-types/<int:pk>/front-ports/', views.ModuleTypeFrontPortsView.as_view(), name='moduletype_frontports'),
|
||||
path('module-types/<int:pk>/rear-ports/', views.ModuleTypeRearPortsView.as_view(), name='moduletype_rearports'),
|
||||
path('module-types/<int:pk>/edit/', views.ModuleTypeEditView.as_view(), name='moduletype_edit'),
|
||||
path('module-types/<int:pk>/delete/', views.ModuleTypeDeleteView.as_view(), name='moduletype_delete'),
|
||||
path('module-types/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='moduletype_changelog', kwargs={'model': ModuleType}),
|
||||
path('module-types/<int:pk>/journal/', ObjectJournalView.as_view(), name='moduletype_journal', kwargs={'model': ModuleType}),
|
||||
path('module-types/<int:pk>/', include(get_model_urls('dcim', 'moduletype'))),
|
||||
|
||||
# Console port templates
|
||||
path('console-port-templates/add/', views.ConsolePortTemplateCreateView.as_view(), name='consoleporttemplate_add'),
|
||||
@ -229,7 +209,7 @@ urlpatterns = [
|
||||
path('device-roles/<int:pk>/', views.DeviceRoleView.as_view(), name='devicerole'),
|
||||
path('device-roles/<int:pk>/edit/', views.DeviceRoleEditView.as_view(), name='devicerole_edit'),
|
||||
path('device-roles/<int:pk>/delete/', views.DeviceRoleDeleteView.as_view(), name='devicerole_delete'),
|
||||
path('device-roles/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='devicerole_changelog', kwargs={'model': DeviceRole}),
|
||||
path('device-roles/<int:pk>/', include(get_model_urls('dcim', 'devicerole'))),
|
||||
|
||||
# Platforms
|
||||
path('platforms/', views.PlatformListView.as_view(), name='platform_list'),
|
||||
@ -240,7 +220,7 @@ urlpatterns = [
|
||||
path('platforms/<int:pk>/', views.PlatformView.as_view(), name='platform'),
|
||||
path('platforms/<int:pk>/edit/', views.PlatformEditView.as_view(), name='platform_edit'),
|
||||
path('platforms/<int:pk>/delete/', views.PlatformDeleteView.as_view(), name='platform_delete'),
|
||||
path('platforms/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='platform_changelog', kwargs={'model': Platform}),
|
||||
path('platforms/<int:pk>/', include(get_model_urls('dcim', 'platform'))),
|
||||
|
||||
# Devices
|
||||
path('devices/', views.DeviceListView.as_view(), name='device_list'),
|
||||
@ -253,22 +233,7 @@ urlpatterns = [
|
||||
path('devices/<int:pk>/', views.DeviceView.as_view(), name='device'),
|
||||
path('devices/<int:pk>/edit/', views.DeviceEditView.as_view(), name='device_edit'),
|
||||
path('devices/<int:pk>/delete/', views.DeviceDeleteView.as_view(), name='device_delete'),
|
||||
path('devices/<int:pk>/console-ports/', views.DeviceConsolePortsView.as_view(), name='device_consoleports'),
|
||||
path('devices/<int:pk>/console-server-ports/', views.DeviceConsoleServerPortsView.as_view(), name='device_consoleserverports'),
|
||||
path('devices/<int:pk>/power-ports/', views.DevicePowerPortsView.as_view(), name='device_powerports'),
|
||||
path('devices/<int:pk>/power-outlets/', views.DevicePowerOutletsView.as_view(), name='device_poweroutlets'),
|
||||
path('devices/<int:pk>/interfaces/', views.DeviceInterfacesView.as_view(), name='device_interfaces'),
|
||||
path('devices/<int:pk>/front-ports/', views.DeviceFrontPortsView.as_view(), name='device_frontports'),
|
||||
path('devices/<int:pk>/rear-ports/', views.DeviceRearPortsView.as_view(), name='device_rearports'),
|
||||
path('devices/<int:pk>/module-bays/', views.DeviceModuleBaysView.as_view(), name='device_modulebays'),
|
||||
path('devices/<int:pk>/device-bays/', views.DeviceDeviceBaysView.as_view(), name='device_devicebays'),
|
||||
path('devices/<int:pk>/inventory/', views.DeviceInventoryView.as_view(), name='device_inventory'),
|
||||
path('devices/<int:pk>/config-context/', views.DeviceConfigContextView.as_view(), name='device_configcontext'),
|
||||
path('devices/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='device_changelog', kwargs={'model': Device}),
|
||||
path('devices/<int:pk>/journal/', ObjectJournalView.as_view(), name='device_journal', kwargs={'model': Device}),
|
||||
path('devices/<int:pk>/status/', views.DeviceStatusView.as_view(), name='device_status'),
|
||||
path('devices/<int:pk>/lldp-neighbors/', views.DeviceLLDPNeighborsView.as_view(), name='device_lldp_neighbors'),
|
||||
path('devices/<int:pk>/config/', views.DeviceConfigView.as_view(), name='device_config'),
|
||||
path('devices/<int:pk>/', include(get_model_urls('dcim', 'device'))),
|
||||
|
||||
# Modules
|
||||
path('modules/', views.ModuleListView.as_view(), name='module_list'),
|
||||
@ -279,8 +244,7 @@ urlpatterns = [
|
||||
path('modules/<int:pk>/', views.ModuleView.as_view(), name='module'),
|
||||
path('modules/<int:pk>/edit/', views.ModuleEditView.as_view(), name='module_edit'),
|
||||
path('modules/<int:pk>/delete/', views.ModuleDeleteView.as_view(), name='module_delete'),
|
||||
path('modules/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='module_changelog', kwargs={'model': Module}),
|
||||
path('modules/<int:pk>/journal/', ObjectJournalView.as_view(), name='module_journal', kwargs={'model': Module}),
|
||||
path('modules/<int:pk>/', include(get_model_urls('dcim', 'module'))),
|
||||
|
||||
# Console ports
|
||||
path('console-ports/', views.ConsolePortListView.as_view(), name='consoleport_list'),
|
||||
@ -293,8 +257,8 @@ urlpatterns = [
|
||||
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.PathTraceView.as_view(), name='consoleport_trace', kwargs={'model': ConsolePort}),
|
||||
path('console-ports/<int:pk>/', include(get_model_urls('dcim', 'consoleport'))),
|
||||
path('devices/console-ports/add/', views.DeviceBulkAddConsolePortView.as_view(), name='device_bulk_add_consoleport'),
|
||||
|
||||
# Console server ports
|
||||
@ -308,8 +272,8 @@ urlpatterns = [
|
||||
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.PathTraceView.as_view(), name='consoleserverport_trace', kwargs={'model': ConsoleServerPort}),
|
||||
path('console-server-ports/<int:pk>/', include(get_model_urls('dcim', 'consoleserverport'))),
|
||||
path('devices/console-server-ports/add/', views.DeviceBulkAddConsoleServerPortView.as_view(), name='device_bulk_add_consoleserverport'),
|
||||
|
||||
# Power ports
|
||||
@ -323,8 +287,8 @@ urlpatterns = [
|
||||
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.PathTraceView.as_view(), name='powerport_trace', kwargs={'model': PowerPort}),
|
||||
path('power-ports/<int:pk>/', include(get_model_urls('dcim', 'powerport'))),
|
||||
path('devices/power-ports/add/', views.DeviceBulkAddPowerPortView.as_view(), name='device_bulk_add_powerport'),
|
||||
|
||||
# Power outlets
|
||||
@ -338,8 +302,8 @@ urlpatterns = [
|
||||
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.PathTraceView.as_view(), name='poweroutlet_trace', kwargs={'model': PowerOutlet}),
|
||||
path('power-outlets/<int:pk>/', include(get_model_urls('dcim', 'poweroutlet'))),
|
||||
path('devices/power-outlets/add/', views.DeviceBulkAddPowerOutletView.as_view(), name='device_bulk_add_poweroutlet'),
|
||||
|
||||
# Interfaces
|
||||
@ -353,8 +317,8 @@ urlpatterns = [
|
||||
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.PathTraceView.as_view(), name='interface_trace', kwargs={'model': Interface}),
|
||||
path('interfaces/<int:pk>/', include(get_model_urls('dcim', 'interface'))),
|
||||
path('devices/interfaces/add/', views.DeviceBulkAddInterfaceView.as_view(), name='device_bulk_add_interface'),
|
||||
|
||||
# Front ports
|
||||
@ -368,8 +332,8 @@ urlpatterns = [
|
||||
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.PathTraceView.as_view(), name='frontport_trace', kwargs={'model': FrontPort}),
|
||||
path('front-ports/<int:pk>/', include(get_model_urls('dcim', 'frontport'))),
|
||||
# path('devices/front-ports/add/', views.DeviceBulkAddFrontPortView.as_view(), name='device_bulk_add_frontport'),
|
||||
|
||||
# Rear ports
|
||||
@ -383,8 +347,8 @@ urlpatterns = [
|
||||
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.PathTraceView.as_view(), name='rearport_trace', kwargs={'model': RearPort}),
|
||||
path('rear-ports/<int:pk>/', include(get_model_urls('dcim', 'rearport'))),
|
||||
path('devices/rear-ports/add/', views.DeviceBulkAddRearPortView.as_view(), name='device_bulk_add_rearport'),
|
||||
|
||||
# Module bays
|
||||
@ -397,7 +361,7 @@ urlpatterns = [
|
||||
path('module-bays/<int:pk>/', views.ModuleBayView.as_view(), name='modulebay'),
|
||||
path('module-bays/<int:pk>/edit/', views.ModuleBayEditView.as_view(), name='modulebay_edit'),
|
||||
path('module-bays/<int:pk>/delete/', views.ModuleBayDeleteView.as_view(), name='modulebay_delete'),
|
||||
path('module-bays/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='modulebay_changelog', kwargs={'model': ModuleBay}),
|
||||
path('module-bays/<int:pk>/', include(get_model_urls('dcim', 'modulebay'))),
|
||||
path('devices/module-bays/add/', views.DeviceBulkAddModuleBayView.as_view(), name='device_bulk_add_modulebay'),
|
||||
|
||||
# Device bays
|
||||
@ -410,9 +374,9 @@ urlpatterns = [
|
||||
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('device-bays/<int:pk>/', include(get_model_urls('dcim', 'devicebay'))),
|
||||
path('devices/device-bays/add/', views.DeviceBulkAddDeviceBayView.as_view(), name='device_bulk_add_devicebay'),
|
||||
|
||||
# Inventory items
|
||||
@ -425,10 +389,10 @@ urlpatterns = [
|
||||
path('inventory-items/<int:pk>/', views.InventoryItemView.as_view(), name='inventoryitem'),
|
||||
path('inventory-items/<int:pk>/edit/', views.InventoryItemEditView.as_view(), name='inventoryitem_edit'),
|
||||
path('inventory-items/<int:pk>/delete/', views.InventoryItemDeleteView.as_view(), name='inventoryitem_delete'),
|
||||
path('inventory-items/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='inventoryitem_changelog', kwargs={'model': InventoryItem}),
|
||||
path('inventory-items/<int:pk>/', include(get_model_urls('dcim', 'inventoryitem'))),
|
||||
path('devices/inventory-items/add/', views.DeviceBulkAddInventoryItemView.as_view(), name='device_bulk_add_inventoryitem'),
|
||||
|
||||
# Device roles
|
||||
# Inventory item roles
|
||||
path('inventory-item-roles/', views.InventoryItemRoleListView.as_view(), name='inventoryitemrole_list'),
|
||||
path('inventory-item-roles/add/', views.InventoryItemRoleEditView.as_view(), name='inventoryitemrole_add'),
|
||||
path('inventory-item-roles/import/', views.InventoryItemRoleBulkImportView.as_view(), name='inventoryitemrole_import'),
|
||||
@ -437,7 +401,7 @@ urlpatterns = [
|
||||
path('inventory-item-roles/<int:pk>/', views.InventoryItemRoleView.as_view(), name='inventoryitemrole'),
|
||||
path('inventory-item-roles/<int:pk>/edit/', views.InventoryItemRoleEditView.as_view(), name='inventoryitemrole_edit'),
|
||||
path('inventory-item-roles/<int:pk>/delete/', views.InventoryItemRoleDeleteView.as_view(), name='inventoryitemrole_delete'),
|
||||
path('inventory-item-roles/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='inventoryitemrole_changelog', kwargs={'model': InventoryItemRole}),
|
||||
path('inventory-item-roles/<int:pk>/', include(get_model_urls('dcim', 'inventoryitemrole'))),
|
||||
|
||||
# Cables
|
||||
path('cables/', views.CableListView.as_view(), name='cable_list'),
|
||||
@ -448,8 +412,7 @@ urlpatterns = [
|
||||
path('cables/<int:pk>/', views.CableView.as_view(), name='cable'),
|
||||
path('cables/<int:pk>/edit/', views.CableEditView.as_view(), name='cable_edit'),
|
||||
path('cables/<int:pk>/delete/', views.CableDeleteView.as_view(), name='cable_delete'),
|
||||
path('cables/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='cable_changelog', kwargs={'model': Cable}),
|
||||
path('cables/<int:pk>/journal/', ObjectJournalView.as_view(), name='cable_journal', kwargs={'model': Cable}),
|
||||
path('cables/<int:pk>/', include(get_model_urls('dcim', 'cable'))),
|
||||
|
||||
# Console/power/interface connections (read-only)
|
||||
path('console-connections/', views.ConsoleConnectionsListView.as_view(), name='console_connections_list'),
|
||||
@ -465,9 +428,8 @@ urlpatterns = [
|
||||
path('virtual-chassis/<int:pk>/', views.VirtualChassisView.as_view(), name='virtualchassis'),
|
||||
path('virtual-chassis/<int:pk>/edit/', views.VirtualChassisEditView.as_view(), name='virtualchassis_edit'),
|
||||
path('virtual-chassis/<int:pk>/delete/', views.VirtualChassisDeleteView.as_view(), name='virtualchassis_delete'),
|
||||
path('virtual-chassis/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='virtualchassis_changelog', kwargs={'model': VirtualChassis}),
|
||||
path('virtual-chassis/<int:pk>/journal/', ObjectJournalView.as_view(), name='virtualchassis_journal', kwargs={'model': VirtualChassis}),
|
||||
path('virtual-chassis/<int:pk>/add-member/', views.VirtualChassisAddMemberView.as_view(), name='virtualchassis_add_member'),
|
||||
path('virtual-chassis/<int:pk>/', include(get_model_urls('dcim', 'virtualchassis'))),
|
||||
path('virtual-chassis-members/<int:pk>/delete/', views.VirtualChassisRemoveMemberView.as_view(), name='virtualchassis_remove_member'),
|
||||
|
||||
# Power panels
|
||||
@ -479,8 +441,7 @@ urlpatterns = [
|
||||
path('power-panels/<int:pk>/', views.PowerPanelView.as_view(), name='powerpanel'),
|
||||
path('power-panels/<int:pk>/edit/', views.PowerPanelEditView.as_view(), name='powerpanel_edit'),
|
||||
path('power-panels/<int:pk>/delete/', views.PowerPanelDeleteView.as_view(), name='powerpanel_delete'),
|
||||
path('power-panels/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='powerpanel_changelog', kwargs={'model': PowerPanel}),
|
||||
path('power-panels/<int:pk>/journal/', ObjectJournalView.as_view(), name='powerpanel_journal', kwargs={'model': PowerPanel}),
|
||||
path('power-panels/<int:pk>/', include(get_model_urls('dcim', 'powerpanel'))),
|
||||
|
||||
# Power feeds
|
||||
path('power-feeds/', views.PowerFeedListView.as_view(), name='powerfeed_list'),
|
||||
@ -493,7 +454,6 @@ urlpatterns = [
|
||||
path('power-feeds/<int:pk>/edit/', views.PowerFeedEditView.as_view(), name='powerfeed_edit'),
|
||||
path('power-feeds/<int:pk>/delete/', views.PowerFeedDeleteView.as_view(), name='powerfeed_delete'),
|
||||
path('power-feeds/<int:pk>/trace/', views.PathTraceView.as_view(), name='powerfeed_trace', kwargs={'model': PowerFeed}),
|
||||
path('power-feeds/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='powerfeed_changelog', kwargs={'model': PowerFeed}),
|
||||
path('power-feeds/<int:pk>/journal/', ObjectJournalView.as_view(), name='powerfeed_journal', kwargs={'model': PowerFeed}),
|
||||
path('power-feeds/<int:pk>/', include(get_model_urls('dcim', 'powerfeed'))),
|
||||
|
||||
]
|
||||
|
@ -8,6 +8,7 @@ from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import View
|
||||
|
||||
from circuits.models import Circuit, CircuitTermination
|
||||
@ -19,7 +20,7 @@ from utilities.forms import ConfirmationForm
|
||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||
from utilities.permissions import get_permission_for_model
|
||||
from utilities.utils import count_related
|
||||
from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin
|
||||
from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin, ViewTab, register_model_view
|
||||
from virtualization.models import VirtualMachine
|
||||
from . import filtersets, forms, tables
|
||||
from .choices import DeviceFaceChoices
|
||||
@ -45,11 +46,6 @@ class DeviceComponentsView(generic.ObjectChildrenView):
|
||||
def get_children(self, request, parent):
|
||||
return self.child_model.objects.restrict(request.user, 'view').filter(device=parent)
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'active_tab': f"{self.child_model._meta.verbose_name_plural.replace(' ', '-')}",
|
||||
}
|
||||
|
||||
|
||||
class DeviceTypeComponentsView(DeviceComponentsView):
|
||||
queryset = DeviceType.objects.all()
|
||||
@ -60,10 +56,9 @@ class DeviceTypeComponentsView(DeviceComponentsView):
|
||||
return self.child_model.objects.restrict(request.user, 'view').filter(device_type=parent)
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
context = super().get_extra_context(request, instance)
|
||||
context['return_url'] = reverse(self.viewname, kwargs={'pk': instance.pk})
|
||||
|
||||
return context
|
||||
return {
|
||||
'return_url': reverse(self.viewname, kwargs={'pk': instance.pk}),
|
||||
}
|
||||
|
||||
|
||||
class ModuleTypeComponentsView(DeviceComponentsView):
|
||||
@ -75,10 +70,9 @@ class ModuleTypeComponentsView(DeviceComponentsView):
|
||||
return self.child_model.objects.restrict(request.user, 'view').filter(module_type=parent)
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
context = super().get_extra_context(request, instance)
|
||||
context['return_url'] = reverse(self.viewname, kwargs={'pk': instance.pk})
|
||||
|
||||
return context
|
||||
return {
|
||||
'return_url': reverse(self.viewname, kwargs={'pk': instance.pk}),
|
||||
}
|
||||
|
||||
|
||||
class BulkDisconnectView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
||||
@ -857,74 +851,134 @@ class DeviceTypeView(generic.ObjectView):
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(DeviceType, 'consoleports', path='console-ports')
|
||||
class DeviceTypeConsolePortsView(DeviceTypeComponentsView):
|
||||
child_model = ConsolePortTemplate
|
||||
table = tables.ConsolePortTemplateTable
|
||||
filterset = filtersets.ConsolePortTemplateFilterSet
|
||||
viewname = 'dcim:devicetype_consoleports'
|
||||
tab = ViewTab(
|
||||
label=_('Console Ports'),
|
||||
badge=lambda obj: obj.consoleporttemplates.count(),
|
||||
permission='dcim.view_consoleporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(DeviceType, 'consoleserverports', path='console-server-ports')
|
||||
class DeviceTypeConsoleServerPortsView(DeviceTypeComponentsView):
|
||||
child_model = ConsoleServerPortTemplate
|
||||
table = tables.ConsoleServerPortTemplateTable
|
||||
filterset = filtersets.ConsoleServerPortTemplateFilterSet
|
||||
viewname = 'dcim:devicetype_consoleserverports'
|
||||
tab = ViewTab(
|
||||
label=_('Console Server Ports'),
|
||||
badge=lambda obj: obj.consoleserverporttemplates.count(),
|
||||
permission='dcim.view_consoleserverporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(DeviceType, 'powerports', path='power-ports')
|
||||
class DeviceTypePowerPortsView(DeviceTypeComponentsView):
|
||||
child_model = PowerPortTemplate
|
||||
table = tables.PowerPortTemplateTable
|
||||
filterset = filtersets.PowerPortTemplateFilterSet
|
||||
viewname = 'dcim:devicetype_powerports'
|
||||
tab = ViewTab(
|
||||
label=_('Power Ports'),
|
||||
badge=lambda obj: obj.powerporttemplates.count(),
|
||||
permission='dcim.view_powerporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(DeviceType, 'poweroutlets', path='power-outlets')
|
||||
class DeviceTypePowerOutletsView(DeviceTypeComponentsView):
|
||||
child_model = PowerOutletTemplate
|
||||
table = tables.PowerOutletTemplateTable
|
||||
filterset = filtersets.PowerOutletTemplateFilterSet
|
||||
viewname = 'dcim:devicetype_poweroutlets'
|
||||
tab = ViewTab(
|
||||
label=_('Power Outlets'),
|
||||
badge=lambda obj: obj.poweroutlettemplates.count(),
|
||||
permission='dcim.view_poweroutlettemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(DeviceType, 'interfaces')
|
||||
class DeviceTypeInterfacesView(DeviceTypeComponentsView):
|
||||
child_model = InterfaceTemplate
|
||||
table = tables.InterfaceTemplateTable
|
||||
filterset = filtersets.InterfaceTemplateFilterSet
|
||||
viewname = 'dcim:devicetype_interfaces'
|
||||
tab = ViewTab(
|
||||
label=_('Interfaces'),
|
||||
badge=lambda obj: obj.interfacetemplates.count(),
|
||||
permission='dcim.view_interfacetemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(DeviceType, 'frontports', path='front-ports')
|
||||
class DeviceTypeFrontPortsView(DeviceTypeComponentsView):
|
||||
child_model = FrontPortTemplate
|
||||
table = tables.FrontPortTemplateTable
|
||||
filterset = filtersets.FrontPortTemplateFilterSet
|
||||
viewname = 'dcim:devicetype_frontports'
|
||||
tab = ViewTab(
|
||||
label=_('Front Ports'),
|
||||
badge=lambda obj: obj.frontporttemplates.count(),
|
||||
permission='dcim.view_frontporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(DeviceType, 'rearports', path='rear-ports')
|
||||
class DeviceTypeRearPortsView(DeviceTypeComponentsView):
|
||||
child_model = RearPortTemplate
|
||||
table = tables.RearPortTemplateTable
|
||||
filterset = filtersets.RearPortTemplateFilterSet
|
||||
viewname = 'dcim:devicetype_rearports'
|
||||
tab = ViewTab(
|
||||
label=_('Rear Ports'),
|
||||
badge=lambda obj: obj.rearporttemplates.count(),
|
||||
permission='dcim.view_rearporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(DeviceType, 'modulebays', path='module-bays')
|
||||
class DeviceTypeModuleBaysView(DeviceTypeComponentsView):
|
||||
child_model = ModuleBayTemplate
|
||||
table = tables.ModuleBayTemplateTable
|
||||
filterset = filtersets.ModuleBayTemplateFilterSet
|
||||
viewname = 'dcim:devicetype_modulebays'
|
||||
tab = ViewTab(
|
||||
label=_('Module Bays'),
|
||||
badge=lambda obj: obj.modulebaytemplates.count(),
|
||||
permission='dcim.view_modulebaytemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(DeviceType, 'devicebays', path='device-bays')
|
||||
class DeviceTypeDeviceBaysView(DeviceTypeComponentsView):
|
||||
child_model = DeviceBayTemplate
|
||||
table = tables.DeviceBayTemplateTable
|
||||
filterset = filtersets.DeviceBayTemplateFilterSet
|
||||
viewname = 'dcim:devicetype_devicebays'
|
||||
tab = ViewTab(
|
||||
label=_('Device Bays'),
|
||||
badge=lambda obj: obj.devicebaytemplates.count(),
|
||||
permission='dcim.view_devicebaytemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(DeviceType, 'inventoryitems', path='inventory-items')
|
||||
class DeviceTypeInventoryItemsView(DeviceTypeComponentsView):
|
||||
child_model = InventoryItemTemplate
|
||||
table = tables.InventoryItemTemplateTable
|
||||
filterset = filtersets.InventoryItemTemplateFilterSet
|
||||
viewname = 'dcim:devicetype_inventoryitems'
|
||||
tab = ViewTab(
|
||||
label=_('Inventory Items'),
|
||||
badge=lambda obj: obj.inventoryitemtemplates.count(),
|
||||
permission='dcim.view_invenotryitemtemplate'
|
||||
)
|
||||
|
||||
|
||||
class DeviceTypeEditView(generic.ObjectEditView):
|
||||
@ -1011,53 +1065,95 @@ class ModuleTypeView(generic.ObjectView):
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(ModuleType, 'consoleports', path='console-ports')
|
||||
class ModuleTypeConsolePortsView(ModuleTypeComponentsView):
|
||||
child_model = ConsolePortTemplate
|
||||
table = tables.ConsolePortTemplateTable
|
||||
filterset = filtersets.ConsolePortTemplateFilterSet
|
||||
viewname = 'dcim:moduletype_consoleports'
|
||||
tab = ViewTab(
|
||||
label=_('Console Ports'),
|
||||
badge=lambda obj: obj.consoleporttemplates.count(),
|
||||
permission='dcim.view_consoleporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(ModuleType, 'consoleserverports', path='console-server-ports')
|
||||
class ModuleTypeConsoleServerPortsView(ModuleTypeComponentsView):
|
||||
child_model = ConsoleServerPortTemplate
|
||||
table = tables.ConsoleServerPortTemplateTable
|
||||
filterset = filtersets.ConsoleServerPortTemplateFilterSet
|
||||
viewname = 'dcim:moduletype_consoleserverports'
|
||||
tab = ViewTab(
|
||||
label=_('Console Server Ports'),
|
||||
badge=lambda obj: obj.consoleserverporttemplates.count(),
|
||||
permission='dcim.view_consoleserverporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(ModuleType, 'powerports', path='power-ports')
|
||||
class ModuleTypePowerPortsView(ModuleTypeComponentsView):
|
||||
child_model = PowerPortTemplate
|
||||
table = tables.PowerPortTemplateTable
|
||||
filterset = filtersets.PowerPortTemplateFilterSet
|
||||
viewname = 'dcim:moduletype_powerports'
|
||||
tab = ViewTab(
|
||||
label=_('Power Ports'),
|
||||
badge=lambda obj: obj.powerporttemplates.count(),
|
||||
permission='dcim.view_powerporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(ModuleType, 'poweroutlets', path='power-outlets')
|
||||
class ModuleTypePowerOutletsView(ModuleTypeComponentsView):
|
||||
child_model = PowerOutletTemplate
|
||||
table = tables.PowerOutletTemplateTable
|
||||
filterset = filtersets.PowerOutletTemplateFilterSet
|
||||
viewname = 'dcim:moduletype_poweroutlets'
|
||||
tab = ViewTab(
|
||||
label=_('Power Outlets'),
|
||||
badge=lambda obj: obj.poweroutlettemplates.count(),
|
||||
permission='dcim.view_poweroutlettemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(ModuleType, 'interfaces')
|
||||
class ModuleTypeInterfacesView(ModuleTypeComponentsView):
|
||||
child_model = InterfaceTemplate
|
||||
table = tables.InterfaceTemplateTable
|
||||
filterset = filtersets.InterfaceTemplateFilterSet
|
||||
viewname = 'dcim:moduletype_interfaces'
|
||||
tab = ViewTab(
|
||||
label=_('Interfaces'),
|
||||
badge=lambda obj: obj.interfacetemplates.count(),
|
||||
permission='dcim.view_interfacetemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(ModuleType, 'frontports', path='front-ports')
|
||||
class ModuleTypeFrontPortsView(ModuleTypeComponentsView):
|
||||
child_model = FrontPortTemplate
|
||||
table = tables.FrontPortTemplateTable
|
||||
filterset = filtersets.FrontPortTemplateFilterSet
|
||||
viewname = 'dcim:moduletype_frontports'
|
||||
tab = ViewTab(
|
||||
label=_('Front Ports'),
|
||||
badge=lambda obj: obj.frontporttemplates.count(),
|
||||
permission='dcim.view_frontporttemplate'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(ModuleType, 'rearports', path='rear-ports')
|
||||
class ModuleTypeRearPortsView(ModuleTypeComponentsView):
|
||||
child_model = RearPortTemplate
|
||||
table = tables.RearPortTemplateTable
|
||||
filterset = filtersets.RearPortTemplateFilterSet
|
||||
viewname = 'dcim:moduletype_rearports'
|
||||
tab = ViewTab(
|
||||
label=_('Rear Ports'),
|
||||
badge=lambda obj: obj.rearporttemplates.count(),
|
||||
permission='dcim.view_rearporttemplate'
|
||||
)
|
||||
|
||||
|
||||
class ModuleTypeEditView(generic.ObjectEditView):
|
||||
@ -1620,39 +1716,69 @@ class DeviceView(generic.ObjectView):
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(Device, 'consoleports', path='console-ports')
|
||||
class DeviceConsolePortsView(DeviceComponentsView):
|
||||
child_model = ConsolePort
|
||||
table = tables.DeviceConsolePortTable
|
||||
filterset = filtersets.ConsolePortFilterSet
|
||||
template_name = 'dcim/device/consoleports.html'
|
||||
tab = ViewTab(
|
||||
label=_('Console Ports'),
|
||||
badge=lambda obj: obj.consoleports.count(),
|
||||
permission='dcim.view_consoleport'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(Device, 'consoleserverports', path='console-server-ports')
|
||||
class DeviceConsoleServerPortsView(DeviceComponentsView):
|
||||
child_model = ConsoleServerPort
|
||||
table = tables.DeviceConsoleServerPortTable
|
||||
filterset = filtersets.ConsoleServerPortFilterSet
|
||||
template_name = 'dcim/device/consoleserverports.html'
|
||||
tab = ViewTab(
|
||||
label=_('Console Server Ports'),
|
||||
badge=lambda obj: obj.consoleserverports.count(),
|
||||
permission='dcim.view_consoleserverport'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(Device, 'powerports', path='power-ports')
|
||||
class DevicePowerPortsView(DeviceComponentsView):
|
||||
child_model = PowerPort
|
||||
table = tables.DevicePowerPortTable
|
||||
filterset = filtersets.PowerPortFilterSet
|
||||
template_name = 'dcim/device/powerports.html'
|
||||
tab = ViewTab(
|
||||
label=_('Power Ports'),
|
||||
badge=lambda obj: obj.powerports.count(),
|
||||
permission='dcim.view_powerport'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(Device, 'poweroutlets', path='power-outlets')
|
||||
class DevicePowerOutletsView(DeviceComponentsView):
|
||||
child_model = PowerOutlet
|
||||
table = tables.DevicePowerOutletTable
|
||||
filterset = filtersets.PowerOutletFilterSet
|
||||
template_name = 'dcim/device/poweroutlets.html'
|
||||
tab = ViewTab(
|
||||
label=_('Power Outlets'),
|
||||
badge=lambda obj: obj.poweroutlets.count(),
|
||||
permission='dcim.view_poweroutlet'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(Device, 'interfaces')
|
||||
class DeviceInterfacesView(DeviceComponentsView):
|
||||
child_model = Interface
|
||||
table = tables.DeviceInterfaceTable
|
||||
filterset = filtersets.InterfaceFilterSet
|
||||
template_name = 'dcim/device/interfaces.html'
|
||||
tab = ViewTab(
|
||||
label=_('Interfaces'),
|
||||
badge=lambda obj: obj.interfaces.count(),
|
||||
permission='dcim.view_interface'
|
||||
)
|
||||
|
||||
def get_children(self, request, parent):
|
||||
return parent.vc_interfaces().restrict(request.user, 'view').prefetch_related(
|
||||
@ -1661,84 +1787,79 @@ class DeviceInterfacesView(DeviceComponentsView):
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(Device, 'frontports', path='front-ports')
|
||||
class DeviceFrontPortsView(DeviceComponentsView):
|
||||
child_model = FrontPort
|
||||
table = tables.DeviceFrontPortTable
|
||||
filterset = filtersets.FrontPortFilterSet
|
||||
template_name = 'dcim/device/frontports.html'
|
||||
tab = ViewTab(
|
||||
label=_('Front Ports'),
|
||||
badge=lambda obj: obj.frontports.count(),
|
||||
permission='dcim.view_frontport'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(Device, 'rearports', path='rear-ports')
|
||||
class DeviceRearPortsView(DeviceComponentsView):
|
||||
child_model = RearPort
|
||||
table = tables.DeviceRearPortTable
|
||||
filterset = filtersets.RearPortFilterSet
|
||||
template_name = 'dcim/device/rearports.html'
|
||||
tab = ViewTab(
|
||||
label=_('Rear Ports'),
|
||||
badge=lambda obj: obj.rearports.count(),
|
||||
permission='dcim.view_rearport'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(Device, 'modulebays', path='module-bays')
|
||||
class DeviceModuleBaysView(DeviceComponentsView):
|
||||
child_model = ModuleBay
|
||||
table = tables.DeviceModuleBayTable
|
||||
filterset = filtersets.ModuleBayFilterSet
|
||||
template_name = 'dcim/device/modulebays.html'
|
||||
tab = ViewTab(
|
||||
label=_('Module Bays'),
|
||||
badge=lambda obj: obj.modulebays.count(),
|
||||
permission='dcim.view_modulebay'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(Device, 'devicebays', path='device-bays')
|
||||
class DeviceDeviceBaysView(DeviceComponentsView):
|
||||
child_model = DeviceBay
|
||||
table = tables.DeviceDeviceBayTable
|
||||
filterset = filtersets.DeviceBayFilterSet
|
||||
template_name = 'dcim/device/devicebays.html'
|
||||
tab = ViewTab(
|
||||
label=_('Device Bays'),
|
||||
badge=lambda obj: obj.devicebays.count(),
|
||||
permission='dcim.view_devicebay'
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(Device, 'inventory')
|
||||
class DeviceInventoryView(DeviceComponentsView):
|
||||
child_model = InventoryItem
|
||||
table = tables.DeviceInventoryItemTable
|
||||
filterset = filtersets.InventoryItemFilterSet
|
||||
template_name = 'dcim/device/inventory.html'
|
||||
|
||||
|
||||
class DeviceStatusView(generic.ObjectView):
|
||||
additional_permissions = ['dcim.napalm_read_device']
|
||||
queryset = Device.objects.all()
|
||||
template_name = 'dcim/device/status.html'
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'active_tab': 'status',
|
||||
}
|
||||
|
||||
|
||||
class DeviceLLDPNeighborsView(generic.ObjectView):
|
||||
additional_permissions = ['dcim.napalm_read_device']
|
||||
queryset = Device.objects.all()
|
||||
template_name = 'dcim/device/lldp_neighbors.html'
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
interfaces = instance.vc_interfaces().restrict(request.user, 'view').prefetch_related(
|
||||
'_path'
|
||||
).exclude(
|
||||
type__in=NONCONNECTABLE_IFACE_TYPES
|
||||
tab = ViewTab(
|
||||
label=_('Inventory Items'),
|
||||
badge=lambda obj: obj.inventoryitems.count(),
|
||||
permission='dcim.view_inventoryitem'
|
||||
)
|
||||
|
||||
return {
|
||||
'interfaces': interfaces,
|
||||
'active_tab': 'lldp-neighbors',
|
||||
}
|
||||
|
||||
|
||||
class DeviceConfigView(generic.ObjectView):
|
||||
additional_permissions = ['dcim.napalm_read_device']
|
||||
queryset = Device.objects.all()
|
||||
template_name = 'dcim/device/config.html'
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'active_tab': 'config',
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(Device, 'configcontext', path='config-context')
|
||||
class DeviceConfigContextView(ObjectConfigContextView):
|
||||
queryset = Device.objects.annotate_config_context_data()
|
||||
base_template = 'dcim/device/base.html'
|
||||
tab = ViewTab(
|
||||
label=_('Config Context'),
|
||||
permission='extras.view_configcontext'
|
||||
)
|
||||
|
||||
|
||||
class DeviceEditView(generic.ObjectEditView):
|
||||
@ -1796,7 +1917,68 @@ class DeviceBulkRenameView(generic.BulkRenameView):
|
||||
|
||||
|
||||
#
|
||||
# Devices
|
||||
# Device NAPALM views
|
||||
#
|
||||
|
||||
class NAPALMViewTab(ViewTab):
|
||||
|
||||
def render(self, instance):
|
||||
# Display NAPALM tabs only for devices which meet certain requirements
|
||||
if not (
|
||||
instance.status == 'active' and
|
||||
instance.primary_ip and
|
||||
instance.platform.napalm_driver
|
||||
):
|
||||
return None
|
||||
return super().render(instance)
|
||||
|
||||
|
||||
@register_model_view(Device, 'status')
|
||||
class DeviceStatusView(generic.ObjectView):
|
||||
additional_permissions = ['dcim.napalm_read_device']
|
||||
queryset = Device.objects.all()
|
||||
template_name = 'dcim/device/status.html'
|
||||
tab = NAPALMViewTab(
|
||||
label=_('Status'),
|
||||
permission='dcim.napalm_read_device',
|
||||
)
|
||||
|
||||
|
||||
@register_model_view(Device, 'lldp_neighbors', path='lldp-neighbors')
|
||||
class DeviceLLDPNeighborsView(generic.ObjectView):
|
||||
additional_permissions = ['dcim.napalm_read_device']
|
||||
queryset = Device.objects.all()
|
||||
template_name = 'dcim/device/lldp_neighbors.html'
|
||||
tab = NAPALMViewTab(
|
||||
label=_('LLDP Neighbors'),
|
||||
permission='dcim.napalm_read_device',
|
||||
)
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
interfaces = instance.vc_interfaces().restrict(request.user, 'view').prefetch_related(
|
||||
'_path'
|
||||
).exclude(
|
||||
type__in=NONCONNECTABLE_IFACE_TYPES
|
||||
)
|
||||
|
||||
return {
|
||||
'interfaces': interfaces,
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(Device, 'config')
|
||||
class DeviceConfigView(generic.ObjectView):
|
||||
additional_permissions = ['dcim.napalm_read_device']
|
||||
queryset = Device.objects.all()
|
||||
template_name = 'dcim/device/config.html'
|
||||
tab = NAPALMViewTab(
|
||||
label=_('Config'),
|
||||
permission='dcim.napalm_read_device',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Modules
|
||||
#
|
||||
|
||||
class ModuleListView(generic.ObjectListView):
|
||||
|
@ -29,3 +29,4 @@ registry['model_features'] = {
|
||||
feature: collections.defaultdict(set) for feature in EXTRAS_FEATURES
|
||||
}
|
||||
registry['denormalized_fields'] = collections.defaultdict(list)
|
||||
registry['views'] = collections.defaultdict(dict)
|
||||
|
@ -1,6 +1,8 @@
|
||||
from django.http import HttpResponse
|
||||
from django.views.generic import View
|
||||
|
||||
from dcim.models import Site
|
||||
from utilities.views import register_model_view
|
||||
from .models import DummyModel
|
||||
|
||||
|
||||
@ -9,3 +11,10 @@ class DummyModelsView(View):
|
||||
def get(self, request):
|
||||
instance_count = DummyModel.objects.count()
|
||||
return HttpResponse(f"Instances: {instance_count}")
|
||||
|
||||
|
||||
@register_model_view(Site, 'extra', path='other-stuff')
|
||||
class ExtraCoreModelView(View):
|
||||
|
||||
def get(self, request, pk):
|
||||
return HttpResponse("Success!")
|
||||
|
@ -59,6 +59,17 @@ class PluginTest(TestCase):
|
||||
response = client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_registered_views(self):
|
||||
|
||||
# Test URL resolution
|
||||
url = reverse('dcim:site_extra', kwargs={'pk': 1})
|
||||
self.assertEqual(url, '/dcim/sites/1/other-stuff/')
|
||||
|
||||
# Test GET request
|
||||
client = Client()
|
||||
response = client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_menu(self):
|
||||
"""
|
||||
Check menu registration.
|
||||
|
@ -1,7 +1,7 @@
|
||||
from django.urls import path, re_path
|
||||
from django.urls import include, path, re_path
|
||||
|
||||
from extras import models, views
|
||||
from netbox.views.generic import ObjectChangeLogView
|
||||
from extras import views
|
||||
from utilities.urls import get_model_urls
|
||||
|
||||
|
||||
app_name = 'extras'
|
||||
@ -16,8 +16,7 @@ urlpatterns = [
|
||||
path('custom-fields/<int:pk>/', views.CustomFieldView.as_view(), name='customfield'),
|
||||
path('custom-fields/<int:pk>/edit/', views.CustomFieldEditView.as_view(), name='customfield_edit'),
|
||||
path('custom-fields/<int:pk>/delete/', views.CustomFieldDeleteView.as_view(), name='customfield_delete'),
|
||||
path('custom-fields/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='customfield_changelog',
|
||||
kwargs={'model': models.CustomField}),
|
||||
path('custom-fields/<int:pk>/', include(get_model_urls('extras', 'customfield'))),
|
||||
|
||||
# Custom links
|
||||
path('custom-links/', views.CustomLinkListView.as_view(), name='customlink_list'),
|
||||
@ -28,8 +27,7 @@ urlpatterns = [
|
||||
path('custom-links/<int:pk>/', views.CustomLinkView.as_view(), name='customlink'),
|
||||
path('custom-links/<int:pk>/edit/', views.CustomLinkEditView.as_view(), name='customlink_edit'),
|
||||
path('custom-links/<int:pk>/delete/', views.CustomLinkDeleteView.as_view(), name='customlink_delete'),
|
||||
path('custom-links/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='customlink_changelog',
|
||||
kwargs={'model': models.CustomLink}),
|
||||
path('custom-links/<int:pk>/', include(get_model_urls('extras', 'customlink'))),
|
||||
|
||||
# Export templates
|
||||
path('export-templates/', views.ExportTemplateListView.as_view(), name='exporttemplate_list'),
|
||||
@ -40,8 +38,7 @@ urlpatterns = [
|
||||
path('export-templates/<int:pk>/', views.ExportTemplateView.as_view(), name='exporttemplate'),
|
||||
path('export-templates/<int:pk>/edit/', views.ExportTemplateEditView.as_view(), name='exporttemplate_edit'),
|
||||
path('export-templates/<int:pk>/delete/', views.ExportTemplateDeleteView.as_view(), name='exporttemplate_delete'),
|
||||
path('export-templates/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='exporttemplate_changelog',
|
||||
kwargs={'model': models.ExportTemplate}),
|
||||
path('export-templates/<int:pk>/', include(get_model_urls('extras', 'exporttemplate'))),
|
||||
|
||||
# Webhooks
|
||||
path('webhooks/', views.WebhookListView.as_view(), name='webhook_list'),
|
||||
@ -52,8 +49,7 @@ urlpatterns = [
|
||||
path('webhooks/<int:pk>/', views.WebhookView.as_view(), name='webhook'),
|
||||
path('webhooks/<int:pk>/edit/', views.WebhookEditView.as_view(), name='webhook_edit'),
|
||||
path('webhooks/<int:pk>/delete/', views.WebhookDeleteView.as_view(), name='webhook_delete'),
|
||||
path('webhooks/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='webhook_changelog',
|
||||
kwargs={'model': models.Webhook}),
|
||||
path('webhooks/<int:pk>/', include(get_model_urls('extras', 'webhook'))),
|
||||
|
||||
# Tags
|
||||
path('tags/', views.TagListView.as_view(), name='tag_list'),
|
||||
@ -64,8 +60,7 @@ urlpatterns = [
|
||||
path('tags/<int:pk>/', views.TagView.as_view(), name='tag'),
|
||||
path('tags/<int:pk>/edit/', views.TagEditView.as_view(), name='tag_edit'),
|
||||
path('tags/<int:pk>/delete/', views.TagDeleteView.as_view(), name='tag_delete'),
|
||||
path('tags/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='tag_changelog',
|
||||
kwargs={'model': models.Tag}),
|
||||
path('tags/<int:pk>/', include(get_model_urls('extras', 'tag'))),
|
||||
|
||||
# Config contexts
|
||||
path('config-contexts/', views.ConfigContextListView.as_view(), name='configcontext_list'),
|
||||
@ -75,8 +70,7 @@ urlpatterns = [
|
||||
path('config-contexts/<int:pk>/', views.ConfigContextView.as_view(), name='configcontext'),
|
||||
path('config-contexts/<int:pk>/edit/', views.ConfigContextEditView.as_view(), name='configcontext_edit'),
|
||||
path('config-contexts/<int:pk>/delete/', views.ConfigContextDeleteView.as_view(), name='configcontext_delete'),
|
||||
path('config-contexts/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='configcontext_changelog',
|
||||
kwargs={'model': models.ConfigContext}),
|
||||
path('config-contexts/<int:pk>/', include(get_model_urls('extras', 'configcontext'))),
|
||||
|
||||
# Image attachments
|
||||
path('image-attachments/add/', views.ImageAttachmentEditView.as_view(), name='imageattachment_add'),
|
||||
@ -91,8 +85,7 @@ urlpatterns = [
|
||||
path('journal-entries/<int:pk>/', views.JournalEntryView.as_view(), name='journalentry'),
|
||||
path('journal-entries/<int:pk>/edit/', views.JournalEntryEditView.as_view(), name='journalentry_edit'),
|
||||
path('journal-entries/<int:pk>/delete/', views.JournalEntryDeleteView.as_view(), name='journalentry_delete'),
|
||||
path('journal-entries/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='journalentry_changelog',
|
||||
kwargs={'model': models.JournalEntry}),
|
||||
path('journal-entries/<int:pk>/', include(get_model_urls('extras', 'journalentry'))),
|
||||
|
||||
# Change logging
|
||||
path('changelog/', views.ObjectChangeListView.as_view(), name='objectchange_list'),
|
||||
|
@ -352,7 +352,6 @@ class ObjectConfigContextView(generic.ObjectView):
|
||||
'source_contexts': source_contexts,
|
||||
'format': format,
|
||||
'base_template': self.base_template,
|
||||
'active_tab': 'config-context',
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
|
||||
from utilities.urls import get_model_urls
|
||||
from . import views
|
||||
from .models import *
|
||||
|
||||
app_name = 'ipam'
|
||||
urlpatterns = [
|
||||
@ -16,8 +15,7 @@ urlpatterns = [
|
||||
path('asns/<int:pk>/', views.ASNView.as_view(), name='asn'),
|
||||
path('asns/<int:pk>/edit/', views.ASNEditView.as_view(), name='asn_edit'),
|
||||
path('asns/<int:pk>/delete/', views.ASNDeleteView.as_view(), name='asn_delete'),
|
||||
path('asns/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='asn_changelog', kwargs={'model': ASN}),
|
||||
path('asns/<int:pk>/journal/', ObjectJournalView.as_view(), name='asn_journal', kwargs={'model': ASN}),
|
||||
path('asns/<int:pk>/', include(get_model_urls('ipam', 'asn'))),
|
||||
|
||||
# VRFs
|
||||
path('vrfs/', views.VRFListView.as_view(), name='vrf_list'),
|
||||
@ -28,8 +26,7 @@ urlpatterns = [
|
||||
path('vrfs/<int:pk>/', views.VRFView.as_view(), name='vrf'),
|
||||
path('vrfs/<int:pk>/edit/', views.VRFEditView.as_view(), name='vrf_edit'),
|
||||
path('vrfs/<int:pk>/delete/', views.VRFDeleteView.as_view(), name='vrf_delete'),
|
||||
path('vrfs/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='vrf_changelog', kwargs={'model': VRF}),
|
||||
path('vrfs/<int:pk>/journal/', ObjectJournalView.as_view(), name='vrf_journal', kwargs={'model': VRF}),
|
||||
path('vrfs/<int:pk>/', include(get_model_urls('ipam', 'vrf'))),
|
||||
|
||||
# Route targets
|
||||
path('route-targets/', views.RouteTargetListView.as_view(), name='routetarget_list'),
|
||||
@ -40,8 +37,7 @@ urlpatterns = [
|
||||
path('route-targets/<int:pk>/', views.RouteTargetView.as_view(), name='routetarget'),
|
||||
path('route-targets/<int:pk>/edit/', views.RouteTargetEditView.as_view(), name='routetarget_edit'),
|
||||
path('route-targets/<int:pk>/delete/', views.RouteTargetDeleteView.as_view(), name='routetarget_delete'),
|
||||
path('route-targets/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='routetarget_changelog', kwargs={'model': RouteTarget}),
|
||||
path('route-targets/<int:pk>/journal/', ObjectJournalView.as_view(), name='routetarget_journal', kwargs={'model': RouteTarget}),
|
||||
path('route-targets/<int:pk>/', include(get_model_urls('ipam', 'routetarget'))),
|
||||
|
||||
# RIRs
|
||||
path('rirs/', views.RIRListView.as_view(), name='rir_list'),
|
||||
@ -52,7 +48,7 @@ urlpatterns = [
|
||||
path('rirs/<int:pk>/', views.RIRView.as_view(), name='rir'),
|
||||
path('rirs/<int:pk>/edit/', views.RIREditView.as_view(), name='rir_edit'),
|
||||
path('rirs/<int:pk>/delete/', views.RIRDeleteView.as_view(), name='rir_delete'),
|
||||
path('rirs/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='rir_changelog', kwargs={'model': RIR}),
|
||||
path('rirs/<int:pk>/', include(get_model_urls('ipam', 'rir'))),
|
||||
|
||||
# Aggregates
|
||||
path('aggregates/', views.AggregateListView.as_view(), name='aggregate_list'),
|
||||
@ -61,11 +57,9 @@ urlpatterns = [
|
||||
path('aggregates/edit/', views.AggregateBulkEditView.as_view(), name='aggregate_bulk_edit'),
|
||||
path('aggregates/delete/', views.AggregateBulkDeleteView.as_view(), name='aggregate_bulk_delete'),
|
||||
path('aggregates/<int:pk>/', views.AggregateView.as_view(), name='aggregate'),
|
||||
path('aggregates/<int:pk>/prefixes/', views.AggregatePrefixesView.as_view(), name='aggregate_prefixes'),
|
||||
path('aggregates/<int:pk>/edit/', views.AggregateEditView.as_view(), name='aggregate_edit'),
|
||||
path('aggregates/<int:pk>/delete/', views.AggregateDeleteView.as_view(), name='aggregate_delete'),
|
||||
path('aggregates/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='aggregate_changelog', kwargs={'model': Aggregate}),
|
||||
path('aggregates/<int:pk>/journal/', ObjectJournalView.as_view(), name='aggregate_journal', kwargs={'model': Aggregate}),
|
||||
path('aggregates/<int:pk>/', include(get_model_urls('ipam', 'aggregate'))),
|
||||
|
||||
# Roles
|
||||
path('roles/', views.RoleListView.as_view(), name='role_list'),
|
||||
@ -76,7 +70,7 @@ urlpatterns = [
|
||||
path('roles/<int:pk>/', views.RoleView.as_view(), name='role'),
|
||||
path('roles/<int:pk>/edit/', views.RoleEditView.as_view(), name='role_edit'),
|
||||
path('roles/<int:pk>/delete/', views.RoleDeleteView.as_view(), name='role_delete'),
|
||||
path('roles/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='role_changelog', kwargs={'model': Role}),
|
||||
path('roles/<int:pk>/', include(get_model_urls('ipam', 'role'))),
|
||||
|
||||
# Prefixes
|
||||
path('prefixes/', views.PrefixListView.as_view(), name='prefix_list'),
|
||||
@ -87,11 +81,7 @@ urlpatterns = [
|
||||
path('prefixes/<int:pk>/', views.PrefixView.as_view(), name='prefix'),
|
||||
path('prefixes/<int:pk>/edit/', views.PrefixEditView.as_view(), name='prefix_edit'),
|
||||
path('prefixes/<int:pk>/delete/', views.PrefixDeleteView.as_view(), name='prefix_delete'),
|
||||
path('prefixes/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='prefix_changelog', kwargs={'model': Prefix}),
|
||||
path('prefixes/<int:pk>/journal/', ObjectJournalView.as_view(), name='prefix_journal', kwargs={'model': Prefix}),
|
||||
path('prefixes/<int:pk>/prefixes/', views.PrefixPrefixesView.as_view(), name='prefix_prefixes'),
|
||||
path('prefixes/<int:pk>/ip-ranges/', views.PrefixIPRangesView.as_view(), name='prefix_ipranges'),
|
||||
path('prefixes/<int:pk>/ip-addresses/', views.PrefixIPAddressesView.as_view(), name='prefix_ipaddresses'),
|
||||
path('prefixes/<int:pk>/', include(get_model_urls('ipam', 'prefix'))),
|
||||
|
||||
# IP ranges
|
||||
path('ip-ranges/', views.IPRangeListView.as_view(), name='iprange_list'),
|
||||
@ -102,9 +92,7 @@ urlpatterns = [
|
||||
path('ip-ranges/<int:pk>/', views.IPRangeView.as_view(), name='iprange'),
|
||||
path('ip-ranges/<int:pk>/edit/', views.IPRangeEditView.as_view(), name='iprange_edit'),
|
||||
path('ip-ranges/<int:pk>/delete/', views.IPRangeDeleteView.as_view(), name='iprange_delete'),
|
||||
path('ip-ranges/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='iprange_changelog', kwargs={'model': IPRange}),
|
||||
path('ip-ranges/<int:pk>/journal/', ObjectJournalView.as_view(), name='iprange_journal', kwargs={'model': IPRange}),
|
||||
path('ip-ranges/<int:pk>/ip-addresses/', views.IPRangeIPAddressesView.as_view(), name='iprange_ipaddresses'),
|
||||
path('ip-ranges/<int:pk>/', include(get_model_urls('ipam', 'iprange'))),
|
||||
|
||||
# IP addresses
|
||||
path('ip-addresses/', views.IPAddressListView.as_view(), name='ipaddress_list'),
|
||||
@ -113,12 +101,11 @@ urlpatterns = [
|
||||
path('ip-addresses/import/', views.IPAddressBulkImportView.as_view(), name='ipaddress_import'),
|
||||
path('ip-addresses/edit/', views.IPAddressBulkEditView.as_view(), name='ipaddress_bulk_edit'),
|
||||
path('ip-addresses/delete/', views.IPAddressBulkDeleteView.as_view(), name='ipaddress_bulk_delete'),
|
||||
path('ip-addresses/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='ipaddress_changelog', kwargs={'model': IPAddress}),
|
||||
path('ip-addresses/<int:pk>/journal/', ObjectJournalView.as_view(), name='ipaddress_journal', kwargs={'model': IPAddress}),
|
||||
path('ip-addresses/assign/', views.IPAddressAssignView.as_view(), name='ipaddress_assign'),
|
||||
path('ip-addresses/<int:pk>/', views.IPAddressView.as_view(), name='ipaddress'),
|
||||
path('ip-addresses/<int:pk>/edit/', views.IPAddressEditView.as_view(), name='ipaddress_edit'),
|
||||
path('ip-addresses/<int:pk>/delete/', views.IPAddressDeleteView.as_view(), name='ipaddress_delete'),
|
||||
path('ip-addresses/<int:pk>/', include(get_model_urls('ipam', 'ipaddress'))),
|
||||
|
||||
# FHRP groups
|
||||
path('fhrp-groups/', views.FHRPGroupListView.as_view(), name='fhrpgroup_list'),
|
||||
@ -129,8 +116,7 @@ urlpatterns = [
|
||||
path('fhrp-groups/<int:pk>/', views.FHRPGroupView.as_view(), name='fhrpgroup'),
|
||||
path('fhrp-groups/<int:pk>/edit/', views.FHRPGroupEditView.as_view(), name='fhrpgroup_edit'),
|
||||
path('fhrp-groups/<int:pk>/delete/', views.FHRPGroupDeleteView.as_view(), name='fhrpgroup_delete'),
|
||||
path('fhrp-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='fhrpgroup_changelog', kwargs={'model': FHRPGroup}),
|
||||
path('fhrp-groups/<int:pk>/journal/', ObjectJournalView.as_view(), name='fhrpgroup_journal', kwargs={'model': FHRPGroup}),
|
||||
path('fhrp-groups/<int:pk>/', include(get_model_urls('ipam', 'fhrpgroup'))),
|
||||
|
||||
# FHRP group assignments
|
||||
path('fhrp-group-assignments/add/', views.FHRPGroupAssignmentEditView.as_view(), name='fhrpgroupassignment_add'),
|
||||
@ -146,7 +132,7 @@ urlpatterns = [
|
||||
path('vlan-groups/<int:pk>/', views.VLANGroupView.as_view(), name='vlangroup'),
|
||||
path('vlan-groups/<int:pk>/edit/', views.VLANGroupEditView.as_view(), name='vlangroup_edit'),
|
||||
path('vlan-groups/<int:pk>/delete/', views.VLANGroupDeleteView.as_view(), name='vlangroup_delete'),
|
||||
path('vlan-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='vlangroup_changelog', kwargs={'model': VLANGroup}),
|
||||
path('vlan-groups/<int:pk>/', include(get_model_urls('ipam', 'vlangroup'))),
|
||||
|
||||
# VLANs
|
||||
path('vlans/', views.VLANListView.as_view(), name='vlan_list'),
|
||||
@ -155,12 +141,9 @@ urlpatterns = [
|
||||
path('vlans/edit/', views.VLANBulkEditView.as_view(), name='vlan_bulk_edit'),
|
||||
path('vlans/delete/', views.VLANBulkDeleteView.as_view(), name='vlan_bulk_delete'),
|
||||
path('vlans/<int:pk>/', views.VLANView.as_view(), name='vlan'),
|
||||
path('vlans/<int:pk>/interfaces/', views.VLANInterfacesView.as_view(), name='vlan_interfaces'),
|
||||
path('vlans/<int:pk>/vm-interfaces/', views.VLANVMInterfacesView.as_view(), name='vlan_vminterfaces'),
|
||||
path('vlans/<int:pk>/edit/', views.VLANEditView.as_view(), name='vlan_edit'),
|
||||
path('vlans/<int:pk>/delete/', views.VLANDeleteView.as_view(), name='vlan_delete'),
|
||||
path('vlans/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='vlan_changelog', kwargs={'model': VLAN}),
|
||||
path('vlans/<int:pk>/journal/', ObjectJournalView.as_view(), name='vlan_journal', kwargs={'model': VLAN}),
|
||||
path('vlans/<int:pk>/', include(get_model_urls('ipam', 'vlan'))),
|
||||
|
||||
# Service templates
|
||||
path('service-templates/', views.ServiceTemplateListView.as_view(), name='servicetemplate_list'),
|
||||
@ -171,8 +154,7 @@ urlpatterns = [
|
||||
path('service-templates/<int:pk>/', views.ServiceTemplateView.as_view(), name='servicetemplate'),
|
||||
path('service-templates/<int:pk>/edit/', views.ServiceTemplateEditView.as_view(), name='servicetemplate_edit'),
|
||||
path('service-templates/<int:pk>/delete/', views.ServiceTemplateDeleteView.as_view(), name='servicetemplate_delete'),
|
||||
path('service-templates/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='servicetemplate_changelog', kwargs={'model': ServiceTemplate}),
|
||||
path('service-templates/<int:pk>/journal/', ObjectJournalView.as_view(), name='servicetemplate_journal', kwargs={'model': ServiceTemplate}),
|
||||
path('service-templates/<int:pk>/', include(get_model_urls('ipam', 'servicetemplate'))),
|
||||
|
||||
# Services
|
||||
path('services/', views.ServiceListView.as_view(), name='service_list'),
|
||||
@ -183,8 +165,7 @@ urlpatterns = [
|
||||
path('services/<int:pk>/', views.ServiceView.as_view(), name='service'),
|
||||
path('services/<int:pk>/edit/', views.ServiceEditView.as_view(), name='service_edit'),
|
||||
path('services/<int:pk>/delete/', views.ServiceDeleteView.as_view(), name='service_delete'),
|
||||
path('services/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='service_changelog', kwargs={'model': Service}),
|
||||
path('services/<int:pk>/journal/', ObjectJournalView.as_view(), name='service_journal', kwargs={'model': Service}),
|
||||
path('services/<int:pk>/', include(get_model_urls('ipam', 'service'))),
|
||||
|
||||
# L2VPN
|
||||
path('l2vpns/', views.L2VPNListView.as_view(), name='l2vpn_list'),
|
||||
@ -195,9 +176,9 @@ urlpatterns = [
|
||||
path('l2vpns/<int:pk>/', views.L2VPNView.as_view(), name='l2vpn'),
|
||||
path('l2vpns/<int:pk>/edit/', views.L2VPNEditView.as_view(), name='l2vpn_edit'),
|
||||
path('l2vpns/<int:pk>/delete/', views.L2VPNDeleteView.as_view(), name='l2vpn_delete'),
|
||||
path('l2vpns/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='l2vpn_changelog', kwargs={'model': L2VPN}),
|
||||
path('l2vpns/<int:pk>/journal/', ObjectJournalView.as_view(), name='l2vpn_journal', kwargs={'model': L2VPN}),
|
||||
path('l2vpns/<int:pk>/', include(get_model_urls('ipam', 'l2vpn'))),
|
||||
|
||||
# L2VPN terminations
|
||||
path('l2vpn-terminations/', views.L2VPNTerminationListView.as_view(), name='l2vpntermination_list'),
|
||||
path('l2vpn-terminations/add/', views.L2VPNTerminationEditView.as_view(), name='l2vpntermination_add'),
|
||||
path('l2vpn-terminations/import/', views.L2VPNTerminationBulkImportView.as_view(), name='l2vpntermination_import'),
|
||||
@ -206,6 +187,5 @@ urlpatterns = [
|
||||
path('l2vpn-terminations/<int:pk>/', views.L2VPNTerminationView.as_view(), name='l2vpntermination'),
|
||||
path('l2vpn-terminations/<int:pk>/edit/', views.L2VPNTerminationEditView.as_view(), name='l2vpntermination_edit'),
|
||||
path('l2vpn-terminations/<int:pk>/delete/', views.L2VPNTerminationDeleteView.as_view(), name='l2vpntermination_delete'),
|
||||
path('l2vpn-terminations/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='l2vpntermination_changelog', kwargs={'model': L2VPNTermination}),
|
||||
path('l2vpn-terminations/<int:pk>/journal/', ObjectJournalView.as_view(), name='l2vpntermination_journal', kwargs={'model': L2VPNTermination}),
|
||||
path('l2vpn-terminations/<int:pk>/', include(get_model_urls('ipam', 'l2vpntermination'))),
|
||||
]
|
||||
|
@ -3,6 +3,7 @@ from django.db.models import Prefetch
|
||||
from django.db.models.expressions import RawSQL
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from circuits.models import Provider, Circuit
|
||||
from circuits.tables import ProviderTable
|
||||
@ -11,6 +12,7 @@ from dcim.models import Interface, Site, Device
|
||||
from dcim.tables import SiteTable
|
||||
from netbox.views import generic
|
||||
from utilities.utils import count_related
|
||||
from utilities.views import ViewTab, register_model_view
|
||||
from virtualization.filtersets import VMInterfaceFilterSet
|
||||
from virtualization.models import VMInterface, VirtualMachine
|
||||
from . import filtersets, forms, tables
|
||||
@ -289,12 +291,18 @@ class AggregateView(generic.ObjectView):
|
||||
queryset = Aggregate.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Aggregate, 'prefixes')
|
||||
class AggregatePrefixesView(generic.ObjectChildrenView):
|
||||
queryset = Aggregate.objects.all()
|
||||
child_model = Prefix
|
||||
table = tables.PrefixTable
|
||||
filterset = filtersets.PrefixFilterSet
|
||||
template_name = 'ipam/aggregate/prefixes.html'
|
||||
tab = ViewTab(
|
||||
label=_('Prefixes'),
|
||||
badge=lambda x: x.get_child_prefixes().count(),
|
||||
permission='ipam.view_prefix'
|
||||
)
|
||||
|
||||
def get_children(self, request, parent):
|
||||
return Prefix.objects.restrict(request.user, 'view').filter(
|
||||
@ -311,7 +319,6 @@ class AggregatePrefixesView(generic.ObjectChildrenView):
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'bulk_querystring': f'within={instance.prefix}',
|
||||
'active_tab': 'prefixes',
|
||||
'first_available_prefix': instance.get_first_available_prefix(),
|
||||
'show_available': bool(request.GET.get('show_available', 'true') == 'true'),
|
||||
'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'),
|
||||
@ -466,12 +473,18 @@ class PrefixView(generic.ObjectView):
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(Prefix, 'prefixes')
|
||||
class PrefixPrefixesView(generic.ObjectChildrenView):
|
||||
queryset = Prefix.objects.all()
|
||||
child_model = Prefix
|
||||
table = tables.PrefixTable
|
||||
filterset = filtersets.PrefixFilterSet
|
||||
template_name = 'ipam/prefix/prefixes.html'
|
||||
tab = ViewTab(
|
||||
label=_('Child Prefixes'),
|
||||
badge=lambda x: x.get_child_prefixes().count(),
|
||||
permission='ipam.view_prefix'
|
||||
)
|
||||
|
||||
def get_children(self, request, parent):
|
||||
return parent.get_child_prefixes().restrict(request.user, 'view').prefetch_related(
|
||||
@ -488,19 +501,24 @@ class PrefixPrefixesView(generic.ObjectChildrenView):
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&within={instance.prefix}",
|
||||
'active_tab': 'prefixes',
|
||||
'first_available_prefix': instance.get_first_available_prefix(),
|
||||
'show_available': bool(request.GET.get('show_available', 'true') == 'true'),
|
||||
'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'),
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(Prefix, 'ipranges', path='ip-ranges')
|
||||
class PrefixIPRangesView(generic.ObjectChildrenView):
|
||||
queryset = Prefix.objects.all()
|
||||
child_model = IPRange
|
||||
table = tables.IPRangeTable
|
||||
filterset = filtersets.IPRangeFilterSet
|
||||
template_name = 'ipam/prefix/ip_ranges.html'
|
||||
tab = ViewTab(
|
||||
label=_('Child Ranges'),
|
||||
badge=lambda x: x.get_child_ranges().count(),
|
||||
permission='ipam.view_iprange'
|
||||
)
|
||||
|
||||
def get_children(self, request, parent):
|
||||
return parent.get_child_ranges().restrict(request.user, 'view').prefetch_related(
|
||||
@ -510,17 +528,22 @@ class PrefixIPRangesView(generic.ObjectChildrenView):
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}",
|
||||
'active_tab': 'ip-ranges',
|
||||
'first_available_ip': instance.get_first_available_ip(),
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(Prefix, 'ipaddresses', path='ip-addresses')
|
||||
class PrefixIPAddressesView(generic.ObjectChildrenView):
|
||||
queryset = Prefix.objects.all()
|
||||
child_model = IPAddress
|
||||
table = tables.IPAddressTable
|
||||
filterset = filtersets.IPAddressFilterSet
|
||||
template_name = 'ipam/prefix/ip_addresses.html'
|
||||
tab = ViewTab(
|
||||
label=_('IP Addresses'),
|
||||
badge=lambda x: x.get_child_ips().count(),
|
||||
permission='ipam.view_ipaddress'
|
||||
)
|
||||
|
||||
def get_children(self, request, parent):
|
||||
return parent.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf', 'tenant', 'tenant__group')
|
||||
@ -533,7 +556,6 @@ class PrefixIPAddressesView(generic.ObjectChildrenView):
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}",
|
||||
'active_tab': 'ip-addresses',
|
||||
'first_available_ip': instance.get_first_available_ip(),
|
||||
}
|
||||
|
||||
@ -581,21 +603,22 @@ class IPRangeView(generic.ObjectView):
|
||||
queryset = IPRange.objects.all()
|
||||
|
||||
|
||||
@register_model_view(IPRange, 'ipaddresses', path='ip-addresses')
|
||||
class IPRangeIPAddressesView(generic.ObjectChildrenView):
|
||||
queryset = IPRange.objects.all()
|
||||
child_model = IPAddress
|
||||
table = tables.IPAddressTable
|
||||
filterset = filtersets.IPAddressFilterSet
|
||||
template_name = 'ipam/iprange/ip_addresses.html'
|
||||
tab = ViewTab(
|
||||
label=_('IP Addresses'),
|
||||
badge=lambda x: x.get_child_ips().count(),
|
||||
permission='ipam.view_ipaddress'
|
||||
)
|
||||
|
||||
def get_children(self, request, parent):
|
||||
return parent.get_child_ips().restrict(request.user, 'view')
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'active_tab': 'ip-addresses',
|
||||
}
|
||||
|
||||
|
||||
class IPRangeEditView(generic.ObjectEditView):
|
||||
queryset = IPRange.objects.all()
|
||||
@ -1000,37 +1023,39 @@ class VLANView(generic.ObjectView):
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(VLAN, 'interfaces')
|
||||
class VLANInterfacesView(generic.ObjectChildrenView):
|
||||
queryset = VLAN.objects.all()
|
||||
child_model = Interface
|
||||
table = tables.VLANDevicesTable
|
||||
filterset = InterfaceFilterSet
|
||||
template_name = 'ipam/vlan/interfaces.html'
|
||||
tab = ViewTab(
|
||||
label=_('Device Interfaces'),
|
||||
badge=lambda x: x.get_interfaces().count(),
|
||||
permission='dcim.view_interface'
|
||||
)
|
||||
|
||||
def get_children(self, request, parent):
|
||||
return parent.get_interfaces().restrict(request.user, 'view')
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'active_tab': 'interfaces',
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(VLAN, 'vminterfaces', path='vm-interfaces')
|
||||
class VLANVMInterfacesView(generic.ObjectChildrenView):
|
||||
queryset = VLAN.objects.all()
|
||||
child_model = VMInterface
|
||||
table = tables.VLANVirtualMachinesTable
|
||||
filterset = VMInterfaceFilterSet
|
||||
template_name = 'ipam/vlan/vminterfaces.html'
|
||||
tab = ViewTab(
|
||||
label=_('VM Interfaces'),
|
||||
badge=lambda x: x.get_vminterfaces().count(),
|
||||
permission='virtualization.view_vminterface'
|
||||
)
|
||||
|
||||
def get_children(self, request, parent):
|
||||
return parent.get_vminterfaces().restrict(request.user, 'view')
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'active_tab': 'vminterfaces',
|
||||
}
|
||||
|
||||
|
||||
class VLANEditView(generic.ObjectEditView):
|
||||
queryset = VLAN.objects.all()
|
||||
|
@ -13,6 +13,7 @@ from extras.utils import is_taggable, register_features
|
||||
from netbox.signals import post_clean
|
||||
from utilities.json import CustomFieldJSONEncoder
|
||||
from utilities.utils import serialize_object
|
||||
from utilities.views import register_model_view
|
||||
|
||||
__all__ = (
|
||||
'ChangeLoggingMixin',
|
||||
@ -292,3 +293,17 @@ def _register_features(sender, **kwargs):
|
||||
feature for feature, cls in FEATURES_MAP if issubclass(sender, cls)
|
||||
}
|
||||
register_features(sender, features)
|
||||
|
||||
# Feature view registration
|
||||
if issubclass(sender, JournalingMixin):
|
||||
register_model_view(
|
||||
sender,
|
||||
'journal',
|
||||
kwargs={'model': sender}
|
||||
)('netbox.views.generic.ObjectJournalView')
|
||||
if issubclass(sender, ChangeLoggingMixin):
|
||||
register_model_view(
|
||||
sender,
|
||||
'changelog',
|
||||
kwargs={'model': sender}
|
||||
)('netbox.views.generic.ObjectChangeLogView')
|
||||
|
@ -1,10 +1,12 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import View
|
||||
|
||||
from extras import forms, tables
|
||||
from extras.models import *
|
||||
from utilities.views import ViewTab
|
||||
|
||||
__all__ = (
|
||||
'ObjectChangeLogView',
|
||||
@ -23,6 +25,10 @@ class ObjectChangeLogView(View):
|
||||
base_template: The name of the template to extend. If not provided, "{app}/{model}.html" will be used.
|
||||
"""
|
||||
base_template = None
|
||||
tab = ViewTab(
|
||||
label=_('Changelog'),
|
||||
permission='extras.view_objectchange'
|
||||
)
|
||||
|
||||
def get(self, request, model, **kwargs):
|
||||
|
||||
@ -56,7 +62,7 @@ class ObjectChangeLogView(View):
|
||||
'object': obj,
|
||||
'table': objectchanges_table,
|
||||
'base_template': self.base_template,
|
||||
'active_tab': 'changelog',
|
||||
'tab': self.tab,
|
||||
})
|
||||
|
||||
|
||||
@ -71,6 +77,11 @@ class ObjectJournalView(View):
|
||||
base_template: The name of the template to extend. If not provided, "{app}/{model}.html" will be used.
|
||||
"""
|
||||
base_template = None
|
||||
tab = ViewTab(
|
||||
label=_('Journal'),
|
||||
badge=lambda obj: obj.journal_entries.count(),
|
||||
permission='extras.view_journalentry'
|
||||
)
|
||||
|
||||
def get(self, request, model, **kwargs):
|
||||
|
||||
@ -111,5 +122,5 @@ class ObjectJournalView(View):
|
||||
'form': form,
|
||||
'table': journalentry_table,
|
||||
'base_template': self.base_template,
|
||||
'active_tab': 'journal',
|
||||
'tab': self.tab,
|
||||
})
|
||||
|
@ -5,7 +5,6 @@ from django.contrib import messages
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import transaction
|
||||
from django.db.models import ProtectedError
|
||||
from django.forms.widgets import HiddenInput
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.html import escape
|
||||
@ -38,7 +37,12 @@ class ObjectView(BaseObjectView):
|
||||
Retrieve a single object for display.
|
||||
|
||||
Note: If `template_name` is not specified, it will be determined automatically based on the queryset model.
|
||||
|
||||
Attributes:
|
||||
tab: A ViewTab instance for the view
|
||||
"""
|
||||
tab = None
|
||||
|
||||
def get_required_permission(self):
|
||||
return get_permission_for_model(self.queryset.model, 'view')
|
||||
|
||||
@ -67,6 +71,7 @@ class ObjectView(BaseObjectView):
|
||||
|
||||
return render(request, self.get_template_name(), {
|
||||
'object': instance,
|
||||
'tab': self.tab,
|
||||
**self.get_extra_context(request, instance),
|
||||
})
|
||||
|
||||
@ -141,6 +146,7 @@ class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin):
|
||||
'child_model': self.child_model,
|
||||
'table': table,
|
||||
'actions': actions,
|
||||
'tab': self.tab,
|
||||
**self.get_extra_context(request, instance),
|
||||
})
|
||||
|
||||
|
@ -54,113 +54,3 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_tabs %}
|
||||
{% with tab_name='device-bays' devicebay_count=object.devicebays.count %}
|
||||
{% if active_tab == tab_name or devicebay_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_devicebays' pk=object.pk %}">Device Bays {% badge devicebay_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='module-bays' modulebay_count=object.modulebays.count %}
|
||||
{% if active_tab == tab_name or modulebay_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_modulebays' pk=object.pk %}">Module Bays {% badge modulebay_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='interfaces' interface_count=object.interfaces_count %}
|
||||
{% if active_tab == tab_name or interface_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_interfaces' pk=object.pk %}">Interfaces {% badge interface_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='front-ports' frontport_count=object.frontports.count %}
|
||||
{% if active_tab == tab_name or frontport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_frontports' pk=object.pk %}">Front Ports {% badge frontport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='rear-ports' rearport_count=object.rearports.count %}
|
||||
{% if active_tab == tab_name or rearport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_rearports' pk=object.pk %}">Rear Ports {% badge rearport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='console-ports' consoleport_count=object.consoleports.count %}
|
||||
{% if active_tab == tab_name or consoleport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_consoleports' pk=object.pk %}">Console Ports {% badge consoleport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='console-server-ports' consoleserverport_count=object.consoleserverports.count %}
|
||||
{% if active_tab == tab_name or consoleserverport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_consoleserverports' pk=object.pk %}">Console Server Ports {% badge consoleserverport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='power-ports' powerport_count=object.powerports.count %}
|
||||
{% if active_tab == tab_name or powerport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_powerports' pk=object.pk %}">Power Ports {% badge powerport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='power-outlets' poweroutlet_count=object.poweroutlets.count %}
|
||||
{% if active_tab == tab_name or poweroutlet_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_poweroutlets' pk=object.pk %}">Power Outlets {% badge poweroutlet_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
|
||||
{% with tab_name='inventory-items' inventoryitem_count=object.inventoryitems.count %}
|
||||
{% if active_tab == tab_name or inventoryitem_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:device_inventory' pk=object.pk %}">Inventory {% badge inventoryitem_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% if perms.dcim.napalm_read_device and object.status == 'active' and object.primary_ip and object.platform.napalm_driver %}
|
||||
{# NAPALM-enabled tabs #}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link{% if active_tab == 'status' %} active{% endif %}" href="{% url 'dcim:device_status' pk=object.pk %}">
|
||||
Status
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link{% if active_tab == 'lldp-neighbors' %} active{% endif %}" href="{% url 'dcim:device_lldp_neighbors' pk=object.pk %}">
|
||||
LLDP Neighbors
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link{% if active_tab == 'config' %} active{% endif %}" href="{% url 'dcim:device_config' pk=object.pk %}">
|
||||
Configuration
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if perms.extras.view_configcontext %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a href="{% url 'dcim:device_configcontext' pk=object.pk %}" class="nav-link{% if active_tab == 'config-context' %} active{% endif %}">
|
||||
Config Context
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -51,85 +51,3 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_tabs %}
|
||||
{% with tab_name='device-bay-templates' devicebay_count=object.devicebaytemplates.count %}
|
||||
{% if active_tab == tab_name or devicebay_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_devicebays' pk=object.pk %}">Device Bays {% badge devicebay_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='module-bay-templates' modulebay_count=object.modulebaytemplates.count %}
|
||||
{% if active_tab == tab_name or modulebay_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_modulebays' pk=object.pk %}">Module Bays {% badge modulebay_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='interface-templates' interface_count=object.interfacetemplates.count %}
|
||||
{% if active_tab == tab_name or interface_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_interfaces' pk=object.pk %}">Interfaces {% badge interface_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='front-port-templates' frontport_count=object.frontporttemplates.count %}
|
||||
{% if active_tab == tab_name or frontport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_frontports' pk=object.pk %}">Front Ports {% badge frontport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='rear-port-templates' rearport_count=object.rearporttemplates.count %}
|
||||
{% if active_tab == tab_name or rearport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_rearports' pk=object.pk %}">Rear Ports {% badge rearport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='console-port-templates' consoleport_count=object.consoleporttemplates.count %}
|
||||
{% if active_tab == tab_name or consoleport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_consoleports' pk=object.pk %}">Console Ports {% badge consoleport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='console-server-port-templates' consoleserverport_count=object.consoleserverporttemplates.count %}
|
||||
{% if active_tab == tab_name or consoleserverport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_consoleserverports' pk=object.pk %}">Console Server Ports {% badge consoleserverport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='power-port-templates' powerport_count=object.powerporttemplates.count %}
|
||||
{% if active_tab == tab_name or powerport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_powerports' pk=object.pk %}">Power Ports {% badge powerport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='power-outlet-templates' poweroutlet_count=object.poweroutlettemplates.count %}
|
||||
{% if active_tab == tab_name or poweroutlet_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_poweroutlets' pk=object.pk %}">Power Outlets {% badge poweroutlet_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='inventory-item-templates' inventoryitem_count=object.inventoryitemtemplates.count %}
|
||||
{% if active_tab == tab_name or inventoryitem_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_inventoryitems' pk=object.pk %}">Inventory Items {% badge inventoryitem_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
@ -42,61 +42,3 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_tabs %}
|
||||
{% with interface_count=object.interfacetemplates.count %}
|
||||
{% if interface_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'interface-templates' %} active{% endif %}" href="{% url 'dcim:moduletype_interfaces' pk=object.pk %}">Interfaces {% badge interface_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with frontport_count=object.frontporttemplates.count %}
|
||||
{% if frontport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'front-port-templates' %} active{% endif %}" href="{% url 'dcim:moduletype_frontports' pk=object.pk %}">Front Ports {% badge frontport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with rearport_count=object.rearporttemplates.count %}
|
||||
{% if rearport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'rear-port-templates' %} active{% endif %}" href="{% url 'dcim:moduletype_rearports' pk=object.pk %}">Rear Ports {% badge rearport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with consoleport_count=object.consoleporttemplates.count %}
|
||||
{% if consoleport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'console-port-templates' %} active{% endif %}" href="{% url 'dcim:moduletype_consoleports' pk=object.pk %}">Console Ports {% badge consoleport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with consoleserverport_count=object.consoleserverporttemplates.count %}
|
||||
{% if consoleserverport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'console-server-port-templates' %} active{% endif %}" href="{% url 'dcim:moduletype_consoleserverports' pk=object.pk %}">Console Server Ports {% badge consoleserverport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with powerport_count=object.powerporttemplates.count %}
|
||||
{% if powerport_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'power-port-templates' %} active{% endif %}" href="{% url 'dcim:moduletype_powerports' pk=object.pk %}">Power Ports {% badge powerport_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with poweroutlet_count=object.poweroutlettemplates.count %}
|
||||
{% if poweroutlet_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'power-outlet-templates' %} active{% endif %}" href="{% url 'dcim:moduletype_poweroutlets' pk=object.pk %}">Power Outlets {% badge poweroutlet_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
@ -4,6 +4,7 @@
|
||||
{% load helpers %}
|
||||
{% load perms %}
|
||||
{% load plugins %}
|
||||
{% load tabs %}
|
||||
|
||||
{% comment %}
|
||||
Blocks:
|
||||
@ -80,37 +81,14 @@ Context:
|
||||
<ul class="nav nav-tabs px-3">
|
||||
{# Primary tab #}
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link{% if not active_tab %} active{% endif %}" href="{{ object.get_absolute_url }}">{{ object|meta:"verbose_name"|bettertitle }}</a>
|
||||
<a class="nav-link{% if not tab %} active{% endif %}" href="{{ object.get_absolute_url }}">{{ object|meta:"verbose_name"|bettertitle }}</a>
|
||||
</li>
|
||||
|
||||
{# Include any additional tabs #}
|
||||
{# Include any extra tabs passed by the view #}
|
||||
{% block extra_tabs %}{% endblock %}
|
||||
|
||||
{# Object journal #}
|
||||
{% if perms.extras.view_journalentry %}
|
||||
{% with journal_viewname=object|viewname:'journal' %}
|
||||
{% url journal_viewname pk=object.pk as journal_url %}
|
||||
{% if journal_url %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a href="{{ journal_url }}" class="nav-link{% if active_tab == 'journal'%} active{% endif %}">
|
||||
Journal {% badge object.journal_entries.count %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{# Object changelog #}
|
||||
{% if perms.extras.view_objectchange %}
|
||||
{% with changelog_viewname=object|viewname:'changelog' %}
|
||||
{% url changelog_viewname pk=object.pk as changelog_url %}
|
||||
{% if changelog_url %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a href="{{ changelog_url }}" class="nav-link{% if active_tab == 'changelog'%} active{% endif %}">Change Log</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
{# Include tabs for registered model views #}
|
||||
{% model_view_tabs object %}
|
||||
</ul>
|
||||
{% endblock tabs %}
|
||||
|
||||
|
@ -6,13 +6,3 @@
|
||||
{{ block.super }}
|
||||
<li class="breadcrumb-item"><a href="{% url 'ipam:aggregate_list' %}?rir_id={{ object.rir.pk }}">{{ object.rir }}</a></li>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_tabs %}
|
||||
{% if perms.ipam.view_prefix %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link{% if active_tab == 'prefixes' %} active{% endif %}" href="{% url 'ipam:aggregate_prefixes' pk=object.pk %}">
|
||||
Prefixes {% badge object.get_child_prefixes.count %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -8,13 +8,3 @@
|
||||
<li class="breadcrumb-item"><a href="{% url 'ipam:iprange_list' %}?vrf_id={{ object.vrf.pk }}">{{ object.vrf }}</a></li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_tabs %}
|
||||
{% if perms.ipam.view_ipaddress %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link{% if active_tab == 'ip-addresses' %} active{% endif %}" href="{% url 'ipam:iprange_ipaddresses' pk=object.pk %}">
|
||||
IP Addresses {% badge object.get_child_ips.count %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -8,21 +8,3 @@
|
||||
<li class="breadcrumb-item"><a href="{% url 'ipam:prefix_list' %}?vrf_id={{ object.vrf.pk }}">{{ object.vrf }}</a></li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_tabs %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link{% if active_tab == 'prefixes' %} active{% endif %}" href="{% url 'ipam:prefix_prefixes' pk=object.pk %}">
|
||||
Child Prefixes {% badge object.get_child_prefixes.count %}
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link{% if active_tab == 'ip-ranges' %} active{% endif %}" href="{% url 'ipam:prefix_ipranges' pk=object.pk %}">
|
||||
Child Ranges {% badge object.get_child_ranges.count %}
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link{% if active_tab == 'ip-addresses' %} active{% endif %}" href="{% url 'ipam:prefix_ipaddresses' pk=object.pk %}">
|
||||
IP Addresses {% badge object.get_child_ips.count %}
|
||||
</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
@ -13,27 +13,3 @@
|
||||
<li class="breadcrumb-item"><a href="{% url 'ipam:vlan_list' %}?group_id={{ object.group.pk }}">{{ object.group }}</a></li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block tabs %}
|
||||
<ul class="nav nav-tabs px-3">
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link{% if not active_tab %} active{% endif %}" href="{% url 'ipam:vlan' pk=object.pk %}">VLAN</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link{% if active_tab == 'interfaces' %} active{% endif %}" href="{% url 'ipam:vlan_interfaces' pk=object.pk %}">Device Interfaces {% badge object.get_interfaces.count %}</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link{% if active_tab == 'vminterfaces' %} active{% endif %}" href="{% url 'ipam:vlan_vminterfaces' pk=object.pk %}">VM Interfaces {% badge object.get_vminterfaces.count %}</a>
|
||||
</li>
|
||||
{% if perms.extras.view_journalentry %}
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link{% if active_tab == 'journal' %} active{% endif %}" href="{% url 'ipam:vlan_journal' pk=object.pk %}">Journal</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link{% if active_tab == 'changelog' %} active{% endif %}" href="{% url 'ipam:vlan_changelog' pk=object.pk %}">Change Log</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
@ -24,20 +24,3 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_tabs %}
|
||||
{% with virtualmachine_count=object.virtual_machines.count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a href="{% url 'virtualization:cluster_virtualmachines' pk=object.pk %}" class="nav-link{% if active_tab == 'virtual-machines' %} active{% endif %}">
|
||||
Virtual Machines {% badge virtualmachine_count %}
|
||||
</a>
|
||||
</li>
|
||||
{% endwith %}
|
||||
{% with device_count=object.devices.count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a href="{% url 'virtualization:cluster_devices' pk=object.pk %}" class="nav-link{% if active_tab == 'devices' %} active{% endif %}">
|
||||
Devices {% badge device_count %}
|
||||
</a>
|
||||
</li>
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
@ -21,18 +21,3 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_tabs %}
|
||||
{% with interface_count=object.interfaces.count %}
|
||||
{% if interface_count %}
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link{% if active_tab == 'interfaces' %} active{% endif %}" href="{% url 'virtualization:virtualmachine_interfaces' pk=object.pk %}">Interfaces {% badge interface_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% if perms.extras.view_configcontext %}
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link{% if active_tab == 'config-context' %} active{% endif %}" href="{% url 'virtualization:virtualmachine_configcontext' pk=object.pk %}">Config Context</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -1,8 +1,7 @@
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
|
||||
from utilities.urls import get_model_urls
|
||||
from . import views
|
||||
from .models import *
|
||||
|
||||
app_name = 'tenancy'
|
||||
urlpatterns = [
|
||||
@ -16,7 +15,7 @@ urlpatterns = [
|
||||
path('tenant-groups/<int:pk>/', views.TenantGroupView.as_view(), name='tenantgroup'),
|
||||
path('tenant-groups/<int:pk>/edit/', views.TenantGroupEditView.as_view(), name='tenantgroup_edit'),
|
||||
path('tenant-groups/<int:pk>/delete/', views.TenantGroupDeleteView.as_view(), name='tenantgroup_delete'),
|
||||
path('tenant-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='tenantgroup_changelog', kwargs={'model': TenantGroup}),
|
||||
path('tenant-groups/<int:pk>/', include(get_model_urls('tenancy', 'tenantgroup'))),
|
||||
|
||||
# Tenants
|
||||
path('tenants/', views.TenantListView.as_view(), name='tenant_list'),
|
||||
@ -27,8 +26,7 @@ urlpatterns = [
|
||||
path('tenants/<int:pk>/', views.TenantView.as_view(), name='tenant'),
|
||||
path('tenants/<int:pk>/edit/', views.TenantEditView.as_view(), name='tenant_edit'),
|
||||
path('tenants/<int:pk>/delete/', views.TenantDeleteView.as_view(), name='tenant_delete'),
|
||||
path('tenants/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='tenant_changelog', kwargs={'model': Tenant}),
|
||||
path('tenants/<int:pk>/journal/', ObjectJournalView.as_view(), name='tenant_journal', kwargs={'model': Tenant}),
|
||||
path('tenants/<int:pk>/', include(get_model_urls('tenancy', 'tenant'))),
|
||||
|
||||
# Contact groups
|
||||
path('contact-groups/', views.ContactGroupListView.as_view(), name='contactgroup_list'),
|
||||
@ -39,7 +37,7 @@ urlpatterns = [
|
||||
path('contact-groups/<int:pk>/', views.ContactGroupView.as_view(), name='contactgroup'),
|
||||
path('contact-groups/<int:pk>/edit/', views.ContactGroupEditView.as_view(), name='contactgroup_edit'),
|
||||
path('contact-groups/<int:pk>/delete/', views.ContactGroupDeleteView.as_view(), name='contactgroup_delete'),
|
||||
path('contact-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='contactgroup_changelog', kwargs={'model': ContactGroup}),
|
||||
path('contact-groups/<int:pk>/', include(get_model_urls('tenancy', 'contactgroup'))),
|
||||
|
||||
# Contact roles
|
||||
path('contact-roles/', views.ContactRoleListView.as_view(), name='contactrole_list'),
|
||||
@ -50,7 +48,7 @@ urlpatterns = [
|
||||
path('contact-roles/<int:pk>/', views.ContactRoleView.as_view(), name='contactrole'),
|
||||
path('contact-roles/<int:pk>/edit/', views.ContactRoleEditView.as_view(), name='contactrole_edit'),
|
||||
path('contact-roles/<int:pk>/delete/', views.ContactRoleDeleteView.as_view(), name='contactrole_delete'),
|
||||
path('contact-roles/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='contactrole_changelog', kwargs={'model': ContactRole}),
|
||||
path('contact-roles/<int:pk>/', include(get_model_urls('tenancy', 'contactrole'))),
|
||||
|
||||
# Contacts
|
||||
path('contacts/', views.ContactListView.as_view(), name='contact_list'),
|
||||
@ -61,8 +59,7 @@ urlpatterns = [
|
||||
path('contacts/<int:pk>/', views.ContactView.as_view(), name='contact'),
|
||||
path('contacts/<int:pk>/edit/', views.ContactEditView.as_view(), name='contact_edit'),
|
||||
path('contacts/<int:pk>/delete/', views.ContactDeleteView.as_view(), name='contact_delete'),
|
||||
path('contacts/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='contact_changelog', kwargs={'model': Contact}),
|
||||
path('contacts/<int:pk>/journal/', ObjectJournalView.as_view(), name='contact_journal', kwargs={'model': Contact}),
|
||||
path('contacts/<int:pk>/', include(get_model_urls('tenancy', 'contact'))),
|
||||
|
||||
# Contact assignments
|
||||
path('contact-assignments/add/', views.ContactAssignmentEditView.as_view(), name='contactassignment_add'),
|
||||
|
7
netbox/utilities/templates/tabs/model_view_tabs.html
Normal file
7
netbox/utilities/templates/tabs/model_view_tabs.html
Normal file
@ -0,0 +1,7 @@
|
||||
{% for tab in tabs %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a href="{{ tab.url }}" class="nav-link{% if tab.is_active %} active{% endif %}">
|
||||
{{ tab.label }} {% badge tab.badge %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
48
netbox/utilities/templatetags/tabs.py
Normal file
48
netbox/utilities/templatetags/tabs.py
Normal file
@ -0,0 +1,48 @@
|
||||
from django import template
|
||||
from django.urls import reverse
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
from extras.registry import registry
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
#
|
||||
# Object detail view tabs
|
||||
#
|
||||
|
||||
@register.inclusion_tag('tabs/model_view_tabs.html', takes_context=True)
|
||||
def model_view_tabs(context, instance):
|
||||
app_label = instance._meta.app_label
|
||||
model_name = instance._meta.model_name
|
||||
user = context['request'].user
|
||||
tabs = []
|
||||
|
||||
# Retrieve registered views for this model
|
||||
try:
|
||||
views = registry['views'][app_label][model_name]
|
||||
except KeyError:
|
||||
# No views have been registered for this model
|
||||
views = []
|
||||
|
||||
# Compile a list of tabs to be displayed in the UI
|
||||
for config in views:
|
||||
view = import_string(config['view']) if type(config['view']) is str else config['view']
|
||||
if tab := getattr(view, 'tab', None):
|
||||
if tab.permission and not user.has_perm(tab.permission):
|
||||
continue
|
||||
|
||||
if attrs := tab.render(instance):
|
||||
viewname = f"{app_label}:{model_name}_{config['name']}"
|
||||
active_tab = context.get('tab')
|
||||
tabs.append({
|
||||
'name': config['name'],
|
||||
'url': reverse(viewname, args=[instance.pk]),
|
||||
'label': attrs['label'],
|
||||
'badge': attrs['badge'],
|
||||
'is_active': active_tab and active_tab == tab,
|
||||
})
|
||||
|
||||
return {
|
||||
'tabs': tabs,
|
||||
}
|
39
netbox/utilities/urls.py
Normal file
39
netbox/utilities/urls.py
Normal file
@ -0,0 +1,39 @@
|
||||
from django.urls import path
|
||||
from django.utils.module_loading import import_string
|
||||
from django.views.generic import View
|
||||
|
||||
from extras.registry import registry
|
||||
|
||||
|
||||
def get_model_urls(app_label, model_name):
|
||||
"""
|
||||
Return a list of URL paths for detail views registered to the given model.
|
||||
|
||||
Args:
|
||||
app_label: App/plugin name
|
||||
model_name: Model name
|
||||
"""
|
||||
paths = []
|
||||
|
||||
# Retrieve registered views for this model
|
||||
try:
|
||||
views = registry['views'][app_label][model_name]
|
||||
except KeyError:
|
||||
# No views have been registered for this model
|
||||
views = []
|
||||
|
||||
for config in views:
|
||||
# Import the view class or function
|
||||
if type(config['view']) is str:
|
||||
view_ = import_string(config['view'])
|
||||
else:
|
||||
view_ = config['view']
|
||||
if issubclass(view_, View):
|
||||
view_ = view_.as_view()
|
||||
|
||||
# Create a path to the view
|
||||
paths.append(
|
||||
path(f"{config['path']}/", view_, name=f"{model_name}_{config['name']}", kwargs=config['kwargs'])
|
||||
)
|
||||
|
||||
return paths
|
@ -3,8 +3,17 @@ from django.core.exceptions import ImproperlyConfigured
|
||||
from django.urls import reverse
|
||||
from django.urls.exceptions import NoReverseMatch
|
||||
|
||||
from extras.registry import registry
|
||||
from .permissions import resolve_permission
|
||||
|
||||
__all__ = (
|
||||
'ContentTypePermissionRequiredMixin',
|
||||
'GetReturnURLMixin',
|
||||
'ObjectPermissionRequiredMixin',
|
||||
'ViewTab',
|
||||
'register_model_view',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# View Mixins
|
||||
@ -122,3 +131,75 @@ class GetReturnURLMixin:
|
||||
|
||||
# If all else fails, return home. Ideally this should never happen.
|
||||
return reverse('home')
|
||||
|
||||
|
||||
class ViewTab:
|
||||
"""
|
||||
ViewTabs are used for navigation among multiple object-specific views, such as the changelog or journal for
|
||||
a particular object.
|
||||
|
||||
Args:
|
||||
label: Human-friendly text
|
||||
badge: A static value or callable to display alongside the label (optional). If a callable is used, it must accept a single
|
||||
argument representing the object being viewed.
|
||||
permission: The permission required to display the tab (optional).
|
||||
"""
|
||||
def __init__(self, label, badge=None, permission=None):
|
||||
self.label = label
|
||||
self.badge = badge
|
||||
self.permission = permission
|
||||
|
||||
def render(self, instance):
|
||||
"""Return the attributes needed to render a tab in HTML."""
|
||||
badge_value = self._get_badge_value(instance)
|
||||
if self.badge and not badge_value:
|
||||
return None
|
||||
return {
|
||||
'label': self.label,
|
||||
'badge': badge_value,
|
||||
}
|
||||
|
||||
def _get_badge_value(self, instance):
|
||||
if not self.badge:
|
||||
return None
|
||||
if callable(self.badge):
|
||||
return self.badge(instance)
|
||||
return self.badge
|
||||
|
||||
|
||||
def register_model_view(model, name, path=None, kwargs=None):
|
||||
"""
|
||||
This decorator can be used to "attach" a view to any model in NetBox. This is typically used to inject
|
||||
additional tabs within a model's detail view. For example, to add a custom tab to NetBox's dcim.Site model:
|
||||
|
||||
@netbox_model_view(Site, 'myview', path='my-custom-view')
|
||||
class MyView(ObjectView):
|
||||
...
|
||||
|
||||
This will automatically create a URL path for MyView at `/dcim/sites/<id>/my-custom-view/` which can be
|
||||
resolved using the view name `dcim:site_myview'.
|
||||
|
||||
Args:
|
||||
model: The Django model class with which this view will be associated.
|
||||
name: The string used to form the view's name for URL resolution (e.g. via `reverse()`). This will be appended
|
||||
to the name of the base view for the model using an underscore.
|
||||
path: The URL path by which the view can be reached (optional). If not provided, `name` will be used.
|
||||
kwargs: A dictionary of keyword arguments for the view to include when registering its URL path (optional).
|
||||
"""
|
||||
def _wrapper(cls):
|
||||
app_label = model._meta.app_label
|
||||
model_name = model._meta.model_name
|
||||
|
||||
if model_name not in registry['views'][app_label]:
|
||||
registry['views'][app_label][model_name] = []
|
||||
|
||||
registry['views'][app_label][model_name].append({
|
||||
'name': name,
|
||||
'view': cls,
|
||||
'path': path or name,
|
||||
'kwargs': kwargs or {},
|
||||
})
|
||||
|
||||
return cls
|
||||
|
||||
return _wrapper
|
||||
|
@ -1,8 +1,7 @@
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
|
||||
from utilities.urls import get_model_urls
|
||||
from . import views
|
||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
||||
|
||||
app_name = 'virtualization'
|
||||
urlpatterns = [
|
||||
@ -16,7 +15,7 @@ urlpatterns = [
|
||||
path('cluster-types/<int:pk>/', views.ClusterTypeView.as_view(), name='clustertype'),
|
||||
path('cluster-types/<int:pk>/edit/', views.ClusterTypeEditView.as_view(), name='clustertype_edit'),
|
||||
path('cluster-types/<int:pk>/delete/', views.ClusterTypeDeleteView.as_view(), name='clustertype_delete'),
|
||||
path('cluster-types/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='clustertype_changelog', kwargs={'model': ClusterType}),
|
||||
path('cluster-types/<int:pk>/', include(get_model_urls('virtualization', 'clustertype'))),
|
||||
|
||||
# Cluster groups
|
||||
path('cluster-groups/', views.ClusterGroupListView.as_view(), name='clustergroup_list'),
|
||||
@ -27,7 +26,7 @@ urlpatterns = [
|
||||
path('cluster-groups/<int:pk>/', views.ClusterGroupView.as_view(), name='clustergroup'),
|
||||
path('cluster-groups/<int:pk>/edit/', views.ClusterGroupEditView.as_view(), name='clustergroup_edit'),
|
||||
path('cluster-groups/<int:pk>/delete/', views.ClusterGroupDeleteView.as_view(), name='clustergroup_delete'),
|
||||
path('cluster-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='clustergroup_changelog', kwargs={'model': ClusterGroup}),
|
||||
path('cluster-groups/<int:pk>/', include(get_model_urls('virtualization', 'clustergroup'))),
|
||||
|
||||
# Clusters
|
||||
path('clusters/', views.ClusterListView.as_view(), name='cluster_list'),
|
||||
@ -36,14 +35,11 @@ urlpatterns = [
|
||||
path('clusters/edit/', views.ClusterBulkEditView.as_view(), name='cluster_bulk_edit'),
|
||||
path('clusters/delete/', views.ClusterBulkDeleteView.as_view(), name='cluster_bulk_delete'),
|
||||
path('clusters/<int:pk>/', views.ClusterView.as_view(), name='cluster'),
|
||||
path('clusters/<int:pk>/devices/', views.ClusterDevicesView.as_view(), name='cluster_devices'),
|
||||
path('clusters/<int:pk>/virtual-machines/', views.ClusterVirtualMachinesView.as_view(), name='cluster_virtualmachines'),
|
||||
path('clusters/<int:pk>/edit/', views.ClusterEditView.as_view(), name='cluster_edit'),
|
||||
path('clusters/<int:pk>/delete/', views.ClusterDeleteView.as_view(), name='cluster_delete'),
|
||||
path('clusters/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='cluster_changelog', kwargs={'model': Cluster}),
|
||||
path('clusters/<int:pk>/journal/', ObjectJournalView.as_view(), name='cluster_journal', kwargs={'model': Cluster}),
|
||||
path('clusters/<int:pk>/devices/add/', views.ClusterAddDevicesView.as_view(), name='cluster_add_devices'),
|
||||
path('clusters/<int:pk>/devices/remove/', views.ClusterRemoveDevicesView.as_view(), name='cluster_remove_devices'),
|
||||
path('clusters/<int:pk>/', include(get_model_urls('virtualization', 'cluster'))),
|
||||
|
||||
# Virtual machines
|
||||
path('virtual-machines/', views.VirtualMachineListView.as_view(), name='virtualmachine_list'),
|
||||
@ -52,12 +48,9 @@ urlpatterns = [
|
||||
path('virtual-machines/edit/', views.VirtualMachineBulkEditView.as_view(), name='virtualmachine_bulk_edit'),
|
||||
path('virtual-machines/delete/', views.VirtualMachineBulkDeleteView.as_view(), name='virtualmachine_bulk_delete'),
|
||||
path('virtual-machines/<int:pk>/', views.VirtualMachineView.as_view(), name='virtualmachine'),
|
||||
path('virtual-machines/<int:pk>/interfaces/', views.VirtualMachineInterfacesView.as_view(), name='virtualmachine_interfaces'),
|
||||
path('virtual-machines/<int:pk>/edit/', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'),
|
||||
path('virtual-machines/<int:pk>/delete/', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'),
|
||||
path('virtual-machines/<int:pk>/config-context/', views.VirtualMachineConfigContextView.as_view(), name='virtualmachine_configcontext'),
|
||||
path('virtual-machines/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='virtualmachine_changelog', kwargs={'model': VirtualMachine}),
|
||||
path('virtual-machines/<int:pk>/journal/', ObjectJournalView.as_view(), name='virtualmachine_journal', kwargs={'model': VirtualMachine}),
|
||||
path('virtual-machines/<int:pk>/', include(get_model_urls('virtualization', 'virtualmachine'))),
|
||||
|
||||
# VM interfaces
|
||||
path('interfaces/', views.VMInterfaceListView.as_view(), name='vminterface_list'),
|
||||
@ -69,7 +62,7 @@ urlpatterns = [
|
||||
path('interfaces/<int:pk>/', views.VMInterfaceView.as_view(), name='vminterface'),
|
||||
path('interfaces/<int:pk>/edit/', views.VMInterfaceEditView.as_view(), name='vminterface_edit'),
|
||||
path('interfaces/<int:pk>/delete/', views.VMInterfaceDeleteView.as_view(), name='vminterface_delete'),
|
||||
path('interfaces/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='vminterface_changelog', kwargs={'model': VMInterface}),
|
||||
path('interfaces/<int:pk>/', include(get_model_urls('virtualization', 'vminterface'))),
|
||||
path('virtual-machines/interfaces/add/', views.VirtualMachineBulkAddInterfaceView.as_view(), name='virtualmachine_bulk_add_vminterface'),
|
||||
|
||||
]
|
||||
|
@ -3,6 +3,7 @@ from django.db import transaction
|
||||
from django.db.models import Prefetch
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from dcim.filtersets import DeviceFilterSet
|
||||
from dcim.models import Device
|
||||
@ -12,6 +13,7 @@ from ipam.models import IPAddress, Service
|
||||
from ipam.tables import AssignedIPAddressesTable, InterfaceVLANTable
|
||||
from netbox.views import generic
|
||||
from utilities.utils import count_related
|
||||
from utilities.views import ViewTab, register_model_view
|
||||
from . import filtersets, forms, tables
|
||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
||||
|
||||
@ -161,37 +163,39 @@ class ClusterView(generic.ObjectView):
|
||||
queryset = Cluster.objects.all()
|
||||
|
||||
|
||||
@register_model_view(Cluster, 'virtualmachines', path='virtual-machines')
|
||||
class ClusterVirtualMachinesView(generic.ObjectChildrenView):
|
||||
queryset = Cluster.objects.all()
|
||||
child_model = VirtualMachine
|
||||
table = tables.VirtualMachineTable
|
||||
filterset = filtersets.VirtualMachineFilterSet
|
||||
template_name = 'virtualization/cluster/virtual_machines.html'
|
||||
tab = ViewTab(
|
||||
label=_('Virtual Machines'),
|
||||
badge=lambda obj: obj.virtual_machines.count(),
|
||||
permission='virtualization.view_virtualmachine'
|
||||
)
|
||||
|
||||
def get_children(self, request, parent):
|
||||
return VirtualMachine.objects.restrict(request.user, 'view').filter(cluster=parent)
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'active_tab': 'virtual-machines',
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(Cluster, 'devices')
|
||||
class ClusterDevicesView(generic.ObjectChildrenView):
|
||||
queryset = Cluster.objects.all()
|
||||
child_model = Device
|
||||
table = DeviceTable
|
||||
filterset = DeviceFilterSet
|
||||
template_name = 'virtualization/cluster/devices.html'
|
||||
tab = ViewTab(
|
||||
label=_('Devices'),
|
||||
badge=lambda obj: obj.devices.count(),
|
||||
permission='virtualization.view_virtualmachine'
|
||||
)
|
||||
|
||||
def get_children(self, request, parent):
|
||||
return Device.objects.restrict(request.user, 'view').filter(cluster=parent)
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'active_tab': 'devices',
|
||||
}
|
||||
|
||||
|
||||
class ClusterEditView(generic.ObjectEditView):
|
||||
queryset = Cluster.objects.all()
|
||||
@ -344,12 +348,18 @@ class VirtualMachineView(generic.ObjectView):
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(VirtualMachine, 'interfaces')
|
||||
class VirtualMachineInterfacesView(generic.ObjectChildrenView):
|
||||
queryset = VirtualMachine.objects.all()
|
||||
child_model = VMInterface
|
||||
table = tables.VirtualMachineVMInterfaceTable
|
||||
filterset = filtersets.VMInterfaceFilterSet
|
||||
template_name = 'virtualization/virtualmachine/interfaces.html'
|
||||
tab = ViewTab(
|
||||
label=_('Interfaces'),
|
||||
badge=lambda obj: obj.interfaces.count(),
|
||||
permission='virtualization.view_vminterface'
|
||||
)
|
||||
|
||||
def get_children(self, request, parent):
|
||||
return parent.interfaces.restrict(request.user, 'view').prefetch_related(
|
||||
@ -357,15 +367,15 @@ class VirtualMachineInterfacesView(generic.ObjectChildrenView):
|
||||
'tags',
|
||||
)
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'active_tab': 'interfaces',
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(VirtualMachine, 'configcontext', path='config-context')
|
||||
class VirtualMachineConfigContextView(ObjectConfigContextView):
|
||||
queryset = VirtualMachine.objects.annotate_config_context_data()
|
||||
base_template = 'virtualization/virtualmachine.html'
|
||||
tab = ViewTab(
|
||||
label=_('Config Context'),
|
||||
permission='extras.view_configcontext'
|
||||
)
|
||||
|
||||
|
||||
class VirtualMachineEditView(generic.ObjectEditView):
|
||||
|
@ -1,8 +1,7 @@
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
|
||||
from utilities.urls import get_model_urls
|
||||
from . import views
|
||||
from .models import *
|
||||
|
||||
app_name = 'wireless'
|
||||
urlpatterns = (
|
||||
@ -16,7 +15,7 @@ urlpatterns = (
|
||||
path('wireless-lan-groups/<int:pk>/', views.WirelessLANGroupView.as_view(), name='wirelesslangroup'),
|
||||
path('wireless-lan-groups/<int:pk>/edit/', views.WirelessLANGroupEditView.as_view(), name='wirelesslangroup_edit'),
|
||||
path('wireless-lan-groups/<int:pk>/delete/', views.WirelessLANGroupDeleteView.as_view(), name='wirelesslangroup_delete'),
|
||||
path('wireless-lan-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='wirelesslangroup_changelog', kwargs={'model': WirelessLANGroup}),
|
||||
path('wireless-lan-groups/<int:pk>/', include(get_model_urls('wireless', 'wirelesslangroup'))),
|
||||
|
||||
# Wireless LANs
|
||||
path('wireless-lans/', views.WirelessLANListView.as_view(), name='wirelesslan_list'),
|
||||
@ -27,8 +26,7 @@ urlpatterns = (
|
||||
path('wireless-lans/<int:pk>/', views.WirelessLANView.as_view(), name='wirelesslan'),
|
||||
path('wireless-lans/<int:pk>/edit/', views.WirelessLANEditView.as_view(), name='wirelesslan_edit'),
|
||||
path('wireless-lans/<int:pk>/delete/', views.WirelessLANDeleteView.as_view(), name='wirelesslan_delete'),
|
||||
path('wireless-lans/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='wirelesslan_changelog', kwargs={'model': WirelessLAN}),
|
||||
path('wireless-lans/<int:pk>/journal/', ObjectJournalView.as_view(), name='wirelesslan_journal', kwargs={'model': WirelessLAN}),
|
||||
path('wireless-lans/<int:pk>/', include(get_model_urls('wireless', 'wirelesslan'))),
|
||||
|
||||
# Wireless links
|
||||
path('wireless-links/', views.WirelessLinkListView.as_view(), name='wirelesslink_list'),
|
||||
@ -39,7 +37,6 @@ urlpatterns = (
|
||||
path('wireless-links/<int:pk>/', views.WirelessLinkView.as_view(), name='wirelesslink'),
|
||||
path('wireless-links/<int:pk>/edit/', views.WirelessLinkEditView.as_view(), name='wirelesslink_edit'),
|
||||
path('wireless-links/<int:pk>/delete/', views.WirelessLinkDeleteView.as_view(), name='wirelesslink_delete'),
|
||||
path('wireless-links/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='wirelesslink_changelog', kwargs={'model': WirelessLink}),
|
||||
path('wireless-links/<int:pk>/journal/', ObjectJournalView.as_view(), name='wirelesslink_journal', kwargs={'model': WirelessLink}),
|
||||
path('wireless-links/<int:pk>/', include(get_model_urls('wireless', 'wirelesslink'))),
|
||||
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user