Просмотр исходного кода

Introduce LinkedCountColumn to standardize approach to counting related items in tables

Jeremy Stretch 5 лет назад
Родитель
Сommit
28f0da0bc1
6 измененных файлов с 82 добавлено и 81 удалено
  1. 26 49
      netbox/dcim/tables.py
  2. 15 15
      netbox/ipam/tables.py
  3. 4 2
      netbox/secrets/tables.py
  4. 4 2
      netbox/tenancy/tables.py
  5. 24 0
      netbox/utilities/tables.py
  6. 9 13
      netbox/virtualization/tables.py

+ 26 - 49
netbox/dcim/tables.py

@@ -3,8 +3,8 @@ from django_tables2.utils import Accessor
 
 
 from tenancy.tables import COL_TENANT
 from tenancy.tables import COL_TENANT
 from utilities.tables import (
 from utilities.tables import (
-    BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, TagColumn,
-    ToggleColumn,
+    BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn,
+    TagColumn, ToggleColumn,
 )
 )
 from .models import (
 from .models import (
     Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
     Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
@@ -49,14 +49,6 @@ RACKGROUP_ELEVATIONS = """
 </a>
 </a>
 """
 """
 
 
-RACK_DEVICE_COUNT = """
-<a href="{% url 'dcim:device_list' %}?rack_id={{ record.pk }}">{{ value }}</a>
-"""
-
-DEVICE_COUNT = """
-<a href="{% url 'dcim:device_list' %}?role={{ record.slug }}">{{ value|default:0 }}</a>
-"""
-
 RACKRESERVATION_ACTIONS = """
 RACKRESERVATION_ACTIONS = """
 <a href="{% url 'dcim:rackreservation_changelog' pk=record.pk %}" class="btn btn-default btn-xs" title="Change log">
 <a href="{% url 'dcim:rackreservation_changelog' pk=record.pk %}" class="btn btn-default btn-xs" title="Change log">
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
@@ -75,14 +67,6 @@ MANUFACTURER_ACTIONS = """
 {% endif %}
 {% endif %}
 """
 """
 
 
-DEVICEROLE_DEVICE_COUNT = """
-<a href="{% url 'dcim:device_list' %}?role={{ record.slug }}">{{ value|default:0 }}</a>
-"""
-
-DEVICEROLE_VM_COUNT = """
-<a href="{% url 'virtualization:virtualmachine_list' %}?role={{ record.slug }}">{{ value|default:0 }}</a>
-"""
-
 DEVICEROLE_ACTIONS = """
 DEVICEROLE_ACTIONS = """
 <a href="{% url 'dcim:devicerole_changelog' slug=record.slug %}" class="btn btn-default btn-xs" title="Change log">
 <a href="{% url 'dcim:devicerole_changelog' slug=record.slug %}" class="btn btn-default btn-xs" title="Change log">
     <i class="fa fa-history"></i>
     <i class="fa fa-history"></i>
@@ -92,24 +76,12 @@ DEVICEROLE_ACTIONS = """
 {% endif %}
 {% endif %}
 """
 """
 
 
-PLATFORM_DEVICE_COUNT = """
-<a href="{% url 'dcim:device_list' %}?platform={{ record.slug }}">{{ value|default:0 }}</a>
-"""
-
-PLATFORM_VM_COUNT = """
-<a href="{% url 'virtualization:virtualmachine_list' %}?platform={{ record.slug }}">{{ value|default:0 }}</a>
-"""
-
 DEVICE_PRIMARY_IP = """
 DEVICE_PRIMARY_IP = """
 {{ record.primary_ip6.address.ip|default:"" }}
 {{ record.primary_ip6.address.ip|default:"" }}
 {% if record.primary_ip6 and record.primary_ip4 %}<br />{% endif %}
 {% if record.primary_ip6 and record.primary_ip4 %}<br />{% endif %}
 {{ record.primary_ip4.address.ip|default:"" }}
 {{ record.primary_ip4.address.ip|default:"" }}
 """
 """
 
 
-DEVICETYPE_INSTANCES_TEMPLATE = """
-<a href="{% url 'dcim:device_list' %}?manufacturer_id={{ record.manufacturer_id }}&device_type_id={{ record.pk }}">{{ record.instance_count }}</a>
-"""
-
 UTILIZATION_GRAPH = """
 UTILIZATION_GRAPH = """
 {% load helpers %}
 {% load helpers %}
 {% utilization_graph value %}
 {% utilization_graph value %}
@@ -129,10 +101,6 @@ CABLE_LENGTH = """
 {% if record.length %}{{ record.length }} {{ record.get_length_unit_display }}{% else %}&mdash;{% endif %}
 {% if record.length %}{{ record.length }} {{ record.get_length_unit_display }}{% else %}&mdash;{% endif %}
 """
 """
 
 
-POWERPANEL_POWERFEED_COUNT = """
-<a href="{% url 'dcim:powerfeed_list' %}?power_panel_id={{ record.pk }}">{{ value }}</a>
-"""
-
 INTERFACE_IPADDRESSES = """
 INTERFACE_IPADDRESSES = """
 {% for ip in record.ip_addresses.unrestricted %}
 {% for ip in record.ip_addresses.unrestricted %}
     <a href="{{ ip.get_absolute_url }}">{{ ip }}</a><br />
     <a href="{{ ip.get_absolute_url }}">{{ ip }}</a><br />
@@ -280,8 +248,9 @@ class RackTable(BaseTable):
 
 
 
 
 class RackDetailTable(RackTable):
 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'
         verbose_name='Devices'
     )
     )
     get_utilization = tables.TemplateColumn(
     get_utilization = tables.TemplateColumn(
@@ -388,8 +357,9 @@ class DeviceTypeTable(BaseTable):
     is_full_depth = BooleanColumn(
     is_full_depth = BooleanColumn(
         verbose_name='Full Depth'
         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'
         verbose_name='Instances'
     )
     )
     tags = TagColumn(
     tags = TagColumn(
@@ -526,12 +496,14 @@ class DeviceBayTemplateTable(ComponentTemplateTable):
 
 
 class DeviceRoleTable(BaseTable):
 class DeviceRoleTable(BaseTable):
     pk = ToggleColumn()
     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'
         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'
         verbose_name='VMs'
     )
     )
     color = tables.TemplateColumn(
     color = tables.TemplateColumn(
@@ -553,12 +525,14 @@ class DeviceRoleTable(BaseTable):
 
 
 class PlatformTable(BaseTable):
 class PlatformTable(BaseTable):
     pk = ToggleColumn()
     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'
         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'
         verbose_name='VMs'
     )
     )
     actions = ButtonsColumn(Platform, pk_field='slug')
     actions = ButtonsColumn(Platform, pk_field='slug')
@@ -994,7 +968,9 @@ class VirtualChassisTable(BaseTable):
     master = tables.Column(
     master = tables.Column(
         linkify=True
         linkify=True
     )
     )
-    member_count = tables.Column(
+    member_count = LinkedCountColumn(
+        viewname='dcim:device_list',
+        url_params={'virtual_chassis_id': 'pk'},
         verbose_name='Members'
         verbose_name='Members'
     )
     )
     tags = TagColumn(
     tags = TagColumn(
@@ -1018,8 +994,9 @@ class PowerPanelTable(BaseTable):
         viewname='dcim:site',
         viewname='dcim:site',
         args=[Accessor('site__slug')]
         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'
         verbose_name='Feeds'
     )
     )
     tags = TagColumn(
     tags = TagColumn(

+ 15 - 15
netbox/ipam/tables.py

@@ -4,7 +4,9 @@ from django_tables2.utils import Accessor
 
 
 from dcim.models import Interface
 from dcim.models import Interface
 from tenancy.tables import COL_TENANT
 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 virtualization.models import VMInterface
 from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
 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 %}&mdash;{% endif %}
 {% if record.pk %}{% utilization_graph record.get_utilization %}{% else %}&mdash;{% endif %}
 """
 """
 
 
-ROLE_PREFIX_COUNT = """
-<a href="{% url 'ipam:prefix_list' %}?role={{ record.slug }}">{{ value|default:0 }}</a>
-"""
-
-ROLE_VLAN_COUNT = """
-<a href="{% url 'ipam:vlan_list' %}?role={{ record.slug }}">{{ value|default:0 }}</a>
-"""
-
 PREFIX_LINK = """
 PREFIX_LINK = """
 {% if record.children %}
 {% if record.children %}
     <span class="text-nowrap" style="padding-left: {{ record.parents }}0px "><i class="fa fa-caret-right"></i></a>
     <span class="text-nowrap" style="padding-left: {{ record.parents }}0px "><i class="fa fa-caret-right"></i></a>
@@ -209,7 +203,9 @@ class RIRTable(BaseTable):
     is_private = BooleanColumn(
     is_private = BooleanColumn(
         verbose_name='Private'
         verbose_name='Private'
     )
     )
-    aggregate_count = tables.Column(
+    aggregate_count = LinkedCountColumn(
+        viewname='ipam:aggregate_list',
+        url_params={'rir': 'slug'},
         verbose_name='Aggregates'
         verbose_name='Aggregates'
     )
     )
     actions = ButtonsColumn(RIR, pk_field='slug')
     actions = ButtonsColumn(RIR, pk_field='slug')
@@ -304,12 +300,14 @@ class AggregateDetailTable(AggregateTable):
 
 
 class RoleTable(BaseTable):
 class RoleTable(BaseTable):
     pk = ToggleColumn()
     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'
         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'
         verbose_name='VLANs'
     )
     )
     actions = ButtonsColumn(Role, pk_field='slug')
     actions = ButtonsColumn(Role, pk_field='slug')
@@ -508,7 +506,9 @@ class VLANGroupTable(BaseTable):
         viewname='dcim:site',
         viewname='dcim:site',
         args=[Accessor('site__slug')]
         args=[Accessor('site__slug')]
     )
     )
-    vlan_count = tables.Column(
+    vlan_count = LinkedCountColumn(
+        viewname='ipam:vlan_list',
+        url_params={'group': 'slug'},
         verbose_name='VLANs'
         verbose_name='VLANs'
     )
     )
     actions = ButtonsColumn(
     actions = ButtonsColumn(

+ 4 - 2
netbox/secrets/tables.py

@@ -1,6 +1,6 @@
 import django_tables2 as tables
 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
 from .models import SecretRole, Secret
 
 
 
 
@@ -11,7 +11,9 @@ from .models import SecretRole, Secret
 class SecretRoleTable(BaseTable):
 class SecretRoleTable(BaseTable):
     pk = ToggleColumn()
     pk = ToggleColumn()
     name = tables.LinkColumn()
     name = tables.LinkColumn()
-    secret_count = tables.Column(
+    secret_count = LinkedCountColumn(
+        viewname='secrets:secret_list',
+        url_params={'role': 'slug'},
         verbose_name='Secrets'
         verbose_name='Secrets'
     )
     )
     actions = ButtonsColumn(SecretRole, pk_field='slug')
     actions = ButtonsColumn(SecretRole, pk_field='slug')

+ 4 - 2
netbox/tenancy/tables.py

@@ -1,6 +1,6 @@
 import django_tables2 as tables
 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
 from .models import Tenant, TenantGroup
 
 
 MPTT_LINK = """
 MPTT_LINK = """
@@ -32,7 +32,9 @@ class TenantGroupTable(BaseTable):
         template_code=MPTT_LINK,
         template_code=MPTT_LINK,
         orderable=False
         orderable=False
     )
     )
-    tenant_count = tables.Column(
+    tenant_count = LinkedCountColumn(
+        viewname='tenancy:tenant_list',
+        url_params={'group': 'slug'},
         verbose_name='Tenants'
         verbose_name='Tenants'
     )
     )
     actions = ButtonsColumn(TenantGroup, pk_field='slug')
     actions = ButtonsColumn(TenantGroup, pk_field='slug')

+ 24 - 0
netbox/utilities/tables.py

@@ -1,6 +1,7 @@
 import django_tables2 as tables
 import django_tables2 as tables
 from django.core.exceptions import FieldDoesNotExist
 from django.core.exceptions import FieldDoesNotExist
 from django.db.models.fields.related import RelatedField
 from django.db.models.fields.related import RelatedField
+from django.urls import reverse
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 from django_tables2.data import TableQuerysetData
 from django_tables2.data import TableQuerysetData
 
 
@@ -213,6 +214,29 @@ class ColoredLabelColumn(tables.TemplateColumn):
         super().__init__(template_code=self.template_code, *args, **kwargs)
         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'<a href="{url}">{value}</a>')
+        return value
+
+
 class TagColumn(tables.TemplateColumn):
 class TagColumn(tables.TemplateColumn):
     """
     """
     Display a list of tags assigned to the object.
     Display a list of tags assigned to the object.

+ 9 - 13
netbox/virtualization/tables.py

@@ -2,7 +2,9 @@ import django_tables2 as tables
 
 
 from dcim.tables import BaseInterfaceTable
 from dcim.tables import BaseInterfaceTable
 from tenancy.tables import COL_TENANT
 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
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 
 
 VIRTUALMACHINE_PRIMARY_IP = """
 VIRTUALMACHINE_PRIMARY_IP = """
@@ -11,14 +13,6 @@ VIRTUALMACHINE_PRIMARY_IP = """
 {{ record.primary_ip4.address.ip|default:"" }}
 {{ record.primary_ip4.address.ip|default:"" }}
 """
 """
 
 
-DEVICE_COUNT = """
-<a href="{% url 'dcim:device_list' %}?cluster_id={{ record.pk }}">{{ value|default:0 }}</a>
-"""
-
-VM_COUNT = """
-<a href="{% url 'virtualization:virtualmachine_list' %}?cluster_id={{ record.pk }}">{{ value|default:0 }}</a>
-"""
-
 
 
 #
 #
 # Cluster types
 # Cluster types
@@ -69,12 +63,14 @@ class ClusterTable(BaseTable):
     site = tables.Column(
     site = tables.Column(
         linkify=True
         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'
         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'
         verbose_name='VMs'
     )
     )
     tags = TagColumn(
     tags = TagColumn(