Skip to content

Commit d3974c9

Browse files
Merge pull request #5929 from netbox-community/4999-table-export
Closes #4999: Support for dynamic table-based exports
2 parents 9c967ee + 0015c8d commit d3974c9

File tree

15 files changed

+204
-178
lines changed

15 files changed

+204
-178
lines changed

base_requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,7 @@ redis
9393
# SVG image rendering (used for rack elevations)
9494
# https://github.com/mozman/svgwrite
9595
svgwrite
96+
97+
# Tabular dataset library (for table-based exports)
98+
# https://github.com/jazzband/tablib
99+
tablib

docs/release-notes/version-2.11.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ In addition to the new `mark_connected` boolean field, the REST API representati
1616

1717
Devices can now be assigned to locations (formerly known as rack groups) within a site without needing to be assigned to a particular rack. This is handy for assigning devices to rooms or floors within a building where racks are not used. The `location` foreign key field has been added to the Device model to support this.
1818

19+
#### Dynamic Object Exports ([#4999](https://github.com/netbox-community/netbox/issues/4999))
20+
21+
When exporting a list of objects in NetBox, users now have the option of selecting the "current view". This will render CSV output matching the configuration of the current table. For example, if you modify the sites list to display on the site name, tenant, and status, the rendered CSV will include only these columns.
22+
23+
The legacy static export behavior has been retained to ensure backward compatibility for dependent integrations. However, users are strongly encouraged to adapt custom export templates where needed as this functionality will be removed in v2.12.
24+
1925
#### Improved Change Logging ([#5913](https://github.com/netbox-community/netbox/issues/5913))
2026

2127
The ObjectChange model (which is used to record the creation, modification, and deletion of NetBox objects) now explicitly records the pre-change and post-change state of each object, rather than only the post-change state. This was done to present a more clear depiction of each change being made, and to prevent the erroneous association of a previous unlogged change with its successor.

netbox/circuits/tables.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import django_tables2 as tables
22
from django_tables2.utils import Accessor
33

4-
from tenancy.tables import COL_TENANT
4+
from tenancy.tables import TenantColumn
55
from utilities.tables import BaseTable, ButtonsColumn, ChoiceFieldColumn, TagColumn, ToggleColumn
66
from .models import Circuit, CircuitType, Provider
77

@@ -60,9 +60,7 @@ class CircuitTable(BaseTable):
6060
linkify=True
6161
)
6262
status = ChoiceFieldColumn()
63-
tenant = tables.TemplateColumn(
64-
template_code=COL_TENANT
65-
)
63+
tenant = TenantColumn()
6664
a_side = tables.Column(
6765
verbose_name='A Side'
6866
)

netbox/dcim/tables/devices.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceRole, FrontPort, Interface, InventoryItem, Platform,
66
PowerOutlet, PowerPort, RearPort, VirtualChassis,
77
)
8-
from tenancy.tables import COL_TENANT
8+
from tenancy.tables import TenantColumn
99
from utilities.tables import (
1010
BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn,
1111
TagColumn, ToggleColumn,
@@ -109,9 +109,7 @@ class DeviceTable(BaseTable):
109109
template_code=DEVICE_LINK
110110
)
111111
status = ChoiceFieldColumn()
112-
tenant = tables.TemplateColumn(
113-
template_code=COL_TENANT
114-
)
112+
tenant = TenantColumn()
115113
site = tables.Column(
116114
linkify=True
117115
)
@@ -178,9 +176,7 @@ class DeviceImportTable(BaseTable):
178176
template_code=DEVICE_LINK
179177
)
180178
status = ChoiceFieldColumn()
181-
tenant = tables.TemplateColumn(
182-
template_code=COL_TENANT
183-
)
179+
tenant = TenantColumn()
184180
site = tables.Column(
185181
linkify=True
186182
)

netbox/dcim/tables/racks.py

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
from django_tables2.utils import Accessor
33

44
from dcim.models import Rack, Location, RackReservation, RackRole
5-
from tenancy.tables import COL_TENANT
5+
from tenancy.tables import TenantColumn
66
from utilities.tables import (
7-
BaseTable, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn, TagColumn,
8-
ToggleColumn,
7+
BaseTable, ButtonsColumn, ChoiceFieldColumn, ColorColumn, ColoredLabelColumn, LinkedCountColumn, MPTTColumn,
8+
TagColumn, ToggleColumn, UtilizationColumn,
99
)
10-
from .template_code import MPTT_LINK, LOCATION_ELEVATIONS, UTILIZATION_GRAPH
10+
from .template_code import LOCATION_ELEVATIONS
1111

1212
__all__ = (
1313
'RackTable',
@@ -24,11 +24,7 @@
2424

2525
class LocationTable(BaseTable):
2626
pk = ToggleColumn()
27-
name = tables.TemplateColumn(
28-
template_code=MPTT_LINK,
29-
orderable=False,
30-
attrs={'td': {'class': 'text-nowrap'}}
31-
)
27+
name = MPTTColumn()
3228
site = tables.Column(
3329
linkify=True
3430
)
@@ -79,9 +75,7 @@ class RackTable(BaseTable):
7975
site = tables.Column(
8076
linkify=True
8177
)
82-
tenant = tables.TemplateColumn(
83-
template_code=COL_TENANT
84-
)
78+
tenant = TenantColumn()
8579
status = ChoiceFieldColumn()
8680
role = ColoredLabelColumn()
8781
u_height = tables.TemplateColumn(
@@ -104,13 +98,10 @@ class RackDetailTable(RackTable):
10498
url_params={'rack_id': 'pk'},
10599
verbose_name='Devices'
106100
)
107-
get_utilization = tables.TemplateColumn(
108-
template_code=UTILIZATION_GRAPH,
109-
orderable=False,
101+
get_utilization = UtilizationColumn(
110102
verbose_name='Space'
111103
)
112-
get_power_utilization = tables.TemplateColumn(
113-
template_code=UTILIZATION_GRAPH,
104+
get_power_utilization = UtilizationColumn(
114105
orderable=False,
115106
verbose_name='Power'
116107
)
@@ -143,9 +134,7 @@ class RackReservationTable(BaseTable):
143134
accessor=Accessor('rack__site'),
144135
linkify=True
145136
)
146-
tenant = tables.TemplateColumn(
147-
template_code=COL_TENANT
148-
)
137+
tenant = TenantColumn()
149138
rack = tables.Column(
150139
linkify=True
151140
)

netbox/dcim/tables/sites.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import django_tables2 as tables
22

33
from dcim.models import Region, Site
4-
from tenancy.tables import COL_TENANT
5-
from utilities.tables import BaseTable, ButtonsColumn, ChoiceFieldColumn, TagColumn, ToggleColumn
6-
from .template_code import MPTT_LINK
4+
from tenancy.tables import TenantColumn
5+
from utilities.tables import BaseTable, ButtonsColumn, ChoiceFieldColumn, MPTTColumn, TagColumn, ToggleColumn
76

87
__all__ = (
98
'RegionTable',
@@ -17,11 +16,7 @@
1716

1817
class RegionTable(BaseTable):
1918
pk = ToggleColumn()
20-
name = tables.TemplateColumn(
21-
template_code=MPTT_LINK,
22-
orderable=False,
23-
attrs={'td': {'class': 'text-nowrap'}}
24-
)
19+
name = MPTTColumn()
2520
site_count = tables.Column(
2621
verbose_name='Sites'
2722
)
@@ -46,9 +41,7 @@ class SiteTable(BaseTable):
4641
region = tables.Column(
4742
linkify=True
4843
)
49-
tenant = tables.TemplateColumn(
50-
template_code=COL_TENANT
51-
)
44+
tenant = TenantColumn()
5245
tags = TagColumn(
5346
url_name='dcim:site_list'
5447
)

netbox/dcim/tables/template_code.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,6 @@
5656
{% endif %}
5757
"""
5858

59-
MPTT_LINK = """
60-
{% for i in record.get_ancestors %}
61-
<i class="mdi mdi-circle-small"></i>
62-
{% endfor %}
63-
<a href="{{ record.get_absolute_url }}">{{ record.name }}</a>
64-
"""
65-
6659
POWERFEED_CABLE = """
6760
<a href="{{ value.get_absolute_url }}">{{ value }}</a>
6861
<a href="{% url 'dcim:powerfeed_trace' pk=record.pk %}" class="btn btn-primary btn-xs" title="Trace">
@@ -82,11 +75,6 @@
8275
</a>
8376
"""
8477

85-
UTILIZATION_GRAPH = """
86-
{% load helpers %}
87-
{% utilization_graph value %}
88-
"""
89-
9078
#
9179
# Device component buttons
9280
#

netbox/ipam/tables.py

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,16 @@
33
from django_tables2.utils import Accessor
44

55
from dcim.models import Interface
6-
from tenancy.tables import COL_TENANT
6+
from tenancy.tables import TenantColumn
77
from utilities.tables import (
88
BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, LinkedCountColumn, TagColumn, ToggleColumn,
9+
UtilizationColumn,
910
)
1011
from virtualization.models import VMInterface
1112
from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
1213

1314
AVAILABLE_LABEL = mark_safe('<span class="label label-success">Available</span>')
1415

15-
UTILIZATION_GRAPH = """
16-
{% load helpers %}
17-
{% if record.pk %}{% utilization_graph record.get_utilization %}{% else %}&mdash;{% endif %}
18-
"""
19-
2016
PREFIX_LINK = """
2117
{% load helpers %}
2218
{% for i in record.parents|as_range %}
@@ -109,16 +105,6 @@
109105
{% endif %}
110106
"""
111107

112-
TENANT_LINK = """
113-
{% if record.tenant %}
114-
<a href="{{ record.tenant.get_absolute_url }}" title="{{ record.tenant.description }}">{{ record.tenant }}</a>
115-
{% elif record.vrf.tenant %}
116-
<a href="{{ record.vrf.tenant.get_absolute_url }}" title="{{ record.vrf.tenant.description }}">{{ record.vrf.tenant }}</a>*
117-
{% else %}
118-
&mdash;
119-
{% endif %}
120-
"""
121-
122108

123109
#
124110
# VRFs
@@ -130,9 +116,7 @@ class VRFTable(BaseTable):
130116
rd = tables.Column(
131117
verbose_name='RD'
132118
)
133-
tenant = tables.TemplateColumn(
134-
template_code=COL_TENANT
135-
)
119+
tenant = TenantColumn()
136120
enforce_unique = BooleanColumn(
137121
verbose_name='Unique'
138122
)
@@ -163,9 +147,7 @@ class Meta(BaseTable.Meta):
163147
class RouteTargetTable(BaseTable):
164148
pk = ToggleColumn()
165149
name = tables.LinkColumn()
166-
tenant = tables.TemplateColumn(
167-
template_code=COL_TENANT
168-
)
150+
tenant = TenantColumn()
169151
tags = TagColumn(
170152
url_name='ipam:vrf_list'
171153
)
@@ -208,9 +190,7 @@ class AggregateTable(BaseTable):
208190
prefix = tables.LinkColumn(
209191
verbose_name='Aggregate'
210192
)
211-
tenant = tables.TemplateColumn(
212-
template_code=TENANT_LINK
213-
)
193+
tenant = TenantColumn()
214194
date_added = tables.DateColumn(
215195
format="Y-m-d",
216196
verbose_name='Added'
@@ -225,8 +205,8 @@ class AggregateDetailTable(AggregateTable):
225205
child_count = tables.Column(
226206
verbose_name='Prefixes'
227207
)
228-
utilization = tables.TemplateColumn(
229-
template_code=UTILIZATION_GRAPH,
208+
utilization = UtilizationColumn(
209+
accessor='get_utilization',
230210
orderable=False
231211
)
232212
tags = TagColumn(
@@ -279,9 +259,7 @@ class PrefixTable(BaseTable):
279259
template_code=VRF_LINK,
280260
verbose_name='VRF'
281261
)
282-
tenant = tables.TemplateColumn(
283-
template_code=TENANT_LINK
284-
)
262+
tenant = TenantColumn()
285263
site = tables.Column(
286264
linkify=True
287265
)
@@ -308,13 +286,11 @@ class Meta(BaseTable.Meta):
308286

309287

310288
class PrefixDetailTable(PrefixTable):
311-
utilization = tables.TemplateColumn(
312-
template_code=UTILIZATION_GRAPH,
289+
utilization = UtilizationColumn(
290+
accessor='get_utilization',
313291
orderable=False
314292
)
315-
tenant = tables.TemplateColumn(
316-
template_code=COL_TENANT
317-
)
293+
tenant = TenantColumn()
318294
tags = TagColumn(
319295
url_name='ipam:prefix_list'
320296
)
@@ -347,9 +323,7 @@ class IPAddressTable(BaseTable):
347323
default=AVAILABLE_LABEL
348324
)
349325
role = ChoiceFieldColumn()
350-
tenant = tables.TemplateColumn(
351-
template_code=TENANT_LINK
352-
)
326+
tenant = TenantColumn()
353327
assigned_object = tables.Column(
354328
linkify=True,
355329
orderable=False,
@@ -379,9 +353,7 @@ class IPAddressDetailTable(IPAddressTable):
379353
orderable=False,
380354
verbose_name='NAT (Inside)'
381355
)
382-
tenant = tables.TemplateColumn(
383-
template_code=COL_TENANT
384-
)
356+
tenant = TenantColumn()
385357
assigned = BooleanColumn(
386358
accessor='assigned_object_id',
387359
verbose_name='Assigned'
@@ -428,9 +400,7 @@ class InterfaceIPAddressTable(BaseTable):
428400
verbose_name='VRF'
429401
)
430402
status = ChoiceFieldColumn()
431-
tenant = tables.TemplateColumn(
432-
template_code=TENANT_LINK
433-
)
403+
tenant = TenantColumn()
434404

435405
class Meta(BaseTable.Meta):
436406
model = IPAddress
@@ -480,9 +450,7 @@ class VLANTable(BaseTable):
480450
viewname='ipam:vlangroup_vlans',
481451
args=[Accessor('group__pk')]
482452
)
483-
tenant = tables.TemplateColumn(
484-
template_code=COL_TENANT
485-
)
453+
tenant = TenantColumn()
486454
status = ChoiceFieldColumn(
487455
default=AVAILABLE_LABEL
488456
)
@@ -504,9 +472,7 @@ class VLANDetailTable(VLANTable):
504472
orderable=False,
505473
verbose_name='Prefixes'
506474
)
507-
tenant = tables.TemplateColumn(
508-
template_code=COL_TENANT
509-
)
475+
tenant = TenantColumn()
510476
tags = TagColumn(
511477
url_name='ipam:vlan_list'
512478
)
@@ -564,9 +530,7 @@ class InterfaceVLANTable(BaseTable):
564530
accessor=Accessor('group__name'),
565531
verbose_name='Group'
566532
)
567-
tenant = tables.TemplateColumn(
568-
template_code=COL_TENANT
569-
)
533+
tenant = TenantColumn()
570534
status = ChoiceFieldColumn()
571535
role = tables.TemplateColumn(
572536
template_code=VLAN_ROLE_LINK

0 commit comments

Comments
 (0)