diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index 140edba09..3d4c4cb25 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -4,6 +4,7 @@ ### Bug Fixes +* [#6895](https://github.com/netbox-community/netbox/issues/6895) - Remove errant markup for null values in CSV export * [#7373](https://github.com/netbox-community/netbox/issues/7373) - Fix flashing when server, client, and browser color-mode preferences are mismatched --- diff --git a/netbox/dcim/tables/cables.py b/netbox/dcim/tables/cables.py index 9509ec2bc..14cf34505 100644 --- a/netbox/dcim/tables/cables.py +++ b/netbox/dcim/tables/cables.py @@ -2,7 +2,7 @@ import django_tables2 as tables from django_tables2.utils import Accessor from dcim.models import Cable -from utilities.tables import BaseTable, ChoiceFieldColumn, ColorColumn, TagColumn, ToggleColumn +from utilities.tables import BaseTable, ChoiceFieldColumn, ColorColumn, TagColumn, TemplateColumn, ToggleColumn from .template_code import CABLE_LENGTH, CABLE_TERMINATION_PARENT __all__ = ( @@ -45,7 +45,7 @@ class CableTable(BaseTable): verbose_name='Termination B' ) status = ChoiceFieldColumn() - length = tables.TemplateColumn( + length = TemplateColumn( template_code=CABLE_LENGTH, order_by='_abs_length' ) diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 306b29b09..c22e673b7 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -9,7 +9,7 @@ from dcim.models import ( from tenancy.tables import TenantColumn from utilities.tables import ( BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn, - MarkdownColumn, TagColumn, ToggleColumn, + MarkdownColumn, TagColumn, TemplateColumn, ToggleColumn, ) from .template_code import ( CABLETERMINATION, CONSOLEPORT_BUTTONS, CONSOLESERVERPORT_BUTTONS, DEVICE_LINK, DEVICEBAY_BUTTONS, DEVICEBAY_STATUS, @@ -258,7 +258,7 @@ class CableTerminationTable(BaseTable): orderable=False, verbose_name='Cable Color' ) - cable_peer = tables.TemplateColumn( + cable_peer = TemplateColumn( accessor='_cable_peer', template_code=CABLETERMINATION, orderable=False, @@ -268,7 +268,7 @@ class CableTerminationTable(BaseTable): class PathEndpointTable(CableTerminationTable): - connection = tables.TemplateColumn( + connection = TemplateColumn( accessor='_path.last_node', template_code=CABLETERMINATION, verbose_name='Connection', @@ -470,7 +470,7 @@ class BaseInterfaceTable(BaseTable): verbose_name='IP Addresses' ) untagged_vlan = tables.Column(linkify=True) - tagged_vlans = tables.TemplateColumn( + tagged_vlans = TemplateColumn( template_code=INTERFACE_TAGGED_VLANS, orderable=False, verbose_name='Tagged VLANs' diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py index 65a30ac61..2f359e1b9 100644 --- a/netbox/dcim/tables/template_code.py +++ b/netbox/dcim/tables/template_code.py @@ -5,13 +5,11 @@ CABLETERMINATION = """ {% endif %} {{ value }} -{% else %} - — {% endif %} """ CABLE_LENGTH = """ -{% if record.length %}{{ record.length }} {{ record.get_length_unit_display }}{% else %}—{% endif %} +{% if record.length %}{{ record.length }} {{ record.get_length_unit_display }}{% endif %} """ CABLE_TERMINATION_PARENT = """ @@ -63,8 +61,6 @@ INTERFACE_TAGGED_VLANS = """ {% endfor %} {% elif record.mode == 'tagged-all' %} All -{% else %} - — {% endif %} """ diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index 57adbb1b8..485e4a123 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -39,15 +39,7 @@ PREFIXFLAT_LINK = """ {% if record.pk %} {{ record.prefix }} {% else %} - — -{% endif %} -""" - -PREFIX_ROLE_LINK = """ -{% if record.role %} - {{ record.role }} -{% else %} - — + {{ record.prefix }} {% endif %} """ @@ -218,8 +210,8 @@ class PrefixTable(BaseTable): linkify=True, verbose_name='VLAN' ) - role = tables.TemplateColumn( - template_code=PREFIX_ROLE_LINK + role = tables.Column( + linkify=True ) is_pool = BooleanColumn( verbose_name='Pool' @@ -264,8 +256,8 @@ class IPRangeTable(BaseTable): status = ChoiceFieldColumn( default=AVAILABLE_LABEL ) - role = tables.TemplateColumn( - template_code=PREFIX_ROLE_LINK + role = tables.Column( + linkify=True ) tenant = TenantColumn() diff --git a/netbox/ipam/tables/vlans.py b/netbox/ipam/tables/vlans.py index 4219a8a52..fd1e92be8 100644 --- a/netbox/ipam/tables/vlans.py +++ b/netbox/ipam/tables/vlans.py @@ -6,7 +6,7 @@ from dcim.models import Interface from tenancy.tables import TenantColumn from utilities.tables import ( BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ContentTypeColumn, LinkedCountColumn, TagColumn, - ToggleColumn, + TemplateColumn, ToggleColumn, ) from virtualization.models import VMInterface from ipam.models import * @@ -35,19 +35,9 @@ VLAN_LINK = """ VLAN_PREFIXES = """ {% for prefix in record.prefixes.all %} {{ prefix }}{% if not forloop.last %}
{% endif %} -{% empty %} - — {% endfor %} """ -VLAN_ROLE_LINK = """ -{% if record.role %} - {{ record.role }} -{% else %} - — -{% endif %} -""" - VLANGROUP_ADD_VLAN = """ {% with next_vid=record.get_next_available_vid %} {% if next_vid and perms.ipam.add_vlan %} @@ -115,10 +105,10 @@ class VLANTable(BaseTable): status = ChoiceFieldColumn( default=AVAILABLE_LABEL ) - role = tables.TemplateColumn( - template_code=VLAN_ROLE_LINK + role = tables.Column( + linkify=True ) - prefixes = tables.TemplateColumn( + prefixes = TemplateColumn( template_code=VLAN_PREFIXES, orderable=False, verbose_name='Prefixes' @@ -190,8 +180,8 @@ class InterfaceVLANTable(BaseTable): ) tenant = TenantColumn() status = ChoiceFieldColumn() - role = tables.TemplateColumn( - template_code=VLAN_ROLE_LINK + role = tables.Column( + linkify=True ) class Meta(BaseTable.Meta): diff --git a/netbox/ipam/tables/vrfs.py b/netbox/ipam/tables/vrfs.py index bea2a6b1f..3a351a856 100644 --- a/netbox/ipam/tables/vrfs.py +++ b/netbox/ipam/tables/vrfs.py @@ -1,7 +1,7 @@ import django_tables2 as tables from tenancy.tables import TenantColumn -from utilities.tables import BaseTable, BooleanColumn, TagColumn, ToggleColumn +from utilities.tables import BaseTable, BooleanColumn, TagColumn, TemplateColumn, ToggleColumn from ipam.models import * __all__ = ( @@ -11,9 +11,7 @@ __all__ = ( VRF_TARGETS = """ {% for rt in value.all %} - {{ rt }}{% if not forloop.last %}
{% endif %} -{% empty %} - — + {{ rt }}{% if not forloop.last %}
{% endif %} {% endfor %} """ @@ -34,11 +32,11 @@ class VRFTable(BaseTable): enforce_unique = BooleanColumn( verbose_name='Unique' ) - import_targets = tables.TemplateColumn( + import_targets = TemplateColumn( template_code=VRF_TARGETS, orderable=False ) - export_targets = tables.TemplateColumn( + export_targets = TemplateColumn( template_code=VRF_TARGETS, orderable=False ) diff --git a/netbox/utilities/tables.py b/netbox/utilities/tables.py index 0a8e8c3ba..446438b5c 100644 --- a/netbox/utilities/tables.py +++ b/netbox/utilities/tables.py @@ -157,6 +157,25 @@ class BooleanColumn(tables.Column): return str(value) +class TemplateColumn(tables.TemplateColumn): + """ + Overrides the stock TemplateColumn to render a placeholder if the returned value is an empty string. + """ + PLACEHOLDER = mark_safe('—') + + def render(self, *args, **kwargs): + ret = super().render(*args, **kwargs) + if not ret.strip(): + return self.PLACEHOLDER + return ret + + def value(self, **kwargs): + ret = super().value(**kwargs) + if ret == self.PLACEHOLDER: + return '' + return ret + + class ButtonsColumn(tables.TemplateColumn): """ Render edit, delete, and changelog buttons for an object.