Add dedicated views for organizational models

This commit is contained in:
Jeremy Stretch 2021-03-26 14:44:43 -04:00
parent bb00f2ff46
commit b7e44a744d
31 changed files with 949 additions and 69 deletions

View File

@ -175,7 +175,7 @@ class CircuitType(OrganizationalModel):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return "{}?type={}".format(reverse('circuits:circuit_list'), self.slug) return reverse('circuits:circuittype', args=[self.pk])
def to_csv(self): def to_csv(self):
return ( return (

View File

@ -38,6 +38,7 @@ urlpatterns = [
path('circuit-types/import/', views.CircuitTypeBulkImportView.as_view(), name='circuittype_import'), path('circuit-types/import/', views.CircuitTypeBulkImportView.as_view(), name='circuittype_import'),
path('circuit-types/edit/', views.CircuitTypeBulkEditView.as_view(), name='circuittype_bulk_edit'), path('circuit-types/edit/', views.CircuitTypeBulkEditView.as_view(), name='circuittype_bulk_edit'),
path('circuit-types/delete/', views.CircuitTypeBulkDeleteView.as_view(), name='circuittype_bulk_delete'), path('circuit-types/delete/', views.CircuitTypeBulkDeleteView.as_view(), name='circuittype_bulk_delete'),
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>/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>/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>/changelog/', ObjectChangeLogView.as_view(), name='circuittype_changelog', kwargs={'model': CircuitType}),

View File

@ -147,6 +147,23 @@ class CircuitTypeListView(generic.ObjectListView):
table = tables.CircuitTypeTable table = tables.CircuitTypeTable
class CircuitTypeView(generic.ObjectView):
queryset = CircuitType.objects.all()
def get_extra_context(self, request, instance):
circuits = Circuit.objects.restrict(request.user, 'view').filter(
type=instance
)
circuits_table = tables.CircuitTable(circuits)
circuits_table.columns.hide('type')
paginate_table(circuits_table, request)
return {
'circuits_table': circuits_table,
}
class CircuitTypeEditView(generic.ObjectEditView): class CircuitTypeEditView(generic.ObjectEditView):
queryset = CircuitType.objects.all() queryset = CircuitType.objects.all()
model_form = forms.CircuitTypeForm model_form = forms.CircuitTypeForm

View File

@ -65,7 +65,7 @@ class Manufacturer(OrganizationalModel):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return "{}?manufacturer={}".format(reverse('dcim:devicetype_list'), self.slug) return reverse('dcim:manufacturer', args=[self.pk])
def to_csv(self): def to_csv(self):
return ( return (
@ -375,6 +375,9 @@ class DeviceRole(OrganizationalModel):
def __str__(self): def __str__(self):
return self.name return self.name
def get_absolute_url(self):
return reverse('dcim:devicerole', args=[self.pk])
def to_csv(self): def to_csv(self):
return ( return (
self.name, self.name,
@ -436,7 +439,7 @@ class Platform(OrganizationalModel):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return "{}?platform={}".format(reverse('dcim:device_list'), self.slug) return reverse('dcim:platform', args=[self.pk])
def to_csv(self): def to_csv(self):
return ( return (

View File

@ -67,7 +67,7 @@ class RackRole(OrganizationalModel):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return "{}?role={}".format(reverse('dcim:rack_list'), self.slug) return reverse('dcim:rackrole', args=[self.pk])
def to_csv(self): def to_csv(self):
return ( return (

View File

@ -50,6 +50,9 @@ __all__ = (
class DeviceRoleTable(BaseTable): class DeviceRoleTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.Column(
linkify=True
)
device_count = LinkedCountColumn( device_count = LinkedCountColumn(
viewname='dcim:device_list', viewname='dcim:device_list',
url_params={'role': 'slug'}, url_params={'role': 'slug'},
@ -76,6 +79,9 @@ class DeviceRoleTable(BaseTable):
class PlatformTable(BaseTable): class PlatformTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.Column(
linkify=True
)
device_count = LinkedCountColumn( device_count = LinkedCountColumn(
viewname='dcim:device_list', viewname='dcim:device_list',
url_params={'platform': 'slug'}, url_params={'platform': 'slug'},

View File

@ -57,6 +57,7 @@ urlpatterns = [
path('rack-roles/import/', views.RackRoleBulkImportView.as_view(), name='rackrole_import'), path('rack-roles/import/', views.RackRoleBulkImportView.as_view(), name='rackrole_import'),
path('rack-roles/edit/', views.RackRoleBulkEditView.as_view(), name='rackrole_bulk_edit'), path('rack-roles/edit/', views.RackRoleBulkEditView.as_view(), name='rackrole_bulk_edit'),
path('rack-roles/delete/', views.RackRoleBulkDeleteView.as_view(), name='rackrole_bulk_delete'), path('rack-roles/delete/', views.RackRoleBulkDeleteView.as_view(), name='rackrole_bulk_delete'),
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>/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>/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>/changelog/', ObjectChangeLogView.as_view(), name='rackrole_changelog', kwargs={'model': RackRole}),
@ -93,6 +94,7 @@ urlpatterns = [
path('manufacturers/import/', views.ManufacturerBulkImportView.as_view(), name='manufacturer_import'), path('manufacturers/import/', views.ManufacturerBulkImportView.as_view(), name='manufacturer_import'),
path('manufacturers/edit/', views.ManufacturerBulkEditView.as_view(), name='manufacturer_bulk_edit'), path('manufacturers/edit/', views.ManufacturerBulkEditView.as_view(), name='manufacturer_bulk_edit'),
path('manufacturers/delete/', views.ManufacturerBulkDeleteView.as_view(), name='manufacturer_bulk_delete'), path('manufacturers/delete/', views.ManufacturerBulkDeleteView.as_view(), name='manufacturer_bulk_delete'),
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>/edit/', views.ManufacturerEditView.as_view(), name='manufacturer_edit'),
path('manufacturers/<int:pk>/delete/', views.ManufacturerDeleteView.as_view(), name='manufacturer_delete'), 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>/changelog/', ObjectChangeLogView.as_view(), name='manufacturer_changelog', kwargs={'model': Manufacturer}),
@ -179,6 +181,7 @@ urlpatterns = [
path('device-roles/import/', views.DeviceRoleBulkImportView.as_view(), name='devicerole_import'), path('device-roles/import/', views.DeviceRoleBulkImportView.as_view(), name='devicerole_import'),
path('device-roles/edit/', views.DeviceRoleBulkEditView.as_view(), name='devicerole_bulk_edit'), path('device-roles/edit/', views.DeviceRoleBulkEditView.as_view(), name='devicerole_bulk_edit'),
path('device-roles/delete/', views.DeviceRoleBulkDeleteView.as_view(), name='devicerole_bulk_delete'), path('device-roles/delete/', views.DeviceRoleBulkDeleteView.as_view(), name='devicerole_bulk_delete'),
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>/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>/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>/changelog/', ObjectChangeLogView.as_view(), name='devicerole_changelog', kwargs={'model': DeviceRole}),
@ -189,6 +192,7 @@ urlpatterns = [
path('platforms/import/', views.PlatformBulkImportView.as_view(), name='platform_import'), path('platforms/import/', views.PlatformBulkImportView.as_view(), name='platform_import'),
path('platforms/edit/', views.PlatformBulkEditView.as_view(), name='platform_bulk_edit'), path('platforms/edit/', views.PlatformBulkEditView.as_view(), name='platform_bulk_edit'),
path('platforms/delete/', views.PlatformBulkDeleteView.as_view(), name='platform_bulk_delete'), path('platforms/delete/', views.PlatformBulkDeleteView.as_view(), name='platform_bulk_delete'),
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>/edit/', views.PlatformEditView.as_view(), name='platform_edit'),
path('platforms/<int:pk>/delete/', views.PlatformDeleteView.as_view(), name='platform_delete'), 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>/changelog/', ObjectChangeLogView.as_view(), name='platform_changelog', kwargs={'model': Platform}),

View File

@ -20,6 +20,7 @@ from secrets.models import Secret
from utilities.forms import ConfirmationForm 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.tables import paginate_table
from utilities.utils import csv_format, count_related from utilities.utils import csv_format, count_related
from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin
from virtualization.models import VirtualMachine from virtualization.models import VirtualMachine
@ -341,6 +342,23 @@ class RackRoleListView(generic.ObjectListView):
table = tables.RackRoleTable table = tables.RackRoleTable
class RackRoleView(generic.ObjectView):
queryset = RackRole.objects.all()
def get_extra_context(self, request, instance):
racks = Rack.objects.restrict(request.user, 'view').filter(
role=instance
)
racks_table = tables.RackTable(racks)
racks_table.columns.hide('role')
paginate_table(racks_table, request)
return {
'racks_table': racks_table,
}
class RackRoleEditView(generic.ObjectEditView): class RackRoleEditView(generic.ObjectEditView):
queryset = RackRole.objects.all() queryset = RackRole.objects.all()
model_form = forms.RackRoleForm model_form = forms.RackRoleForm
@ -567,6 +585,23 @@ class ManufacturerListView(generic.ObjectListView):
table = tables.ManufacturerTable table = tables.ManufacturerTable
class ManufacturerView(generic.ObjectView):
queryset = Manufacturer.objects.all()
def get_extra_context(self, request, instance):
devicetypes = DeviceType.objects.restrict(request.user, 'view').filter(
manufacturer=instance
)
devicetypes_table = tables.DeviceTypeTable(devicetypes)
devicetypes_table.columns.hide('manufacturer')
paginate_table(devicetypes_table, request)
return {
'devicetypes_table': devicetypes_table,
}
class ManufacturerEditView(generic.ObjectEditView): class ManufacturerEditView(generic.ObjectEditView):
queryset = Manufacturer.objects.all() queryset = Manufacturer.objects.all()
model_form = forms.ManufacturerForm model_form = forms.ManufacturerForm
@ -1017,6 +1052,23 @@ class DeviceRoleListView(generic.ObjectListView):
table = tables.DeviceRoleTable table = tables.DeviceRoleTable
class DeviceRoleView(generic.ObjectView):
queryset = DeviceRole.objects.all()
def get_extra_context(self, request, instance):
devices = Device.objects.restrict(request.user, 'view').filter(
device_role=instance
)
devices_table = tables.DeviceTable(devices)
devices_table.columns.hide('device_role')
paginate_table(devices_table, request)
return {
'devices_table': devices_table,
}
class DeviceRoleEditView(generic.ObjectEditView): class DeviceRoleEditView(generic.ObjectEditView):
queryset = DeviceRole.objects.all() queryset = DeviceRole.objects.all()
model_form = forms.DeviceRoleForm model_form = forms.DeviceRoleForm
@ -1056,6 +1108,23 @@ class PlatformListView(generic.ObjectListView):
table = tables.PlatformTable table = tables.PlatformTable
class PlatformView(generic.ObjectView):
queryset = Platform.objects.all()
def get_extra_context(self, request, instance):
devices = Device.objects.restrict(request.user, 'view').filter(
platform=instance
)
devices_table = tables.DeviceTable(devices)
devices_table.columns.hide('platform')
paginate_table(devices_table, request)
return {
'devices_table': devices_table,
}
class PlatformEditView(generic.ObjectEditView): class PlatformEditView(generic.ObjectEditView):
queryset = Platform.objects.all() queryset = Platform.objects.all()
model_form = forms.PlatformForm model_form = forms.PlatformForm

View File

@ -66,7 +66,7 @@ class RIR(OrganizationalModel):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return "{}?rir={}".format(reverse('ipam:aggregate_list'), self.slug) return reverse('ipam:rir', args=[self.pk])
def to_csv(self): def to_csv(self):
return ( return (
@ -216,6 +216,9 @@ class Role(OrganizationalModel):
def __str__(self): def __str__(self):
return self.name return self.name
def get_absolute_url(self):
return reverse('ipam:role', args=[self.pk])
def to_csv(self): def to_csv(self):
return ( return (
self.name, self.name,

View File

@ -70,7 +70,7 @@ class VLANGroup(OrganizationalModel):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return reverse('ipam:vlangroup_vlans', args=[self.pk]) return reverse('ipam:vlangroup', args=[self.pk])
def clean(self): def clean(self):
super().clean() super().clean()

View File

@ -224,6 +224,9 @@ class AggregateDetailTable(AggregateTable):
class RoleTable(BaseTable): class RoleTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.Column(
linkify=True
)
prefix_count = LinkedCountColumn( prefix_count = LinkedCountColumn(
viewname='ipam:prefix_list', viewname='ipam:prefix_list',
url_params={'role': 'slug'}, url_params={'role': 'slug'},
@ -450,9 +453,8 @@ class VLANTable(BaseTable):
site = tables.Column( site = tables.Column(
linkify=True linkify=True
) )
group = tables.LinkColumn( group = tables.Column(
viewname='ipam:vlangroup_vlans', linkify=True
args=[Accessor('group__pk')]
) )
tenant = TenantColumn() tenant = TenantColumn()
status = ChoiceFieldColumn( status = ChoiceFieldColumn(

View File

@ -37,6 +37,7 @@ urlpatterns = [
path('rirs/import/', views.RIRBulkImportView.as_view(), name='rir_import'), path('rirs/import/', views.RIRBulkImportView.as_view(), name='rir_import'),
path('rirs/edit/', views.RIRBulkEditView.as_view(), name='rir_bulk_edit'), path('rirs/edit/', views.RIRBulkEditView.as_view(), name='rir_bulk_edit'),
path('rirs/delete/', views.RIRBulkDeleteView.as_view(), name='rir_bulk_delete'), path('rirs/delete/', views.RIRBulkDeleteView.as_view(), name='rir_bulk_delete'),
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>/edit/', views.RIREditView.as_view(), name='rir_edit'),
path('rirs/<int:pk>/delete/', views.RIRDeleteView.as_view(), name='rir_delete'), 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>/changelog/', ObjectChangeLogView.as_view(), name='rir_changelog', kwargs={'model': RIR}),
@ -59,6 +60,7 @@ urlpatterns = [
path('roles/import/', views.RoleBulkImportView.as_view(), name='role_import'), path('roles/import/', views.RoleBulkImportView.as_view(), name='role_import'),
path('roles/edit/', views.RoleBulkEditView.as_view(), name='role_bulk_edit'), path('roles/edit/', views.RoleBulkEditView.as_view(), name='role_bulk_edit'),
path('roles/delete/', views.RoleBulkDeleteView.as_view(), name='role_bulk_delete'), path('roles/delete/', views.RoleBulkDeleteView.as_view(), name='role_bulk_delete'),
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>/edit/', views.RoleEditView.as_view(), name='role_edit'),
path('roles/<int:pk>/delete/', views.RoleDeleteView.as_view(), name='role_delete'), 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>/changelog/', ObjectChangeLogView.as_view(), name='role_changelog', kwargs={'model': Role}),
@ -97,9 +99,9 @@ urlpatterns = [
path('vlan-groups/import/', views.VLANGroupBulkImportView.as_view(), name='vlangroup_import'), path('vlan-groups/import/', views.VLANGroupBulkImportView.as_view(), name='vlangroup_import'),
path('vlan-groups/edit/', views.VLANGroupBulkEditView.as_view(), name='vlangroup_bulk_edit'), path('vlan-groups/edit/', views.VLANGroupBulkEditView.as_view(), name='vlangroup_bulk_edit'),
path('vlan-groups/delete/', views.VLANGroupBulkDeleteView.as_view(), name='vlangroup_bulk_delete'), path('vlan-groups/delete/', views.VLANGroupBulkDeleteView.as_view(), name='vlangroup_bulk_delete'),
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>/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>/delete/', views.VLANGroupDeleteView.as_view(), name='vlangroup_delete'),
path('vlan-groups/<int:pk>/vlans/', views.VLANGroupVLANsView.as_view(), name='vlangroup_vlans'),
path('vlan-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='vlangroup_changelog', kwargs={'model': VLANGroup}), path('vlan-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='vlangroup_changelog', kwargs={'model': VLANGroup}),
# VLANs # VLANs

View File

@ -148,6 +148,23 @@ class RIRListView(generic.ObjectListView):
template_name = 'ipam/rir_list.html' template_name = 'ipam/rir_list.html'
class RIRView(generic.ObjectView):
queryset = RIR.objects.all()
def get_extra_context(self, request, instance):
aggregates = Aggregate.objects.restrict(request.user, 'view').filter(
rir=instance
)
aggregates_table = tables.AggregateTable(aggregates)
aggregates_table.columns.hide('rir')
paginate_table(aggregates_table, request)
return {
'aggregates_table': aggregates_table,
}
class RIREditView(generic.ObjectEditView): class RIREditView(generic.ObjectEditView):
queryset = RIR.objects.all() queryset = RIR.objects.all()
model_form = forms.RIRForm model_form = forms.RIRForm
@ -286,6 +303,23 @@ class RoleListView(generic.ObjectListView):
table = tables.RoleTable table = tables.RoleTable
class RoleView(generic.ObjectView):
queryset = Role.objects.all()
def get_extra_context(self, request, instance):
prefixes = Prefix.objects.restrict(request.user, 'view').filter(
role=instance
)
prefixes_table = tables.PrefixTable(prefixes)
prefixes_table.columns.hide('role')
paginate_table(prefixes_table, request)
return {
'prefixes_table': prefixes_table,
}
class RoleEditView(generic.ObjectEditView): class RoleEditView(generic.ObjectEditView):
queryset = Role.objects.all() queryset = Role.objects.all()
model_form = forms.RoleForm model_form = forms.RoleForm
@ -633,6 +667,29 @@ class VLANGroupListView(generic.ObjectListView):
table = tables.VLANGroupTable table = tables.VLANGroupTable
class VLANGroupView(generic.ObjectView):
queryset = VLANGroup.objects.all()
def get_extra_context(self, request, instance):
vlans = VLAN.objects.restrict(request.user, 'view').filter(group=instance).prefetch_related(
Prefetch('prefixes', queryset=Prefix.objects.restrict(request.user))
)
vlans_count = vlans.count()
vlans = add_available_vlans(instance, vlans)
vlans_table = tables.VLANDetailTable(vlans)
if request.user.has_perm('ipam.change_vlan') or request.user.has_perm('ipam.delete_vlan'):
vlans_table.columns.show('pk')
vlans_table.columns.hide('site')
vlans_table.columns.hide('group')
paginate_table(vlans_table, request)
return {
'vlans_count': vlans_count,
'vlans_table': vlans_table,
}
class VLANGroupEditView(generic.ObjectEditView): class VLANGroupEditView(generic.ObjectEditView):
queryset = VLANGroup.objects.all() queryset = VLANGroup.objects.all()
model_form = forms.VLANGroupForm model_form = forms.VLANGroupForm
@ -666,38 +723,6 @@ class VLANGroupBulkDeleteView(generic.BulkDeleteView):
table = tables.VLANGroupTable table = tables.VLANGroupTable
class VLANGroupVLANsView(generic.ObjectView):
queryset = VLANGroup.objects.all()
template_name = 'ipam/vlangroup_vlans.html'
def get_extra_context(self, request, instance):
vlans = VLAN.objects.restrict(request.user, 'view').filter(group=instance).prefetch_related(
Prefetch('prefixes', queryset=Prefix.objects.restrict(request.user))
)
vlans = add_available_vlans(instance, vlans)
vlan_table = tables.VLANDetailTable(vlans)
if request.user.has_perm('ipam.change_vlan') or request.user.has_perm('ipam.delete_vlan'):
vlan_table.columns.show('pk')
vlan_table.columns.hide('site')
vlan_table.columns.hide('group')
paginate_table(vlan_table, request)
# Compile permissions list for rendering the object table
permissions = {
'add': request.user.has_perm('ipam.add_vlan'),
'change': request.user.has_perm('ipam.change_vlan'),
'delete': request.user.has_perm('ipam.delete_vlan'),
}
return {
'first_available_vlan': instance.get_next_available_vid(),
'bulk_querystring': f'group_id={instance.pk}',
'vlan_table': vlan_table,
'permissions': permissions,
}
# #
# VLANs # VLANs
# #

View File

@ -263,7 +263,7 @@ class SecretRole(OrganizationalModel):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return "{}?role={}".format(reverse('secrets:secret_list'), self.slug) return reverse('secrets:secretrole', args=[self.pk])
def to_csv(self): def to_csv(self):
return ( return (

View File

@ -13,6 +13,7 @@ urlpatterns = [
path('secret-roles/import/', views.SecretRoleBulkImportView.as_view(), name='secretrole_import'), path('secret-roles/import/', views.SecretRoleBulkImportView.as_view(), name='secretrole_import'),
path('secret-roles/edit/', views.SecretRoleBulkEditView.as_view(), name='secretrole_bulk_edit'), path('secret-roles/edit/', views.SecretRoleBulkEditView.as_view(), name='secretrole_bulk_edit'),
path('secret-roles/delete/', views.SecretRoleBulkDeleteView.as_view(), name='secretrole_bulk_delete'), path('secret-roles/delete/', views.SecretRoleBulkDeleteView.as_view(), name='secretrole_bulk_delete'),
path('secret-roles/<int:pk>/', views.SecretRoleView.as_view(), name='secretrole'),
path('secret-roles/<int:pk>/edit/', views.SecretRoleEditView.as_view(), name='secretrole_edit'), path('secret-roles/<int:pk>/edit/', views.SecretRoleEditView.as_view(), name='secretrole_edit'),
path('secret-roles/<int:pk>/delete/', views.SecretRoleDeleteView.as_view(), name='secretrole_delete'), path('secret-roles/<int:pk>/delete/', views.SecretRoleDeleteView.as_view(), name='secretrole_delete'),
path('secret-roles/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='secretrole_changelog', kwargs={'model': SecretRole}), path('secret-roles/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='secretrole_changelog', kwargs={'model': SecretRole}),

View File

@ -7,6 +7,7 @@ from django.utils.html import escape
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from netbox.views import generic from netbox.views import generic
from utilities.tables import paginate_table
from utilities.utils import count_related from utilities.utils import count_related
from . import filters, forms, tables from . import filters, forms, tables
from .models import SecretRole, Secret, SessionKey, UserKey from .models import SecretRole, Secret, SessionKey, UserKey
@ -33,6 +34,23 @@ class SecretRoleListView(generic.ObjectListView):
table = tables.SecretRoleTable table = tables.SecretRoleTable
class SecretRoleView(generic.ObjectView):
queryset = SecretRole.objects.all()
def get_extra_context(self, request, instance):
secrets = Secret.objects.restrict(request.user, 'view').filter(
role=instance
)
secrets_table = tables.SecretTable(secrets)
secrets_table.columns.hide('role')
paginate_table(secrets_table, request)
return {
'secrets_table': secrets_table,
}
class SecretRoleEditView(generic.ObjectEditView): class SecretRoleEditView(generic.ObjectEditView):
queryset = SecretRole.objects.all() queryset = SecretRole.objects.all()
model_form = forms.SecretRoleForm model_form = forms.SecretRoleForm

View File

@ -0,0 +1,60 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'circuits:circuittype_list' %}">Circuit Types</a></li>
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Circuit Type</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>Circuits</td>
<td>
<a href="{% url 'circuits:circuit_list' %}?type_id={{ object.pk }}">{{ circuits_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Circuits</strong>
</div>
{% include 'inc/table.html' with table=circuits_table %}
{% if perms.circuits.add_circuit %}
<div class="panel-footer text-right noprint">
<a href="{% url 'circuits:circuit_add' %}?type={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add circuit
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=circuits_table.paginator page=circuits_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,76 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'dcim:devicerole_list' %}">Device Roles</a></li>
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Device Role</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>Color</td>
<td>
<span class="label color-block" style="background-color: #{{ object.color }}">&nbsp;</span>
</td>
</tr>
<tr>
<td>VM Role</td>
<td>
{% if object.vm_role %}
<i class="mdi mdi-check-bold text-success" title="Yes"></i>
{% else %}
<i class="mdi mdi-close-thick text-danger" title="No"></i>
{% endif %}
</td>
</tr>
<tr>
<td>Devices</td>
<td>
<a href="{% url 'dcim:device_list' %}?role_id={{ object.pk }}">{{ devices_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Devices</strong>
</div>
{% include 'inc/table.html' with table=devices_table %}
{% if perms.dcim.add_device %}
<div class="panel-footer text-right noprint">
<a href="{% url 'dcim:device_add' %}?device_role={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add device
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=devices_table.paginator page=devices_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,60 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'dcim:manufacturer_list' %}">Manufacturers</a></li>
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Manufacturer</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>Device types</td>
<td>
<a href="{% url 'dcim:devicetype_list' %}?manufacturer_id={{ object.pk }}">{{ devicetypes_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Device Types</strong>
</div>
{% include 'inc/table.html' with table=devicetypes_table %}
{% if perms.dcim.add_devicetype %}
<div class="panel-footer text-right noprint">
<a href="{% url 'dcim:devicetype_add' %}?manufacturer={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add device type
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=devicetypes_table.paginator page=devicetypes_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,68 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'dcim:platform_list' %}">Platforms</a></li>
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Platform</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>NAPALM Driver</td>
<td>{{ object.napalm_driver }}</td>
</tr>
<tr>
<td>NAPALM Arguments</td>
<td><pre>{{ object.napalm_args }}</pre></td>
</tr>
<tr>
<td>Devices</td>
<td>
<a href="{% url 'dcim:device_list' %}?platform_id={{ object.pk }}">{{ devices_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Devices</strong>
</div>
{% include 'inc/table.html' with table=devices_table %}
{% if perms.dcim.add_device %}
<div class="panel-footer text-right noprint">
<a href="{% url 'dcim:device_add' %}?device_role={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add device
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=devices_table.paginator page=devices_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,66 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'dcim:rackrole_list' %}">Rack Roles</a></li>
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Rack Role</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>Color</td>
<td>
<span class="label color-block" style="background-color: #{{ object.color }}">&nbsp;</span>
</td>
</tr>
<tr>
<td>Racks</td>
<td>
<a href="{% url 'dcim:rack_list' %}?role_id={{ object.pk }}">{{ racks_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Racks</strong>
</div>
{% include 'inc/table.html' with table=racks_table %}
{% if perms.dcim.add_rack %}
<div class="panel-footer text-right noprint">
<a href="{% url 'dcim:rack_add' %}?role={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add rack
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=racks_table.paginator page=racks_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,70 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'ipam:rir_list' %}">RIRs</a></li>
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>RIR</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>Private</td>
<td>
{% if object.is_private %}
<i class="mdi mdi-check-bold text-success" title="Yes"></i>
{% else %}
<i class="mdi mdi-close-thick text-danger" title="No"></i>
{% endif %}
</td>
</tr>
<tr>
<td>Aggregates</td>
<td>
<a href="{% url 'ipam:aggregate_list' %}?rir_id={{ object.pk }}">{{ aggregates_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Aggregates</strong>
</div>
{% include 'inc/table.html' with table=aggregates_table %}
{% if perms.ipam.add_aggregate %}
<div class="panel-footer text-right noprint">
<a href="{% url 'ipam:aggregate_add' %}?rir={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add aggregate
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=aggregates_table.paginator page=aggregates_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,64 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'ipam:role_list' %}">Roles</a></li>
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Role</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>Weight</td>
<td>{{ object.weight }}</td>
</tr>
<tr>
<td>Prefixes</td>
<td>
<a href="{% url 'ipam:prefix_list' %}?role_id={{ object.pk }}">{{ prefixes_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Prefixes</strong>
</div>
{% include 'inc/table.html' with table=prefixes_table %}
{% if perms.ipam.add_prefix %}
<div class="panel-footer text-right noprint">
<a href="{% url 'ipam:prefix_add' %}?role={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add prefix
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=prefixes_table.paginator page=prefixes_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,72 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'ipam:vlangroup_list' %}">VLAN Groups</a></li>
{% if object.scope %}
<li><a href="{{ object.scope.get_absolute_url }}">{{ object.scope }}</a></li>
{% endif %}
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>VLAN Group</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>Scope</td>
<td>
{% if object.scope %}
<a href="{{ object.scope.get_absolute_url }}">{{ object.scope }}</a>
{% else %}
<span class="text-muted">&mdash;</span>
{% endif %}
</tr>
<tr>
<td>VLANs</td>
<td>
<a href="{% url 'ipam:vlan_list' %}?group_id={{ object.pk }}">{{ vlans_count }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>VLANs</strong>
</div>
{% include 'inc/table.html' with table=vlans_table %}
{% if perms.ipam.add_vlan %}
<div class="panel-footer text-right noprint">
<a href="{% url 'ipam:vlan_add' %}?role={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add VLAN
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=vlans_table.paginator page=vlans_table.page %}
</div>
</div>
{% endblock %}

View File

@ -1,24 +0,0 @@
{% extends 'base.html' %}
{% block title %}{{ object }} - VLANs{% endblock %}
{% block content %}
<div class="row noprint">
<div class="col-sm-12 col-md-12">
<ol class="breadcrumb">
<li><a href="{% url 'ipam:vlangroup_list' %}">VLAN Groups</a></li>
{% if object.site %}
<li><a href="{{ object.site.get_absolute_url }}">{{ object.site }}</a></li>
{% endif %}
<li>{{ object }}</li>
</ol>
</div>
</div>
{% include 'ipam/inc/vlangroup_header.html' %}
<div class="row">
<div class="col-md-12">
{% include 'utilities/obj_table.html' with table=vlan_table table_template='panel_table.html' heading='VLANs' bulk_edit_url='ipam:vlan_bulk_edit' bulk_delete_url='ipam:vlan_bulk_delete' %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,60 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'secrets:secretrole_list' %}">Secret Roles</a></li>
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Secret Role</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>Secrets</td>
<td>
<a href="{% url 'secrets:secret_list' %}?role_id={{ object.pk }}">{{ secrets_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Secrets</strong>
</div>
{% include 'inc/table.html' with table=secrets_table %}
{% if perms.secrets.add_secret %}
<div class="panel-footer text-right noprint">
<a href="{% url 'secrets:secret_add' %}?role={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add secret
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=secrets_table.paginator page=secrets_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,60 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'virtualization:clustertype_list' %}">Cluster Groups</a></li>
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Cluster Group</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>Clusters</td>
<td>
<a href="{% url 'virtualization:cluster_list' %}?group_id={{ object.pk }}">{{ clusters_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Clusters</strong>
</div>
{% include 'inc/table.html' with table=clusters_table %}
{% if perms.virtualization.add_cluster %}
<div class="panel-footer text-right noprint">
<a href="{% url 'virtualization:cluster_add' %}?type={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add cluster
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=clusters_table.paginator page=clusters_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,60 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% block breadcrumbs %}
<li><a href="{% url 'virtualization:clustertype_list' %}">Cluster Types</a></li>
<li>{{ object }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Cluster Type</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Description</td>
<td>{{ object.description|placeholder }}</td>
</tr>
<tr>
<td>Clusters</td>
<td>
<a href="{% url 'virtualization:cluster_list' %}?type_id={{ object.pk }}">{{ clusters_table.rows|length }}</a>
</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' %}
{% plugin_right_page object %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Clusters</strong>
</div>
{% include 'inc/table.html' with table=clusters_table %}
{% if perms.virtualization.add_cluster %}
<div class="panel-footer text-right noprint">
<a href="{% url 'virtualization:cluster_add' %}?type={{ object.pk }}" class="btn btn-xs btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add cluster
</a>
</div>
{% endif %}
</div>
{% include 'inc/paginator.html' with paginator=clusters_table.paginator page=clusters_table.page %}
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@ -59,7 +59,7 @@ class ClusterType(OrganizationalModel):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return "{}?type={}".format(reverse('virtualization:cluster_list'), self.slug) return reverse('virtualization:clustertype', args=[self.pk])
def to_csv(self): def to_csv(self):
return ( return (
@ -102,7 +102,7 @@ class ClusterGroup(OrganizationalModel):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return "{}?group={}".format(reverse('virtualization:cluster_list'), self.slug) return reverse('virtualization:clustergroup', args=[self.pk])
def to_csv(self): def to_csv(self):
return ( return (

View File

@ -14,6 +14,7 @@ urlpatterns = [
path('cluster-types/import/', views.ClusterTypeBulkImportView.as_view(), name='clustertype_import'), path('cluster-types/import/', views.ClusterTypeBulkImportView.as_view(), name='clustertype_import'),
path('cluster-types/edit/', views.ClusterTypeBulkEditView.as_view(), name='clustertype_bulk_edit'), path('cluster-types/edit/', views.ClusterTypeBulkEditView.as_view(), name='clustertype_bulk_edit'),
path('cluster-types/delete/', views.ClusterTypeBulkDeleteView.as_view(), name='clustertype_bulk_delete'), path('cluster-types/delete/', views.ClusterTypeBulkDeleteView.as_view(), name='clustertype_bulk_delete'),
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>/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>/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>/changelog/', ObjectChangeLogView.as_view(), name='clustertype_changelog', kwargs={'model': ClusterType}),
@ -24,6 +25,7 @@ urlpatterns = [
path('cluster-groups/import/', views.ClusterGroupBulkImportView.as_view(), name='clustergroup_import'), path('cluster-groups/import/', views.ClusterGroupBulkImportView.as_view(), name='clustergroup_import'),
path('cluster-groups/edit/', views.ClusterGroupBulkEditView.as_view(), name='clustergroup_bulk_edit'), path('cluster-groups/edit/', views.ClusterGroupBulkEditView.as_view(), name='clustergroup_bulk_edit'),
path('cluster-groups/delete/', views.ClusterGroupBulkDeleteView.as_view(), name='clustergroup_bulk_delete'), path('cluster-groups/delete/', views.ClusterGroupBulkDeleteView.as_view(), name='clustergroup_bulk_delete'),
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>/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>/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>/changelog/', ObjectChangeLogView.as_view(), name='clustergroup_changelog', kwargs={'model': ClusterGroup}),

View File

@ -11,6 +11,7 @@ from ipam.models import IPAddress, Service
from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable
from netbox.views import generic from netbox.views import generic
from secrets.models import Secret from secrets.models import Secret
from utilities.tables import paginate_table
from utilities.utils import count_related from utilities.utils import count_related
from . import filters, forms, tables from . import filters, forms, tables
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
@ -27,6 +28,23 @@ class ClusterTypeListView(generic.ObjectListView):
table = tables.ClusterTypeTable table = tables.ClusterTypeTable
class ClusterTypeView(generic.ObjectView):
queryset = ClusterType.objects.all()
def get_extra_context(self, request, instance):
clusters = Cluster.objects.restrict(request.user, 'view').filter(
type=instance
)
clusters_table = tables.ClusterTable(clusters)
clusters_table.columns.hide('type')
paginate_table(clusters_table, request)
return {
'clusters_table': clusters_table,
}
class ClusterTypeEditView(generic.ObjectEditView): class ClusterTypeEditView(generic.ObjectEditView):
queryset = ClusterType.objects.all() queryset = ClusterType.objects.all()
model_form = forms.ClusterTypeForm model_form = forms.ClusterTypeForm
@ -69,6 +87,23 @@ class ClusterGroupListView(generic.ObjectListView):
table = tables.ClusterGroupTable table = tables.ClusterGroupTable
class ClusterGroupView(generic.ObjectView):
queryset = ClusterGroup.objects.all()
def get_extra_context(self, request, instance):
clusters = Cluster.objects.restrict(request.user, 'view').filter(
group=instance
)
clusters_table = tables.ClusterTable(clusters)
clusters_table.columns.hide('group')
paginate_table(clusters_table, request)
return {
'clusters_table': clusters_table,
}
class ClusterGroupEditView(generic.ObjectEditView): class ClusterGroupEditView(generic.ObjectEditView):
queryset = ClusterGroup.objects.all() queryset = ClusterGroup.objects.all()
model_form = forms.ClusterGroupForm model_form = forms.ClusterGroupForm