Skip to content

Commit

Permalink
Closes #14173: Enable plugins to register columns on core tables
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremystretch committed Nov 14, 2023
1 parent 840b7d8 commit 02c2d9f
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 1 deletion.
24 changes: 24 additions & 0 deletions docs/plugins/development/tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,27 @@ The table column classes listed below are supported for use in plugins. These cl
options:
members:
- __init__

## Extending Core Tables

!!! info "This feature was introduced in NetBox v3.7."

Plugins can register their own custom columns on core tables using the `register_table_column()` utility function. This allows a plugin to attach additional information, such as relationships to its own models, to built-in object lists.

```python
import django_tables2

from dcim.tables import SiteTable
from utilities.tables import register_table_column

mycol = django_tables2.Column(
verbose_name='My Column',
accessor=django_tables2.A('description')
)

register_table_column(mycol, 'foo', SiteTable)
```

You'll typically want to define an accessor identifying the desired model field or relationship when defining a custom column. See the [django-tables2 documentation](https://django-tables2.readthedocs.io/) for more information on creating custom columns.

::: utilities.tables.register_table_column
1 change: 1 addition & 0 deletions netbox/netbox/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __delitem__(self, key):
'models': collections.defaultdict(set),
'plugins': dict(),
'search': dict(),
'tables': collections.defaultdict(dict),
'views': collections.defaultdict(dict),
'widgets': dict(),
})
10 changes: 9 additions & 1 deletion netbox/netbox/tables/tables.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from copy import deepcopy

import django_tables2 as tables
from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.fields import GenericForeignKey
Expand All @@ -12,6 +14,7 @@

from extras.models import CustomField, CustomLink
from extras.choices import CustomFieldVisibilityChoices
from netbox.registry import registry
from netbox.tables import columns
from utilities.paginator import EnhancedPaginator, get_paginate_count
from utilities.utils import get_viewname, highlight_string, title
Expand Down Expand Up @@ -191,12 +194,17 @@ def __init__(self, *args, extra_columns=None, **kwargs):
if extra_columns is None:
extra_columns = []

if registered_columns := registry['tables'].get(self.__class__):
extra_columns.extend([
# Create a copy to avoid modifying the original Column
(name, deepcopy(column)) for name, column in registered_columns.items()
])

# Add custom field & custom link columns
content_type = ContentType.objects.get_for_model(self._meta.model)
custom_fields = CustomField.objects.filter(
content_types=content_type
).exclude(ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_HIDDEN)

extra_columns.extend([
(f'cf_{cf.name}', columns.CustomFieldColumn(cf)) for cf in custom_fields
])
Expand Down
11 changes: 11 additions & 0 deletions netbox/netbox/tests/dummy_plugin/tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import django_tables2 as tables

from dcim.tables import SiteTable
from utilities.tables import register_table_column

mycol = tables.Column(
verbose_name='My column',
accessor=tables.A('description')
)

register_table_column(mycol, 'foo', SiteTable)
2 changes: 2 additions & 0 deletions netbox/netbox/tests/dummy_plugin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from dcim.models import Site
from utilities.views import register_model_view
from .models import DummyModel
# Trigger registration of custom column
from .tables import mycol


class DummyModelsView(View):
Expand Down
10 changes: 10 additions & 0 deletions netbox/netbox/tests/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ def test_template_extensions(self):

self.assertIn(SiteContent, registry['plugins']['template_extensions']['dcim.site'])

def test_registered_columns(self):
"""
Check that a plugin can register a custom column on a core model table.
"""
from dcim.models import Site
from dcim.tables import SiteTable

table = SiteTable(Site.objects.all())
self.assertIn('foo', table.columns.names())

def test_user_preferences(self):
"""
Check that plugin UserPreferences are registered.
Expand Down
19 changes: 19 additions & 0 deletions netbox/utilities/tables.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from netbox.registry import registry

__all__ = (
'get_table_ordering',
'linkify_phone',
'register_table_column'
)


Expand All @@ -26,3 +29,19 @@ def linkify_phone(value):
if value is None:
return None
return f"tel:{value}"


def register_table_column(column, name, *tables):
"""
Register a custom column for use on one or more tables.
Args:
column: The column instance to register
name: The name of the table column
tables: One or more table classes
"""
for table in tables:
reg = registry['tables'][table]
if name in reg:
raise ValueError(f"A column named {name} is already defined for table {table.__name__}")
reg[name] = column

0 comments on commit 02c2d9f

Please sign in to comment.