We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Clearing the ordering of a table does not clear the `ordering` key from the UserConfig data
### NetBox version

v3.5.3

### Python version

3.8

### Steps to Reproduce

1. Open the sites list and order the entries by name.
2. In a shell, retrieve the UserConfig instance for your user and inspect the entry for `tables.SiteTable.ordering`:

```
>>> uc=UserConfig.objects.get(user__username='admin')
>>> uc.data['tables']['SiteTable']['ordering']
['name']
```

3. In the UI, clear the applied ordering by clicking the X in the column header.
4. Refresh and re-inspect the UserConfig data:

```
>>> uc.refresh_from_db()
>>> uc.data['tables']['SiteTable']['ordering']
['']
```

### Expected Behavior

The `ordering` key should be removed from the data, as there is no longer any preference stored.

### Observed Behavior

`ordering` is set to a list containing an empty string. This does not effect any breaking behavior AFAICT, however it should be cleaned up as it can pose complications.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `netbox/netbox/tables/tables.py`
Content:
```
1 import django_tables2 as tables
2 from django.contrib.auth.models import AnonymousUser
3 from django.contrib.contenttypes.fields import GenericForeignKey
4 from django.contrib.contenttypes.models import ContentType
5 from django.core.exceptions import FieldDoesNotExist
6 from django.db.models.fields.related import RelatedField
7 from django.urls import reverse
8 from django.urls.exceptions import NoReverseMatch
9 from django.utils.safestring import mark_safe
10 from django.utils.translation import gettext as _
11 from django_tables2.data import TableQuerysetData
12 
13 from extras.models import CustomField, CustomLink
14 from extras.choices import CustomFieldVisibilityChoices
15 from netbox.tables import columns
16 from utilities.paginator import EnhancedPaginator, get_paginate_count
17 from utilities.utils import get_viewname, highlight_string, title
18 
19 __all__ = (
20     'BaseTable',
21     'NetBoxTable',
22     'SearchTable',
23 )
24 
25 
26 class BaseTable(tables.Table):
27     """
28     Base table class for NetBox objects. Adds support for:
29 
30         * User configuration (column preferences)
31         * Automatic prefetching of related objects
32         * BS5 styling
33 
34     :param user: Personalize table display for the given user (optional). Has no effect if AnonymousUser is passed.
35     """
36     exempt_columns = ()
37 
38     class Meta:
39         attrs = {
40             'class': 'table table-hover object-list',
41         }
42 
43     def __init__(self, *args, user=None, **kwargs):
44 
45         super().__init__(*args, **kwargs)
46 
47         # Set default empty_text if none was provided
48         if self.empty_text is None:
49             self.empty_text = f"No {self._meta.model._meta.verbose_name_plural} found"
50 
51         # Determine the table columns to display by checking the following:
52         #   1. User's configuration for the table
53         #   2. Meta.default_columns
54         #   3. Meta.fields
55         selected_columns = None
56         if user is not None and not isinstance(user, AnonymousUser):
57             selected_columns = user.config.get(f"tables.{self.__class__.__name__}.columns")
58         if not selected_columns:
59             selected_columns = getattr(self.Meta, 'default_columns', self.Meta.fields)
60 
61         # Hide non-selected columns which are not exempt
62         for column in self.columns:
63             if column.name not in [*selected_columns, *self.exempt_columns]:
64                 self.columns.hide(column.name)
65 
66         # Rearrange the sequence to list selected columns first, followed by all remaining columns
67         # TODO: There's probably a more clever way to accomplish this
68         self.sequence = [
69             *[c for c in selected_columns if c in self.columns.names()],
70             *[c for c in self.columns.names() if c not in selected_columns]
71         ]
72 
73         # PK column should always come first
74         if 'pk' in self.sequence:
75             self.sequence.remove('pk')
76             self.sequence.insert(0, 'pk')
77 
78         # Actions column should always come last
79         if 'actions' in self.sequence:
80             self.sequence.remove('actions')
81             self.sequence.append('actions')
82 
83         # Dynamically update the table's QuerySet to ensure related fields are pre-fetched
84         if isinstance(self.data, TableQuerysetData):
85 
86             prefetch_fields = []
87             for column in self.columns:
88                 if column.visible:
89                     model = getattr(self.Meta, 'model')
90                     accessor = column.accessor
91                     prefetch_path = []
92                     for field_name in accessor.split(accessor.SEPARATOR):
93                         try:
94                             field = model._meta.get_field(field_name)
95                         except FieldDoesNotExist:
96                             break
97                         if isinstance(field, RelatedField):
98                             # Follow ForeignKeys to the related model
99                             prefetch_path.append(field_name)
100                             model = field.remote_field.model
101                         elif isinstance(field, GenericForeignKey):
102                             # Can't prefetch beyond a GenericForeignKey
103                             prefetch_path.append(field_name)
104                             break
105                     if prefetch_path:
106                         prefetch_fields.append('__'.join(prefetch_path))
107             self.data.data = self.data.data.prefetch_related(*prefetch_fields)
108 
109     def _get_columns(self, visible=True):
110         columns = []
111         for name, column in self.columns.items():
112             if column.visible == visible and name not in self.exempt_columns:
113                 columns.append((name, column.verbose_name))
114         return columns
115 
116     @property
117     def available_columns(self):
118         return self._get_columns(visible=False)
119 
120     @property
121     def selected_columns(self):
122         return self._get_columns(visible=True)
123 
124     @property
125     def objects_count(self):
126         """
127         Return the total number of real objects represented by the Table. This is useful when dealing with
128         prefixes/IP addresses/etc., where some table rows may represent available address space.
129         """
130         if not hasattr(self, '_objects_count'):
131             self._objects_count = sum(1 for obj in self.data if hasattr(obj, 'pk'))
132         return self._objects_count
133 
134     def configure(self, request):
135         """
136         Configure the table for a specific request context. This performs pagination and records
137         the user's preferred ordering logic.
138         """
139         # Save ordering preference
140         if request.user.is_authenticated:
141             table_name = self.__class__.__name__
142             if self.prefixed_order_by_field in request.GET:
143                 # If an ordering has been specified as a query parameter, save it as the
144                 # user's preferred ordering for this table.
145                 ordering = request.GET.getlist(self.prefixed_order_by_field)
146                 request.user.config.set(f'tables.{table_name}.ordering', ordering, commit=True)
147             elif ordering := request.user.config.get(f'tables.{table_name}.ordering'):
148                 # If no ordering has been specified, set the preferred ordering (if any).
149                 self.order_by = ordering
150 
151         # Paginate the table results
152         paginate = {
153             'paginator_class': EnhancedPaginator,
154             'per_page': get_paginate_count(request)
155         }
156         tables.RequestConfig(request, paginate).configure(self)
157 
158 
159 class NetBoxTable(BaseTable):
160     """
161     Table class for most NetBox objects. Adds support for custom field & custom link columns. Includes
162     default columns for:
163 
164         * PK (row selection)
165         * ID
166         * Actions
167     """
168     pk = columns.ToggleColumn(
169         visible=False
170     )
171     id = tables.Column(
172         linkify=True,
173         verbose_name='ID'
174     )
175     actions = columns.ActionsColumn()
176 
177     exempt_columns = ('pk', 'actions')
178 
179     class Meta(BaseTable.Meta):
180         pass
181 
182     def __init__(self, *args, extra_columns=None, **kwargs):
183         if extra_columns is None:
184             extra_columns = []
185 
186         # Add custom field & custom link columns
187         content_type = ContentType.objects.get_for_model(self._meta.model)
188         custom_fields = CustomField.objects.filter(
189             content_types=content_type
190         ).exclude(ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_HIDDEN)
191 
192         extra_columns.extend([
193             (f'cf_{cf.name}', columns.CustomFieldColumn(cf)) for cf in custom_fields
194         ])
195         custom_links = CustomLink.objects.filter(content_types=content_type, enabled=True)
196         extra_columns.extend([
197             (f'cl_{cl.name}', columns.CustomLinkColumn(cl)) for cl in custom_links
198         ])
199 
200         super().__init__(*args, extra_columns=extra_columns, **kwargs)
201 
202     @property
203     def htmx_url(self):
204         """
205         Return the base HTML request URL for embedded tables.
206         """
207         if getattr(self, 'embedded', False):
208             viewname = get_viewname(self._meta.model, action='list')
209             try:
210                 return reverse(viewname)
211             except NoReverseMatch:
212                 pass
213         return ''
214 
215 
216 class SearchTable(tables.Table):
217     object_type = columns.ContentTypeColumn(
218         verbose_name=_('Type'),
219         order_by="object___meta__verbose_name",
220     )
221     object = tables.Column(
222         linkify=True,
223         order_by=('name', )
224     )
225     field = tables.Column()
226     value = tables.Column()
227 
228     trim_length = 30
229 
230     class Meta:
231         attrs = {
232             'class': 'table table-hover object-list',
233         }
234         empty_text = _('No results found')
235 
236     def __init__(self, data, highlight=None, **kwargs):
237         self.highlight = highlight
238         super().__init__(data, **kwargs)
239 
240     def render_field(self, value, record):
241         if hasattr(record.object, value):
242             return title(record.object._meta.get_field(value).verbose_name)
243         return value
244 
245     def render_value(self, value):
246         if not self.highlight:
247             return value
248 
249         value = highlight_string(value, self.highlight, trim_pre=self.trim_length, trim_post=self.trim_length)
250 
251         return mark_safe(value)
252
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/netbox/netbox/tables/tables.py b/netbox/netbox/tables/tables.py
--- a/netbox/netbox/tables/tables.py
+++ b/netbox/netbox/tables/tables.py
@@ -140,10 +140,14 @@
         if request.user.is_authenticated:
             table_name = self.__class__.__name__
             if self.prefixed_order_by_field in request.GET:
-                # If an ordering has been specified as a query parameter, save it as the
-                # user's preferred ordering for this table.
-                ordering = request.GET.getlist(self.prefixed_order_by_field)
-                request.user.config.set(f'tables.{table_name}.ordering', ordering, commit=True)
+                if request.GET[self.prefixed_order_by_field]:
+                    # If an ordering has been specified as a query parameter, save it as the
+                    # user's preferred ordering for this table.
+                    ordering = request.GET.getlist(self.prefixed_order_by_field)
+                    request.user.config.set(f'tables.{table_name}.ordering', ordering, commit=True)
+                else:
+                    # If the ordering has been set to none (empty), clear any existing preference.
+                    request.user.config.clear(f'tables.{table_name}.ordering', commit=True)
             elif ordering := request.user.config.get(f'tables.{table_name}.ordering'):
                 # If no ordering has been specified, set the preferred ordering (if any).
                 self.order_by = ordering

{"golden_diff": "diff --git a/netbox/netbox/tables/tables.py b/netbox/netbox/tables/tables.py\n--- a/netbox/netbox/tables/tables.py\n+++ b/netbox/netbox/tables/tables.py\n@@ -140,10 +140,14 @@\n         if request.user.is_authenticated:\n             table_name = self.__class__.__name__\n             if self.prefixed_order_by_field in request.GET:\n-                # If an ordering has been specified as a query parameter, save it as the\n-                # user's preferred ordering for this table.\n-                ordering = request.GET.getlist(self.prefixed_order_by_field)\n-                request.user.config.set(f'tables.{table_name}.ordering', ordering, commit=True)\n+                if request.GET[self.prefixed_order_by_field]:\n+                    # If an ordering has been specified as a query parameter, save it as the\n+                    # user's preferred ordering for this table.\n+                    ordering = request.GET.getlist(self.prefixed_order_by_field)\n+                    request.user.config.set(f'tables.{table_name}.ordering', ordering, commit=True)\n+                else:\n+                    # If the ordering has been set to none (empty), clear any existing preference.\n+                    request.user.config.clear(f'tables.{table_name}.ordering', commit=True)\n             elif ordering := request.user.config.get(f'tables.{table_name}.ordering'):\n                 # If no ordering has been specified, set the preferred ordering (if any).\n                 self.order_by = ordering\n", "issue": "Clearing the ordering of a table does not clear the `ordering` key from the UserConfig data\n### NetBox version\n\nv3.5.3\n\n### Python version\n\n3.8\n\n### Steps to Reproduce\n\n1. Open the sites list and order the entries by name.\r\n2. In a shell, retrieve the UserConfig instance for your user and inspect the entry for `tables.SiteTable.ordering`:\r\n\r\n```\r\n>>> uc=UserConfig.objects.get(user__username='admin')\r\n>>> uc.data['tables']['SiteTable']['ordering']\r\n['name']\r\n```\r\n\r\n3. In the UI, clear the applied ordering by clicking the X in the column header.\r\n4. Refresh and re-inspect the UserConfig data:\r\n\r\n```\r\n>>> uc.refresh_from_db()\r\n>>> uc.data['tables']['SiteTable']['ordering']\r\n['']\r\n```\n\n### Expected Behavior\n\nThe `ordering` key should be removed from the data, as there is no longer any preference stored.\n\n### Observed Behavior\n\n`ordering` is set to a list containing an empty string. This does not effect any breaking behavior AFAICT, however it should be cleaned up as it can pose complications.\n", "before_files": [{"content": "import django_tables2 as tables\nfrom django.contrib.auth.models import AnonymousUser\nfrom django.contrib.contenttypes.fields import GenericForeignKey\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.core.exceptions import FieldDoesNotExist\nfrom django.db.models.fields.related import RelatedField\nfrom django.urls import reverse\nfrom django.urls.exceptions import NoReverseMatch\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import gettext as _\nfrom django_tables2.data import TableQuerysetData\n\nfrom extras.models import CustomField, CustomLink\nfrom extras.choices import CustomFieldVisibilityChoices\nfrom netbox.tables import columns\nfrom utilities.paginator import EnhancedPaginator, get_paginate_count\nfrom utilities.utils import get_viewname, highlight_string, title\n\n__all__ = (\n    'BaseTable',\n    'NetBoxTable',\n    'SearchTable',\n)\n\n\nclass BaseTable(tables.Table):\n    \"\"\"\n    Base table class for NetBox objects. Adds support for:\n\n        * User configuration (column preferences)\n        * Automatic prefetching of related objects\n        * BS5 styling\n\n    :param user: Personalize table display for the given user (optional). Has no effect if AnonymousUser is passed.\n    \"\"\"\n    exempt_columns = ()\n\n    class Meta:\n        attrs = {\n            'class': 'table table-hover object-list',\n        }\n\n    def __init__(self, *args, user=None, **kwargs):\n\n        super().__init__(*args, **kwargs)\n\n        # Set default empty_text if none was provided\n        if self.empty_text is None:\n            self.empty_text = f\"No {self._meta.model._meta.verbose_name_plural} found\"\n\n        # Determine the table columns to display by checking the following:\n        #   1. User's configuration for the table\n        #   2. Meta.default_columns\n        #   3. Meta.fields\n        selected_columns = None\n        if user is not None and not isinstance(user, AnonymousUser):\n            selected_columns = user.config.get(f\"tables.{self.__class__.__name__}.columns\")\n        if not selected_columns:\n            selected_columns = getattr(self.Meta, 'default_columns', self.Meta.fields)\n\n        # Hide non-selected columns which are not exempt\n        for column in self.columns:\n            if column.name not in [*selected_columns, *self.exempt_columns]:\n                self.columns.hide(column.name)\n\n        # Rearrange the sequence to list selected columns first, followed by all remaining columns\n        # TODO: There's probably a more clever way to accomplish this\n        self.sequence = [\n            *[c for c in selected_columns if c in self.columns.names()],\n            *[c for c in self.columns.names() if c not in selected_columns]\n        ]\n\n        # PK column should always come first\n        if 'pk' in self.sequence:\n            self.sequence.remove('pk')\n            self.sequence.insert(0, 'pk')\n\n        # Actions column should always come last\n        if 'actions' in self.sequence:\n            self.sequence.remove('actions')\n            self.sequence.append('actions')\n\n        # Dynamically update the table's QuerySet to ensure related fields are pre-fetched\n        if isinstance(self.data, TableQuerysetData):\n\n            prefetch_fields = []\n            for column in self.columns:\n                if column.visible:\n                    model = getattr(self.Meta, 'model')\n                    accessor = column.accessor\n                    prefetch_path = []\n                    for field_name in accessor.split(accessor.SEPARATOR):\n                        try:\n                            field = model._meta.get_field(field_name)\n                        except FieldDoesNotExist:\n                            break\n                        if isinstance(field, RelatedField):\n                            # Follow ForeignKeys to the related model\n                            prefetch_path.append(field_name)\n                            model = field.remote_field.model\n                        elif isinstance(field, GenericForeignKey):\n                            # Can't prefetch beyond a GenericForeignKey\n                            prefetch_path.append(field_name)\n                            break\n                    if prefetch_path:\n                        prefetch_fields.append('__'.join(prefetch_path))\n            self.data.data = self.data.data.prefetch_related(*prefetch_fields)\n\n    def _get_columns(self, visible=True):\n        columns = []\n        for name, column in self.columns.items():\n            if column.visible == visible and name not in self.exempt_columns:\n                columns.append((name, column.verbose_name))\n        return columns\n\n    @property\n    def available_columns(self):\n        return self._get_columns(visible=False)\n\n    @property\n    def selected_columns(self):\n        return self._get_columns(visible=True)\n\n    @property\n    def objects_count(self):\n        \"\"\"\n        Return the total number of real objects represented by the Table. This is useful when dealing with\n        prefixes/IP addresses/etc., where some table rows may represent available address space.\n        \"\"\"\n        if not hasattr(self, '_objects_count'):\n            self._objects_count = sum(1 for obj in self.data if hasattr(obj, 'pk'))\n        return self._objects_count\n\n    def configure(self, request):\n        \"\"\"\n        Configure the table for a specific request context. This performs pagination and records\n        the user's preferred ordering logic.\n        \"\"\"\n        # Save ordering preference\n        if request.user.is_authenticated:\n            table_name = self.__class__.__name__\n            if self.prefixed_order_by_field in request.GET:\n                # If an ordering has been specified as a query parameter, save it as the\n                # user's preferred ordering for this table.\n                ordering = request.GET.getlist(self.prefixed_order_by_field)\n                request.user.config.set(f'tables.{table_name}.ordering', ordering, commit=True)\n            elif ordering := request.user.config.get(f'tables.{table_name}.ordering'):\n                # If no ordering has been specified, set the preferred ordering (if any).\n                self.order_by = ordering\n\n        # Paginate the table results\n        paginate = {\n            'paginator_class': EnhancedPaginator,\n            'per_page': get_paginate_count(request)\n        }\n        tables.RequestConfig(request, paginate).configure(self)\n\n\nclass NetBoxTable(BaseTable):\n    \"\"\"\n    Table class for most NetBox objects. Adds support for custom field & custom link columns. Includes\n    default columns for:\n\n        * PK (row selection)\n        * ID\n        * Actions\n    \"\"\"\n    pk = columns.ToggleColumn(\n        visible=False\n    )\n    id = tables.Column(\n        linkify=True,\n        verbose_name='ID'\n    )\n    actions = columns.ActionsColumn()\n\n    exempt_columns = ('pk', 'actions')\n\n    class Meta(BaseTable.Meta):\n        pass\n\n    def __init__(self, *args, extra_columns=None, **kwargs):\n        if extra_columns is None:\n            extra_columns = []\n\n        # Add custom field & custom link columns\n        content_type = ContentType.objects.get_for_model(self._meta.model)\n        custom_fields = CustomField.objects.filter(\n            content_types=content_type\n        ).exclude(ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_HIDDEN)\n\n        extra_columns.extend([\n            (f'cf_{cf.name}', columns.CustomFieldColumn(cf)) for cf in custom_fields\n        ])\n        custom_links = CustomLink.objects.filter(content_types=content_type, enabled=True)\n        extra_columns.extend([\n            (f'cl_{cl.name}', columns.CustomLinkColumn(cl)) for cl in custom_links\n        ])\n\n        super().__init__(*args, extra_columns=extra_columns, **kwargs)\n\n    @property\n    def htmx_url(self):\n        \"\"\"\n        Return the base HTML request URL for embedded tables.\n        \"\"\"\n        if getattr(self, 'embedded', False):\n            viewname = get_viewname(self._meta.model, action='list')\n            try:\n                return reverse(viewname)\n            except NoReverseMatch:\n                pass\n        return ''\n\n\nclass SearchTable(tables.Table):\n    object_type = columns.ContentTypeColumn(\n        verbose_name=_('Type'),\n        order_by=\"object___meta__verbose_name\",\n    )\n    object = tables.Column(\n        linkify=True,\n        order_by=('name', )\n    )\n    field = tables.Column()\n    value = tables.Column()\n\n    trim_length = 30\n\n    class Meta:\n        attrs = {\n            'class': 'table table-hover object-list',\n        }\n        empty_text = _('No results found')\n\n    def __init__(self, data, highlight=None, **kwargs):\n        self.highlight = highlight\n        super().__init__(data, **kwargs)\n\n    def render_field(self, value, record):\n        if hasattr(record.object, value):\n            return title(record.object._meta.get_field(value).verbose_name)\n        return value\n\n    def render_value(self, value):\n        if not self.highlight:\n            return value\n\n        value = highlight_string(value, self.highlight, trim_pre=self.trim_length, trim_post=self.trim_length)\n\n        return mark_safe(value)\n", "path": "netbox/netbox/tables/tables.py"}], "after_files": [{"content": "import django_tables2 as tables\nfrom django.contrib.auth.models import AnonymousUser\nfrom django.contrib.contenttypes.fields import GenericForeignKey\nfrom django.contrib.contenttypes.models import ContentType\nfrom django.core.exceptions import FieldDoesNotExist\nfrom django.db.models.fields.related import RelatedField\nfrom django.urls import reverse\nfrom django.urls.exceptions import NoReverseMatch\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import gettext as _\nfrom django_tables2.data import TableQuerysetData\n\nfrom extras.models import CustomField, CustomLink\nfrom extras.choices import CustomFieldVisibilityChoices\nfrom netbox.tables import columns\nfrom utilities.paginator import EnhancedPaginator, get_paginate_count\nfrom utilities.utils import get_viewname, highlight_string, title\n\n__all__ = (\n    'BaseTable',\n    'NetBoxTable',\n    'SearchTable',\n)\n\n\nclass BaseTable(tables.Table):\n    \"\"\"\n    Base table class for NetBox objects. Adds support for:\n\n        * User configuration (column preferences)\n        * Automatic prefetching of related objects\n        * BS5 styling\n\n    :param user: Personalize table display for the given user (optional). Has no effect if AnonymousUser is passed.\n    \"\"\"\n    exempt_columns = ()\n\n    class Meta:\n        attrs = {\n            'class': 'table table-hover object-list',\n        }\n\n    def __init__(self, *args, user=None, **kwargs):\n\n        super().__init__(*args, **kwargs)\n\n        # Set default empty_text if none was provided\n        if self.empty_text is None:\n            self.empty_text = f\"No {self._meta.model._meta.verbose_name_plural} found\"\n\n        # Determine the table columns to display by checking the following:\n        #   1. User's configuration for the table\n        #   2. Meta.default_columns\n        #   3. Meta.fields\n        selected_columns = None\n        if user is not None and not isinstance(user, AnonymousUser):\n            selected_columns = user.config.get(f\"tables.{self.__class__.__name__}.columns\")\n        if not selected_columns:\n            selected_columns = getattr(self.Meta, 'default_columns', self.Meta.fields)\n\n        # Hide non-selected columns which are not exempt\n        for column in self.columns:\n            if column.name not in [*selected_columns, *self.exempt_columns]:\n                self.columns.hide(column.name)\n\n        # Rearrange the sequence to list selected columns first, followed by all remaining columns\n        # TODO: There's probably a more clever way to accomplish this\n        self.sequence = [\n            *[c for c in selected_columns if c in self.columns.names()],\n            *[c for c in self.columns.names() if c not in selected_columns]\n        ]\n\n        # PK column should always come first\n        if 'pk' in self.sequence:\n            self.sequence.remove('pk')\n            self.sequence.insert(0, 'pk')\n\n        # Actions column should always come last\n        if 'actions' in self.sequence:\n            self.sequence.remove('actions')\n            self.sequence.append('actions')\n\n        # Dynamically update the table's QuerySet to ensure related fields are pre-fetched\n        if isinstance(self.data, TableQuerysetData):\n\n            prefetch_fields = []\n            for column in self.columns:\n                if column.visible:\n                    model = getattr(self.Meta, 'model')\n                    accessor = column.accessor\n                    prefetch_path = []\n                    for field_name in accessor.split(accessor.SEPARATOR):\n                        try:\n                            field = model._meta.get_field(field_name)\n                        except FieldDoesNotExist:\n                            break\n                        if isinstance(field, RelatedField):\n                            # Follow ForeignKeys to the related model\n                            prefetch_path.append(field_name)\n                            model = field.remote_field.model\n                        elif isinstance(field, GenericForeignKey):\n                            # Can't prefetch beyond a GenericForeignKey\n                            prefetch_path.append(field_name)\n                            break\n                    if prefetch_path:\n                        prefetch_fields.append('__'.join(prefetch_path))\n            self.data.data = self.data.data.prefetch_related(*prefetch_fields)\n\n    def _get_columns(self, visible=True):\n        columns = []\n        for name, column in self.columns.items():\n            if column.visible == visible and name not in self.exempt_columns:\n                columns.append((name, column.verbose_name))\n        return columns\n\n    @property\n    def available_columns(self):\n        return self._get_columns(visible=False)\n\n    @property\n    def selected_columns(self):\n        return self._get_columns(visible=True)\n\n    @property\n    def objects_count(self):\n        \"\"\"\n        Return the total number of real objects represented by the Table. This is useful when dealing with\n        prefixes/IP addresses/etc., where some table rows may represent available address space.\n        \"\"\"\n        if not hasattr(self, '_objects_count'):\n            self._objects_count = sum(1 for obj in self.data if hasattr(obj, 'pk'))\n        return self._objects_count\n\n    def configure(self, request):\n        \"\"\"\n        Configure the table for a specific request context. This performs pagination and records\n        the user's preferred ordering logic.\n        \"\"\"\n        # Save ordering preference\n        if request.user.is_authenticated:\n            table_name = self.__class__.__name__\n            if self.prefixed_order_by_field in request.GET:\n                if request.GET[self.prefixed_order_by_field]:\n                    # If an ordering has been specified as a query parameter, save it as the\n                    # user's preferred ordering for this table.\n                    ordering = request.GET.getlist(self.prefixed_order_by_field)\n                    request.user.config.set(f'tables.{table_name}.ordering', ordering, commit=True)\n                else:\n                    # If the ordering has been set to none (empty), clear any existing preference.\n                    request.user.config.clear(f'tables.{table_name}.ordering', commit=True)\n            elif ordering := request.user.config.get(f'tables.{table_name}.ordering'):\n                # If no ordering has been specified, set the preferred ordering (if any).\n                self.order_by = ordering\n\n        # Paginate the table results\n        paginate = {\n            'paginator_class': EnhancedPaginator,\n            'per_page': get_paginate_count(request)\n        }\n        tables.RequestConfig(request, paginate).configure(self)\n\n\nclass NetBoxTable(BaseTable):\n    \"\"\"\n    Table class for most NetBox objects. Adds support for custom field & custom link columns. Includes\n    default columns for:\n\n        * PK (row selection)\n        * ID\n        * Actions\n    \"\"\"\n    pk = columns.ToggleColumn(\n        visible=False\n    )\n    id = tables.Column(\n        linkify=True,\n        verbose_name='ID'\n    )\n    actions = columns.ActionsColumn()\n\n    exempt_columns = ('pk', 'actions')\n\n    class Meta(BaseTable.Meta):\n        pass\n\n    def __init__(self, *args, extra_columns=None, **kwargs):\n        if extra_columns is None:\n            extra_columns = []\n\n        # Add custom field & custom link columns\n        content_type = ContentType.objects.get_for_model(self._meta.model)\n        custom_fields = CustomField.objects.filter(\n            content_types=content_type\n        ).exclude(ui_visibility=CustomFieldVisibilityChoices.VISIBILITY_HIDDEN)\n\n        extra_columns.extend([\n            (f'cf_{cf.name}', columns.CustomFieldColumn(cf)) for cf in custom_fields\n        ])\n        custom_links = CustomLink.objects.filter(content_types=content_type, enabled=True)\n        extra_columns.extend([\n            (f'cl_{cl.name}', columns.CustomLinkColumn(cl)) for cl in custom_links\n        ])\n\n        super().__init__(*args, extra_columns=extra_columns, **kwargs)\n\n    @property\n    def htmx_url(self):\n        \"\"\"\n        Return the base HTML request URL for embedded tables.\n        \"\"\"\n        if getattr(self, 'embedded', False):\n            viewname = get_viewname(self._meta.model, action='list')\n            try:\n                return reverse(viewname)\n            except NoReverseMatch:\n                pass\n        return ''\n\n\nclass SearchTable(tables.Table):\n    object_type = columns.ContentTypeColumn(\n        verbose_name=_('Type'),\n        order_by=\"object___meta__verbose_name\",\n    )\n    object = tables.Column(\n        linkify=True,\n        order_by=('name', )\n    )\n    field = tables.Column()\n    value = tables.Column()\n\n    trim_length = 30\n\n    class Meta:\n        attrs = {\n            'class': 'table table-hover object-list',\n        }\n        empty_text = _('No results found')\n\n    def __init__(self, data, highlight=None, **kwargs):\n        self.highlight = highlight\n        super().__init__(data, **kwargs)\n\n    def render_field(self, value, record):\n        if hasattr(record.object, value):\n            return title(record.object._meta.get_field(value).verbose_name)\n        return value\n\n    def render_value(self, value):\n        if not self.highlight:\n            return value\n\n        value = highlight_string(value, self.highlight, trim_pre=self.trim_length, trim_post=self.trim_length)\n\n        return mark_safe(value)\n", "path": "netbox/netbox/tables/tables.py"}]}