From b7e44a744dc87c34dbcac75c79dea7ec503325f4 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 26 Mar 2021 14:44:43 -0400 Subject: [PATCH 1/4] Add dedicated views for organizational models --- netbox/circuits/models.py | 2 +- netbox/circuits/urls.py | 1 + netbox/circuits/views.py | 17 ++++ netbox/dcim/models/devices.py | 7 +- netbox/dcim/models/racks.py | 2 +- netbox/dcim/tables/devices.py | 6 ++ netbox/dcim/urls.py | 4 + netbox/dcim/views.py | 69 ++++++++++++++ netbox/ipam/models/ip.py | 5 +- netbox/ipam/models/vlans.py | 2 +- netbox/ipam/tables.py | 8 +- netbox/ipam/urls.py | 4 +- netbox/ipam/views.py | 89 ++++++++++++------- netbox/secrets/models.py | 2 +- netbox/secrets/urls.py | 1 + netbox/secrets/views.py | 18 ++++ netbox/templates/circuits/circuittype.html | 60 +++++++++++++ netbox/templates/dcim/devicerole.html | 76 ++++++++++++++++ netbox/templates/dcim/manufacturer.html | 60 +++++++++++++ netbox/templates/dcim/platform.html | 68 ++++++++++++++ netbox/templates/dcim/rackrole.html | 66 ++++++++++++++ netbox/templates/ipam/rir.html | 70 +++++++++++++++ netbox/templates/ipam/role.html | 64 +++++++++++++ netbox/templates/ipam/vlangroup.html | 72 +++++++++++++++ netbox/templates/ipam/vlangroup_vlans.html | 24 ----- netbox/templates/secrets/secretrole.html | 60 +++++++++++++ .../virtualization/clustergroup.html | 60 +++++++++++++ .../templates/virtualization/clustertype.html | 60 +++++++++++++ netbox/virtualization/models.py | 4 +- netbox/virtualization/urls.py | 2 + netbox/virtualization/views.py | 35 ++++++++ 31 files changed, 949 insertions(+), 69 deletions(-) create mode 100644 netbox/templates/circuits/circuittype.html create mode 100644 netbox/templates/dcim/devicerole.html create mode 100644 netbox/templates/dcim/manufacturer.html create mode 100644 netbox/templates/dcim/platform.html create mode 100644 netbox/templates/dcim/rackrole.html create mode 100644 netbox/templates/ipam/rir.html create mode 100644 netbox/templates/ipam/role.html create mode 100644 netbox/templates/ipam/vlangroup.html delete mode 100644 netbox/templates/ipam/vlangroup_vlans.html create mode 100644 netbox/templates/secrets/secretrole.html create mode 100644 netbox/templates/virtualization/clustergroup.html create mode 100644 netbox/templates/virtualization/clustertype.html diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index 73df7f2d4..b163834e6 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -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 ( diff --git a/netbox/circuits/urls.py b/netbox/circuits/urls.py index acc3baac5..58f52bb42 100644 --- a/netbox/circuits/urls.py +++ b/netbox/circuits/urls.py @@ -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//', views.CircuitTypeView.as_view(), name='circuittype'), path('circuit-types//edit/', views.CircuitTypeEditView.as_view(), name='circuittype_edit'), path('circuit-types//delete/', views.CircuitTypeDeleteView.as_view(), name='circuittype_delete'), path('circuit-types//changelog/', ObjectChangeLogView.as_view(), name='circuittype_changelog', kwargs={'model': CircuitType}), diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index 1c0a152b2..6f3c5b2be 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -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 diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 7f22d9325..a5efadac5 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -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 ( diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py index 1942e3cb0..2869c4265 100644 --- a/netbox/dcim/models/racks.py +++ b/netbox/dcim/models/racks.py @@ -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 ( diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index c4204dd4a..c6ad3ad6d 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -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'}, diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index e7c29ae9f..f07e1911a 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -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//', views.RackRoleView.as_view(), name='rackrole'), path('rack-roles//edit/', views.RackRoleEditView.as_view(), name='rackrole_edit'), path('rack-roles//delete/', views.RackRoleDeleteView.as_view(), name='rackrole_delete'), path('rack-roles//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//', views.ManufacturerView.as_view(), name='manufacturer'), path('manufacturers//edit/', views.ManufacturerEditView.as_view(), name='manufacturer_edit'), path('manufacturers//delete/', views.ManufacturerDeleteView.as_view(), name='manufacturer_delete'), path('manufacturers//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//', views.DeviceRoleView.as_view(), name='devicerole'), path('device-roles//edit/', views.DeviceRoleEditView.as_view(), name='devicerole_edit'), path('device-roles//delete/', views.DeviceRoleDeleteView.as_view(), name='devicerole_delete'), path('device-roles//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//', views.PlatformView.as_view(), name='platform'), path('platforms//edit/', views.PlatformEditView.as_view(), name='platform_edit'), path('platforms//delete/', views.PlatformDeleteView.as_view(), name='platform_delete'), path('platforms//changelog/', ObjectChangeLogView.as_view(), name='platform_changelog', kwargs={'model': Platform}), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index e624b6926..e1f24df25 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -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 diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index 9867f6069..b11a88d54 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -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, diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py index 26cb5299f..0c184adff 100644 --- a/netbox/ipam/models/vlans.py +++ b/netbox/ipam/models/vlans.py @@ -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() diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index e25bf5351..f41c7cfff 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -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( diff --git a/netbox/ipam/urls.py b/netbox/ipam/urls.py index 4b576d21f..262537b8b 100644 --- a/netbox/ipam/urls.py +++ b/netbox/ipam/urls.py @@ -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//', views.RIRView.as_view(), name='rir'), path('rirs//edit/', views.RIREditView.as_view(), name='rir_edit'), path('rirs//delete/', views.RIRDeleteView.as_view(), name='rir_delete'), path('rirs//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//', views.RoleView.as_view(), name='role'), path('roles//edit/', views.RoleEditView.as_view(), name='role_edit'), path('roles//delete/', views.RoleDeleteView.as_view(), name='role_delete'), path('roles//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//', views.VLANGroupView.as_view(), name='vlangroup'), path('vlan-groups//edit/', views.VLANGroupEditView.as_view(), name='vlangroup_edit'), path('vlan-groups//delete/', views.VLANGroupDeleteView.as_view(), name='vlangroup_delete'), - path('vlan-groups//vlans/', views.VLANGroupVLANsView.as_view(), name='vlangroup_vlans'), path('vlan-groups//changelog/', ObjectChangeLogView.as_view(), name='vlangroup_changelog', kwargs={'model': VLANGroup}), # VLANs diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 47eccead1..b0da44b6d 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -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 # diff --git a/netbox/secrets/models.py b/netbox/secrets/models.py index 932242060..dc3a65747 100644 --- a/netbox/secrets/models.py +++ b/netbox/secrets/models.py @@ -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 ( diff --git a/netbox/secrets/urls.py b/netbox/secrets/urls.py index 7c72b848c..a47070152 100644 --- a/netbox/secrets/urls.py +++ b/netbox/secrets/urls.py @@ -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//', views.SecretRoleView.as_view(), name='secretrole'), path('secret-roles//edit/', views.SecretRoleEditView.as_view(), name='secretrole_edit'), path('secret-roles//delete/', views.SecretRoleDeleteView.as_view(), name='secretrole_delete'), path('secret-roles//changelog/', ObjectChangeLogView.as_view(), name='secretrole_changelog', kwargs={'model': SecretRole}), diff --git a/netbox/secrets/views.py b/netbox/secrets/views.py index 0ae2f6231..57d64f064 100644 --- a/netbox/secrets/views.py +++ b/netbox/secrets/views.py @@ -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 diff --git a/netbox/templates/circuits/circuittype.html b/netbox/templates/circuits/circuittype.html new file mode 100644 index 000000000..aee7bf944 --- /dev/null +++ b/netbox/templates/circuits/circuittype.html @@ -0,0 +1,60 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Circuit Types
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Circuit Type +
    + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Circuits + {{ circuits_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Circuits +
    + {% include 'inc/table.html' with table=circuits_table %} + {% if perms.circuits.add_circuit %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=circuits_table.paginator page=circuits_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/dcim/devicerole.html b/netbox/templates/dcim/devicerole.html new file mode 100644 index 000000000..c6cbf4952 --- /dev/null +++ b/netbox/templates/dcim/devicerole.html @@ -0,0 +1,76 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Device Roles
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Device Role +
    + + + + + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Color +   +
    VM Role + {% if object.vm_role %} + + {% else %} + + {% endif %} +
    Devices + {{ devices_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Devices +
    + {% include 'inc/table.html' with table=devices_table %} + {% if perms.dcim.add_device %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=devices_table.paginator page=devices_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/dcim/manufacturer.html b/netbox/templates/dcim/manufacturer.html new file mode 100644 index 000000000..b2ecacbb1 --- /dev/null +++ b/netbox/templates/dcim/manufacturer.html @@ -0,0 +1,60 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Manufacturers
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Manufacturer +
    + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Device types + {{ devicetypes_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Device Types +
    + {% include 'inc/table.html' with table=devicetypes_table %} + {% if perms.dcim.add_devicetype %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=devicetypes_table.paginator page=devicetypes_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/dcim/platform.html b/netbox/templates/dcim/platform.html new file mode 100644 index 000000000..8a3ff5b6e --- /dev/null +++ b/netbox/templates/dcim/platform.html @@ -0,0 +1,68 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Platforms
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Platform +
    + + + + + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    NAPALM Driver{{ object.napalm_driver }}
    NAPALM Arguments
    {{ object.napalm_args }}
    Devices + {{ devices_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Devices +
    + {% include 'inc/table.html' with table=devices_table %} + {% if perms.dcim.add_device %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=devices_table.paginator page=devices_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/dcim/rackrole.html b/netbox/templates/dcim/rackrole.html new file mode 100644 index 000000000..89306b481 --- /dev/null +++ b/netbox/templates/dcim/rackrole.html @@ -0,0 +1,66 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Rack Roles
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Rack Role +
    + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Color +   +
    Racks + {{ racks_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Racks +
    + {% include 'inc/table.html' with table=racks_table %} + {% if perms.dcim.add_rack %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=racks_table.paginator page=racks_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/ipam/rir.html b/netbox/templates/ipam/rir.html new file mode 100644 index 000000000..332c90da7 --- /dev/null +++ b/netbox/templates/ipam/rir.html @@ -0,0 +1,70 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • RIRs
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + RIR +
    + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Private + {% if object.is_private %} + + {% else %} + + {% endif %} +
    Aggregates + {{ aggregates_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Aggregates +
    + {% include 'inc/table.html' with table=aggregates_table %} + {% if perms.ipam.add_aggregate %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=aggregates_table.paginator page=aggregates_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/ipam/role.html b/netbox/templates/ipam/role.html new file mode 100644 index 000000000..307e6e21a --- /dev/null +++ b/netbox/templates/ipam/role.html @@ -0,0 +1,64 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Roles
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Role +
    + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Weight{{ object.weight }}
    Prefixes + {{ prefixes_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Prefixes +
    + {% include 'inc/table.html' with table=prefixes_table %} + {% if perms.ipam.add_prefix %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=prefixes_table.paginator page=prefixes_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/ipam/vlangroup.html b/netbox/templates/ipam/vlangroup.html new file mode 100644 index 000000000..e265aa608 --- /dev/null +++ b/netbox/templates/ipam/vlangroup.html @@ -0,0 +1,72 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • VLAN Groups
  • + {% if object.scope %} +
  • {{ object.scope }}
  • + {% endif %} +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + VLAN Group +
    + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Scope + {% if object.scope %} + {{ object.scope }} + {% else %} + + {% endif %} +
    VLANs + {{ vlans_count }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + VLANs +
    + {% include 'inc/table.html' with table=vlans_table %} + {% if perms.ipam.add_vlan %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=vlans_table.paginator page=vlans_table.page %} +
    +
    +{% endblock %} + diff --git a/netbox/templates/ipam/vlangroup_vlans.html b/netbox/templates/ipam/vlangroup_vlans.html deleted file mode 100644 index ffb75d3b8..000000000 --- a/netbox/templates/ipam/vlangroup_vlans.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}{{ object }} - VLANs{% endblock %} - -{% block content %} -
    -
    - -
    -
    - {% include 'ipam/inc/vlangroup_header.html' %} -
    -
    - {% 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' %} -
    -
    -{% endblock %} - diff --git a/netbox/templates/secrets/secretrole.html b/netbox/templates/secrets/secretrole.html new file mode 100644 index 000000000..0e284fb75 --- /dev/null +++ b/netbox/templates/secrets/secretrole.html @@ -0,0 +1,60 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Secret Roles
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Secret Role +
    + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Secrets + {{ secrets_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Secrets +
    + {% include 'inc/table.html' with table=secrets_table %} + {% if perms.secrets.add_secret %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=secrets_table.paginator page=secrets_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/virtualization/clustergroup.html b/netbox/templates/virtualization/clustergroup.html new file mode 100644 index 000000000..9b2085189 --- /dev/null +++ b/netbox/templates/virtualization/clustergroup.html @@ -0,0 +1,60 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Cluster Groups
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Cluster Group +
    + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Clusters + {{ clusters_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Clusters +
    + {% include 'inc/table.html' with table=clusters_table %} + {% if perms.virtualization.add_cluster %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=clusters_table.paginator page=clusters_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/virtualization/clustertype.html b/netbox/templates/virtualization/clustertype.html new file mode 100644 index 000000000..d36934a74 --- /dev/null +++ b/netbox/templates/virtualization/clustertype.html @@ -0,0 +1,60 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Cluster Types
  • +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Cluster Type +
    + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Clusters + {{ clusters_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Clusters +
    + {% include 'inc/table.html' with table=clusters_table %} + {% if perms.virtualization.add_cluster %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=clusters_table.paginator page=clusters_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index e26fc8fc9..a34d09662 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -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 ( diff --git a/netbox/virtualization/urls.py b/netbox/virtualization/urls.py index a036ab0bd..17750e806 100644 --- a/netbox/virtualization/urls.py +++ b/netbox/virtualization/urls.py @@ -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//', views.ClusterTypeView.as_view(), name='clustertype'), path('cluster-types//edit/', views.ClusterTypeEditView.as_view(), name='clustertype_edit'), path('cluster-types//delete/', views.ClusterTypeDeleteView.as_view(), name='clustertype_delete'), path('cluster-types//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//', views.ClusterGroupView.as_view(), name='clustergroup'), path('cluster-groups//edit/', views.ClusterGroupEditView.as_view(), name='clustergroup_edit'), path('cluster-groups//delete/', views.ClusterGroupDeleteView.as_view(), name='clustergroup_delete'), path('cluster-groups//changelog/', ObjectChangeLogView.as_view(), name='clustergroup_changelog', kwargs={'model': ClusterGroup}), diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index d0bb0cf76..a874d2a92 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -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 From 2820d26a0f1898a02220de6a64dae893f58f7471 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 26 Mar 2021 15:07:29 -0400 Subject: [PATCH 2/4] Add dedicated views for nested group models --- netbox/dcim/models/sites.py | 6 +- netbox/dcim/tables/racks.py | 6 +- netbox/dcim/tables/sites.py | 8 ++- netbox/dcim/urls.py | 3 + netbox/dcim/views.py | 51 ++++++++++++++++ netbox/templates/dcim/location.html | 73 +++++++++++++++++++++++ netbox/templates/dcim/region.html | 73 +++++++++++++++++++++++ netbox/templates/dcim/sitegroup.html | 73 +++++++++++++++++++++++ netbox/templates/tenancy/tenantgroup.html | 73 +++++++++++++++++++++++ netbox/tenancy/models.py | 2 +- netbox/tenancy/tables.py | 4 +- netbox/tenancy/urls.py | 1 + netbox/tenancy/views.py | 20 ++++++- 13 files changed, 382 insertions(+), 11 deletions(-) create mode 100644 netbox/templates/dcim/location.html create mode 100644 netbox/templates/dcim/region.html create mode 100644 netbox/templates/dcim/sitegroup.html create mode 100644 netbox/templates/tenancy/tenantgroup.html diff --git a/netbox/dcim/models/sites.py b/netbox/dcim/models/sites.py index 51cb63d08..3f2c209c3 100644 --- a/netbox/dcim/models/sites.py +++ b/netbox/dcim/models/sites.py @@ -56,7 +56,7 @@ class Region(NestedGroupModel): csv_headers = ['name', 'slug', 'parent', 'description'] def get_absolute_url(self): - return "{}?region={}".format(reverse('dcim:site_list'), self.slug) + return reverse('dcim:region', args=[self.pk]) def to_csv(self): return ( @@ -108,7 +108,7 @@ class SiteGroup(NestedGroupModel): csv_headers = ['name', 'slug', 'parent', 'description'] def get_absolute_url(self): - return "{}?group={}".format(reverse('dcim:site_list'), self.slug) + return reverse('dcim:sitegroup', args=[self.pk]) def to_csv(self): return ( @@ -324,7 +324,7 @@ class Location(NestedGroupModel): ] def get_absolute_url(self): - return "{}?location_id={}".format(reverse('dcim:rack_list'), self.pk) + return reverse('dcim:location', args=[self.pk]) def to_csv(self): return ( diff --git a/netbox/dcim/tables/racks.py b/netbox/dcim/tables/racks.py index eb4b28710..b56e3bbc4 100644 --- a/netbox/dcim/tables/racks.py +++ b/netbox/dcim/tables/racks.py @@ -19,12 +19,14 @@ __all__ = ( # -# Rack groups +# Locations # class LocationTable(BaseTable): pk = ToggleColumn() - name = MPTTColumn() + name = MPTTColumn( + linkify=True + ) site = tables.Column( linkify=True ) diff --git a/netbox/dcim/tables/sites.py b/netbox/dcim/tables/sites.py index e22037255..b8d06beb6 100644 --- a/netbox/dcim/tables/sites.py +++ b/netbox/dcim/tables/sites.py @@ -17,7 +17,9 @@ __all__ = ( class RegionTable(BaseTable): pk = ToggleColumn() - name = MPTTColumn() + name = MPTTColumn( + linkify=True + ) site_count = tables.Column( verbose_name='Sites' ) @@ -35,7 +37,9 @@ class RegionTable(BaseTable): class SiteGroupTable(BaseTable): pk = ToggleColumn() - name = MPTTColumn() + name = MPTTColumn( + linkify=True + ) site_count = tables.Column( verbose_name='Sites' ) diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index f07e1911a..b23603c97 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -14,6 +14,7 @@ urlpatterns = [ path('regions/import/', views.RegionBulkImportView.as_view(), name='region_import'), path('regions/edit/', views.RegionBulkEditView.as_view(), name='region_bulk_edit'), path('regions/delete/', views.RegionBulkDeleteView.as_view(), name='region_bulk_delete'), + path('regions//', views.RegionView.as_view(), name='region'), path('regions//edit/', views.RegionEditView.as_view(), name='region_edit'), path('regions//delete/', views.RegionDeleteView.as_view(), name='region_delete'), path('regions//changelog/', ObjectChangeLogView.as_view(), name='region_changelog', kwargs={'model': Region}), @@ -24,6 +25,7 @@ urlpatterns = [ path('site-groups/import/', views.SiteGroupBulkImportView.as_view(), name='sitegroup_import'), path('site-groups/edit/', views.SiteGroupBulkEditView.as_view(), name='sitegroup_bulk_edit'), path('site-groups/delete/', views.SiteGroupBulkDeleteView.as_view(), name='sitegroup_bulk_delete'), + path('site-groups//', views.SiteGroupView.as_view(), name='sitegroup'), path('site-groups//edit/', views.SiteGroupEditView.as_view(), name='sitegroup_edit'), path('site-groups//delete/', views.SiteGroupDeleteView.as_view(), name='sitegroup_delete'), path('site-groups//changelog/', ObjectChangeLogView.as_view(), name='sitegroup_changelog', kwargs={'model': SiteGroup}), @@ -47,6 +49,7 @@ urlpatterns = [ path('locations/import/', views.LocationBulkImportView.as_view(), name='location_import'), path('locations/edit/', views.LocationBulkEditView.as_view(), name='location_bulk_edit'), path('locations/delete/', views.LocationBulkDeleteView.as_view(), name='location_bulk_delete'), + path('locations//', views.LocationView.as_view(), name='location'), path('locations//edit/', views.LocationEditView.as_view(), name='location_edit'), path('locations//delete/', views.LocationDeleteView.as_view(), name='location_delete'), path('locations//changelog/', ObjectChangeLogView.as_view(), name='location_changelog', kwargs={'model': Location}), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index e1f24df25..3bd456bcc 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -112,6 +112,23 @@ class RegionListView(generic.ObjectListView): table = tables.RegionTable +class RegionView(generic.ObjectView): + queryset = Region.objects.all() + + def get_extra_context(self, request, instance): + sites = Site.objects.restrict(request.user, 'view').filter( + region=instance + ) + + sites_table = tables.SiteTable(sites) + sites_table.columns.hide('region') + paginate_table(sites_table, request) + + return { + 'sites_table': sites_table, + } + + class RegionEditView(generic.ObjectEditView): queryset = Region.objects.all() model_form = forms.RegionForm @@ -169,6 +186,23 @@ class SiteGroupListView(generic.ObjectListView): table = tables.SiteGroupTable +class SiteGroupView(generic.ObjectView): + queryset = SiteGroup.objects.all() + + def get_extra_context(self, request, instance): + sites = Site.objects.restrict(request.user, 'view').filter( + group=instance + ) + + sites_table = tables.SiteTable(sites) + sites_table.columns.hide('group') + paginate_table(sites_table, request) + + return { + 'sites_table': sites_table, + } + + class SiteGroupEditView(generic.ObjectEditView): queryset = SiteGroup.objects.all() model_form = forms.SiteGroupForm @@ -291,6 +325,23 @@ class LocationListView(generic.ObjectListView): table = tables.LocationTable +class LocationView(generic.ObjectView): + queryset = Location.objects.all() + + def get_extra_context(self, request, instance): + devices = Device.objects.restrict(request.user, 'view').filter( + location=instance + ) + + devices_table = tables.DeviceTable(devices) + devices_table.columns.hide('location') + paginate_table(devices_table, request) + + return { + 'devices_table': devices_table, + } + + class LocationEditView(generic.ObjectEditView): queryset = Location.objects.all() model_form = forms.LocationForm diff --git a/netbox/templates/dcim/location.html b/netbox/templates/dcim/location.html new file mode 100644 index 000000000..c12c9f533 --- /dev/null +++ b/netbox/templates/dcim/location.html @@ -0,0 +1,73 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Location
  • + {% for location in object.get_ancestors %} +
  • {{ location }}
  • + {% endfor %} +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Location +
    + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Parent + {% if object.parent %} + {{ object.parent }} + {% else %} + + {% endif %} +
    Devices + {{ devices_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Devices +
    + {% include 'inc/table.html' with table=devices_table %} + {% if perms.dcim.add_device %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=devices_table.paginator page=devices_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/dcim/region.html b/netbox/templates/dcim/region.html new file mode 100644 index 000000000..c79336962 --- /dev/null +++ b/netbox/templates/dcim/region.html @@ -0,0 +1,73 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Region
  • + {% for region in object.get_ancestors %} +
  • {{ region }}
  • + {% endfor %} +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Region +
    + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Parent + {% if object.parent %} + {{ object.parent }} + {% else %} + + {% endif %} +
    Sites + {{ sites_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Sites +
    + {% include 'inc/table.html' with table=sites_table %} + {% if perms.dcim.add_site %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=sites_table.paginator page=sites_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/dcim/sitegroup.html b/netbox/templates/dcim/sitegroup.html new file mode 100644 index 000000000..95cba7aeb --- /dev/null +++ b/netbox/templates/dcim/sitegroup.html @@ -0,0 +1,73 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Site Groups
  • + {% for sitegroup in object.get_ancestors %} +
  • {{ sitegroup }}
  • + {% endfor %} +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Site Group +
    + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Parent + {% if object.parent %} + {{ object.parent }} + {% else %} + + {% endif %} +
    Sites + {{ sites_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Sites +
    + {% include 'inc/table.html' with table=sites_table %} + {% if perms.dcim.add_site %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=sites_table.paginator page=sites_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/templates/tenancy/tenantgroup.html b/netbox/templates/tenancy/tenantgroup.html new file mode 100644 index 000000000..4f65ef21a --- /dev/null +++ b/netbox/templates/tenancy/tenantgroup.html @@ -0,0 +1,73 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} + +{% block breadcrumbs %} +
  • Tenant Groups
  • + {% for tenantgroup in object.get_ancestors %} +
  • {{ tenantgroup }}
  • + {% endfor %} +
  • {{ object }}
  • +{% endblock %} + +{% block content %} +
    +
    +
    +
    + Tenant Group +
    + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Parent + {% if object.parent %} + {{ object.parent }} + {% else %} + + {% endif %} +
    Sites + {{ tenants_table.rows|length }} +
    +
    + {% plugin_left_page object %} +
    +
    + {% include 'inc/custom_fields_panel.html' %} + {% plugin_right_page object %} +
    +
    +
    +
    +
    +
    + Tenants +
    + {% include 'inc/table.html' with table=tenants_table %} + {% if perms.tenancy.add_tenant %} + + {% endif %} +
    + {% include 'inc/paginator.html' with paginator=tenants_table.paginator page=tenants_table.page %} + {% plugin_full_width_page object %} +
    +
    +{% endblock %} diff --git a/netbox/tenancy/models.py b/netbox/tenancy/models.py index e5ecec639..aa26a60e3 100644 --- a/netbox/tenancy/models.py +++ b/netbox/tenancy/models.py @@ -45,7 +45,7 @@ class TenantGroup(NestedGroupModel): ordering = ['name'] def get_absolute_url(self): - return "{}?group={}".format(reverse('tenancy:tenant_list'), self.slug) + return reverse('tenancy:tenantgroup', args=[self.pk]) def to_csv(self): return ( diff --git a/netbox/tenancy/tables.py b/netbox/tenancy/tables.py index 5b5bc6d73..a826fe5a0 100644 --- a/netbox/tenancy/tables.py +++ b/netbox/tenancy/tables.py @@ -35,7 +35,9 @@ class TenantColumn(tables.TemplateColumn): class TenantGroupTable(BaseTable): pk = ToggleColumn() - name = MPTTColumn() + name = MPTTColumn( + linkify=True + ) tenant_count = LinkedCountColumn( viewname='tenancy:tenant_list', url_params={'group': 'slug'}, diff --git a/netbox/tenancy/urls.py b/netbox/tenancy/urls.py index 1b9dcbe26..a3db431da 100644 --- a/netbox/tenancy/urls.py +++ b/netbox/tenancy/urls.py @@ -13,6 +13,7 @@ urlpatterns = [ path('tenant-groups/import/', views.TenantGroupBulkImportView.as_view(), name='tenantgroup_import'), path('tenant-groups/edit/', views.TenantGroupBulkEditView.as_view(), name='tenantgroup_bulk_edit'), path('tenant-groups/delete/', views.TenantGroupBulkDeleteView.as_view(), name='tenantgroup_bulk_delete'), + path('tenant-groups//', views.TenantGroupView.as_view(), name='tenantgroup'), path('tenant-groups//edit/', views.TenantGroupEditView.as_view(), name='tenantgroup_edit'), path('tenant-groups//delete/', views.TenantGroupDeleteView.as_view(), name='tenantgroup_delete'), path('tenant-groups//changelog/', ObjectChangeLogView.as_view(), name='tenantgroup_changelog', kwargs={'model': TenantGroup}), diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 11f5ead00..206ff6c7a 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -1,9 +1,8 @@ -from django.shortcuts import get_object_or_404, render - from circuits.models import Circuit from dcim.models import Site, Rack, Device, RackReservation from ipam.models import IPAddress, Prefix, VLAN, VRF from netbox.views import generic +from utilities.tables import paginate_table from virtualization.models import VirtualMachine, Cluster from . import filters, forms, tables from .models import Tenant, TenantGroup @@ -24,6 +23,23 @@ class TenantGroupListView(generic.ObjectListView): table = tables.TenantGroupTable +class TenantGroupView(generic.ObjectView): + queryset = TenantGroup.objects.all() + + def get_extra_context(self, request, instance): + tenants = Tenant.objects.restrict(request.user, 'view').filter( + group=instance + ) + + tenants_table = tables.TenantTable(tenants) + tenants_table.columns.hide('group') + paginate_table(tenants_table, request) + + return { + 'tenants_table': tenants_table, + } + + class TenantGroupEditView(generic.ObjectEditView): queryset = TenantGroup.objects.all() model_form = forms.TenantGroupForm From 981e7017bbebc35bb1475897e5b8fdcc0935dc06 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 26 Mar 2021 15:15:59 -0400 Subject: [PATCH 3/4] Enable get view tests for organizational objects --- netbox/extras/views.py | 10 ---------- netbox/utilities/testing/views.py | 1 + 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 9add772d7..841510dd3 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -235,11 +235,6 @@ class ObjectChangeLogView(View): # fall back to using base.html. if self.base_template is None: self.base_template = f"{model._meta.app_label}/{model._meta.model_name}.html" - # TODO: This can be removed once an object view has been established for every model. - try: - template.loader.get_template(self.base_template) - except template.TemplateDoesNotExist: - self.base_template = 'base.html' return render(request, 'extras/object_changelog.html', { 'object': obj, @@ -368,11 +363,6 @@ class ObjectJournalView(View): # fall back to using base.html. if self.base_template is None: self.base_template = f"{model._meta.app_label}/{model._meta.model_name}.html" - # TODO: This can be removed once an object view has been established for every model. - try: - template.loader.get_template(self.base_template) - except template.TemplateDoesNotExist: - self.base_template = 'base.html' return render(request, 'extras/object_journal.html', { 'object': obj, diff --git a/netbox/utilities/testing/views.py b/netbox/utilities/testing/views.py index aa8db346d..703780f95 100644 --- a/netbox/utilities/testing/views.py +++ b/netbox/utilities/testing/views.py @@ -1018,6 +1018,7 @@ class ViewTestCases: maxDiff = None class OrganizationalObjectViewTestCase( + GetObjectViewTestCase, GetObjectChangelogViewTestCase, CreateObjectViewTestCase, EditObjectViewTestCase, From 36c903da04986314269942525d4238d26dce5faf Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 26 Mar 2021 15:25:18 -0400 Subject: [PATCH 4/4] Add dedicated view for tags --- netbox/extras/models/tags.py | 4 + netbox/extras/tables.py | 3 + netbox/extras/urls.py | 1 + netbox/extras/views.py | 11 +++ netbox/templates/extras/tag.html | 135 ++++++++++--------------------- 5 files changed, 63 insertions(+), 91 deletions(-) diff --git a/netbox/extras/models/tags.py b/netbox/extras/models/tags.py index 4c61f7f88..6268751b2 100644 --- a/netbox/extras/models/tags.py +++ b/netbox/extras/models/tags.py @@ -1,4 +1,5 @@ from django.db import models +from django.urls import reverse from django.utils.text import slugify from taggit.models import TagBase, GenericTaggedItemBase @@ -30,6 +31,9 @@ class Tag(ChangeLoggedModel, TagBase): class Meta: ordering = ['name'] + def get_absolute_url(self): + return reverse('extras:tag', args=[self.pk]) + def slugify(self, tag, i=None): # Allow Unicode in Tag slugs (avoids empty slugs for Tags with all-Unicode names) slug = slugify(tag, allow_unicode=True) diff --git a/netbox/extras/tables.py b/netbox/extras/tables.py index 24648b952..cd0ecbb36 100644 --- a/netbox/extras/tables.py +++ b/netbox/extras/tables.py @@ -38,6 +38,9 @@ OBJECTCHANGE_REQUEST_ID = """ class TagTable(BaseTable): pk = ToggleColumn() + name = tables.Column( + linkify=True + ) color = ColorColumn() actions = ButtonsColumn(Tag) diff --git a/netbox/extras/urls.py b/netbox/extras/urls.py index ee435307d..f38a2ecd3 100644 --- a/netbox/extras/urls.py +++ b/netbox/extras/urls.py @@ -13,6 +13,7 @@ urlpatterns = [ path('tags/import/', views.TagBulkImportView.as_view(), name='tag_import'), path('tags/edit/', views.TagBulkEditView.as_view(), name='tag_bulk_edit'), path('tags/delete/', views.TagBulkDeleteView.as_view(), name='tag_bulk_delete'), + path('tags//', views.TagView.as_view(), name='tag'), path('tags//edit/', views.TagEditView.as_view(), name='tag_edit'), path('tags//delete/', views.TagDeleteView.as_view(), name='tag_delete'), path('tags//changelog/', views.ObjectChangeLogView.as_view(), name='tag_changelog', kwargs={'model': Tag}), diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 841510dd3..64a28c7ac 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -34,6 +34,17 @@ class TagListView(generic.ObjectListView): table = tables.TagTable +class TagView(generic.ObjectView): + queryset = Tag.objects.all() + + def get_extra_context(self, request, instance): + tagged_items = TaggedItem.objects.filter(tag=instance) + + return { + 'tagged_item_count': tagged_items.count(), + } + + class TagEditView(generic.ObjectEditView): queryset = Tag.objects.all() model_form = forms.TagForm diff --git a/netbox/templates/extras/tag.html b/netbox/templates/extras/tag.html index 2cee8f882..cd6d09c29 100644 --- a/netbox/templates/extras/tag.html +++ b/netbox/templates/extras/tag.html @@ -1,98 +1,51 @@ -{% extends 'base.html' %} +{% extends 'generic/object.html' %} {% load helpers %} +{% load plugins %} -{% block header %} -
    -
    - -
    -
    -
    -
    - - - - -
    -
    -
    -
    -
    - {% if perms.taggit.change_tag %} - - - Edit this tag - - {% endif %} - {% if perms.taggit.delete_tag %} - - - Delete this tag - - {% endif %} -
    -

    {% block title %}Tag: {{ object }}{% endblock %}

    - {% include 'inc/created_updated.html' %} - +{% block breadcrumbs %} +
  • Tags
  • +
  • {{ object }}
  • {% endblock %} {% block content %} -
    -
    -
    -
    - Tag -
    - - - - - - - - - - - - - - - - - - - - -
    Name - {{ object.name }} -
    Slug - {{ object.slug }} -
    Tagged Items - {{ items_count }} -
    Color -   -
    Description - {{ object.description|placeholder }} -
    -
    -
    -
    - {% include 'panel_table.html' with table=items_table heading='Tagged Objects' %} - {% include 'inc/paginator.html' with paginator=items_table.paginator page=items_table.page %} -
    +
    +
    +
    +
    + Tag +
    + + + + + + + + + + + + + + + + + +
    Name{{ object.name }}
    Description{{ object.description|placeholder }}
    Color +   +
    Tagged Items + {{ tagged_item_count }} +
    + {% plugin_left_page object %} +
    +
    + {% plugin_right_page object %} +
    +
    +
    +
    + {% plugin_full_width_page object %} +
    +
    {% endblock %}