From 28f0da0bc1570bd17e1846c921a72eb7f2697a9c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 25 Sep 2020 12:42:17 -0400 Subject: [PATCH] Introduce LinkedCountColumn to standardize approach to counting related items in tables --- netbox/dcim/tables.py | 75 ++++++++++++--------------------- netbox/ipam/tables.py | 30 ++++++------- netbox/secrets/tables.py | 6 ++- netbox/tenancy/tables.py | 6 ++- netbox/utilities/tables.py | 24 +++++++++++ netbox/virtualization/tables.py | 22 ++++------ 6 files changed, 82 insertions(+), 81 deletions(-) diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 4486ecd1b..1053287f7 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -3,8 +3,8 @@ from django_tables2.utils import Accessor from tenancy.tables import COL_TENANT from utilities.tables import ( - BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, TagColumn, - ToggleColumn, + BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn, + TagColumn, ToggleColumn, ) from .models import ( Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, @@ -49,14 +49,6 @@ RACKGROUP_ELEVATIONS = """ """ -RACK_DEVICE_COUNT = """ -{{ value }} -""" - -DEVICE_COUNT = """ -{{ value|default:0 }} -""" - RACKRESERVATION_ACTIONS = """ @@ -75,14 +67,6 @@ MANUFACTURER_ACTIONS = """ {% endif %} """ -DEVICEROLE_DEVICE_COUNT = """ -{{ value|default:0 }} -""" - -DEVICEROLE_VM_COUNT = """ -{{ value|default:0 }} -""" - DEVICEROLE_ACTIONS = """ @@ -92,24 +76,12 @@ DEVICEROLE_ACTIONS = """ {% endif %} """ -PLATFORM_DEVICE_COUNT = """ -{{ value|default:0 }} -""" - -PLATFORM_VM_COUNT = """ -{{ value|default:0 }} -""" - DEVICE_PRIMARY_IP = """ {{ record.primary_ip6.address.ip|default:"" }} {% if record.primary_ip6 and record.primary_ip4 %}
{% endif %} {{ record.primary_ip4.address.ip|default:"" }} """ -DEVICETYPE_INSTANCES_TEMPLATE = """ -{{ record.instance_count }} -""" - UTILIZATION_GRAPH = """ {% load helpers %} {% utilization_graph value %} @@ -129,10 +101,6 @@ CABLE_LENGTH = """ {% if record.length %}{{ record.length }} {{ record.get_length_unit_display }}{% else %}—{% endif %} """ -POWERPANEL_POWERFEED_COUNT = """ -{{ value }} -""" - INTERFACE_IPADDRESSES = """ {% for ip in record.ip_addresses.unrestricted %} {{ ip }}
@@ -280,8 +248,9 @@ class RackTable(BaseTable): class RackDetailTable(RackTable): - device_count = tables.TemplateColumn( - template_code=RACK_DEVICE_COUNT, + device_count = LinkedCountColumn( + viewname='dcim:device_list', + url_params={'rack_id': 'pk'}, verbose_name='Devices' ) get_utilization = tables.TemplateColumn( @@ -388,8 +357,9 @@ class DeviceTypeTable(BaseTable): is_full_depth = BooleanColumn( verbose_name='Full Depth' ) - instance_count = tables.TemplateColumn( - template_code=DEVICETYPE_INSTANCES_TEMPLATE, + instance_count = LinkedCountColumn( + viewname='dcim:device_list', + url_params={'device_type_id': 'pk'}, verbose_name='Instances' ) tags = TagColumn( @@ -526,12 +496,14 @@ class DeviceBayTemplateTable(ComponentTemplateTable): class DeviceRoleTable(BaseTable): pk = ToggleColumn() - device_count = tables.TemplateColumn( - template_code=DEVICEROLE_DEVICE_COUNT, + device_count = LinkedCountColumn( + viewname='dcim:device_list', + url_params={'role': 'slug'}, verbose_name='Devices' ) - vm_count = tables.TemplateColumn( - template_code=DEVICEROLE_VM_COUNT, + vm_count = LinkedCountColumn( + viewname='virtualization:virtualmachine_list', + url_params={'role': 'slug'}, verbose_name='VMs' ) color = tables.TemplateColumn( @@ -553,12 +525,14 @@ class DeviceRoleTable(BaseTable): class PlatformTable(BaseTable): pk = ToggleColumn() - device_count = tables.TemplateColumn( - template_code=PLATFORM_DEVICE_COUNT, + device_count = LinkedCountColumn( + viewname='dcim:device_list', + url_params={'platform': 'slug'}, verbose_name='Devices' ) - vm_count = tables.TemplateColumn( - template_code=PLATFORM_VM_COUNT, + vm_count = LinkedCountColumn( + viewname='virtualization:virtualmachine_list', + url_params={'platform': 'slug'}, verbose_name='VMs' ) actions = ButtonsColumn(Platform, pk_field='slug') @@ -994,7 +968,9 @@ class VirtualChassisTable(BaseTable): master = tables.Column( linkify=True ) - member_count = tables.Column( + member_count = LinkedCountColumn( + viewname='dcim:device_list', + url_params={'virtual_chassis_id': 'pk'}, verbose_name='Members' ) tags = TagColumn( @@ -1018,8 +994,9 @@ class PowerPanelTable(BaseTable): viewname='dcim:site', args=[Accessor('site__slug')] ) - powerfeed_count = tables.TemplateColumn( - template_code=POWERPANEL_POWERFEED_COUNT, + powerfeed_count = LinkedCountColumn( + viewname='dcim:powerfeed_list', + url_params={'power_panel_id': 'pk'}, verbose_name='Feeds' ) tags = TagColumn( diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index e8b2474ea..1d2ff9243 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -4,7 +4,9 @@ from django_tables2.utils import Accessor from dcim.models import Interface from tenancy.tables import COL_TENANT -from utilities.tables import BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, TagColumn, ToggleColumn +from utilities.tables import ( + BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, LinkedCountColumn, TagColumn, ToggleColumn, +) from virtualization.models import VMInterface from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF @@ -34,14 +36,6 @@ UTILIZATION_GRAPH = """ {% if record.pk %}{% utilization_graph record.get_utilization %}{% else %}—{% endif %} """ -ROLE_PREFIX_COUNT = """ -{{ value|default:0 }} -""" - -ROLE_VLAN_COUNT = """ -{{ value|default:0 }} -""" - PREFIX_LINK = """ {% if record.children %} @@ -209,7 +203,9 @@ class RIRTable(BaseTable): is_private = BooleanColumn( verbose_name='Private' ) - aggregate_count = tables.Column( + aggregate_count = LinkedCountColumn( + viewname='ipam:aggregate_list', + url_params={'rir': 'slug'}, verbose_name='Aggregates' ) actions = ButtonsColumn(RIR, pk_field='slug') @@ -304,12 +300,14 @@ class AggregateDetailTable(AggregateTable): class RoleTable(BaseTable): pk = ToggleColumn() - prefix_count = tables.TemplateColumn( - template_code=ROLE_PREFIX_COUNT, + prefix_count = LinkedCountColumn( + viewname='ipam:prefix_list', + url_params={'role': 'slug'}, verbose_name='Prefixes' ) - vlan_count = tables.TemplateColumn( - template_code=ROLE_VLAN_COUNT, + vlan_count = LinkedCountColumn( + viewname='ipam:vlan_list', + url_params={'role': 'slug'}, verbose_name='VLANs' ) actions = ButtonsColumn(Role, pk_field='slug') @@ -508,7 +506,9 @@ class VLANGroupTable(BaseTable): viewname='dcim:site', args=[Accessor('site__slug')] ) - vlan_count = tables.Column( + vlan_count = LinkedCountColumn( + viewname='ipam:vlan_list', + url_params={'group': 'slug'}, verbose_name='VLANs' ) actions = ButtonsColumn( diff --git a/netbox/secrets/tables.py b/netbox/secrets/tables.py index 7158b0b13..345c8a689 100644 --- a/netbox/secrets/tables.py +++ b/netbox/secrets/tables.py @@ -1,6 +1,6 @@ import django_tables2 as tables -from utilities.tables import BaseTable, ButtonsColumn, TagColumn, ToggleColumn +from utilities.tables import BaseTable, ButtonsColumn, LinkedCountColumn, TagColumn, ToggleColumn from .models import SecretRole, Secret @@ -11,7 +11,9 @@ from .models import SecretRole, Secret class SecretRoleTable(BaseTable): pk = ToggleColumn() name = tables.LinkColumn() - secret_count = tables.Column( + secret_count = LinkedCountColumn( + viewname='secrets:secret_list', + url_params={'role': 'slug'}, verbose_name='Secrets' ) actions = ButtonsColumn(SecretRole, pk_field='slug') diff --git a/netbox/tenancy/tables.py b/netbox/tenancy/tables.py index dc96b839c..8f9073025 100644 --- a/netbox/tenancy/tables.py +++ b/netbox/tenancy/tables.py @@ -1,6 +1,6 @@ import django_tables2 as tables -from utilities.tables import BaseTable, ButtonsColumn, TagColumn, ToggleColumn +from utilities.tables import BaseTable, ButtonsColumn, LinkedCountColumn, TagColumn, ToggleColumn from .models import Tenant, TenantGroup MPTT_LINK = """ @@ -32,7 +32,9 @@ class TenantGroupTable(BaseTable): template_code=MPTT_LINK, orderable=False ) - tenant_count = tables.Column( + tenant_count = LinkedCountColumn( + viewname='tenancy:tenant_list', + url_params={'group': 'slug'}, verbose_name='Tenants' ) actions = ButtonsColumn(TenantGroup, pk_field='slug') diff --git a/netbox/utilities/tables.py b/netbox/utilities/tables.py index 76c37b403..d4861c93a 100644 --- a/netbox/utilities/tables.py +++ b/netbox/utilities/tables.py @@ -1,6 +1,7 @@ import django_tables2 as tables from django.core.exceptions import FieldDoesNotExist from django.db.models.fields.related import RelatedField +from django.urls import reverse from django.utils.safestring import mark_safe from django_tables2.data import TableQuerysetData @@ -213,6 +214,29 @@ class ColoredLabelColumn(tables.TemplateColumn): super().__init__(template_code=self.template_code, *args, **kwargs) +class LinkedCountColumn(tables.Column): + """ + Render a count of related objects linked to a filtered URL. + + :param viewname: The view name to use for URL resolution + :param view_kwargs: Additional kwargs to pass for URL resolution (optional) + :param url_params: A dict of query parameters to append to the URL (e.g. ?foo=bar) (optional) + """ + def __init__(self, viewname, *args, view_kwargs=None, url_params=None, default=0, **kwargs): + self.viewname = viewname + self.view_kwargs = view_kwargs or {} + self.url_params = url_params + super().__init__(*args, default=default, **kwargs) + + def render(self, record, value): + if value: + url = reverse(self.viewname, kwargs=self.view_kwargs) + if self.url_params: + url += '?' + '&'.join([f'{k}={getattr(record, v)}' for k, v in self.url_params.items()]) + return mark_safe(f'{value}') + return value + + class TagColumn(tables.TemplateColumn): """ Display a list of tags assigned to the object. diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py index 7cb684dee..81cf986d8 100644 --- a/netbox/virtualization/tables.py +++ b/netbox/virtualization/tables.py @@ -2,7 +2,9 @@ import django_tables2 as tables from dcim.tables import BaseInterfaceTable from tenancy.tables import COL_TENANT -from utilities.tables import BaseTable, ButtonsColumn, ChoiceFieldColumn, ColoredLabelColumn, TagColumn, ToggleColumn +from utilities.tables import ( + BaseTable, ButtonsColumn, ChoiceFieldColumn, ColoredLabelColumn, LinkedCountColumn, TagColumn, ToggleColumn, +) from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface VIRTUALMACHINE_PRIMARY_IP = """ @@ -11,14 +13,6 @@ VIRTUALMACHINE_PRIMARY_IP = """ {{ record.primary_ip4.address.ip|default:"" }} """ -DEVICE_COUNT = """ -{{ value|default:0 }} -""" - -VM_COUNT = """ -{{ value|default:0 }} -""" - # # Cluster types @@ -69,12 +63,14 @@ class ClusterTable(BaseTable): site = tables.Column( linkify=True ) - device_count = tables.TemplateColumn( - template_code=DEVICE_COUNT, + device_count = LinkedCountColumn( + viewname='dcim:device_list', + url_params={'cluster_id': 'pk'}, verbose_name='Devices' ) - vm_count = tables.TemplateColumn( - template_code=VM_COUNT, + vm_count = LinkedCountColumn( + viewname='virtualization:virtualmachine_list', + url_params={'cluster_id': 'pk'}, verbose_name='VMs' ) tags = TagColumn(