diff --git a/netbox/utilities/tables.py b/netbox/utilities/tables.py
index 3d48d9875..7a7ab91ed 100644
--- a/netbox/utilities/tables.py
+++ b/netbox/utilities/tables.py
@@ -1,7 +1,9 @@
import django_tables2 as tables
from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.fields import GenericForeignKey
+from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldDoesNotExist
+from django.db.models import Func, F, Value
from django.db.models.fields.related import RelatedField
from django.urls import reverse
from django.utils.html import strip_tags
@@ -9,6 +11,7 @@ from django.utils.safestring import mark_safe
from django_tables2 import RequestConfig
from django_tables2.data import TableQuerysetData
+from .models import CustomField
from .paginator import EnhancedPaginator, get_paginate_count
@@ -30,12 +33,25 @@ class BaseTable(tables.Table):
:param user: Personalize table display for the given user (optional). Has no effect if AnonymousUser is passed.
"""
+
class Meta:
attrs = {
'class': 'table table-hover table-headings',
}
def __init__(self, *args, user=None, **kwargs):
+ # Add custom field columns
+ obj_type = ContentType.objects.get_for_model(self._meta.model)
+ custom_fields = {}
+
+ for cf in CustomField.objects.filter(content_types=obj_type):
+ name = 'cf_{}'.format(cf.name)
+ label = cf.label if cf.label != '' else cf.name
+ self.base_columns[name] = CustomFieldColumn(verbose_name=label)
+ custom_fields[name] = cf
+ self._meta.fields += tuple(custom_fields.keys())
+
+ # Init table
super().__init__(*args, **kwargs)
# Set default empty_text if none was provided
@@ -73,6 +89,12 @@ class BaseTable(tables.Table):
# Dynamically update the table's QuerySet to ensure related fields are pre-fetched
if isinstance(self.data, TableQuerysetData):
+ # Extract custom field values
+ cf_fields = {}
+ for key, cf in custom_fields.items():
+ cf_fields[key] = Func(F('custom_field_data'), Value(cf.name), function='jsonb_extract_path_text')
+ self.data.data = self.data.data.annotate(**cf_fields)
+
prefetch_fields = []
for column in self.columns:
if column.visible:
@@ -316,6 +338,28 @@ class TagColumn(tables.TemplateColumn):
return ",".join([tag.name for tag in value.all()])
+class CustomFieldColumn(tables.Column):
+ """
+ Display custom fields in the appropriate format.
+ """
+ def render(self, record, bound_column, value):
+ if isinstance(value, list):
+ if len(value):
+ template = ''
+ for v in value:
+ template += f'{v} '
+ else:
+ template = '—'
+ elif value:
+ template = value
+ else:
+ return self.default
+ return mark_safe(template)
+
+ def value(self, value):
+ return value
+
+
class MPTTColumn(tables.TemplateColumn):
"""
Display a nested hierarchy for MPTT-enabled models.
@@ -362,3 +406,4 @@ def paginate_table(table, request):
'per_page': get_paginate_count(request)
}
RequestConfig(request, paginate).configure(table)
+