Checkboxes are displayed in Bulk Edit views even when there are no bulk actions #6164

Closed
opened 2025-12-29 19:37:35 +01:00 by adam · 9 comments
Owner

Originally created by @peteeckel on GitHub (Mar 3, 2022).

Originally assigned to: @jeremystretch on GitHub.

NetBox version

v3.2-beta1

Python version

3.8

Steps to Reproduce

Create a view with no bulk actions defined, e.g.

class ManagedRecordListView(generic.ObjectListView):
    queryset = Record.objects.filter(managed=True)
    filterset = RecordFilter
    filterset_form = RecordFilterForm
    table = ManagedRecordTable
    actions = ("export",)

(code from netbox-dns)

Expected Behavior

Since no bulk actions are defined, there shouldn't be any checkboxes in the rows of the table.

Observed Behavior

There are checkboxes.

Originally created by @peteeckel on GitHub (Mar 3, 2022). Originally assigned to: @jeremystretch on GitHub. ### NetBox version v3.2-beta1 ### Python version 3.8 ### Steps to Reproduce Create a view with no bulk actions defined, e.g. ``` class ManagedRecordListView(generic.ObjectListView): queryset = Record.objects.filter(managed=True) filterset = RecordFilter filterset_form = RecordFilterForm table = ManagedRecordTable actions = ("export",) ``` (code from netbox-dns) ### Expected Behavior Since no bulk actions are defined, there shouldn't be any checkboxes in the rows of the table. ### Observed Behavior There are checkboxes.
adam added the type: bugstatus: acceptedbeta labels 2025-12-29 19:37:35 +01:00
adam closed this issue 2025-12-29 19:37:35 +01:00
Author
Owner

@jeremystretch commented on GitHub (Mar 3, 2022):

This works as expected for the views within NetBox. How are you declaring the pk column on your table? It should be hidden by default, so there's no need to call hide() within the view.

    pk = columns.ToggleColumn(
        visible=False
    )
@jeremystretch commented on GitHub (Mar 3, 2022): This works as expected for the views within NetBox. How are you declaring the `pk` column on your table? It should be hidden by default, so there's no need to call `hide()` within the view. ``` pk = columns.ToggleColumn( visible=False ) ```
Author
Owner

@peteeckel commented on GitHub (Mar 3, 2022):

I don't declare pk at all and just inherit the visibility (False) from NetBoxTable - at least that's what I expected:

class RecordBaseTable(NetBoxTable):
    """Base class for tables displaying Records"""

    zone = tables.Column(
        linkify=True,
    )
    type = tables.Column()
    name = tables.Column(
        linkify=True,
    )
    ttl = tables.Column(
        verbose_name="TTL",
    )
    active = tables.BooleanColumn(
        verbose_name="Active",
    )
class ManagedRecordTable(RecordBaseTable):
    """Table for displaying managed Record objects."""

    address_record = tables.Column(
        verbose_name="Address Record",
        linkify=True,
    )
    actions = ActionsColumn(sequence=("changelog",))

    class Meta(NetBoxTable.Meta):
        model = Record
        fields = (
            "zone",
            "name",
            "ttl",
            "type",
            "value",
            "address_record",
            "active",
        )
        default_columns = (
            "zone",
            "name",
            "ttl",
            "type",
            "value",
            "active",
        )

No mention of pk whatsoever.

I would expect it to be False as well, but it isn't ...

@peteeckel commented on GitHub (Mar 3, 2022): I don't declare `pk` at all and just inherit the visibility (`False`) from `NetBoxTable` - at least that's what I expected: ``` class RecordBaseTable(NetBoxTable): """Base class for tables displaying Records""" zone = tables.Column( linkify=True, ) type = tables.Column() name = tables.Column( linkify=True, ) ttl = tables.Column( verbose_name="TTL", ) active = tables.BooleanColumn( verbose_name="Active", ) ``` ``` class ManagedRecordTable(RecordBaseTable): """Table for displaying managed Record objects.""" address_record = tables.Column( verbose_name="Address Record", linkify=True, ) actions = ActionsColumn(sequence=("changelog",)) class Meta(NetBoxTable.Meta): model = Record fields = ( "zone", "name", "ttl", "type", "value", "address_record", "active", ) default_columns = ( "zone", "name", "ttl", "type", "value", "active", ) ``` No mention of `pk` whatsoever. I would expect it to be `False` as well, but it isn't ...
Author
Owner

@peteeckel commented on GitHub (Mar 3, 2022):

I just tried explicitly setting the visibility of pk to false:

class ManagedRecordTable(RecordBaseTable):
    """Table for displaying managed Record objects."""

    address_record = tables.Column(
        verbose_name="Address Record",
        linkify=True,
    )
    actions = ActionsColumn(sequence=("changelog",))
    pk = ToggleColumn(
        visible=False
    )
    class Meta(NetBoxTable.Meta):
        model = Record
        fields = (

then added some debug code to get_tables():

        logger = logging.getLogger('netbox_dns')
        for column in table.columns.all():
            logger.debug(f'{column.name}: {column.visible}')

And indeed the visibility of the column is True:

pk: True
zone: True
name: True
ttl: True
type: True
value: True
active: True
address_record: False
id: False
actions: True

So obviously something sets it. In the plugin code there are exactly two occurrences of show(), once for an instance of ZoneTable and once for one of ZoneRecordTable, never for any ManagedRecordTable instances.

Bottom line: Even if one sets the visibility of the column in the table class to False it's not safe to assume it stays that way (why that is the case is a different question).

@peteeckel commented on GitHub (Mar 3, 2022): I just tried explicitly setting the visibility of `pk` to false: ``` class ManagedRecordTable(RecordBaseTable): """Table for displaying managed Record objects.""" address_record = tables.Column( verbose_name="Address Record", linkify=True, ) actions = ActionsColumn(sequence=("changelog",)) pk = ToggleColumn( visible=False ) class Meta(NetBoxTable.Meta): model = Record fields = ( ``` then added some debug code to `get_tables()`: ``` logger = logging.getLogger('netbox_dns') for column in table.columns.all(): logger.debug(f'{column.name}: {column.visible}') ``` And indeed the visibility of the column is `True`: ``` pk: True zone: True name: True ttl: True type: True value: True active: True address_record: False id: False actions: True ``` So obviously something sets it. In the plugin code there are exactly two occurrences of `show()`, once for an instance of `ZoneTable` and once for one of `ZoneRecordTable`, never for any `ManagedRecordTable` instances. Bottom line: Even if one sets the visibility of the column in the table class to `False` it's not safe to assume it stays that way (why that is the case is a different question).
Author
Owner

@peteeckel commented on GitHub (Mar 3, 2022):

Found it.

It's in netbox/tables/tables.py:

                # Show only persistent or selected columns
                for name, column in self.columns.items():
                    if name in [*self.exempt_columns, *selected_columns]:
                        self.columns.show(name)
                    else:
                        self.columns.hide(name)

I just tentatively disabled the show() call and now the column visibility in my debug output is OK:

pk: False
zone: True
name: True
ttl: True
type: True
value: True
active: True
address_record: False
id: False
actions: True
@peteeckel commented on GitHub (Mar 3, 2022): Found it. It's in `netbox/tables/tables.py`: ``` # Show only persistent or selected columns for name, column in self.columns.items(): if name in [*self.exempt_columns, *selected_columns]: self.columns.show(name) else: self.columns.hide(name) ``` I just tentatively disabled the `show()` call and now the column visibility in my debug output is OK: ``` pk: False zone: True name: True ttl: True type: True value: True active: True address_record: False id: False actions: True ```
Author
Owner

@peteeckel commented on GitHub (Mar 3, 2022):

My first idea was that the visibility of pK might be a result from config in the user configuration table, but nope:

netbox=# select * from users_userconfig;
 id |                                                                                                               data                                                                                                               | user_id 
----+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------
  1 | {"tables": {"ZoneTable": {"columns": ["name", "tags", "soa_serial", "status"], "available_columns": []}, "ManagedRecordTable": {"columns": ["zone", "name", "ttl", "type", "value", "active"]}}, "pagination": {"per_page": 25}} |       1
(1 row)

As I was suspecting it might have something to do with corrupted user configuration from previous tests I removed all user config from the users_userconfig table and retested. No change - the column stubbornly remains visible.

Further debugging shows that the reason for making the pk column visible is that it's included in exempt_columns, which gets set in the NetBoxTable class:

class NetBoxTable(BaseTable):
    """
    Table class for most NetBox objects. Adds support for custom field & custom link columns. Includes
    default columns for:

        * PK (row selection)
        * ID
        * Actions
    """
    pk = columns.ToggleColumn(
        visible=False
    )
    id = tables.Column(
        linkify=True,
        verbose_name='ID'
    )
    actions = columns.ActionsColumn()

    exempt_columns = ('pk', 'actions')

    class Meta(BaseTable.Meta):
        pass

    def __init__(self, *args, extra_columns=None, **kwargs):
        if extra_columns is None:
            extra_columns = []
@peteeckel commented on GitHub (Mar 3, 2022): My first idea was that the visibility of `pK` might be a result from config in the user configuration table, but nope: ``` netbox=# select * from users_userconfig; id | data | user_id ----+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------- 1 | {"tables": {"ZoneTable": {"columns": ["name", "tags", "soa_serial", "status"], "available_columns": []}, "ManagedRecordTable": {"columns": ["zone", "name", "ttl", "type", "value", "active"]}}, "pagination": {"per_page": 25}} | 1 (1 row) ``` As I was suspecting it might have something to do with corrupted user configuration from previous tests I removed all user config from the `users_userconfig` table and retested. No change - the column stubbornly remains visible. Further debugging shows that the reason for making the `pk` column visible is that it's included in `exempt_columns`, which gets set in the `NetBoxTable` class: ``` class NetBoxTable(BaseTable): """ Table class for most NetBox objects. Adds support for custom field & custom link columns. Includes default columns for: * PK (row selection) * ID * Actions """ pk = columns.ToggleColumn( visible=False ) id = tables.Column( linkify=True, verbose_name='ID' ) actions = columns.ActionsColumn() exempt_columns = ('pk', 'actions') class Meta(BaseTable.Meta): pass def __init__(self, *args, extra_columns=None, **kwargs): if extra_columns is None: extra_columns = [] ```
Author
Owner

@jeremystretch commented on GitHub (Mar 3, 2022):

@peteeckel thanks for digging into this. I'm still not quite clear on why it doesn't seem to be an issue with the core tables, however it's a good opportunity for some cleanup anyway. I've refactored the logic within BaseTable.__init__() to avoid calling show() or hide() on columns unnecessarily, which should fix the issue. Please give it a try and let me know.

@jeremystretch commented on GitHub (Mar 3, 2022): @peteeckel thanks for digging into this. I'm still not quite clear on why it doesn't seem to be an issue with the core tables, however it's a good opportunity for some cleanup anyway. I've refactored the logic within `BaseTable.__init__()` to avoid calling `show()` or `hide()` on columns unnecessarily, which should fix the issue. Please give it a try and let me know.
Author
Owner

@peteeckel commented on GitHub (Mar 3, 2022):

I'm still not quite clear on why it doesn't seem to be an issue with the core tables,

Because they do not inherit from NetBoxTable:

pete@lagavulin netbox % git grep NetBoxTable 
tables/tables.py:    'NetBoxTable',
tables/tables.py:class NetBoxTable(BaseTable):
tests/test_tables.py:from netbox.tables import NetBoxTable, columns
tests/test_tables.py:class TagColumnTable(NetBoxTable):
tests/test_tables.py:    class Meta(NetBoxTable.Meta):

As far as my tests cover it it looks perfect now. Thanks!

@peteeckel commented on GitHub (Mar 3, 2022): > I'm still not quite clear on why it doesn't seem to be an issue with the core tables, Because they do not inherit from `NetBoxTable`: ``` pete@lagavulin netbox % git grep NetBoxTable tables/tables.py: 'NetBoxTable', tables/tables.py:class NetBoxTable(BaseTable): tests/test_tables.py:from netbox.tables import NetBoxTable, columns tests/test_tables.py:class TagColumnTable(NetBoxTable): tests/test_tables.py: class Meta(NetBoxTable.Meta): ``` As far as my tests cover it it looks perfect now. Thanks!
Author
Owner

@jeremystretch commented on GitHub (Mar 3, 2022):

Because they do not seem to inherit from NetBoxTable

I think you're one directory too deep. They definitely all inherit from NetBoxTable; otherwise a ton of functionality would be outright broken.

At any rate, I'm glad this resolves the issue. I'll go ahead and close this out. Thanks for your help!

@jeremystretch commented on GitHub (Mar 3, 2022): > Because they do not seem to inherit from NetBoxTable I think you're one directory too deep. They definitely all inherit from NetBoxTable; otherwise a ton of functionality would be outright broken. At any rate, I'm glad this resolves the issue. I'll go ahead and close this out. Thanks for your help!
Author
Owner

@peteeckel commented on GitHub (Mar 3, 2022):

I think you're one directory too deep. They definitely all inherit from NetBoxTable; otherwise a ton of functionality would be outright broken.

Oops ... you're correct.

@peteeckel commented on GitHub (Mar 3, 2022): > I think you're one directory too deep. They definitely all inherit from NetBoxTable; otherwise a ton of functionality would be outright broken. Oops ... you're correct.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/netbox#6164