1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

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
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):
return (

View File

@ -38,6 +38,7 @@ urlpatterns = [
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/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>/delete/', views.CircuitTypeDeleteView.as_view(), name='circuittype_delete'),
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
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):
queryset = CircuitType.objects.all()
model_form = forms.CircuitTypeForm

View File

@ -65,7 +65,7 @@ class Manufacturer(OrganizationalModel):
return self.name
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):
return (
@ -375,6 +375,9 @@ class DeviceRole(OrganizationalModel):
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('dcim:devicerole', args=[self.pk])
def to_csv(self):
return (
self.name,
@ -436,7 +439,7 @@ class Platform(OrganizationalModel):
return self.name
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):
return (

View File

@ -67,7 +67,7 @@ class RackRole(OrganizationalModel):
return self.name
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):
return (

View File

@ -50,6 +50,9 @@ __all__ = (
class DeviceRoleTable(BaseTable):
pk = ToggleColumn()
name = tables.Column(
linkify=True
)
device_count = LinkedCountColumn(
viewname='dcim:device_list',
url_params={'role': 'slug'},
@ -76,6 +79,9 @@ class DeviceRoleTable(BaseTable):
class PlatformTable(BaseTable):
pk = ToggleColumn()
name = tables.Column(
linkify=True
)
device_count = LinkedCountColumn(
viewname='dcim:device_list',
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/edit/', views.RackRoleBulkEditView.as_view(), name='rackrole_bulk_edit'),
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>/delete/', views.RackRoleDeleteView.as_view(), name='rackrole_delete'),
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/edit/', views.ManufacturerBulkEditView.as_view(), name='manufacturer_bulk_edit'),
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>/delete/', views.ManufacturerDeleteView.as_view(), name='manufacturer_delete'),
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/edit/', views.DeviceRoleBulkEditView.as_view(), name='devicerole_bulk_edit'),
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>/delete/', views.DeviceRoleDeleteView.as_view(), name='devicerole_delete'),
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/edit/', views.PlatformBulkEditView.as_view(), name='platform_bulk_edit'),
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>/delete/', views.PlatformDeleteView.as_view(), name='platform_delete'),
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.paginator import EnhancedPaginator, get_paginate_count
from utilities.permissions import get_permission_for_model
from utilities.tables import paginate_table
from utilities.utils import csv_format, count_related
from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin
from virtualization.models import VirtualMachine
@ -341,6 +342,23 @@ class RackRoleListView(generic.ObjectListView):
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):
queryset = RackRole.objects.all()
model_form = forms.RackRoleForm
@ -567,6 +585,23 @@ class ManufacturerListView(generic.ObjectListView):
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):
queryset = Manufacturer.objects.all()
model_form = forms.ManufacturerForm
@ -1017,6 +1052,23 @@ class DeviceRoleListView(generic.ObjectListView):
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):
queryset = DeviceRole.objects.all()
model_form = forms.DeviceRoleForm
@ -1056,6 +1108,23 @@ class PlatformListView(generic.ObjectListView):
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):
queryset = Platform.objects.all()
model_form = forms.PlatformForm

View File

@ -66,7 +66,7 @@ class RIR(OrganizationalModel):
return self.name
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):
return (
@ -216,6 +216,9 @@ class Role(OrganizationalModel):
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('ipam:role', args=[self.pk])
def to_csv(self):
return (
self.name,

View File

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

View File

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

View File

@ -37,6 +37,7 @@ urlpatterns = [
path('rirs/import/', views.RIRBulkImportView.as_view(), name='rir_import'),
path('rirs/edit/', views.RIRBulkEditView.as_view(), name='rir_bulk_edit'),
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>/delete/', views.RIRDeleteView.as_view(), name='rir_delete'),
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/edit/', views.RoleBulkEditView.as_view(), name='role_bulk_edit'),
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>/delete/', views.RoleDeleteView.as_view(), name='role_delete'),
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/edit/', views.VLANGroupBulkEditView.as_view(), name='vlangroup_bulk_edit'),
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>/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}),
# VLANs

View File

@ -148,6 +148,23 @@ class RIRListView(generic.ObjectListView):
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):
queryset = RIR.objects.all()
model_form = forms.RIRForm
@ -286,6 +303,23 @@ class RoleListView(generic.ObjectListView):
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):
queryset = Role.objects.all()
model_form = forms.RoleForm
@ -633,6 +667,29 @@ class VLANGroupListView(generic.ObjectListView):
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):
queryset = VLANGroup.objects.all()
model_form = forms.VLANGroupForm
@ -666,38 +723,6 @@ class VLANGroupBulkDeleteView(generic.BulkDeleteView):
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
#

View File

@ -263,7 +263,7 @@ class SecretRole(OrganizationalModel):
return self.name
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):
return (

View File

@ -13,6 +13,7 @@ urlpatterns = [
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/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>/delete/', views.SecretRoleDeleteView.as_view(), name='secretrole_delete'),
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 netbox.views import generic
from utilities.tables import paginate_table
from utilities.utils import count_related
from . import filters, forms, tables
from .models import SecretRole, Secret, SessionKey, UserKey
@ -33,6 +34,23 @@ class SecretRoleListView(generic.ObjectListView):
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):
queryset = SecretRole.objects.all()
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
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):
return (
@ -102,7 +102,7 @@ class ClusterGroup(OrganizationalModel):
return self.name
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):
return (

View File

@ -14,6 +14,7 @@ urlpatterns = [
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/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>/delete/', views.ClusterTypeDeleteView.as_view(), name='clustertype_delete'),
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/edit/', views.ClusterGroupBulkEditView.as_view(), name='clustergroup_bulk_edit'),
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>/delete/', views.ClusterGroupDeleteView.as_view(), name='clustergroup_delete'),
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 netbox.views import generic
from secrets.models import Secret
from utilities.tables import paginate_table
from utilities.utils import count_related
from . import filters, forms, tables
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
@ -27,6 +28,23 @@ class ClusterTypeListView(generic.ObjectListView):
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):
queryset = ClusterType.objects.all()
model_form = forms.ClusterTypeForm
@ -69,6 +87,23 @@ class ClusterGroupListView(generic.ObjectListView):
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):
queryset = ClusterGroup.objects.all()
model_form = forms.ClusterGroupForm