diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index b1a53e93c..b4096973a 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -36,26 +36,15 @@ from .models import ( ) -class DeviceComponentsView(generic.ObjectView): +class DeviceComponentsView(generic.ObjectChildrenView): queryset = Device.objects.all() - model = None - table = None - def get_components(self, request, instance): - return self.model.objects.restrict(request.user, 'view').filter(device=instance) + def get_children(self, request, parent): + return self.child_model.objects.restrict(request.user, 'view').filter(device=parent) def get_extra_context(self, request, instance): - components = self.get_components(request, instance) - table = self.table(data=components, user=request.user) - change_perm = f'{self.model._meta.app_label}.change_{self.model._meta.model_name}' - delete_perm = f'{self.model._meta.app_label}.delete_{self.model._meta.model_name}' - if request.user.has_perm(change_perm) or request.user.has_perm(delete_perm): - table.columns.show('pk') - paginate_table(table, request) - return { - 'table': table, - 'active_tab': f"{self.model._meta.verbose_name_plural.replace(' ', '-')}", + 'active_tab': f"{self.child_model._meta.verbose_name_plural.replace(' ', '-')}", } @@ -63,8 +52,8 @@ class DeviceTypeComponentsView(DeviceComponentsView): queryset = DeviceType.objects.all() template_name = 'dcim/devicetype/component_templates.html' - def get_components(self, request, instance): - return self.model.objects.restrict(request.user, 'view').filter(device_type=instance) + def get_children(self, request, parent): + return self.child_model.objects.restrict(request.user, 'view').filter(device_type=parent) class BulkDisconnectView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): @@ -806,42 +795,42 @@ class DeviceTypeView(generic.ObjectView): class DeviceTypeConsolePortsView(DeviceTypeComponentsView): - model = ConsolePortTemplate + child_model = ConsolePortTemplate table = tables.ConsolePortTemplateTable class DeviceTypeConsoleServerPortsView(DeviceTypeComponentsView): - model = ConsoleServerPortTemplate + child_model = ConsoleServerPortTemplate table = tables.ConsoleServerPortTemplateTable class DeviceTypePowerPortsView(DeviceTypeComponentsView): - model = PowerPortTemplate + child_model = PowerPortTemplate table = tables.PowerPortTemplateTable class DeviceTypePowerOutletsView(DeviceTypeComponentsView): - model = PowerOutletTemplate + child_model = PowerOutletTemplate table = tables.PowerOutletTemplateTable class DeviceTypeInterfacesView(DeviceTypeComponentsView): - model = InterfaceTemplate + child_model = InterfaceTemplate table = tables.InterfaceTemplateTable class DeviceTypeFrontPortsView(DeviceTypeComponentsView): - model = FrontPortTemplate + child_model = FrontPortTemplate table = tables.FrontPortTemplateTable class DeviceTypeRearPortsView(DeviceTypeComponentsView): - model = RearPortTemplate + child_model = RearPortTemplate table = tables.RearPortTemplateTable class DeviceTypeDeviceBaysView(DeviceTypeComponentsView): - model = DeviceBayTemplate + child_model = DeviceBayTemplate table = tables.DeviceBayTemplateTable @@ -1337,61 +1326,61 @@ class DeviceView(generic.ObjectView): class DeviceConsolePortsView(DeviceComponentsView): - model = ConsolePort + child_model = ConsolePort table = tables.DeviceConsolePortTable template_name = 'dcim/device/consoleports.html' class DeviceConsoleServerPortsView(DeviceComponentsView): - model = ConsoleServerPort + child_model = ConsoleServerPort table = tables.DeviceConsoleServerPortTable template_name = 'dcim/device/consoleserverports.html' class DevicePowerPortsView(DeviceComponentsView): - model = PowerPort + child_model = PowerPort table = tables.DevicePowerPortTable template_name = 'dcim/device/powerports.html' class DevicePowerOutletsView(DeviceComponentsView): - model = PowerOutlet + child_model = PowerOutlet table = tables.DevicePowerOutletTable template_name = 'dcim/device/poweroutlets.html' class DeviceInterfacesView(DeviceComponentsView): - model = Interface + child_model = Interface table = tables.DeviceInterfaceTable template_name = 'dcim/device/interfaces.html' - def get_components(self, request, instance): - return instance.vc_interfaces().restrict(request.user, 'view').prefetch_related( + def get_children(self, request, parent): + return parent.vc_interfaces().restrict(request.user, 'view').prefetch_related( Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user)), Prefetch('member_interfaces', queryset=Interface.objects.restrict(request.user)) ) class DeviceFrontPortsView(DeviceComponentsView): - model = FrontPort + child_model = FrontPort table = tables.DeviceFrontPortTable template_name = 'dcim/device/frontports.html' class DeviceRearPortsView(DeviceComponentsView): - model = RearPort + child_model = RearPort table = tables.DeviceRearPortTable template_name = 'dcim/device/rearports.html' class DeviceDeviceBaysView(DeviceComponentsView): - model = DeviceBay + child_model = DeviceBay table = tables.DeviceDeviceBayTable template_name = 'dcim/device/devicebays.html' class DeviceInventoryView(DeviceComponentsView): - model = InventoryItem + child_model = InventoryItem table = tables.DeviceInventoryItemTable template_name = 'dcim/device/inventory.html' diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index e2a6be7fe..08253c054 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -453,106 +453,62 @@ class PrefixView(generic.ObjectView): } -class PrefixPrefixesView(generic.ObjectView): +class PrefixPrefixesView(generic.ObjectChildrenView): queryset = Prefix.objects.all() + child_model = Prefix + table = tables.PrefixTable template_name = 'ipam/prefix/prefixes.html' - def get_extra_context(self, request, instance): - # Find all child prefixes contained in this prefix - prefix_list = instance.get_child_prefixes().restrict(request.user, 'view').prefetch_related( - 'site', 'vlan', 'role', - ) + def get_children(self, request, parent): + child_prefixes = parent.get_child_prefixes().restrict(request.user, 'view') - # Return List of requested Prefixes + # Add available prefixes if requested show_available = bool(request.GET.get('show_available', 'true') == 'true') show_assigned = bool(request.GET.get('show_assigned', 'true') == 'true') - child_prefixes = add_requested_prefixes(instance.prefix, prefix_list, show_available, show_assigned) + child_prefixes = add_requested_prefixes(parent.prefix, child_prefixes, show_available, show_assigned) - table = tables.PrefixTable(child_prefixes, user=request.user, exclude=('utilization',)) - if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'): - table.columns.show('pk') - paginate_table(table, request) - - bulk_querystring = 'vrf_id={}&within={}'.format(instance.vrf.pk if instance.vrf else '0', instance.prefix) - - # Compile permissions list for rendering the object table - permissions = { - 'change': request.user.has_perm('ipam.change_prefix'), - 'delete': request.user.has_perm('ipam.delete_prefix'), - } - - return { - 'table': table, - 'permissions': permissions, - 'bulk_querystring': bulk_querystring, - 'active_tab': 'prefixes', - 'first_available_prefix': instance.get_first_available_prefix(), - 'show_available': show_available, - 'show_assigned': show_assigned, - } - - -class PrefixIPRangesView(generic.ObjectView): - queryset = Prefix.objects.all() - template_name = 'ipam/prefix/ip_ranges.html' + return child_prefixes def get_extra_context(self, request, instance): - # Find all IPRanges belonging to this Prefix - ip_ranges = instance.get_child_ranges().restrict(request.user, 'view').prefetch_related('vrf') - - table = tables.IPRangeTable(ip_ranges, user=request.user) - if request.user.has_perm('ipam.change_iprange') or request.user.has_perm('ipam.delete_iprange'): - table.columns.show('pk') - paginate_table(table, request) - - bulk_querystring = 'vrf_id={}&parent={}'.format(instance.vrf.pk if instance.vrf else '0', instance.prefix) - - # Compile permissions list for rendering the object table - permissions = { - 'change': request.user.has_perm('ipam.change_iprange'), - 'delete': request.user.has_perm('ipam.delete_iprange'), + return { + 'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&within={instance.prefix}", + 'active_tab': 'prefixes', + 'first_available_prefix': instance.get_first_available_prefix(), + 'show_available': bool(request.GET.get('show_available', 'true') == 'true'), + 'show_assigned': bool(request.GET.get('show_assigned', 'true') == 'true'), } + +class PrefixIPRangesView(generic.ObjectChildrenView): + queryset = Prefix.objects.all() + child_model = IPRange + table = tables.IPRangeTable + template_name = 'ipam/prefix/ip_ranges.html' + + def get_children(self, request, parent): + return parent.get_child_ranges().restrict(request.user, 'view') + + def get_extra_context(self, request, instance): return { - 'table': table, - 'permissions': permissions, - 'bulk_querystring': bulk_querystring, + 'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}", 'active_tab': 'ip-ranges', } -class PrefixIPAddressesView(generic.ObjectView): +class PrefixIPAddressesView(generic.ObjectChildrenView): queryset = Prefix.objects.all() + child_model = IPAddress + table = tables.IPAddressTable template_name = 'ipam/prefix/ip_addresses.html' + def get_children(self, request, parent): + return parent.get_child_ips().restrict(request.user, 'view') + def get_extra_context(self, request, instance): - # Find all IPAddresses belonging to this Prefix - ipaddresses = instance.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf') - - # Add available IP addresses to the table if requested - if request.GET.get('show_available', 'true') == 'true': - ipaddresses = add_available_ipaddresses(instance.prefix, ipaddresses, instance.is_pool) - - table = tables.IPAddressTable(ipaddresses, user=request.user) - if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'): - table.columns.show('pk') - paginate_table(table, request) - - bulk_querystring = 'vrf_id={}&parent={}'.format(instance.vrf.pk if instance.vrf else '0', instance.prefix) - - # Compile permissions list for rendering the object table - permissions = { - 'change': request.user.has_perm('ipam.change_ipaddress'), - 'delete': request.user.has_perm('ipam.delete_ipaddress'), - } - return { - 'table': table, - 'permissions': permissions, - 'bulk_querystring': bulk_querystring, + 'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}", 'active_tab': 'ip-addresses', 'first_available_ip': instance.get_first_available_ip(), - 'show_available': request.GET.get('show_available', 'true') == 'true', } @@ -600,35 +556,18 @@ class IPRangeView(generic.ObjectView): queryset = IPRange.objects.all() -class IPRangeIPAddressesView(generic.ObjectView): +class IPRangeIPAddressesView(generic.ObjectChildrenView): queryset = IPRange.objects.all() + child_model = IPAddress + table = tables.IPAddressTable template_name = 'ipam/iprange/ip_addresses.html' + def get_children(self, request, parent): + return parent.get_child_ips().restrict(request.user, 'view') + def get_extra_context(self, request, instance): - # Find all IPAddresses within this range - ipaddresses = instance.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf') - - # Add available IP addresses to the table if requested - # if request.GET.get('show_available', 'true') == 'true': - # ipaddresses = add_available_ipaddresses(instance.prefix, ipaddresses, instance.is_pool) - - ip_table = tables.IPAddressTable(ipaddresses) - if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'): - ip_table.columns.show('pk') - paginate_table(ip_table, request) - - # Compile permissions list for rendering the object table - permissions = { - 'add': request.user.has_perm('ipam.add_ipaddress'), - 'change': request.user.has_perm('ipam.change_ipaddress'), - 'delete': request.user.has_perm('ipam.delete_ipaddress'), - } - return { - 'ip_table': ip_table, - 'permissions': permissions, 'active_tab': 'ip-addresses', - 'show_available': request.GET.get('show_available', 'true') == 'true', } @@ -1016,32 +955,32 @@ class VLANView(generic.ObjectView): } -class VLANInterfacesView(generic.ObjectView): +class VLANInterfacesView(generic.ObjectChildrenView): queryset = VLAN.objects.all() + child_model = Interface + table = tables.VLANDevicesTable template_name = 'ipam/vlan/interfaces.html' - def get_extra_context(self, request, instance): - interfaces = instance.get_interfaces().prefetch_related('device') - members_table = tables.VLANDevicesTable(interfaces) - paginate_table(members_table, request) + def get_children(self, request, parent): + return parent.get_interfaces().restrict(request.user, 'view') + def get_extra_context(self, request, instance): return { - 'members_table': members_table, 'active_tab': 'interfaces', } -class VLANVMInterfacesView(generic.ObjectView): +class VLANVMInterfacesView(generic.ObjectChildrenView): queryset = VLAN.objects.all() + child_model = VMInterface + table = tables.VLANVirtualMachinesTable template_name = 'ipam/vlan/vminterfaces.html' - def get_extra_context(self, request, instance): - interfaces = instance.get_vminterfaces().prefetch_related('virtual_machine') - members_table = tables.VLANVirtualMachinesTable(interfaces) - paginate_table(members_table, request) + def get_children(self, request, parent): + return parent.get_vminterfaces().restrict(request.user, 'view') + def get_extra_context(self, request, instance): return { - 'members_table': members_table, 'active_tab': 'vminterfaces', } diff --git a/netbox/netbox/views/generic.py b/netbox/netbox/views/generic.py index 1c2ff9917..b27d4ee17 100644 --- a/netbox/netbox/views/generic.py +++ b/netbox/netbox/views/generic.py @@ -72,6 +72,54 @@ class ObjectView(ObjectPermissionRequiredMixin, View): }) +class ObjectChildrenView(ObjectView): + """ + Display a table of child objects associated with the parent object. + + queryset: The base queryset for retrieving the *parent* object + table: Table class used to render child objects list + template_name: Name of the template to use + """ + queryset = None + child_model = None + table = None + template_name = None + + def get_children(self, request, parent): + """ + Return a QuerySet or iterable of child objects. + + request: The current request + parent: The parent object + """ + raise NotImplementedError(f'{self.__class__.__name__} must implement get_children()') + + def get(self, request, *args, **kwargs): + """ + GET handler for rendering child objects. + """ + instance = get_object_or_404(self.queryset, **kwargs) + child_objects = self.get_children(request, instance) + + permissions = {} + for action in ('change', 'delete'): + perm_name = get_permission_for_model(self.child_model, action) + permissions[action] = request.user.has_perm(perm_name) + + table = self.table(child_objects, user=request.user) + # Determine whether to display bulk action checkboxes + if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']): + table.columns.show('pk') + paginate_table(table, request) + + return render(request, self.get_template_name(), { + 'object': instance, + 'table': table, + 'permissions': permissions, + **self.get_extra_context(request, instance), + }) + + class ObjectListView(ObjectPermissionRequiredMixin, View): """ List a series of objects. diff --git a/netbox/templates/ipam/iprange/ip_addresses.html b/netbox/templates/ipam/iprange/ip_addresses.html index f034d5622..f23db1a01 100644 --- a/netbox/templates/ipam/iprange/ip_addresses.html +++ b/netbox/templates/ipam/iprange/ip_addresses.html @@ -11,7 +11,7 @@ {% block content %}
- {% include 'utilities/obj_table.html' with table=ip_table heading='IP Addresses' bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %} + {% include 'utilities/obj_table.html' with heading='IP Addresses' bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %}
{% endblock %} diff --git a/netbox/templates/ipam/vlan/interfaces.html b/netbox/templates/ipam/vlan/interfaces.html index d0eff51e5..b115c39a3 100644 --- a/netbox/templates/ipam/vlan/interfaces.html +++ b/netbox/templates/ipam/vlan/interfaces.html @@ -3,7 +3,7 @@ {% block content %}
- {% include 'utilities/obj_table.html' with table=members_table heading='Device Interfaces' parent=vlan %} + {% include 'utilities/obj_table.html' with heading='Device Interfaces' parent=vlan %}
{% endblock %} diff --git a/netbox/templates/ipam/vlan/vminterfaces.html b/netbox/templates/ipam/vlan/vminterfaces.html index 33e78a39f..024c14a30 100644 --- a/netbox/templates/ipam/vlan/vminterfaces.html +++ b/netbox/templates/ipam/vlan/vminterfaces.html @@ -3,7 +3,7 @@ {% block content %}
- {% include 'utilities/obj_table.html' with table=members_table heading='Virtual Machine Interfaces' parent=vlan %} + {% include 'utilities/obj_table.html' with heading='Virtual Machine Interfaces' parent=vlan %}
{% endblock %} diff --git a/netbox/templates/virtualization/cluster/devices.html b/netbox/templates/virtualization/cluster/devices.html index 9f4b7fc3e..fbaabd8f1 100644 --- a/netbox/templates/virtualization/cluster/devices.html +++ b/netbox/templates/virtualization/cluster/devices.html @@ -12,7 +12,7 @@
{% csrf_token %}
- {% render_table devices_table 'inc/table.html' %} + {% render_table table 'inc/table.html' %}
{% if perms.virtualization.change_cluster %} diff --git a/netbox/templates/virtualization/virtualmachine/interfaces.html b/netbox/templates/virtualization/virtualmachine/interfaces.html index fe9e00435..6a6f1d32a 100644 --- a/netbox/templates/virtualization/virtualmachine/interfaces.html +++ b/netbox/templates/virtualization/virtualmachine/interfaces.html @@ -8,7 +8,7 @@ {% csrf_token %} {% include 'inc/table_controls.html' with table_modal="VirtualMachineVMInterfaceTable_config" %}
- {% render_table interface_table 'inc/table.html' %} + {% render_table table 'inc/table.html' %}
{% if perms.virtualization.change_vminterface %} @@ -34,5 +34,5 @@
- {% table_config_form interface_table %} + {% table_config_form table %} {% endblock %} diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 5cb4f133a..e260384fd 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -161,38 +161,32 @@ class ClusterView(generic.ObjectView): queryset = Cluster.objects.all() -class ClusterVirtualMachinesView(generic.ObjectView): +class ClusterVirtualMachinesView(generic.ObjectChildrenView): queryset = Cluster.objects.all() + child_model = VirtualMachine + table = tables.VirtualMachineTable template_name = 'virtualization/cluster/virtual_machines.html' - def get_extra_context(self, request, instance): - virtualmachines = VirtualMachine.objects.restrict(request.user, 'view').filter(cluster=instance) - virtualmachines_table = tables.VirtualMachineTable( - virtualmachines, - exclude=('cluster',), - orderable=False - ) + def get_children(self, request, parent): + return VirtualMachine.objects.restrict(request.user, 'view').filter(cluster=parent) + def get_extra_context(self, request, instance): return { - 'virtualmachines_table': virtualmachines_table, 'active_tab': 'virtual-machines', } -class ClusterDevicesView(generic.ObjectView): +class ClusterDevicesView(generic.ObjectChildrenView): queryset = Cluster.objects.all() + child_model = Device + table = DeviceTable template_name = 'virtualization/cluster/devices.html' - def get_extra_context(self, request, instance): - devices = Device.objects.restrict(request.user, 'view').filter(cluster=instance).prefetch_related( - 'site', 'rack', 'tenant', 'device_type__manufacturer' - ) - devices_table = DeviceTable(list(devices), orderable=False) - if request.user.has_perm('virtualization.change_cluster'): - devices_table.columns.show('pk') + def get_children(self, request, parent): + return Device.objects.restrict(request.user, 'view').filter(cluster=parent) + def get_extra_context(self, request, instance): return { - 'devices_table': devices_table, 'active_tab': 'devices', } @@ -347,26 +341,20 @@ class VirtualMachineView(generic.ObjectView): } -class VirtualMachineInterfacesView(generic.ObjectView): +class VirtualMachineInterfacesView(generic.ObjectChildrenView): queryset = VirtualMachine.objects.all() + child_model = VMInterface + table = tables.VMInterfaceTable template_name = 'virtualization/virtualmachine/interfaces.html' - def get_extra_context(self, request, instance): - interfaces = instance.interfaces.restrict(request.user, 'view').prefetch_related( + def get_children(self, request, parent): + return parent.interfaces.restrict(request.user, 'view').prefetch_related( Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user)), 'tags', ) - interface_table = tables.VirtualMachineVMInterfaceTable( - data=interfaces, - user=request.user, - orderable=False - ) - if request.user.has_perm('virtualization.change_vminterface') or \ - request.user.has_perm('virtualization.delete_vminterface'): - interface_table.columns.show('pk') + def get_extra_context(self, request, instance): return { - 'interface_table': interface_table, 'active_tab': 'interfaces', }