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) +