Compare commits

...

1 Commits

Author SHA1 Message Date
Jeremy Stretch
64735d587c Closes #21459: Avoid prefetching data for hidden table columns 2026-02-17 17:12:17 -05:00
4 changed files with 40 additions and 34 deletions

View File

@@ -52,43 +52,14 @@ class BaseTable(tables.Table):
'class': 'table table-hover object-list',
}
def __init__(self, *args, user=None, **kwargs):
# TODO: Remove user kwarg in NetBox v4.7
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Set default empty_text if none was provided
if self.empty_text is None:
self.empty_text = _("No {model_name} found").format(model_name=self._meta.model._meta.verbose_name_plural)
# Dynamically update the table's QuerySet to ensure related fields are pre-fetched
if isinstance(self.data, TableQuerysetData):
prefetch_fields = []
for column in self.columns:
if column.visible:
model = getattr(self.Meta, 'model')
accessor = column.accessor
if accessor.startswith('custom_field_data__'):
# Ignore custom field references
continue
prefetch_path = []
for field_name in accessor.split(accessor.SEPARATOR):
try:
field = model._meta.get_field(field_name)
except FieldDoesNotExist:
break
if isinstance(field, (RelatedField, ManyToOneRel)):
# Follow ForeignKeys to the related model
prefetch_path.append(field_name)
model = field.remote_field.model
elif isinstance(field, GenericForeignKey):
# Can't prefetch beyond a GenericForeignKey
prefetch_path.append(field_name)
break
if prefetch_path:
prefetch_fields.append('__'.join(prefetch_path))
self.data.data = self.data.data.prefetch_related(*prefetch_fields)
def _get_columns(self, visible=True):
columns = []
for name, column in self.columns.items():
@@ -144,6 +115,41 @@ class BaseTable(tables.Table):
self.sequence.remove('actions')
self.sequence.append('actions')
def _apply_prefetching(self):
"""
Dynamically update the table's QuerySet to ensure related fields are pre-fetched
"""
if not isinstance(self.data, TableQuerysetData):
return
prefetch_fields = []
for column in self.columns:
if not column.visible:
# Skip hidden columns
continue
model = getattr(self.Meta, 'model') # Must be called *after* resolving columns
accessor = column.accessor
if accessor.startswith('custom_field_data__'):
# Ignore custom field references
continue
prefetch_path = []
for field_name in accessor.split(accessor.SEPARATOR):
try:
field = model._meta.get_field(field_name)
except FieldDoesNotExist:
break
if isinstance(field, (RelatedField, ManyToOneRel)):
# Follow ForeignKeys to the related model
prefetch_path.append(field_name)
model = field.remote_field.model
elif isinstance(field, GenericForeignKey):
# Can't prefetch beyond a GenericForeignKey
prefetch_path.append(field_name)
break
if prefetch_path:
prefetch_fields.append('__'.join(prefetch_path))
self.data.data = self.data.data.prefetch_related(*prefetch_fields)
def configure(self, request):
"""
Configure the table for a specific request context. This performs pagination and records
@@ -178,6 +184,7 @@ class BaseTable(tables.Table):
columns = getattr(self.Meta, 'default_columns', self.Meta.fields)
self._set_columns(columns)
self._apply_prefetching()
if ordering is not None:
self.order_by = ordering

View File

@@ -68,7 +68,6 @@ class ObjectChangeLogView(ConditionalLoginRequiredMixin, View):
objectchanges_table = ObjectChangeTable(
data=objectchanges,
orderable=False,
user=request.user
)
objectchanges_table.configure(request)

View File

@@ -92,7 +92,7 @@ class TableMixin:
request.user.config.set(f'tables.{table}.columns', tableconfig.columns)
request.user.config.set(f'tables.{table}.ordering', tableconfig.ordering, commit=True)
table = self.table(data, user=request.user)
table = self.table(data)
if 'pk' in table.base_columns and bulk_actions:
table.columns.show('pk')
table.configure(request)

View File

@@ -109,7 +109,7 @@ class WirelessLANView(generic.ObjectView):
attached_interfaces = Interface.objects.restrict(request.user, 'view').filter(
wireless_lans=instance
)
interfaces_table = tables.WirelessLANInterfacesTable(attached_interfaces, user=request.user)
interfaces_table = tables.WirelessLANInterfacesTable(attached_interfaces)
interfaces_table.configure(request)
return {