mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Wrap model detail views with register_model_view()
This commit is contained in:
@ -105,16 +105,6 @@ urlpatterns = [
|
|||||||
path('device-types/edit/', views.DeviceTypeBulkEditView.as_view(), name='devicetype_bulk_edit'),
|
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/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>/', 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>/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>/delete/', views.DeviceTypeDeleteView.as_view(), name='devicetype_delete'),
|
||||||
path('device-types/<int:pk>/', include(get_model_urls('dcim', 'devicetype'))),
|
path('device-types/<int:pk>/', include(get_model_urls('dcim', 'devicetype'))),
|
||||||
@ -126,13 +116,6 @@ urlpatterns = [
|
|||||||
path('module-types/edit/', views.ModuleTypeBulkEditView.as_view(), name='moduletype_bulk_edit'),
|
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/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>/', 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>/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>/delete/', views.ModuleTypeDeleteView.as_view(), name='moduletype_delete'),
|
||||||
path('module-types/<int:pk>/', include(get_model_urls('dcim', 'moduletype'))),
|
path('module-types/<int:pk>/', include(get_model_urls('dcim', 'moduletype'))),
|
||||||
@ -250,17 +233,7 @@ urlpatterns = [
|
|||||||
path('devices/<int:pk>/', views.DeviceView.as_view(), name='device'),
|
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>/edit/', views.DeviceEditView.as_view(), name='device_edit'),
|
||||||
path('devices/<int:pk>/delete/', views.DeviceDeleteView.as_view(), name='device_delete'),
|
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>/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>/status/', views.DeviceStatusView.as_view(), name='device_status'),
|
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>/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>/config/', views.DeviceConfigView.as_view(), name='device_config'),
|
||||||
|
@ -8,6 +8,7 @@ from django.shortcuts import get_object_or_404, redirect, render
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
|
||||||
from circuits.models import Circuit, CircuitTermination
|
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.paginator import EnhancedPaginator, get_paginate_count
|
||||||
from utilities.permissions import get_permission_for_model
|
from utilities.permissions import get_permission_for_model
|
||||||
from utilities.utils import count_related
|
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 virtualization.models import VirtualMachine
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .choices import DeviceFaceChoices
|
from .choices import DeviceFaceChoices
|
||||||
@ -47,7 +48,7 @@ class DeviceComponentsView(generic.ObjectChildrenView):
|
|||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
return {
|
return {
|
||||||
'active_tab': f"{self.child_model._meta.verbose_name_plural.replace(' ', '-')}",
|
'active_tab': f"{self.child_model._meta.verbose_name_plural.replace(' ', '')}",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -60,10 +61,11 @@ class DeviceTypeComponentsView(DeviceComponentsView):
|
|||||||
return self.child_model.objects.restrict(request.user, 'view').filter(device_type=parent)
|
return self.child_model.objects.restrict(request.user, 'view').filter(device_type=parent)
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
context = super().get_extra_context(request, instance)
|
model_name = self.child_model._meta.verbose_name_plural
|
||||||
context['return_url'] = reverse(self.viewname, kwargs={'pk': instance.pk})
|
return {
|
||||||
|
'active_tab': f"{model_name.replace(' ', '').replace('template', '')}",
|
||||||
return context
|
'return_url': reverse(self.viewname, kwargs={'pk': instance.pk}),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ModuleTypeComponentsView(DeviceComponentsView):
|
class ModuleTypeComponentsView(DeviceComponentsView):
|
||||||
@ -75,10 +77,11 @@ class ModuleTypeComponentsView(DeviceComponentsView):
|
|||||||
return self.child_model.objects.restrict(request.user, 'view').filter(module_type=parent)
|
return self.child_model.objects.restrict(request.user, 'view').filter(module_type=parent)
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
context = super().get_extra_context(request, instance)
|
model_name = self.child_model._meta.verbose_name_plural
|
||||||
context['return_url'] = reverse(self.viewname, kwargs={'pk': instance.pk})
|
return {
|
||||||
|
'active_tab': f"{model_name.replace(' ', '').replace('template', '')}",
|
||||||
return context
|
'return_url': reverse(self.viewname, kwargs={'pk': instance.pk}),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class BulkDisconnectView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
class BulkDisconnectView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
||||||
@ -857,74 +860,144 @@ class DeviceTypeView(generic.ObjectView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(DeviceType, 'consoleports', path='console-ports')
|
||||||
class DeviceTypeConsolePortsView(DeviceTypeComponentsView):
|
class DeviceTypeConsolePortsView(DeviceTypeComponentsView):
|
||||||
child_model = ConsolePortTemplate
|
child_model = ConsolePortTemplate
|
||||||
table = tables.ConsolePortTemplateTable
|
table = tables.ConsolePortTemplateTable
|
||||||
filterset = filtersets.ConsolePortTemplateFilterSet
|
filterset = filtersets.ConsolePortTemplateFilterSet
|
||||||
viewname = 'dcim:devicetype_consoleports'
|
viewname = 'dcim:devicetype_consoleports'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Console Ports'),
|
||||||
|
badge=lambda obj: obj.consoleporttemplates.count(),
|
||||||
|
permission='dcim.view_consoleporttemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(DeviceType, 'consoleserverports', path='console-server-ports')
|
||||||
class DeviceTypeConsoleServerPortsView(DeviceTypeComponentsView):
|
class DeviceTypeConsoleServerPortsView(DeviceTypeComponentsView):
|
||||||
child_model = ConsoleServerPortTemplate
|
child_model = ConsoleServerPortTemplate
|
||||||
table = tables.ConsoleServerPortTemplateTable
|
table = tables.ConsoleServerPortTemplateTable
|
||||||
filterset = filtersets.ConsoleServerPortTemplateFilterSet
|
filterset = filtersets.ConsoleServerPortTemplateFilterSet
|
||||||
viewname = 'dcim:devicetype_consoleserverports'
|
viewname = 'dcim:devicetype_consoleserverports'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Console Server Ports'),
|
||||||
|
badge=lambda obj: obj.consoleserverporttemplates.count(),
|
||||||
|
permission='dcim.view_consoleserverporttemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(DeviceType, 'powerports', path='power-ports')
|
||||||
class DeviceTypePowerPortsView(DeviceTypeComponentsView):
|
class DeviceTypePowerPortsView(DeviceTypeComponentsView):
|
||||||
child_model = PowerPortTemplate
|
child_model = PowerPortTemplate
|
||||||
table = tables.PowerPortTemplateTable
|
table = tables.PowerPortTemplateTable
|
||||||
filterset = filtersets.PowerPortTemplateFilterSet
|
filterset = filtersets.PowerPortTemplateFilterSet
|
||||||
viewname = 'dcim:devicetype_powerports'
|
viewname = 'dcim:devicetype_powerports'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Power Ports'),
|
||||||
|
badge=lambda obj: obj.powerporttemplates.count(),
|
||||||
|
permission='dcim.view_powerporttemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(DeviceType, 'poweroutlets', path='power-outlets')
|
||||||
class DeviceTypePowerOutletsView(DeviceTypeComponentsView):
|
class DeviceTypePowerOutletsView(DeviceTypeComponentsView):
|
||||||
child_model = PowerOutletTemplate
|
child_model = PowerOutletTemplate
|
||||||
table = tables.PowerOutletTemplateTable
|
table = tables.PowerOutletTemplateTable
|
||||||
filterset = filtersets.PowerOutletTemplateFilterSet
|
filterset = filtersets.PowerOutletTemplateFilterSet
|
||||||
viewname = 'dcim:devicetype_poweroutlets'
|
viewname = 'dcim:devicetype_poweroutlets'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Power Outlets'),
|
||||||
|
badge=lambda obj: obj.poweroutlettemplates.count(),
|
||||||
|
permission='dcim.view_poweroutlettemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(DeviceType, 'interfaces')
|
||||||
class DeviceTypeInterfacesView(DeviceTypeComponentsView):
|
class DeviceTypeInterfacesView(DeviceTypeComponentsView):
|
||||||
child_model = InterfaceTemplate
|
child_model = InterfaceTemplate
|
||||||
table = tables.InterfaceTemplateTable
|
table = tables.InterfaceTemplateTable
|
||||||
filterset = filtersets.InterfaceTemplateFilterSet
|
filterset = filtersets.InterfaceTemplateFilterSet
|
||||||
viewname = 'dcim:devicetype_interfaces'
|
viewname = 'dcim:devicetype_interfaces'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Interfaces'),
|
||||||
|
badge=lambda obj: obj.interfacetemplates.count(),
|
||||||
|
permission='dcim.view_interfacetemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(DeviceType, 'frontports', path='front-ports')
|
||||||
class DeviceTypeFrontPortsView(DeviceTypeComponentsView):
|
class DeviceTypeFrontPortsView(DeviceTypeComponentsView):
|
||||||
child_model = FrontPortTemplate
|
child_model = FrontPortTemplate
|
||||||
table = tables.FrontPortTemplateTable
|
table = tables.FrontPortTemplateTable
|
||||||
filterset = filtersets.FrontPortTemplateFilterSet
|
filterset = filtersets.FrontPortTemplateFilterSet
|
||||||
viewname = 'dcim:devicetype_frontports'
|
viewname = 'dcim:devicetype_frontports'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Front Ports'),
|
||||||
|
badge=lambda obj: obj.frontporttemplates.count(),
|
||||||
|
permission='dcim.view_frontporttemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(DeviceType, 'rearports', path='rear-ports')
|
||||||
class DeviceTypeRearPortsView(DeviceTypeComponentsView):
|
class DeviceTypeRearPortsView(DeviceTypeComponentsView):
|
||||||
child_model = RearPortTemplate
|
child_model = RearPortTemplate
|
||||||
table = tables.RearPortTemplateTable
|
table = tables.RearPortTemplateTable
|
||||||
filterset = filtersets.RearPortTemplateFilterSet
|
filterset = filtersets.RearPortTemplateFilterSet
|
||||||
viewname = 'dcim:devicetype_rearports'
|
viewname = 'dcim:devicetype_rearports'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Rear Ports'),
|
||||||
|
badge=lambda obj: obj.rearporttemplates.count(),
|
||||||
|
permission='dcim.view_rearporttemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(DeviceType, 'modulebays', path='module-bays')
|
||||||
class DeviceTypeModuleBaysView(DeviceTypeComponentsView):
|
class DeviceTypeModuleBaysView(DeviceTypeComponentsView):
|
||||||
child_model = ModuleBayTemplate
|
child_model = ModuleBayTemplate
|
||||||
table = tables.ModuleBayTemplateTable
|
table = tables.ModuleBayTemplateTable
|
||||||
filterset = filtersets.ModuleBayTemplateFilterSet
|
filterset = filtersets.ModuleBayTemplateFilterSet
|
||||||
viewname = 'dcim:devicetype_modulebays'
|
viewname = 'dcim:devicetype_modulebays'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Module Bays'),
|
||||||
|
badge=lambda obj: obj.modulebaytemplates.count(),
|
||||||
|
permission='dcim.view_modulebaytemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(DeviceType, 'devicebays', path='device-bays')
|
||||||
class DeviceTypeDeviceBaysView(DeviceTypeComponentsView):
|
class DeviceTypeDeviceBaysView(DeviceTypeComponentsView):
|
||||||
child_model = DeviceBayTemplate
|
child_model = DeviceBayTemplate
|
||||||
table = tables.DeviceBayTemplateTable
|
table = tables.DeviceBayTemplateTable
|
||||||
filterset = filtersets.DeviceBayTemplateFilterSet
|
filterset = filtersets.DeviceBayTemplateFilterSet
|
||||||
viewname = 'dcim:devicetype_devicebays'
|
viewname = 'dcim:devicetype_devicebays'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Device Bays'),
|
||||||
|
badge=lambda obj: obj.devicebaytemplates.count(),
|
||||||
|
permission='dcim.view_devicebaytemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(DeviceType, 'inventoryitems', path='inventory-items')
|
||||||
class DeviceTypeInventoryItemsView(DeviceTypeComponentsView):
|
class DeviceTypeInventoryItemsView(DeviceTypeComponentsView):
|
||||||
child_model = InventoryItemTemplate
|
child_model = InventoryItemTemplate
|
||||||
table = tables.InventoryItemTemplateTable
|
table = tables.InventoryItemTemplateTable
|
||||||
filterset = filtersets.InventoryItemTemplateFilterSet
|
filterset = filtersets.InventoryItemTemplateFilterSet
|
||||||
viewname = 'dcim:devicetype_inventoryitems'
|
viewname = 'dcim:devicetype_inventoryitems'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Inventory Items'),
|
||||||
|
badge=lambda obj: obj.inventoryitemtemplates.count(),
|
||||||
|
permission='dcim.view_invenotryitemtemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeEditView(generic.ObjectEditView):
|
class DeviceTypeEditView(generic.ObjectEditView):
|
||||||
@ -1011,53 +1084,102 @@ class ModuleTypeView(generic.ObjectView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleType, 'consoleports', path='console-ports')
|
||||||
class ModuleTypeConsolePortsView(ModuleTypeComponentsView):
|
class ModuleTypeConsolePortsView(ModuleTypeComponentsView):
|
||||||
child_model = ConsolePortTemplate
|
child_model = ConsolePortTemplate
|
||||||
table = tables.ConsolePortTemplateTable
|
table = tables.ConsolePortTemplateTable
|
||||||
filterset = filtersets.ConsolePortTemplateFilterSet
|
filterset = filtersets.ConsolePortTemplateFilterSet
|
||||||
viewname = 'dcim:moduletype_consoleports'
|
viewname = 'dcim:moduletype_consoleports'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Console Ports'),
|
||||||
|
badge=lambda obj: obj.consoleporttemplates.count(),
|
||||||
|
permission='dcim.view_consoleporttemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleType, 'consoleserverports', path='console-server-ports')
|
||||||
class ModuleTypeConsoleServerPortsView(ModuleTypeComponentsView):
|
class ModuleTypeConsoleServerPortsView(ModuleTypeComponentsView):
|
||||||
child_model = ConsoleServerPortTemplate
|
child_model = ConsoleServerPortTemplate
|
||||||
table = tables.ConsoleServerPortTemplateTable
|
table = tables.ConsoleServerPortTemplateTable
|
||||||
filterset = filtersets.ConsoleServerPortTemplateFilterSet
|
filterset = filtersets.ConsoleServerPortTemplateFilterSet
|
||||||
viewname = 'dcim:moduletype_consoleserverports'
|
viewname = 'dcim:moduletype_consoleserverports'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Console Server Ports'),
|
||||||
|
badge=lambda obj: obj.consoleserverporttemplates.count(),
|
||||||
|
permission='dcim.view_consoleserverporttemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleType, 'powerports', path='power-ports')
|
||||||
class ModuleTypePowerPortsView(ModuleTypeComponentsView):
|
class ModuleTypePowerPortsView(ModuleTypeComponentsView):
|
||||||
child_model = PowerPortTemplate
|
child_model = PowerPortTemplate
|
||||||
table = tables.PowerPortTemplateTable
|
table = tables.PowerPortTemplateTable
|
||||||
filterset = filtersets.PowerPortTemplateFilterSet
|
filterset = filtersets.PowerPortTemplateFilterSet
|
||||||
viewname = 'dcim:moduletype_powerports'
|
viewname = 'dcim:moduletype_powerports'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Power Ports'),
|
||||||
|
badge=lambda obj: obj.powerporttemplates.count(),
|
||||||
|
permission='dcim.view_powerporttemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleType, 'poweroutlets', path='power-outlets')
|
||||||
class ModuleTypePowerOutletsView(ModuleTypeComponentsView):
|
class ModuleTypePowerOutletsView(ModuleTypeComponentsView):
|
||||||
child_model = PowerOutletTemplate
|
child_model = PowerOutletTemplate
|
||||||
table = tables.PowerOutletTemplateTable
|
table = tables.PowerOutletTemplateTable
|
||||||
filterset = filtersets.PowerOutletTemplateFilterSet
|
filterset = filtersets.PowerOutletTemplateFilterSet
|
||||||
viewname = 'dcim:moduletype_poweroutlets'
|
viewname = 'dcim:moduletype_poweroutlets'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Power Outlets'),
|
||||||
|
badge=lambda obj: obj.poweroutlettemplates.count(),
|
||||||
|
permission='dcim.view_poweroutlettemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleType, 'interfaces')
|
||||||
class ModuleTypeInterfacesView(ModuleTypeComponentsView):
|
class ModuleTypeInterfacesView(ModuleTypeComponentsView):
|
||||||
child_model = InterfaceTemplate
|
child_model = InterfaceTemplate
|
||||||
table = tables.InterfaceTemplateTable
|
table = tables.InterfaceTemplateTable
|
||||||
filterset = filtersets.InterfaceTemplateFilterSet
|
filterset = filtersets.InterfaceTemplateFilterSet
|
||||||
viewname = 'dcim:moduletype_interfaces'
|
viewname = 'dcim:moduletype_interfaces'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Interfaces'),
|
||||||
|
badge=lambda obj: obj.interfacetemplates.count(),
|
||||||
|
permission='dcim.view_interfacetemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleType, 'frontports', path='front-ports')
|
||||||
class ModuleTypeFrontPortsView(ModuleTypeComponentsView):
|
class ModuleTypeFrontPortsView(ModuleTypeComponentsView):
|
||||||
child_model = FrontPortTemplate
|
child_model = FrontPortTemplate
|
||||||
table = tables.FrontPortTemplateTable
|
table = tables.FrontPortTemplateTable
|
||||||
filterset = filtersets.FrontPortTemplateFilterSet
|
filterset = filtersets.FrontPortTemplateFilterSet
|
||||||
viewname = 'dcim:moduletype_frontports'
|
viewname = 'dcim:moduletype_frontports'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Front Ports'),
|
||||||
|
badge=lambda obj: obj.frontporttemplates.count(),
|
||||||
|
permission='dcim.view_frontporttemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleType, 'rearports', path='rear-ports')
|
||||||
class ModuleTypeRearPortsView(ModuleTypeComponentsView):
|
class ModuleTypeRearPortsView(ModuleTypeComponentsView):
|
||||||
child_model = RearPortTemplate
|
child_model = RearPortTemplate
|
||||||
table = tables.RearPortTemplateTable
|
table = tables.RearPortTemplateTable
|
||||||
filterset = filtersets.RearPortTemplateFilterSet
|
filterset = filtersets.RearPortTemplateFilterSet
|
||||||
viewname = 'dcim:moduletype_rearports'
|
viewname = 'dcim:moduletype_rearports'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Rear Ports'),
|
||||||
|
badge=lambda obj: obj.rearporttemplates.count(),
|
||||||
|
permission='dcim.view_rearporttemplate',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ModuleTypeEditView(generic.ObjectEditView):
|
class ModuleTypeEditView(generic.ObjectEditView):
|
||||||
@ -1620,39 +1742,74 @@ class DeviceView(generic.ObjectView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Device, 'consoleports', path='console-ports')
|
||||||
class DeviceConsolePortsView(DeviceComponentsView):
|
class DeviceConsolePortsView(DeviceComponentsView):
|
||||||
child_model = ConsolePort
|
child_model = ConsolePort
|
||||||
table = tables.DeviceConsolePortTable
|
table = tables.DeviceConsolePortTable
|
||||||
filterset = filtersets.ConsolePortFilterSet
|
filterset = filtersets.ConsolePortFilterSet
|
||||||
template_name = 'dcim/device/consoleports.html'
|
template_name = 'dcim/device/consoleports.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Console Ports'),
|
||||||
|
badge=lambda obj: obj.consoleports.count(),
|
||||||
|
permission='dcim.view_consoleport',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Device, 'consoleserverports', path='console-server-ports')
|
||||||
class DeviceConsoleServerPortsView(DeviceComponentsView):
|
class DeviceConsoleServerPortsView(DeviceComponentsView):
|
||||||
child_model = ConsoleServerPort
|
child_model = ConsoleServerPort
|
||||||
table = tables.DeviceConsoleServerPortTable
|
table = tables.DeviceConsoleServerPortTable
|
||||||
filterset = filtersets.ConsoleServerPortFilterSet
|
filterset = filtersets.ConsoleServerPortFilterSet
|
||||||
template_name = 'dcim/device/consoleserverports.html'
|
template_name = 'dcim/device/consoleserverports.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Console Server Ports'),
|
||||||
|
badge=lambda obj: obj.consoleserverports.count(),
|
||||||
|
permission='dcim.view_consoleserverport',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Device, 'powerports', path='power-ports')
|
||||||
class DevicePowerPortsView(DeviceComponentsView):
|
class DevicePowerPortsView(DeviceComponentsView):
|
||||||
child_model = PowerPort
|
child_model = PowerPort
|
||||||
table = tables.DevicePowerPortTable
|
table = tables.DevicePowerPortTable
|
||||||
filterset = filtersets.PowerPortFilterSet
|
filterset = filtersets.PowerPortFilterSet
|
||||||
template_name = 'dcim/device/powerports.html'
|
template_name = 'dcim/device/powerports.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Power Ports'),
|
||||||
|
badge=lambda obj: obj.powerports.count(),
|
||||||
|
permission='dcim.view_powerport',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Device, 'poweroutlets', path='power-outlets')
|
||||||
class DevicePowerOutletsView(DeviceComponentsView):
|
class DevicePowerOutletsView(DeviceComponentsView):
|
||||||
child_model = PowerOutlet
|
child_model = PowerOutlet
|
||||||
table = tables.DevicePowerOutletTable
|
table = tables.DevicePowerOutletTable
|
||||||
filterset = filtersets.PowerOutletFilterSet
|
filterset = filtersets.PowerOutletFilterSet
|
||||||
template_name = 'dcim/device/poweroutlets.html'
|
template_name = 'dcim/device/poweroutlets.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Power Outlets'),
|
||||||
|
badge=lambda obj: obj.poweroutlets.count(),
|
||||||
|
permission='dcim.view_poweroutlet',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Device, 'interfaces')
|
||||||
class DeviceInterfacesView(DeviceComponentsView):
|
class DeviceInterfacesView(DeviceComponentsView):
|
||||||
child_model = Interface
|
child_model = Interface
|
||||||
table = tables.DeviceInterfaceTable
|
table = tables.DeviceInterfaceTable
|
||||||
filterset = filtersets.InterfaceFilterSet
|
filterset = filtersets.InterfaceFilterSet
|
||||||
template_name = 'dcim/device/interfaces.html'
|
template_name = 'dcim/device/interfaces.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Interfaces'),
|
||||||
|
badge=lambda obj: obj.interfaces.count(),
|
||||||
|
permission='dcim.view_interface',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
def get_children(self, request, parent):
|
def get_children(self, request, parent):
|
||||||
return parent.vc_interfaces().restrict(request.user, 'view').prefetch_related(
|
return parent.vc_interfaces().restrict(request.user, 'view').prefetch_related(
|
||||||
@ -1661,39 +1818,74 @@ class DeviceInterfacesView(DeviceComponentsView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Device, 'frontports', path='front-ports')
|
||||||
class DeviceFrontPortsView(DeviceComponentsView):
|
class DeviceFrontPortsView(DeviceComponentsView):
|
||||||
child_model = FrontPort
|
child_model = FrontPort
|
||||||
table = tables.DeviceFrontPortTable
|
table = tables.DeviceFrontPortTable
|
||||||
filterset = filtersets.FrontPortFilterSet
|
filterset = filtersets.FrontPortFilterSet
|
||||||
template_name = 'dcim/device/frontports.html'
|
template_name = 'dcim/device/frontports.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Front Ports'),
|
||||||
|
badge=lambda obj: obj.frontports.count(),
|
||||||
|
permission='dcim.view_frontport',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Device, 'rearports', path='rear-ports')
|
||||||
class DeviceRearPortsView(DeviceComponentsView):
|
class DeviceRearPortsView(DeviceComponentsView):
|
||||||
child_model = RearPort
|
child_model = RearPort
|
||||||
table = tables.DeviceRearPortTable
|
table = tables.DeviceRearPortTable
|
||||||
filterset = filtersets.RearPortFilterSet
|
filterset = filtersets.RearPortFilterSet
|
||||||
template_name = 'dcim/device/rearports.html'
|
template_name = 'dcim/device/rearports.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Rear Ports'),
|
||||||
|
badge=lambda obj: obj.rearports.count(),
|
||||||
|
permission='dcim.view_rearport',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Device, 'modulebays', path='module-bays')
|
||||||
class DeviceModuleBaysView(DeviceComponentsView):
|
class DeviceModuleBaysView(DeviceComponentsView):
|
||||||
child_model = ModuleBay
|
child_model = ModuleBay
|
||||||
table = tables.DeviceModuleBayTable
|
table = tables.DeviceModuleBayTable
|
||||||
filterset = filtersets.ModuleBayFilterSet
|
filterset = filtersets.ModuleBayFilterSet
|
||||||
template_name = 'dcim/device/modulebays.html'
|
template_name = 'dcim/device/modulebays.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Module Bays'),
|
||||||
|
badge=lambda obj: obj.modulebays.count(),
|
||||||
|
permission='dcim.view_modulebay',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Device, 'devicebays', path='device-bays')
|
||||||
class DeviceDeviceBaysView(DeviceComponentsView):
|
class DeviceDeviceBaysView(DeviceComponentsView):
|
||||||
child_model = DeviceBay
|
child_model = DeviceBay
|
||||||
table = tables.DeviceDeviceBayTable
|
table = tables.DeviceDeviceBayTable
|
||||||
filterset = filtersets.DeviceBayFilterSet
|
filterset = filtersets.DeviceBayFilterSet
|
||||||
template_name = 'dcim/device/devicebays.html'
|
template_name = 'dcim/device/devicebays.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Device Bays'),
|
||||||
|
badge=lambda obj: obj.devicebays.count(),
|
||||||
|
permission='dcim.view_devicebay',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Device, 'inventory')
|
||||||
class DeviceInventoryView(DeviceComponentsView):
|
class DeviceInventoryView(DeviceComponentsView):
|
||||||
child_model = InventoryItem
|
child_model = InventoryItem
|
||||||
table = tables.DeviceInventoryItemTable
|
table = tables.DeviceInventoryItemTable
|
||||||
filterset = filtersets.InventoryItemFilterSet
|
filterset = filtersets.InventoryItemFilterSet
|
||||||
template_name = 'dcim/device/inventory.html'
|
template_name = 'dcim/device/inventory.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Inventory Items'),
|
||||||
|
badge=lambda obj: obj.inventoryitems.count(),
|
||||||
|
permission='dcim.view_inventoryitem',
|
||||||
|
always_display=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DeviceStatusView(generic.ObjectView):
|
class DeviceStatusView(generic.ObjectView):
|
||||||
@ -1736,9 +1928,14 @@ class DeviceConfigView(generic.ObjectView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Device, 'configcontext', path='config-context')
|
||||||
class DeviceConfigContextView(ObjectConfigContextView):
|
class DeviceConfigContextView(ObjectConfigContextView):
|
||||||
queryset = Device.objects.annotate_config_context_data()
|
queryset = Device.objects.annotate_config_context_data()
|
||||||
base_template = 'dcim/device/base.html'
|
base_template = 'dcim/device/base.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Config Context'),
|
||||||
|
permission='extras.view_configcontext'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DeviceEditView(generic.ObjectEditView):
|
class DeviceEditView(generic.ObjectEditView):
|
||||||
|
@ -352,7 +352,7 @@ class ObjectConfigContextView(generic.ObjectView):
|
|||||||
'source_contexts': source_contexts,
|
'source_contexts': source_contexts,
|
||||||
'format': format,
|
'format': format,
|
||||||
'base_template': self.base_template,
|
'base_template': self.base_template,
|
||||||
'active_tab': 'config-context',
|
'active_tab': 'configcontext',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,7 +57,6 @@ urlpatterns = [
|
|||||||
path('aggregates/edit/', views.AggregateBulkEditView.as_view(), name='aggregate_bulk_edit'),
|
path('aggregates/edit/', views.AggregateBulkEditView.as_view(), name='aggregate_bulk_edit'),
|
||||||
path('aggregates/delete/', views.AggregateBulkDeleteView.as_view(), name='aggregate_bulk_delete'),
|
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>/', 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>/edit/', views.AggregateEditView.as_view(), name='aggregate_edit'),
|
||||||
path('aggregates/<int:pk>/delete/', views.AggregateDeleteView.as_view(), name='aggregate_delete'),
|
path('aggregates/<int:pk>/delete/', views.AggregateDeleteView.as_view(), name='aggregate_delete'),
|
||||||
path('aggregates/<int:pk>/', include(get_model_urls('ipam', 'aggregate'))),
|
path('aggregates/<int:pk>/', include(get_model_urls('ipam', 'aggregate'))),
|
||||||
@ -82,9 +81,6 @@ urlpatterns = [
|
|||||||
path('prefixes/<int:pk>/', views.PrefixView.as_view(), name='prefix'),
|
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>/edit/', views.PrefixEditView.as_view(), name='prefix_edit'),
|
||||||
path('prefixes/<int:pk>/delete/', views.PrefixDeleteView.as_view(), name='prefix_delete'),
|
path('prefixes/<int:pk>/delete/', views.PrefixDeleteView.as_view(), name='prefix_delete'),
|
||||||
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'))),
|
path('prefixes/<int:pk>/', include(get_model_urls('ipam', 'prefix'))),
|
||||||
|
|
||||||
# IP ranges
|
# IP ranges
|
||||||
@ -96,7 +92,6 @@ urlpatterns = [
|
|||||||
path('ip-ranges/<int:pk>/', views.IPRangeView.as_view(), name='iprange'),
|
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>/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>/delete/', views.IPRangeDeleteView.as_view(), name='iprange_delete'),
|
||||||
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'))),
|
path('ip-ranges/<int:pk>/', include(get_model_urls('ipam', 'iprange'))),
|
||||||
|
|
||||||
# IP addresses
|
# IP addresses
|
||||||
|
@ -3,6 +3,7 @@ from django.db.models import Prefetch
|
|||||||
from django.db.models.expressions import RawSQL
|
from django.db.models.expressions import RawSQL
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from circuits.models import Provider, Circuit
|
from circuits.models import Provider, Circuit
|
||||||
from circuits.tables import ProviderTable
|
from circuits.tables import ProviderTable
|
||||||
@ -11,6 +12,7 @@ from dcim.models import Interface, Site, Device
|
|||||||
from dcim.tables import SiteTable
|
from dcim.tables import SiteTable
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
|
from utilities.views import ViewTab, register_model_view
|
||||||
from virtualization.filtersets import VMInterfaceFilterSet
|
from virtualization.filtersets import VMInterfaceFilterSet
|
||||||
from virtualization.models import VMInterface, VirtualMachine
|
from virtualization.models import VMInterface, VirtualMachine
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
@ -289,12 +291,18 @@ class AggregateView(generic.ObjectView):
|
|||||||
queryset = Aggregate.objects.all()
|
queryset = Aggregate.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Aggregate, 'prefixes')
|
||||||
class AggregatePrefixesView(generic.ObjectChildrenView):
|
class AggregatePrefixesView(generic.ObjectChildrenView):
|
||||||
queryset = Aggregate.objects.all()
|
queryset = Aggregate.objects.all()
|
||||||
child_model = Prefix
|
child_model = Prefix
|
||||||
table = tables.PrefixTable
|
table = tables.PrefixTable
|
||||||
filterset = filtersets.PrefixFilterSet
|
filterset = filtersets.PrefixFilterSet
|
||||||
template_name = 'ipam/aggregate/prefixes.html'
|
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):
|
def get_children(self, request, parent):
|
||||||
return Prefix.objects.restrict(request.user, 'view').filter(
|
return Prefix.objects.restrict(request.user, 'view').filter(
|
||||||
@ -466,12 +474,18 @@ class PrefixView(generic.ObjectView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Prefix, 'prefixes')
|
||||||
class PrefixPrefixesView(generic.ObjectChildrenView):
|
class PrefixPrefixesView(generic.ObjectChildrenView):
|
||||||
queryset = Prefix.objects.all()
|
queryset = Prefix.objects.all()
|
||||||
child_model = Prefix
|
child_model = Prefix
|
||||||
table = tables.PrefixTable
|
table = tables.PrefixTable
|
||||||
filterset = filtersets.PrefixFilterSet
|
filterset = filtersets.PrefixFilterSet
|
||||||
template_name = 'ipam/prefix/prefixes.html'
|
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):
|
def get_children(self, request, parent):
|
||||||
return parent.get_child_prefixes().restrict(request.user, 'view').prefetch_related(
|
return parent.get_child_prefixes().restrict(request.user, 'view').prefetch_related(
|
||||||
@ -495,12 +509,18 @@ class PrefixPrefixesView(generic.ObjectChildrenView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Prefix, 'ipranges', path='ip-ranges')
|
||||||
class PrefixIPRangesView(generic.ObjectChildrenView):
|
class PrefixIPRangesView(generic.ObjectChildrenView):
|
||||||
queryset = Prefix.objects.all()
|
queryset = Prefix.objects.all()
|
||||||
child_model = IPRange
|
child_model = IPRange
|
||||||
table = tables.IPRangeTable
|
table = tables.IPRangeTable
|
||||||
filterset = filtersets.IPRangeFilterSet
|
filterset = filtersets.IPRangeFilterSet
|
||||||
template_name = 'ipam/prefix/ip_ranges.html'
|
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):
|
def get_children(self, request, parent):
|
||||||
return parent.get_child_ranges().restrict(request.user, 'view').prefetch_related(
|
return parent.get_child_ranges().restrict(request.user, 'view').prefetch_related(
|
||||||
@ -510,17 +530,23 @@ class PrefixIPRangesView(generic.ObjectChildrenView):
|
|||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
return {
|
return {
|
||||||
'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}",
|
'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}",
|
||||||
'active_tab': 'ip-ranges',
|
'active_tab': 'ipranges',
|
||||||
'first_available_ip': instance.get_first_available_ip(),
|
'first_available_ip': instance.get_first_available_ip(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Prefix, 'ipaddresses', path='ip-addresses')
|
||||||
class PrefixIPAddressesView(generic.ObjectChildrenView):
|
class PrefixIPAddressesView(generic.ObjectChildrenView):
|
||||||
queryset = Prefix.objects.all()
|
queryset = Prefix.objects.all()
|
||||||
child_model = IPAddress
|
child_model = IPAddress
|
||||||
table = tables.IPAddressTable
|
table = tables.IPAddressTable
|
||||||
filterset = filtersets.IPAddressFilterSet
|
filterset = filtersets.IPAddressFilterSet
|
||||||
template_name = 'ipam/prefix/ip_addresses.html'
|
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):
|
def get_children(self, request, parent):
|
||||||
return parent.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf', 'tenant', 'tenant__group')
|
return parent.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf', 'tenant', 'tenant__group')
|
||||||
@ -533,7 +559,7 @@ class PrefixIPAddressesView(generic.ObjectChildrenView):
|
|||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
return {
|
return {
|
||||||
'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}",
|
'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}",
|
||||||
'active_tab': 'ip-addresses',
|
'active_tab': 'ipaddresses',
|
||||||
'first_available_ip': instance.get_first_available_ip(),
|
'first_available_ip': instance.get_first_available_ip(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -581,19 +607,25 @@ class IPRangeView(generic.ObjectView):
|
|||||||
queryset = IPRange.objects.all()
|
queryset = IPRange.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(IPRange, 'ipaddresses', path='ip-addresses')
|
||||||
class IPRangeIPAddressesView(generic.ObjectChildrenView):
|
class IPRangeIPAddressesView(generic.ObjectChildrenView):
|
||||||
queryset = IPRange.objects.all()
|
queryset = IPRange.objects.all()
|
||||||
child_model = IPAddress
|
child_model = IPAddress
|
||||||
table = tables.IPAddressTable
|
table = tables.IPAddressTable
|
||||||
filterset = filtersets.IPAddressFilterSet
|
filterset = filtersets.IPAddressFilterSet
|
||||||
template_name = 'ipam/iprange/ip_addresses.html'
|
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):
|
def get_children(self, request, parent):
|
||||||
return parent.get_child_ips().restrict(request.user, 'view')
|
return parent.get_child_ips().restrict(request.user, 'view')
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
return {
|
return {
|
||||||
'active_tab': 'ip-addresses',
|
'active_tab': 'ipaddresses',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ from extras.utils import is_taggable, register_features
|
|||||||
from netbox.signals import post_clean
|
from netbox.signals import post_clean
|
||||||
from utilities.json import CustomFieldJSONEncoder
|
from utilities.json import CustomFieldJSONEncoder
|
||||||
from utilities.utils import serialize_object
|
from utilities.utils import serialize_object
|
||||||
from utilities.views import ViewTab, register_model_view
|
from utilities.views import register_model_view
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ChangeLoggingMixin',
|
'ChangeLoggingMixin',
|
||||||
@ -299,13 +299,11 @@ def _register_features(sender, **kwargs):
|
|||||||
register_model_view(
|
register_model_view(
|
||||||
sender,
|
sender,
|
||||||
'journal',
|
'journal',
|
||||||
'netbox.views.generic.ObjectJournalView',
|
|
||||||
kwargs={'model': sender}
|
kwargs={'model': sender}
|
||||||
)
|
)('netbox.views.generic.ObjectJournalView')
|
||||||
if issubclass(sender, ChangeLoggingMixin):
|
if issubclass(sender, ChangeLoggingMixin):
|
||||||
register_model_view(
|
register_model_view(
|
||||||
sender,
|
sender,
|
||||||
'changelog',
|
'changelog',
|
||||||
'netbox.views.generic.ObjectChangeLogView',
|
|
||||||
kwargs={'model': sender}
|
kwargs={'model': sender}
|
||||||
)
|
)('netbox.views.generic.ObjectChangeLogView')
|
||||||
|
@ -56,87 +56,6 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_tabs %}
|
{% 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 %}
|
{% if perms.dcim.napalm_read_device and object.status == 'active' and object.primary_ip and object.platform.napalm_driver %}
|
||||||
{# NAPALM-enabled tabs #}
|
{# NAPALM-enabled tabs #}
|
||||||
<li role="presentation" class="nav-item">
|
<li role="presentation" class="nav-item">
|
||||||
@ -155,12 +74,4 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% 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 %}
|
{% endblock %}
|
||||||
|
@ -51,85 +51,3 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% 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>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% 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 %}
|
|
||||||
|
@ -6,13 +6,3 @@
|
|||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<li class="breadcrumb-item"><a href="{% url 'ipam:aggregate_list' %}?rir_id={{ object.rir.pk }}">{{ object.rir }}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'ipam:aggregate_list' %}?rir_id={{ object.rir.pk }}">{{ object.rir }}</a></li>
|
||||||
{% endblock %}
|
{% 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>
|
<li class="breadcrumb-item"><a href="{% url 'ipam:iprange_list' %}?vrf_id={{ object.vrf.pk }}">{{ object.vrf }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% 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>
|
<li class="breadcrumb-item"><a href="{% url 'ipam:prefix_list' %}?vrf_id={{ object.vrf.pk }}">{{ object.vrf }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% 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 %}
|
|
||||||
|
@ -24,20 +24,3 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% 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>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% 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,6 +1,6 @@
|
|||||||
from django import template
|
from django import template
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.module_loading import import_string
|
||||||
|
|
||||||
from extras.registry import registry
|
from extras.registry import registry
|
||||||
|
|
||||||
@ -26,23 +26,27 @@ def model_view_tabs(context, instance):
|
|||||||
views = []
|
views = []
|
||||||
|
|
||||||
# Compile a list of tabs to be displayed in the UI
|
# Compile a list of tabs to be displayed in the UI
|
||||||
for view in views:
|
for config in views:
|
||||||
if view['tab_label'] and (not view['tab_permission'] or user.has_perm(view['tab_permission'])):
|
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
|
||||||
|
|
||||||
# Determine the value of the tab's badge (if any)
|
# Determine the value of the tab's badge (if any)
|
||||||
if view['tab_badge'] and callable(view['tab_badge']):
|
if tab.badge and callable(tab.badge):
|
||||||
badge_value = view['tab_badge'](instance)
|
badge_value = tab.badge(instance)
|
||||||
elif view['tab_badge']:
|
|
||||||
badge_value = view['tab_badge']
|
|
||||||
else:
|
else:
|
||||||
badge_value = None
|
badge_value = tab.badge
|
||||||
|
|
||||||
|
if not tab.always_display and not badge_value:
|
||||||
|
continue
|
||||||
|
|
||||||
tabs.append({
|
tabs.append({
|
||||||
'name': view['name'],
|
'name': config['name'],
|
||||||
'url': reverse(f"{app_label}:{model_name}_{view['name']}", args=[instance.pk]),
|
'url': reverse(f"{app_label}:{model_name}_{config['name']}", args=[instance.pk]),
|
||||||
'label': view['tab_label'],
|
'label': tab.label,
|
||||||
'badge_value': badge_value,
|
'badge_value': badge_value,
|
||||||
'is_active': context.get('active_tab') == view['name'],
|
'is_active': context.get('active_tab') == config['name'],
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -30,9 +30,10 @@ def get_model_urls(app_label, model_name):
|
|||||||
view_ = config['view']
|
view_ = config['view']
|
||||||
if issubclass(view_, View):
|
if issubclass(view_, View):
|
||||||
view_ = view_.as_view()
|
view_ = view_.as_view()
|
||||||
|
|
||||||
# Create a path to the view
|
# Create a path to the view
|
||||||
paths.append(
|
paths.append(
|
||||||
path(f"{config['name']}/", view_, name=f"{model_name}_{config['name']}", kwargs=config['kwargs'])
|
path(f"{config['path']}/", view_, name=f"{model_name}_{config['name']}", kwargs=config['kwargs'])
|
||||||
)
|
)
|
||||||
|
|
||||||
return paths
|
return paths
|
||||||
|
@ -142,24 +142,39 @@ class ViewTab:
|
|||||||
self.always_display = always_display
|
self.always_display = always_display
|
||||||
|
|
||||||
|
|
||||||
def register_model_view(model, name, view, kwargs=None):
|
def register_model_view(model, name, path=None, kwargs=None):
|
||||||
"""
|
"""
|
||||||
Register a subview for a core model.
|
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:
|
Args:
|
||||||
model: The Django model class with which this view will be associated
|
model: The Django model class with which this view will be associated.
|
||||||
name: The name to register when creating a URL path
|
name: The string used to form the view's name for URL resolution (e.g. via `reverse()`). This will be appended
|
||||||
view: A class-based or function view, or the dotted path to it (e.g. 'myplugin.views.FooView')
|
to the name of the base view for the model using an underscore.
|
||||||
kwargs: A dictionary of keyword arguments to send to the view (optional)
|
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)
|
||||||
"""
|
"""
|
||||||
app_label = model._meta.app_label
|
def _wrapper(cls):
|
||||||
model_name = model._meta.model_name
|
app_label = model._meta.app_label
|
||||||
|
model_name = model._meta.model_name
|
||||||
|
|
||||||
if model_name not in registry['views'][app_label]:
|
if model_name not in registry['views'][app_label]:
|
||||||
registry['views'][app_label][model_name] = []
|
registry['views'][app_label][model_name] = []
|
||||||
|
|
||||||
registry['views'][app_label][model_name].append({
|
registry['views'][app_label][model_name].append({
|
||||||
'name': name,
|
'name': name,
|
||||||
'view': view,
|
'view': cls,
|
||||||
'kwargs': kwargs or {},
|
'path': path or name,
|
||||||
})
|
'kwargs': kwargs or {},
|
||||||
|
})
|
||||||
|
|
||||||
|
return cls
|
||||||
|
|
||||||
|
return _wrapper
|
||||||
|
@ -35,8 +35,6 @@ urlpatterns = [
|
|||||||
path('clusters/edit/', views.ClusterBulkEditView.as_view(), name='cluster_bulk_edit'),
|
path('clusters/edit/', views.ClusterBulkEditView.as_view(), name='cluster_bulk_edit'),
|
||||||
path('clusters/delete/', views.ClusterBulkDeleteView.as_view(), name='cluster_bulk_delete'),
|
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>/', 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>/edit/', views.ClusterEditView.as_view(), name='cluster_edit'),
|
||||||
path('clusters/<int:pk>/delete/', views.ClusterDeleteView.as_view(), name='cluster_delete'),
|
path('clusters/<int:pk>/delete/', views.ClusterDeleteView.as_view(), name='cluster_delete'),
|
||||||
path('clusters/<int:pk>/devices/add/', views.ClusterAddDevicesView.as_view(), name='cluster_add_devices'),
|
path('clusters/<int:pk>/devices/add/', views.ClusterAddDevicesView.as_view(), name='cluster_add_devices'),
|
||||||
@ -50,10 +48,8 @@ urlpatterns = [
|
|||||||
path('virtual-machines/edit/', views.VirtualMachineBulkEditView.as_view(), name='virtualmachine_bulk_edit'),
|
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/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>/', 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>/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>/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>/', include(get_model_urls('virtualization', 'virtualmachine'))),
|
path('virtual-machines/<int:pk>/', include(get_model_urls('virtualization', 'virtualmachine'))),
|
||||||
|
|
||||||
# VM interfaces
|
# VM interfaces
|
||||||
|
@ -3,6 +3,7 @@ from django.db import transaction
|
|||||||
from django.db.models import Prefetch
|
from django.db.models import Prefetch
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from dcim.filtersets import DeviceFilterSet
|
from dcim.filtersets import DeviceFilterSet
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
@ -12,6 +13,7 @@ from ipam.models import IPAddress, Service
|
|||||||
from ipam.tables import AssignedIPAddressesTable, InterfaceVLANTable
|
from ipam.tables import AssignedIPAddressesTable, InterfaceVLANTable
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
|
from utilities.views import ViewTab, register_model_view
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
||||||
|
|
||||||
@ -161,28 +163,40 @@ class ClusterView(generic.ObjectView):
|
|||||||
queryset = Cluster.objects.all()
|
queryset = Cluster.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Cluster, 'virtualmachines', path='virtual-machines')
|
||||||
class ClusterVirtualMachinesView(generic.ObjectChildrenView):
|
class ClusterVirtualMachinesView(generic.ObjectChildrenView):
|
||||||
queryset = Cluster.objects.all()
|
queryset = Cluster.objects.all()
|
||||||
child_model = VirtualMachine
|
child_model = VirtualMachine
|
||||||
table = tables.VirtualMachineTable
|
table = tables.VirtualMachineTable
|
||||||
filterset = filtersets.VirtualMachineFilterSet
|
filterset = filtersets.VirtualMachineFilterSet
|
||||||
template_name = 'virtualization/cluster/virtual_machines.html'
|
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):
|
def get_children(self, request, parent):
|
||||||
return VirtualMachine.objects.restrict(request.user, 'view').filter(cluster=parent)
|
return VirtualMachine.objects.restrict(request.user, 'view').filter(cluster=parent)
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
return {
|
return {
|
||||||
'active_tab': 'virtual-machines',
|
'active_tab': 'virtualmachines',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(Cluster, 'devices')
|
||||||
class ClusterDevicesView(generic.ObjectChildrenView):
|
class ClusterDevicesView(generic.ObjectChildrenView):
|
||||||
queryset = Cluster.objects.all()
|
queryset = Cluster.objects.all()
|
||||||
child_model = Device
|
child_model = Device
|
||||||
table = DeviceTable
|
table = DeviceTable
|
||||||
filterset = DeviceFilterSet
|
filterset = DeviceFilterSet
|
||||||
template_name = 'virtualization/cluster/devices.html'
|
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):
|
def get_children(self, request, parent):
|
||||||
return Device.objects.restrict(request.user, 'view').filter(cluster=parent)
|
return Device.objects.restrict(request.user, 'view').filter(cluster=parent)
|
||||||
@ -344,12 +358,18 @@ class VirtualMachineView(generic.ObjectView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VirtualMachine, 'interfaces')
|
||||||
class VirtualMachineInterfacesView(generic.ObjectChildrenView):
|
class VirtualMachineInterfacesView(generic.ObjectChildrenView):
|
||||||
queryset = VirtualMachine.objects.all()
|
queryset = VirtualMachine.objects.all()
|
||||||
child_model = VMInterface
|
child_model = VMInterface
|
||||||
table = tables.VirtualMachineVMInterfaceTable
|
table = tables.VirtualMachineVMInterfaceTable
|
||||||
filterset = filtersets.VMInterfaceFilterSet
|
filterset = filtersets.VMInterfaceFilterSet
|
||||||
template_name = 'virtualization/virtualmachine/interfaces.html'
|
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):
|
def get_children(self, request, parent):
|
||||||
return parent.interfaces.restrict(request.user, 'view').prefetch_related(
|
return parent.interfaces.restrict(request.user, 'view').prefetch_related(
|
||||||
@ -363,9 +383,14 @@ class VirtualMachineInterfacesView(generic.ObjectChildrenView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(VirtualMachine, 'configcontext', path='config-context')
|
||||||
class VirtualMachineConfigContextView(ObjectConfigContextView):
|
class VirtualMachineConfigContextView(ObjectConfigContextView):
|
||||||
queryset = VirtualMachine.objects.annotate_config_context_data()
|
queryset = VirtualMachine.objects.annotate_config_context_data()
|
||||||
base_template = 'virtualization/virtualmachine.html'
|
base_template = 'virtualization/virtualmachine.html'
|
||||||
|
tab = ViewTab(
|
||||||
|
label=_('Config Context'),
|
||||||
|
permission='extras.view_configcontext'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class VirtualMachineEditView(generic.ObjectEditView):
|
class VirtualMachineEditView(generic.ObjectEditView):
|
||||||
|
Reference in New Issue
Block a user