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

Closes #3064: Include tags in object lists as a toggleable table column

Jeremy Stretch 5 лет назад
Родитель
Сommit
a4dbd2dae5

+ 1 - 0
docs/release-notes/version-2.8.md

@@ -6,6 +6,7 @@
 
 * [#492](https://github.com/netbox-community/netbox/issues/492) - Enable toggling and rearranging table columns
 * [#3147](https://github.com/netbox-community/netbox/issues/3147) - Allow specifying related objects by arbitrary attribute during CSV import
+* [#3064](https://github.com/netbox-community/netbox/issues/3064) - Include tags in object lists as a toggleable table column
 * [#3294](https://github.com/netbox-community/netbox/issues/3294) - Implement mechanism for storing user preferences
 * [#4421](https://github.com/netbox-community/netbox/issues/4421) - Retain user's preference for config context format
 * [#4502](https://github.com/netbox-community/netbox/issues/4502) - Enable configuration of proxies for outbound HTTP requests

+ 27 - 8
netbox/circuits/tables.py

@@ -2,7 +2,7 @@ import django_tables2 as tables
 from django_tables2.utils import Accessor
 
 from tenancy.tables import COL_TENANT
-from utilities.tables import BaseTable, ToggleColumn
+from utilities.tables import BaseTable, TagColumn, ToggleColumn
 from .models import Circuit, CircuitType, Provider
 
 CIRCUITTYPE_ACTIONS = """
@@ -31,10 +31,15 @@ class ProviderTable(BaseTable):
         accessor=Accessor('count_circuits'),
         verbose_name='Circuits'
     )
+    tags = TagColumn(
+        url_name='circuits:provider_list'
+    )
 
     class Meta(BaseTable.Meta):
         model = Provider
-        fields = ('pk', 'name', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'circuit_count')
+        fields = (
+            'pk', 'name', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'circuit_count', 'tags',
+        )
         default_columns = ('pk', 'name', 'asn', 'account', 'circuit_count')
 
 
@@ -45,7 +50,9 @@ class ProviderTable(BaseTable):
 class CircuitTypeTable(BaseTable):
     pk = ToggleColumn()
     name = tables.LinkColumn()
-    circuit_count = tables.Column(verbose_name='Circuits')
+    circuit_count = tables.Column(
+        verbose_name='Circuits'
+    )
     actions = tables.TemplateColumn(
         template_code=CIRCUITTYPE_ACTIONS,
         attrs={'td': {'class': 'text-right noprint'}},
@@ -64,21 +71,33 @@ class CircuitTypeTable(BaseTable):
 
 class CircuitTable(BaseTable):
     pk = ToggleColumn()
-    cid = tables.LinkColumn(verbose_name='ID')
-    provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')])
-    status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
-    tenant = tables.TemplateColumn(template_code=COL_TENANT)
+    cid = tables.LinkColumn(
+        verbose_name='ID'
+    )
+    provider = tables.LinkColumn(
+        viewname='circuits:provider',
+        args=[Accessor('provider.slug')]
+    )
+    status = tables.TemplateColumn(
+        template_code=STATUS_LABEL
+    )
+    tenant = tables.TemplateColumn(
+        template_code=COL_TENANT
+    )
     a_side = tables.Column(
         verbose_name='A Side'
     )
     z_side = tables.Column(
         verbose_name='Z Side'
     )
+    tags = TagColumn(
+        url_name='circuits:circuit_list'
+    )
 
     class Meta(BaseTable.Meta):
         model = Circuit
         fields = (
             'pk', 'cid', 'provider', 'type', 'status', 'tenant', 'a_side', 'z_side', 'install_date', 'commit_rate',
-            'description',
+            'description', 'tags',
         )
         default_columns = ('pk', 'cid', 'provider', 'type', 'status', 'tenant', 'a_side', 'z_side', 'description')

+ 28 - 8
netbox/dcim/tables.py

@@ -2,7 +2,7 @@ import django_tables2 as tables
 from django_tables2.utils import Accessor
 
 from tenancy.tables import COL_TENANT
-from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ToggleColumn
+from utilities.tables import BaseTable, BooleanColumn, ColorColumn, TagColumn, ToggleColumn
 from .models import (
     Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
     DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
@@ -242,13 +242,16 @@ class SiteTable(BaseTable):
     tenant = tables.TemplateColumn(
         template_code=COL_TENANT
     )
+    tags = TagColumn(
+        url_name='dcim:site_list'
+    )
 
     class Meta(BaseTable.Meta):
         model = Site
         fields = (
             'pk', 'name', 'slug', 'status', 'facility', 'region', 'tenant', 'asn', 'time_zone', 'description',
             'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone',
-            'contact_email',
+            'contact_email', 'tags',
         )
         default_columns = ('pk', 'name', 'status', 'facility', 'region', 'tenant', 'asn', 'description')
 
@@ -354,11 +357,14 @@ class RackDetailTable(RackTable):
         orderable=False,
         verbose_name='Power'
     )
+    tags = TagColumn(
+        url_name='dcim:rack_list'
+    )
 
     class Meta(RackTable.Meta):
         fields = (
             'pk', 'name', 'site', 'group', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag', 'type',
-            'width', 'u_height', 'device_count', 'get_utilization', 'get_power_utilization',
+            'width', 'u_height', 'device_count', 'get_utilization', 'get_power_utilization', 'tags',
         )
         default_columns = (
             'pk', 'name', 'site', 'group', 'status', 'facility_id', 'tenant', 'role', 'u_height', 'device_count',
@@ -450,17 +456,22 @@ class DeviceTypeTable(BaseTable):
         args=[Accessor('pk')],
         verbose_name='Device Type'
     )
-    is_full_depth = BooleanColumn(verbose_name='Full Depth')
+    is_full_depth = BooleanColumn(
+        verbose_name='Full Depth'
+    )
     instance_count = tables.TemplateColumn(
         template_code=DEVICETYPE_INSTANCES_TEMPLATE,
         verbose_name='Instances'
     )
+    tags = TagColumn(
+        url_name='dcim:devicetype_list'
+    )
 
     class Meta(BaseTable.Meta):
         model = DeviceType
         fields = (
             'pk', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
-            'instance_count',
+            'instance_count', 'tags',
         )
         default_columns = (
             'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'instance_count',
@@ -834,13 +845,16 @@ class DeviceTable(BaseTable):
     vc_priority = tables.Column(
         verbose_name='VC Priority'
     )
+    tags = TagColumn(
+        url_name='dcim:device_list'
+    )
 
     class Meta(BaseTable.Meta):
         model = Device
         fields = (
             'pk', 'name', 'status', 'tenant', 'device_role', 'device_type', 'platform', 'serial', 'asset_tag', 'site',
             'rack', 'position', 'face', 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis',
-            'vc_position', 'vc_priority',
+            'vc_position', 'vc_priority', 'tags',
         )
         default_columns = (
             'pk', 'name', 'status', 'tenant', 'site', 'rack', 'device_role', 'device_type', 'primary_ip',
@@ -1206,10 +1220,13 @@ class VirtualChassisTable(BaseTable):
     member_count = tables.Column(
         verbose_name='Members'
     )
+    tags = TagColumn(
+        url_name='dcim:virtualchassis_list'
+    )
 
     class Meta(BaseTable.Meta):
         model = VirtualChassis
-        fields = ('pk', 'name', 'domain', 'member_count')
+        fields = ('pk', 'name', 'domain', 'member_count', 'tags')
         default_columns = ('pk', 'name', 'domain', 'member_count')
 
 
@@ -1262,12 +1279,15 @@ class PowerFeedTable(BaseTable):
     available_power = tables.Column(
         verbose_name='Available power (VA)'
     )
+    tags = TagColumn(
+        url_name='dcim:powerfeed_list'
+    )
 
     class Meta(BaseTable.Meta):
         model = PowerFeed
         fields = (
             'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase',
-            'max_utilization', 'available_power',
+            'max_utilization', 'available_power', 'tags',
         )
         default_columns = (
             'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase',

+ 27 - 6
netbox/ipam/tables.py

@@ -3,7 +3,7 @@ from django_tables2.utils import Accessor
 
 from dcim.models import Interface
 from tenancy.tables import COL_TENANT
-from utilities.tables import BaseTable, BooleanColumn, ToggleColumn
+from utilities.tables import BaseTable, BooleanColumn, TagColumn, ToggleColumn
 from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
 
 RIR_UTILIZATION = """
@@ -199,10 +199,13 @@ class VRFTable(BaseTable):
     enforce_unique = BooleanColumn(
         verbose_name='Unique'
     )
+    tags = TagColumn(
+        url_name='ipam:vrf_list'
+    )
 
     class Meta(BaseTable.Meta):
         model = VRF
-        fields = ('pk', 'name', 'rd', 'tenant', 'enforce_unique', 'description')
+        fields = ('pk', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'tags')
         default_columns = ('pk', 'name', 'rd', 'tenant', 'description')
 
 
@@ -300,9 +303,13 @@ class AggregateDetailTable(AggregateTable):
         template_code=UTILIZATION_GRAPH,
         orderable=False
     )
+    tags = TagColumn(
+        url_name='ipam:aggregate_list'
+    )
 
     class Meta(AggregateTable.Meta):
-        fields = ('pk', 'prefix', 'rir', 'child_count', 'utilization', 'date_added', 'description')
+        fields = ('pk', 'prefix', 'rir', 'child_count', 'utilization', 'date_added', 'description', 'tags')
+        default_columns = ('pk', 'prefix', 'rir', 'child_count', 'utilization', 'date_added', 'description')
 
 
 #
@@ -388,10 +395,14 @@ class PrefixDetailTable(PrefixTable):
     tenant = tables.TemplateColumn(
         template_code=COL_TENANT
     )
+    tags = TagColumn(
+        url_name='ipam:prefix_list'
+    )
 
     class Meta(PrefixTable.Meta):
         fields = (
             'pk', 'prefix', 'status', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'is_pool', 'description',
+            'tags',
         )
         default_columns = (
             'pk', 'prefix', 'status', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description',
@@ -446,11 +457,14 @@ class IPAddressDetailTable(IPAddressTable):
     tenant = tables.TemplateColumn(
         template_code=COL_TENANT
     )
+    tags = TagColumn(
+        url_name='ipam:ipaddress_list'
+    )
 
     class Meta(IPAddressTable.Meta):
         fields = (
             'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'parent', 'interface', 'dns_name',
-            'description',
+            'description', 'tags',
         )
         default_columns = (
             'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'dns_name', 'description',
@@ -573,9 +587,13 @@ class VLANDetailTable(VLANTable):
     tenant = tables.TemplateColumn(
         template_code=COL_TENANT
     )
+    tags = TagColumn(
+        url_name='ipam:vlan_list'
+    )
 
     class Meta(VLANTable.Meta):
-        fields = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description')
+        fields = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description', 'tags')
+        default_columns = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description')
 
 
 class VLANMemberTable(BaseTable):
@@ -647,8 +665,11 @@ class ServiceTable(BaseTable):
         viewname='ipam:service',
         args=[Accessor('pk')]
     )
+    tags = TagColumn(
+        url_name='ipam:service_list'
+    )
 
     class Meta(BaseTable.Meta):
         model = Service
-        fields = ('pk', 'name', 'parent', 'protocol', 'port', 'ipaddresses', 'description')
+        fields = ('pk', 'name', 'parent', 'protocol', 'port', 'ipaddresses', 'description', 'tags')
         default_columns = ('pk', 'name', 'parent', 'protocol', 'port', 'description')

+ 5 - 2
netbox/secrets/tables.py

@@ -1,6 +1,6 @@
 import django_tables2 as tables
 
-from utilities.tables import BaseTable, ToggleColumn
+from utilities.tables import BaseTable, TagColumn, ToggleColumn
 from .models import SecretRole, Secret
 
 SECRETROLE_ACTIONS = """
@@ -42,8 +42,11 @@ class SecretRoleTable(BaseTable):
 class SecretTable(BaseTable):
     pk = ToggleColumn()
     device = tables.LinkColumn()
+    tags = TagColumn(
+        url_name='secrets:secret_list'
+    )
 
     class Meta(BaseTable.Meta):
         model = Secret
-        fields = ('pk', 'device', 'role', 'name', 'last_updated', 'hash')
+        fields = ('pk', 'device', 'role', 'name', 'last_updated', 'hash', 'tags')
         default_columns = ('pk', 'device', 'role', 'name', 'last_updated')

+ 1 - 3
netbox/templates/utilities/templatetags/tag.html

@@ -1,5 +1,3 @@
 {% load helpers %}
 
-{% if url_name %}<a href="{% url url_name %}?tag={{ tag.slug }}">{% endif %}
-<span class="label label-default" style="color: {{ tag.color|fgcolor }}; background-color: #{{ tag.color }}">{{ tag }}</span>
-{% if url_name %}</a>{% endif %}
+{% if url_name %}<a href="{% url url_name %}?tag={{ tag.slug }}">{% endif %}<span class="label label-default" style="color: {{ tag.color|fgcolor }}; background-color: #{{ tag.color }}">{{ tag }}</span>{% if url_name %}</a>{% endif %}

+ 5 - 2
netbox/tenancy/tables.py

@@ -1,6 +1,6 @@
 import django_tables2 as tables
 
-from utilities.tables import BaseTable, ToggleColumn
+from utilities.tables import BaseTable, TagColumn, ToggleColumn
 from .models import Tenant, TenantGroup
 
 MPTT_LINK = """
@@ -63,8 +63,11 @@ class TenantGroupTable(BaseTable):
 class TenantTable(BaseTable):
     pk = ToggleColumn()
     name = tables.LinkColumn()
+    tags = TagColumn(
+        url_name='tenancy:tenant_list'
+    )
 
     class Meta(BaseTable.Meta):
         model = Tenant
-        fields = ('pk', 'name', 'slug', 'group', 'description')
+        fields = ('pk', 'name', 'slug', 'group', 'description', 'tags')
         default_columns = ('pk', 'name', 'group', 'description')

+ 22 - 2
netbox/utilities/tables.py

@@ -1,8 +1,9 @@
 import django_tables2 as tables
 from django.core.exceptions import FieldDoesNotExist
 from django.db.models import ForeignKey
-from django_tables2.data import TableQuerysetData
+from django.db.models.fields.related import RelatedField
 from django.utils.safestring import mark_safe
+from django_tables2.data import TableQuerysetData
 
 
 class BaseTable(tables.Table):
@@ -57,7 +58,7 @@ class BaseTable(tables.Table):
                     field_path = column.accessor.split('.')
                     try:
                         model_field = model._meta.get_field(field_path[0])
-                        if isinstance(model_field, ForeignKey):
+                        if isinstance(model_field, RelatedField):
                             prefetch_fields.append('__'.join(field_path))
                     except FieldDoesNotExist:
                         pass
@@ -121,3 +122,22 @@ class ColorColumn(tables.Column):
         return mark_safe(
             '<span class="label color-block" style="background-color: #{}">&nbsp;</span>'.format(value)
         )
+
+
+class TagColumn(tables.TemplateColumn):
+    """
+    Display a list of tags assigned to the object.
+    """
+    template_code = """
+    {% for tag in value.all %}
+        {% include 'utilities/templatetags/tag.html' %}
+    {% empty %}
+        <span class="text-muted">&mdash;</span>
+    {% endfor %}
+    """
+
+    def __init__(self, url_name=None):
+        super().__init__(
+            template_code=self.template_code,
+            extra_context={'url_name': url_name}
+        )

+ 10 - 3
netbox/virtualization/tables.py

@@ -3,7 +3,7 @@ from django_tables2.utils import Accessor
 
 from dcim.models import Interface
 from tenancy.tables import COL_TENANT
-from utilities.tables import BaseTable, ToggleColumn
+from utilities.tables import BaseTable, TagColumn, ToggleColumn
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 
 CLUSTERTYPE_ACTIONS = """
@@ -108,10 +108,14 @@ class ClusterTable(BaseTable):
         orderable=False,
         verbose_name='VMs'
     )
+    tags = TagColumn(
+        url_name='virtualization:cluster_list'
+    )
 
     class Meta(BaseTable.Meta):
         model = Cluster
-        fields = ('pk', 'name', 'type', 'group', 'tenant', 'site', 'device_count', 'vm_count')
+        fields = ('pk', 'name', 'type', 'group', 'tenant', 'site', 'device_count', 'vm_count', 'tags')
+        default_columns = ('pk', 'name', 'type', 'group', 'tenant', 'site', 'device_count', 'vm_count')
 
 
 #
@@ -156,12 +160,15 @@ class VirtualMachineDetailTable(VirtualMachineTable):
         verbose_name='IP Address',
         template_code=VIRTUALMACHINE_PRIMARY_IP
     )
+    tags = TagColumn(
+        url_name='virtualization:virtualmachine_list'
+    )
 
     class Meta(BaseTable.Meta):
         model = VirtualMachine
         fields = (
             'pk', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'primary_ip4',
-            'primary_ip6', 'primary_ip',
+            'primary_ip6', 'primary_ip', 'tags',
         )
         default_columns = (
             'pk', 'name', 'status', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip',