Skip to content

Commit 134742a

Browse files
Merge pull request #8090 from netbox-community/8054-configurable-choice-fields
Closes #8054: Configurable choice fields
2 parents 28f5777 + d8be8e2 commit 134742a

File tree

23 files changed

+197
-249
lines changed

23 files changed

+197
-249
lines changed

docs/configuration/optional-settings.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,41 @@ EXEMPT_VIEW_PERMISSIONS = ['*']
140140

141141
---
142142

143+
## FIELD_CHOICES
144+
145+
Default: Empty dictionary
146+
147+
Some static choice fields on models can be configured with custom values. This is done by defining `FIELD_CHOICES` as a dictionary mapping model fields to their choices list. Each choice in the list must have a database value and a human-friendly label, and may optionally specify a color.
148+
149+
For example, to specify a custom set of choices for the site status field:
150+
151+
```python
152+
FIELD_CHOICES = {
153+
'dcim.Site.status': (
154+
('foo', 'Foo'),
155+
('bar', 'Bar'),
156+
('baz', 'Baz'),
157+
)
158+
}
159+
```
160+
161+
These will be appended to the stock choices for the field.
162+
163+
The following model field support configurable choices:
164+
165+
* `circuits.Circuit.status`
166+
* `dcim.Device.status`
167+
* `dcim.PowerFeed.status`
168+
* `dcim.Rack.status`
169+
* `dcim.Site.status`
170+
* `ipam.IPAddress.status`
171+
* `ipam.IPRange.status`
172+
* `ipam.Prefix.status`
173+
* `ipam.VLAN.status`
174+
* `virtualization.VirtualMachine.status`
175+
176+
---
177+
143178
## HTTP_PROXIES
144179

145180
Default: None

netbox/circuits/choices.py

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#
77

88
class CircuitStatusChoices(ChoiceSet):
9+
key = 'circuits.Circuit.status'
910

1011
STATUS_DEPROVISIONING = 'deprovisioning'
1112
STATUS_ACTIVE = 'active'
@@ -14,23 +15,14 @@ class CircuitStatusChoices(ChoiceSet):
1415
STATUS_OFFLINE = 'offline'
1516
STATUS_DECOMMISSIONED = 'decommissioned'
1617

17-
CHOICES = (
18-
(STATUS_PLANNED, 'Planned'),
19-
(STATUS_PROVISIONING, 'Provisioning'),
20-
(STATUS_ACTIVE, 'Active'),
21-
(STATUS_OFFLINE, 'Offline'),
22-
(STATUS_DEPROVISIONING, 'Deprovisioning'),
23-
(STATUS_DECOMMISSIONED, 'Decommissioned'),
24-
)
25-
26-
CSS_CLASSES = {
27-
STATUS_DEPROVISIONING: 'warning',
28-
STATUS_ACTIVE: 'success',
29-
STATUS_PLANNED: 'info',
30-
STATUS_PROVISIONING: 'primary',
31-
STATUS_OFFLINE: 'danger',
32-
STATUS_DECOMMISSIONED: 'secondary',
33-
}
18+
CHOICES = [
19+
(STATUS_PLANNED, 'Planned', 'info'),
20+
(STATUS_PROVISIONING, 'Provisioning', 'primary'),
21+
(STATUS_ACTIVE, 'Active', 'success'),
22+
(STATUS_OFFLINE, 'Offline', 'danger'),
23+
(STATUS_DEPROVISIONING, 'Deprovisioning', 'warning'),
24+
(STATUS_DECOMMISSIONED, 'Decommissioned', 'secondary'),
25+
]
3426

3527

3628
#

netbox/circuits/models/circuits.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def get_absolute_url(self):
135135
return reverse('circuits:circuit', args=[self.pk])
136136

137137
def get_status_class(self):
138-
return CircuitStatusChoices.CSS_CLASSES.get(self.status)
138+
return CircuitStatusChoices.colors.get(self.status, 'secondary')
139139

140140

141141
@extras_features('webhooks')

netbox/dcim/choices.py

Lines changed: 38 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,21 @@
66
#
77

88
class SiteStatusChoices(ChoiceSet):
9+
key = 'dcim.Site.status'
910

1011
STATUS_PLANNED = 'planned'
1112
STATUS_STAGING = 'staging'
1213
STATUS_ACTIVE = 'active'
1314
STATUS_DECOMMISSIONING = 'decommissioning'
1415
STATUS_RETIRED = 'retired'
1516

16-
CHOICES = (
17-
(STATUS_PLANNED, 'Planned'),
18-
(STATUS_STAGING, 'Staging'),
19-
(STATUS_ACTIVE, 'Active'),
20-
(STATUS_DECOMMISSIONING, 'Decommissioning'),
21-
(STATUS_RETIRED, 'Retired'),
22-
)
23-
24-
CSS_CLASSES = {
25-
STATUS_PLANNED: 'info',
26-
STATUS_STAGING: 'primary',
27-
STATUS_ACTIVE: 'success',
28-
STATUS_DECOMMISSIONING: 'warning',
29-
STATUS_RETIRED: 'danger',
30-
}
17+
CHOICES = [
18+
(STATUS_PLANNED, 'Planned', 'info'),
19+
(STATUS_STAGING, 'Staging', 'primary'),
20+
(STATUS_ACTIVE, 'Active', 'primary'),
21+
(STATUS_DECOMMISSIONING, 'Decommissioning', 'warning'),
22+
(STATUS_RETIRED, 'Retired', 'danger'),
23+
]
3124

3225

3326
#
@@ -67,28 +60,21 @@ class RackWidthChoices(ChoiceSet):
6760

6861

6962
class RackStatusChoices(ChoiceSet):
63+
key = 'dcim.Rack.status'
7064

7165
STATUS_RESERVED = 'reserved'
7266
STATUS_AVAILABLE = 'available'
7367
STATUS_PLANNED = 'planned'
7468
STATUS_ACTIVE = 'active'
7569
STATUS_DEPRECATED = 'deprecated'
7670

77-
CHOICES = (
78-
(STATUS_RESERVED, 'Reserved'),
79-
(STATUS_AVAILABLE, 'Available'),
80-
(STATUS_PLANNED, 'Planned'),
81-
(STATUS_ACTIVE, 'Active'),
82-
(STATUS_DEPRECATED, 'Deprecated'),
83-
)
84-
85-
CSS_CLASSES = {
86-
STATUS_RESERVED: 'warning',
87-
STATUS_AVAILABLE: 'success',
88-
STATUS_PLANNED: 'info',
89-
STATUS_ACTIVE: 'primary',
90-
STATUS_DEPRECATED: 'danger',
91-
}
71+
CHOICES = [
72+
(STATUS_RESERVED, 'Reserved', 'warning'),
73+
(STATUS_AVAILABLE, 'Available', 'success'),
74+
(STATUS_PLANNED, 'Planned', 'info'),
75+
(STATUS_ACTIVE, 'Active', 'primary'),
76+
(STATUS_DEPRECATED, 'Deprecated', 'danger'),
77+
]
9278

9379

9480
class RackDimensionUnitChoices(ChoiceSet):
@@ -144,6 +130,7 @@ class DeviceFaceChoices(ChoiceSet):
144130

145131

146132
class DeviceStatusChoices(ChoiceSet):
133+
key = 'dcim.Device.status'
147134

148135
STATUS_OFFLINE = 'offline'
149136
STATUS_ACTIVE = 'active'
@@ -153,25 +140,15 @@ class DeviceStatusChoices(ChoiceSet):
153140
STATUS_INVENTORY = 'inventory'
154141
STATUS_DECOMMISSIONING = 'decommissioning'
155142

156-
CHOICES = (
157-
(STATUS_OFFLINE, 'Offline'),
158-
(STATUS_ACTIVE, 'Active'),
159-
(STATUS_PLANNED, 'Planned'),
160-
(STATUS_STAGED, 'Staged'),
161-
(STATUS_FAILED, 'Failed'),
162-
(STATUS_INVENTORY, 'Inventory'),
163-
(STATUS_DECOMMISSIONING, 'Decommissioning'),
164-
)
165-
166-
CSS_CLASSES = {
167-
STATUS_OFFLINE: 'warning',
168-
STATUS_ACTIVE: 'success',
169-
STATUS_PLANNED: 'info',
170-
STATUS_STAGED: 'primary',
171-
STATUS_FAILED: 'danger',
172-
STATUS_INVENTORY: 'secondary',
173-
STATUS_DECOMMISSIONING: 'warning',
174-
}
143+
CHOICES = [
144+
(STATUS_OFFLINE, 'Offline', 'warning'),
145+
(STATUS_ACTIVE, 'Active', 'success'),
146+
(STATUS_PLANNED, 'Planned', 'info'),
147+
(STATUS_STAGED, 'Staged', 'primary'),
148+
(STATUS_FAILED, 'Failed', 'danger'),
149+
(STATUS_INVENTORY, 'Inventory', 'secondary'),
150+
(STATUS_DECOMMISSIONING, 'Decommissioning', 'warning'),
151+
]
175152

176153

177154
class DeviceAirflowChoices(ChoiceSet):
@@ -1144,17 +1121,11 @@ class LinkStatusChoices(ChoiceSet):
11441121
STATUS_DECOMMISSIONING = 'decommissioning'
11451122

11461123
CHOICES = (
1147-
(STATUS_CONNECTED, 'Connected'),
1148-
(STATUS_PLANNED, 'Planned'),
1149-
(STATUS_DECOMMISSIONING, 'Decommissioning'),
1124+
(STATUS_CONNECTED, 'Connected', 'success'),
1125+
(STATUS_PLANNED, 'Planned', 'info'),
1126+
(STATUS_DECOMMISSIONING, 'Decommissioning', 'warning'),
11501127
)
11511128

1152-
CSS_CLASSES = {
1153-
STATUS_CONNECTED: 'success',
1154-
STATUS_PLANNED: 'info',
1155-
STATUS_DECOMMISSIONING: 'warning',
1156-
}
1157-
11581129

11591130
class CableLengthUnitChoices(ChoiceSet):
11601131

@@ -1183,25 +1154,19 @@ class CableLengthUnitChoices(ChoiceSet):
11831154
#
11841155

11851156
class PowerFeedStatusChoices(ChoiceSet):
1157+
key = 'dcim.PowerFeed.status'
11861158

11871159
STATUS_OFFLINE = 'offline'
11881160
STATUS_ACTIVE = 'active'
11891161
STATUS_PLANNED = 'planned'
11901162
STATUS_FAILED = 'failed'
11911163

1192-
CHOICES = (
1193-
(STATUS_OFFLINE, 'Offline'),
1194-
(STATUS_ACTIVE, 'Active'),
1195-
(STATUS_PLANNED, 'Planned'),
1196-
(STATUS_FAILED, 'Failed'),
1197-
)
1198-
1199-
CSS_CLASSES = {
1200-
STATUS_OFFLINE: 'warning',
1201-
STATUS_ACTIVE: 'success',
1202-
STATUS_PLANNED: 'info',
1203-
STATUS_FAILED: 'danger',
1204-
}
1164+
CHOICES = [
1165+
(STATUS_OFFLINE, 'Offline', 'warning'),
1166+
(STATUS_ACTIVE, 'Active', 'success'),
1167+
(STATUS_PLANNED, 'Planned', 'info'),
1168+
(STATUS_FAILED, 'Failed', 'danger'),
1169+
]
12051170

12061171

12071172
class PowerFeedTypeChoices(ChoiceSet):
@@ -1210,15 +1175,10 @@ class PowerFeedTypeChoices(ChoiceSet):
12101175
TYPE_REDUNDANT = 'redundant'
12111176

12121177
CHOICES = (
1213-
(TYPE_PRIMARY, 'Primary'),
1214-
(TYPE_REDUNDANT, 'Redundant'),
1178+
(TYPE_PRIMARY, 'Primary', 'success'),
1179+
(TYPE_REDUNDANT, 'Redundant', 'info'),
12151180
)
12161181

1217-
CSS_CLASSES = {
1218-
TYPE_PRIMARY: 'success',
1219-
TYPE_REDUNDANT: 'info',
1220-
}
1221-
12221182

12231183
class PowerFeedSupplyChoices(ChoiceSet):
12241184

netbox/dcim/models/cables.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ def save(self, *args, **kwargs):
289289
self._pk = self.pk
290290

291291
def get_status_class(self):
292-
return LinkStatusChoices.CSS_CLASSES.get(self.status)
292+
return LinkStatusChoices.colors.get(self.status)
293293

294294
def get_compatible_types(self):
295295
"""

netbox/dcim/models/devices.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,7 @@ def get_children(self):
862862
return Device.objects.filter(parent_bay__device=self.pk)
863863

864864
def get_status_class(self):
865-
return DeviceStatusChoices.CSS_CLASSES.get(self.status)
865+
return DeviceStatusChoices.colors.get(self.status, 'secondary')
866866

867867

868868
#

netbox/dcim/models/power.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ def parent_object(self):
174174
return self.power_panel
175175

176176
def get_type_class(self):
177-
return PowerFeedTypeChoices.CSS_CLASSES.get(self.type)
177+
return PowerFeedTypeChoices.colors.get(self.type)
178178

179179
def get_status_class(self):
180-
return PowerFeedStatusChoices.CSS_CLASSES.get(self.status)
180+
return PowerFeedStatusChoices.colors.get(self.status, 'secondary')

netbox/dcim/models/racks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def units(self):
251251
return reversed(range(1, self.u_height + 1))
252252

253253
def get_status_class(self):
254-
return RackStatusChoices.CSS_CLASSES.get(self.status)
254+
return RackStatusChoices.colors.get(self.status, 'secondary')
255255

256256
def get_rack_units(self, user=None, face=DeviceFaceChoices.FACE_FRONT, exclude=None, expand_devices=True):
257257
"""

netbox/dcim/models/sites.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ def get_absolute_url(self):
315315
return reverse('dcim:site', args=[self.pk])
316316

317317
def get_status_class(self):
318-
return SiteStatusChoices.CSS_CLASSES.get(self.status)
318+
return SiteStatusChoices.colors.get(self.status, 'secondary')
319319

320320

321321
#

netbox/extras/choices.py

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,11 @@ class ObjectChangeActionChoices(ChoiceSet):
9191
ACTION_DELETE = 'delete'
9292

9393
CHOICES = (
94-
(ACTION_CREATE, 'Created'),
95-
(ACTION_UPDATE, 'Updated'),
96-
(ACTION_DELETE, 'Deleted'),
94+
(ACTION_CREATE, 'Created', 'success'),
95+
(ACTION_UPDATE, 'Updated', 'primary'),
96+
(ACTION_DELETE, 'Deleted', 'danger'),
9797
)
9898

99-
CSS_CLASSES = {
100-
ACTION_CREATE: 'success',
101-
ACTION_UPDATE: 'primary',
102-
ACTION_DELETE: 'danger',
103-
}
104-
10599

106100
#
107101
# Jounral entries
@@ -115,19 +109,12 @@ class JournalEntryKindChoices(ChoiceSet):
115109
KIND_DANGER = 'danger'
116110

117111
CHOICES = (
118-
(KIND_INFO, 'Info'),
119-
(KIND_SUCCESS, 'Success'),
120-
(KIND_WARNING, 'Warning'),
121-
(KIND_DANGER, 'Danger'),
112+
(KIND_INFO, 'Info', 'info'),
113+
(KIND_SUCCESS, 'Success', 'success'),
114+
(KIND_WARNING, 'Warning', 'warning'),
115+
(KIND_DANGER, 'Danger', 'danger'),
122116
)
123117

124-
CSS_CLASSES = {
125-
KIND_INFO: 'info',
126-
KIND_SUCCESS: 'success',
127-
KIND_WARNING: 'warning',
128-
KIND_DANGER: 'danger',
129-
}
130-
131118

132119
#
133120
# Log Levels for Reports and Scripts
@@ -142,21 +129,13 @@ class LogLevelChoices(ChoiceSet):
142129
LOG_FAILURE = 'failure'
143130

144131
CHOICES = (
145-
(LOG_DEFAULT, 'Default'),
146-
(LOG_SUCCESS, 'Success'),
147-
(LOG_INFO, 'Info'),
148-
(LOG_WARNING, 'Warning'),
149-
(LOG_FAILURE, 'Failure'),
132+
(LOG_DEFAULT, 'Default', 'secondary'),
133+
(LOG_SUCCESS, 'Success', 'success'),
134+
(LOG_INFO, 'Info', 'info'),
135+
(LOG_WARNING, 'Warning', 'warning'),
136+
(LOG_FAILURE, 'Failure', 'danger'),
150137
)
151138

152-
CSS_CLASSES = {
153-
LOG_DEFAULT: 'secondary',
154-
LOG_SUCCESS: 'success',
155-
LOG_INFO: 'info',
156-
LOG_WARNING: 'warning',
157-
LOG_FAILURE: 'danger',
158-
}
159-
160139

161140
#
162141
# Job results

0 commit comments

Comments
 (0)