1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Move configure_table() logic to NetBoxTable.configure()

This commit is contained in:
jeremystretch
2022-02-09 14:10:54 -05:00
parent 10e6ae2094
commit 23a80770e1
13 changed files with 65 additions and 66 deletions

View File

@ -35,3 +35,14 @@ class MyModelTable(NetBoxTable):
fields = ('pk', 'id', 'name', ...) fields = ('pk', 'id', 'name', ...)
default_columns = ('pk', 'name', ...) default_columns = ('pk', 'name', ...)
``` ```
### Table Configuration
The NetBoxTable class supports dynamic configuration to support pagination and to effect user preferences. To configure a table for a specific request, simply call its `configure()` method and pass the current HTTPRequest object. For example:
```python
table = MyModelTable(data=MyModel.objects.all())
table.configure(request)
```
If using a generic view provided by NetBox, table configuration is handled automatically.

View File

@ -5,7 +5,6 @@ from django.shortcuts import get_object_or_404, redirect, render
from netbox.views import generic from netbox.views import generic
from utilities.forms import ConfirmationForm from utilities.forms import ConfirmationForm
from netbox.tables import configure_table
from utilities.utils import count_related from utilities.utils import count_related
from . import filtersets, forms, tables from . import filtersets, forms, tables
from .models import * from .models import *
@ -34,7 +33,7 @@ class ProviderView(generic.ObjectView):
'type', 'tenant', 'terminations__site' 'type', 'tenant', 'terminations__site'
) )
circuits_table = tables.CircuitTable(circuits, exclude=('provider',)) circuits_table = tables.CircuitTable(circuits, exclude=('provider',))
configure_table(circuits_table, request) circuits_table.configure(request)
return { return {
'circuits_table': circuits_table, 'circuits_table': circuits_table,
@ -95,7 +94,7 @@ class ProviderNetworkView(generic.ObjectView):
'type', 'tenant', 'terminations__site' 'type', 'tenant', 'terminations__site'
) )
circuits_table = tables.CircuitTable(circuits) circuits_table = tables.CircuitTable(circuits)
configure_table(circuits_table, request) circuits_table.configure(request)
return { return {
'circuits_table': circuits_table, 'circuits_table': circuits_table,
@ -149,7 +148,7 @@ class CircuitTypeView(generic.ObjectView):
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
circuits = Circuit.objects.restrict(request.user, 'view').filter(type=instance) circuits = Circuit.objects.restrict(request.user, 'view').filter(type=instance)
circuits_table = tables.CircuitTable(circuits, exclude=('type',)) circuits_table = tables.CircuitTable(circuits, exclude=('type',))
configure_table(circuits_table, request) circuits_table.configure(request)
return { return {
'circuits_table': circuits_table, 'circuits_table': circuits_table,

View File

@ -20,7 +20,6 @@ from netbox.views import generic
from utilities.forms import ConfirmationForm from utilities.forms import ConfirmationForm
from utilities.paginator import EnhancedPaginator, get_paginate_count from utilities.paginator import EnhancedPaginator, get_paginate_count
from utilities.permissions import get_permission_for_model from utilities.permissions import get_permission_for_model
from netbox.tables import configure_table
from utilities.utils import count_related from utilities.utils import count_related
from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin
from virtualization.models import VirtualMachine from virtualization.models import VirtualMachine
@ -165,7 +164,7 @@ class RegionView(generic.ObjectView):
region=instance region=instance
) )
sites_table = tables.SiteTable(sites, exclude=('region',)) sites_table = tables.SiteTable(sites, exclude=('region',))
configure_table(sites_table, request) sites_table.configure(request)
return { return {
'child_regions_table': child_regions_table, 'child_regions_table': child_regions_table,
@ -250,7 +249,7 @@ class SiteGroupView(generic.ObjectView):
group=instance group=instance
) )
sites_table = tables.SiteTable(sites, exclude=('group',)) sites_table = tables.SiteTable(sites, exclude=('group',))
configure_table(sites_table, request) sites_table.configure(request)
return { return {
'child_groups_table': child_groups_table, 'child_groups_table': child_groups_table,
@ -422,7 +421,7 @@ class LocationView(generic.ObjectView):
cumulative=True cumulative=True
).filter(pk__in=location_ids).exclude(pk=instance.pk) ).filter(pk__in=location_ids).exclude(pk=instance.pk)
child_locations_table = tables.LocationTable(child_locations) child_locations_table = tables.LocationTable(child_locations)
configure_table(child_locations_table, request) child_locations_table.configure(request)
return { return {
'rack_count': rack_count, 'rack_count': rack_count,
@ -493,7 +492,7 @@ class RackRoleView(generic.ObjectView):
) )
racks_table = tables.RackTable(racks, exclude=('role', 'get_utilization', 'get_power_utilization')) racks_table = tables.RackTable(racks, exclude=('role', 'get_utilization', 'get_power_utilization'))
configure_table(racks_table, request) racks_table.configure(request)
return { return {
'racks_table': racks_table, 'racks_table': racks_table,
@ -743,7 +742,7 @@ class ManufacturerView(generic.ObjectView):
) )
devicetypes_table = tables.DeviceTypeTable(devicetypes, exclude=('manufacturer',)) devicetypes_table = tables.DeviceTypeTable(devicetypes, exclude=('manufacturer',))
configure_table(devicetypes_table, request) devicetypes_table.configure(request)
return { return {
'devicetypes_table': devicetypes_table, 'devicetypes_table': devicetypes_table,
@ -1437,7 +1436,7 @@ class DeviceRoleView(generic.ObjectView):
device_role=instance device_role=instance
) )
devices_table = tables.DeviceTable(devices, exclude=('device_role',)) devices_table = tables.DeviceTable(devices, exclude=('device_role',))
configure_table(devices_table, request) devices_table.configure(request)
return { return {
'devices_table': devices_table, 'devices_table': devices_table,
@ -1501,7 +1500,7 @@ class PlatformView(generic.ObjectView):
platform=instance platform=instance
) )
devices_table = tables.DeviceTable(devices, exclude=('platform',)) devices_table = tables.DeviceTable(devices, exclude=('platform',))
configure_table(devices_table, request) devices_table.configure(request)
return { return {
'devices_table': devices_table, 'devices_table': devices_table,

View File

@ -11,7 +11,6 @@ from rq import Worker
from netbox.views import generic from netbox.views import generic
from utilities.forms import ConfirmationForm from utilities.forms import ConfirmationForm
from utilities.htmx import is_htmx from utilities.htmx import is_htmx
from netbox.tables import configure_table
from utilities.utils import copy_safe_request, count_related, normalize_querydict, shallow_compare_dict from utilities.utils import copy_safe_request, count_related, normalize_querydict, shallow_compare_dict
from utilities.views import ContentTypePermissionRequiredMixin from utilities.views import ContentTypePermissionRequiredMixin
from . import filtersets, forms, tables from . import filtersets, forms, tables
@ -215,7 +214,7 @@ class TagView(generic.ObjectView):
data=tagged_items, data=tagged_items,
orderable=False orderable=False
) )
configure_table(taggeditem_table, request) taggeditem_table.configure(request)
object_types = [ object_types = [
{ {
@ -451,7 +450,7 @@ class ObjectChangeLogView(View):
data=objectchanges, data=objectchanges,
orderable=False orderable=False
) )
configure_table(objectchanges_table, request) objectchanges_table.configure(request)
# Default to using "<app>/<model>.html" as the template, if it exists. Otherwise, # Default to using "<app>/<model>.html" as the template, if it exists. Otherwise,
# fall back to using base.html. # fall back to using base.html.
@ -571,7 +570,7 @@ class ObjectJournalView(View):
assigned_object_id=obj.pk assigned_object_id=obj.pk
) )
journalentry_table = tables.ObjectJournalTable(journalentries) journalentry_table = tables.ObjectJournalTable(journalentries)
configure_table(journalentry_table, request) journalentry_table.configure(request)
if request.user.has_perm('extras.add_journalentry'): if request.user.has_perm('extras.add_journalentry'):
form = forms.JournalEntryForm( form = forms.JournalEntryForm(

View File

@ -8,7 +8,6 @@ from dcim.filtersets import InterfaceFilterSet
from dcim.models import Interface, Site from dcim.models import Interface, Site
from dcim.tables import SiteTable from dcim.tables import SiteTable
from netbox.views import generic from netbox.views import generic
from netbox.tables import configure_table
from utilities.utils import count_related from utilities.utils import count_related
from virtualization.filtersets import VMInterfaceFilterSet from virtualization.filtersets import VMInterfaceFilterSet
from virtualization.models import VMInterface from virtualization.models import VMInterface
@ -161,7 +160,7 @@ class RIRView(generic.ObjectView):
rir=instance rir=instance
) )
aggregates_table = tables.AggregateTable(aggregates, exclude=('rir', 'utilization')) aggregates_table = tables.AggregateTable(aggregates, exclude=('rir', 'utilization'))
configure_table(aggregates_table, request) aggregates_table.configure(request)
return { return {
'aggregates_table': aggregates_table, 'aggregates_table': aggregates_table,
@ -219,7 +218,7 @@ class ASNView(generic.ObjectView):
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
sites = instance.sites.restrict(request.user, 'view') sites = instance.sites.restrict(request.user, 'view')
sites_table = SiteTable(sites) sites_table = SiteTable(sites)
configure_table(sites_table, request) sites_table.configure(request)
return { return {
'sites_table': sites_table, 'sites_table': sites_table,
@ -357,7 +356,7 @@ class RoleView(generic.ObjectView):
) )
prefixes_table = tables.PrefixTable(prefixes, exclude=('role', 'utilization')) prefixes_table = tables.PrefixTable(prefixes, exclude=('role', 'utilization'))
configure_table(prefixes_table, request) prefixes_table.configure(request)
return { return {
'prefixes_table': prefixes_table, 'prefixes_table': prefixes_table,
@ -662,7 +661,7 @@ class IPAddressView(generic.ObjectView):
vrf=instance.vrf, address__net_contained_or_equal=str(instance.address) vrf=instance.vrf, address__net_contained_or_equal=str(instance.address)
) )
related_ips_table = tables.IPAddressTable(related_ips, orderable=False) related_ips_table = tables.IPAddressTable(related_ips, orderable=False)
configure_table(related_ips_table, request) related_ips_table.configure(request)
return { return {
'parent_prefixes_table': parent_prefixes_table, 'parent_prefixes_table': parent_prefixes_table,
@ -798,7 +797,7 @@ class VLANGroupView(generic.ObjectView):
vlans_table = tables.VLANTable(vlans, exclude=('site', 'group', 'prefixes')) vlans_table = tables.VLANTable(vlans, exclude=('site', 'group', 'prefixes'))
if request.user.has_perm('ipam.change_vlan') or request.user.has_perm('ipam.delete_vlan'): if request.user.has_perm('ipam.change_vlan') or request.user.has_perm('ipam.delete_vlan'):
vlans_table.columns.show('pk') vlans_table.columns.show('pk')
configure_table(vlans_table, request) vlans_table.configure(request)
# Compile permissions list for rendering the object table # Compile permissions list for rendering the object table
permissions = { permissions = {

View File

@ -1,29 +1,2 @@
from django_tables2 import RequestConfig
from utilities.paginator import EnhancedPaginator, get_paginate_count
from .columns import * from .columns import *
from .tables import * from .tables import *
def configure_table(table, request):
"""
Paginate a table given a request context.
"""
# Save ordering preference
if request.user.is_authenticated:
table_name = table.__class__.__name__
if table.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(table.prefixed_order_by_field)
request.user.config.set(f'tables.{table_name}.ordering', 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).
table.order_by = ordering
# Paginate the table results
paginate = {
'paginator_class': EnhancedPaginator,
'per_page': get_paginate_count(request)
}
RequestConfig(request, paginate).configure(table)

View File

@ -8,6 +8,7 @@ from django_tables2.data import TableQuerysetData
from extras.models import CustomField, CustomLink from extras.models import CustomField, CustomLink
from netbox.tables import columns from netbox.tables import columns
from utilities.paginator import EnhancedPaginator, get_paginate_count
__all__ = ( __all__ = (
'BaseTable', 'BaseTable',
@ -166,3 +167,27 @@ class NetBoxTable(BaseTable):
]) ])
super().__init__(*args, extra_columns=extra_columns, **kwargs) super().__init__(*args, extra_columns=extra_columns, **kwargs)
def configure(self, request):
"""
Configure the table for a specific request context. This performs pagination and records
the user's preferred ordering logic.
"""
# Save ordering preference
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)
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
# Paginate the table results
paginate = {
'paginator_class': EnhancedPaginator,
'per_page': get_paginate_count(request)
}
tables.RequestConfig(request, paginate).configure(self)

View File

@ -14,7 +14,6 @@ from django_tables2.export import TableExport
from extras.models import ExportTemplate from extras.models import ExportTemplate
from extras.signals import clear_webhooks from extras.signals import clear_webhooks
from netbox.tables import configure_table
from utilities.error_handlers import handle_protectederror from utilities.error_handlers import handle_protectederror
from utilities.exceptions import PermissionsViolation from utilities.exceptions import PermissionsViolation
from utilities.forms import ( from utilities.forms import (
@ -169,7 +168,7 @@ class ObjectListView(BaseMultiObjectView):
# Render the objects table # Render the objects table
table = self.get_table(request, permissions) table = self.get_table(request, permissions)
configure_table(table, request) table.configure(request)
# If this is an HTMX request, return only the rendered table HTML # If this is an HTMX request, return only the rendered table HTML
if is_htmx(request): if is_htmx(request):

View File

@ -17,7 +17,6 @@ from utilities.exceptions import AbortTransaction, PermissionsViolation
from utilities.forms import ConfirmationForm, ImportForm, restrict_form_fields from utilities.forms import ConfirmationForm, ImportForm, restrict_form_fields
from utilities.htmx import is_htmx from utilities.htmx import is_htmx
from utilities.permissions import get_permission_for_model from utilities.permissions import get_permission_for_model
from netbox.tables import configure_table
from utilities.utils import normalize_querydict, prepare_cloned_fields from utilities.utils import normalize_querydict, prepare_cloned_fields
from utilities.views import GetReturnURLMixin from utilities.views import GetReturnURLMixin
from .base import BaseObjectView from .base import BaseObjectView
@ -124,7 +123,7 @@ class ObjectChildrenView(ObjectView):
# Determine whether to display bulk action checkboxes # Determine whether to display bulk action checkboxes
if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']): if 'pk' in table.base_columns and (permissions['change'] or permissions['delete']):
table.columns.show('pk') table.columns.show('pk')
configure_table(table, request) table.configure(request)
# If this is an HTMX request, return only the rendered table HTML # If this is an HTMX request, return only the rendered table HTML
if is_htmx(request): if is_htmx(request):

View File

@ -5,7 +5,6 @@ from circuits.models import Circuit
from dcim.models import Cable, Device, Location, Rack, RackReservation, Site from dcim.models import Cable, Device, Location, Rack, RackReservation, Site
from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF, ASN from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF, ASN
from netbox.views import generic from netbox.views import generic
from netbox.tables import configure_table
from utilities.utils import count_related from utilities.utils import count_related
from virtualization.models import VirtualMachine, Cluster from virtualization.models import VirtualMachine, Cluster
from . import filtersets, forms, tables from . import filtersets, forms, tables
@ -37,7 +36,7 @@ class TenantGroupView(generic.ObjectView):
group=instance group=instance
) )
tenants_table = tables.TenantTable(tenants, exclude=('group',)) tenants_table = tables.TenantTable(tenants, exclude=('group',))
configure_table(tenants_table, request) tenants_table.configure(request)
return { return {
'tenants_table': tenants_table, 'tenants_table': tenants_table,
@ -186,7 +185,7 @@ class ContactGroupView(generic.ObjectView):
group=instance group=instance
) )
contacts_table = tables.ContactTable(contacts, exclude=('group',)) contacts_table = tables.ContactTable(contacts, exclude=('group',))
configure_table(contacts_table, request) contacts_table.configure(request)
return { return {
'child_groups_table': child_groups_table, 'child_groups_table': child_groups_table,
@ -253,7 +252,7 @@ class ContactRoleView(generic.ObjectView):
) )
contacts_table = tables.ContactAssignmentTable(contact_assignments) contacts_table = tables.ContactAssignmentTable(contact_assignments)
contacts_table.columns.hide('role') contacts_table.columns.hide('role')
configure_table(contacts_table, request) contacts_table.configure(request)
return { return {
'contacts_table': contacts_table, 'contacts_table': contacts_table,
@ -310,7 +309,7 @@ class ContactView(generic.ObjectView):
) )
assignments_table = tables.ContactAssignmentTable(contact_assignments) assignments_table = tables.ContactAssignmentTable(contact_assignments)
assignments_table.columns.hide('contact') assignments_table.columns.hide('contact')
configure_table(assignments_table, request) assignments_table.configure(request)
return { return {
'assignments_table': assignments_table, 'assignments_table': assignments_table,

View File

@ -6,7 +6,6 @@ from django.urls import reverse
from dcim.models import Site from dcim.models import Site
from dcim.tables import SiteTable from dcim.tables import SiteTable
from users.preferences import UserPreference from users.preferences import UserPreference
from netbox.tables import configure_table
from utilities.testing import TestCase from utilities.testing import TestCase
@ -60,5 +59,5 @@ class UserPreferencesTest(TestCase):
table = SiteTable(Site.objects.all()) table = SiteTable(Site.objects.all())
request = RequestFactory().get(url) request = RequestFactory().get(url)
request.user = self.user request.user = self.user
configure_table(table, request) table.configure(request)
self.assertEqual(table.order_by, ('-status',)) self.assertEqual(table.order_by, ('-status',))

View File

@ -11,7 +11,6 @@ from extras.views import ObjectConfigContextView
from ipam.models import IPAddress, Service from ipam.models import IPAddress, Service
from ipam.tables import AssignedIPAddressesTable, InterfaceVLANTable from ipam.tables import AssignedIPAddressesTable, InterfaceVLANTable
from netbox.views import generic from netbox.views import generic
from netbox.tables import configure_table
from utilities.utils import count_related from utilities.utils import count_related
from . import filtersets, forms, tables from . import filtersets, forms, tables
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
@ -41,7 +40,7 @@ class ClusterTypeView(generic.ObjectView):
vm_count=count_related(VirtualMachine, 'cluster') vm_count=count_related(VirtualMachine, 'cluster')
) )
clusters_table = tables.ClusterTable(clusters, exclude=('type',)) clusters_table = tables.ClusterTable(clusters, exclude=('type',))
configure_table(clusters_table, request) clusters_table.configure(request)
return { return {
'clusters_table': clusters_table, 'clusters_table': clusters_table,
@ -103,7 +102,7 @@ class ClusterGroupView(generic.ObjectView):
vm_count=count_related(VirtualMachine, 'cluster') vm_count=count_related(VirtualMachine, 'cluster')
) )
clusters_table = tables.ClusterTable(clusters, exclude=('group',)) clusters_table = tables.ClusterTable(clusters, exclude=('group',))
configure_table(clusters_table, request) clusters_table.configure(request)
return { return {
'clusters_table': clusters_table, 'clusters_table': clusters_table,

View File

@ -1,6 +1,5 @@
from dcim.models import Interface from dcim.models import Interface
from netbox.views import generic from netbox.views import generic
from netbox.tables import configure_table
from utilities.utils import count_related from utilities.utils import count_related
from . import filtersets, forms, tables from . import filtersets, forms, tables
from .models import * from .models import *
@ -31,7 +30,7 @@ class WirelessLANGroupView(generic.ObjectView):
group=instance group=instance
) )
wirelesslans_table = tables.WirelessLANTable(wirelesslans, exclude=('group',)) wirelesslans_table = tables.WirelessLANTable(wirelesslans, exclude=('group',))
configure_table(wirelesslans_table, request) wirelesslans_table.configure(request)
return { return {
'wirelesslans_table': wirelesslans_table, 'wirelesslans_table': wirelesslans_table,
@ -99,7 +98,7 @@ class WirelessLANView(generic.ObjectView):
wireless_lans=instance wireless_lans=instance
) )
interfaces_table = tables.WirelessLANInterfacesTable(attached_interfaces) interfaces_table = tables.WirelessLANInterfacesTable(attached_interfaces)
configure_table(interfaces_table, request) interfaces_table.configure(request)
return { return {
'interfaces_table': interfaces_table, 'interfaces_table': interfaces_table,