#8054: Support configurable status choices

This commit is contained in:
jeremystretch 2021-12-16 09:36:15 -05:00
parent 28f577738a
commit 419f86a4a5
7 changed files with 80 additions and 20 deletions

View File

@ -140,6 +140,41 @@ EXEMPT_VIEW_PERMISSIONS = ['*']
---
## FIELD_CHOICES
Default: Empty dictionary
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.
For example, to specify a custom set of choices for the site status field:
```python
FIELD_CHOICES = {
'dcim.Site.status': (
('foo', 'Foo'),
('bar', 'Bar'),
('baz', 'Baz'),
)
}
```
These will be appended to the stock choices for the field.
The following model field support configurable choices:
* `circuits.Circuit.status`
* `dcim.Device.status`
* `dcim.PowerFeed.status`
* `dcim.Rack.status`
* `dcim.Site.status`
* `ipam.IPAddress.status`
* `ipam.IPRange.status`
* `ipam.Prefix.status`
* `ipam.VLAN.status`
* `virtualization.VirtualMachine.status`
---
## HTTP_PROXIES
Default: None

View File

@ -6,6 +6,7 @@ from utilities.choices import ChoiceSet
#
class CircuitStatusChoices(ChoiceSet):
key = 'circuits.Circuit.status'
STATUS_DEPROVISIONING = 'deprovisioning'
STATUS_ACTIVE = 'active'
@ -14,14 +15,14 @@ class CircuitStatusChoices(ChoiceSet):
STATUS_OFFLINE = 'offline'
STATUS_DECOMMISSIONED = 'decommissioned'
CHOICES = (
CHOICES = [
(STATUS_PLANNED, 'Planned'),
(STATUS_PROVISIONING, 'Provisioning'),
(STATUS_ACTIVE, 'Active'),
(STATUS_OFFLINE, 'Offline'),
(STATUS_DEPROVISIONING, 'Deprovisioning'),
(STATUS_DECOMMISSIONED, 'Decommissioned'),
)
]
CSS_CLASSES = {
STATUS_DEPROVISIONING: 'warning',

View File

@ -6,6 +6,7 @@ from utilities.choices import ChoiceSet
#
class SiteStatusChoices(ChoiceSet):
key = 'dcim.Site.status'
STATUS_PLANNED = 'planned'
STATUS_STAGING = 'staging'
@ -13,13 +14,13 @@ class SiteStatusChoices(ChoiceSet):
STATUS_DECOMMISSIONING = 'decommissioning'
STATUS_RETIRED = 'retired'
CHOICES = (
CHOICES = [
(STATUS_PLANNED, 'Planned'),
(STATUS_STAGING, 'Staging'),
(STATUS_ACTIVE, 'Active'),
(STATUS_DECOMMISSIONING, 'Decommissioning'),
(STATUS_RETIRED, 'Retired'),
)
]
CSS_CLASSES = {
STATUS_PLANNED: 'info',
@ -67,6 +68,7 @@ class RackWidthChoices(ChoiceSet):
class RackStatusChoices(ChoiceSet):
key = 'dcim.Rack.status'
STATUS_RESERVED = 'reserved'
STATUS_AVAILABLE = 'available'
@ -74,13 +76,13 @@ class RackStatusChoices(ChoiceSet):
STATUS_ACTIVE = 'active'
STATUS_DEPRECATED = 'deprecated'
CHOICES = (
CHOICES = [
(STATUS_RESERVED, 'Reserved'),
(STATUS_AVAILABLE, 'Available'),
(STATUS_PLANNED, 'Planned'),
(STATUS_ACTIVE, 'Active'),
(STATUS_DEPRECATED, 'Deprecated'),
)
]
CSS_CLASSES = {
STATUS_RESERVED: 'warning',
@ -144,6 +146,7 @@ class DeviceFaceChoices(ChoiceSet):
class DeviceStatusChoices(ChoiceSet):
key = 'dcim.Device.status'
STATUS_OFFLINE = 'offline'
STATUS_ACTIVE = 'active'
@ -153,7 +156,7 @@ class DeviceStatusChoices(ChoiceSet):
STATUS_INVENTORY = 'inventory'
STATUS_DECOMMISSIONING = 'decommissioning'
CHOICES = (
CHOICES = [
(STATUS_OFFLINE, 'Offline'),
(STATUS_ACTIVE, 'Active'),
(STATUS_PLANNED, 'Planned'),
@ -161,7 +164,7 @@ class DeviceStatusChoices(ChoiceSet):
(STATUS_FAILED, 'Failed'),
(STATUS_INVENTORY, 'Inventory'),
(STATUS_DECOMMISSIONING, 'Decommissioning'),
)
]
CSS_CLASSES = {
STATUS_OFFLINE: 'warning',
@ -1183,18 +1186,19 @@ class CableLengthUnitChoices(ChoiceSet):
#
class PowerFeedStatusChoices(ChoiceSet):
key = 'dcim.PowerFeed.status'
STATUS_OFFLINE = 'offline'
STATUS_ACTIVE = 'active'
STATUS_PLANNED = 'planned'
STATUS_FAILED = 'failed'
CHOICES = (
CHOICES = [
(STATUS_OFFLINE, 'Offline'),
(STATUS_ACTIVE, 'Active'),
(STATUS_PLANNED, 'Planned'),
(STATUS_FAILED, 'Failed'),
)
]
CSS_CLASSES = {
STATUS_OFFLINE: 'warning',

View File

@ -17,18 +17,19 @@ class IPAddressFamilyChoices(ChoiceSet):
#
class PrefixStatusChoices(ChoiceSet):
key = 'ipam.Prefix.status'
STATUS_CONTAINER = 'container'
STATUS_ACTIVE = 'active'
STATUS_RESERVED = 'reserved'
STATUS_DEPRECATED = 'deprecated'
CHOICES = (
CHOICES = [
(STATUS_CONTAINER, 'Container'),
(STATUS_ACTIVE, 'Active'),
(STATUS_RESERVED, 'Reserved'),
(STATUS_DEPRECATED, 'Deprecated'),
)
]
CSS_CLASSES = {
STATUS_CONTAINER: 'secondary',
@ -43,16 +44,17 @@ class PrefixStatusChoices(ChoiceSet):
#
class IPRangeStatusChoices(ChoiceSet):
key = 'ipam.IPRange.status'
STATUS_ACTIVE = 'active'
STATUS_RESERVED = 'reserved'
STATUS_DEPRECATED = 'deprecated'
CHOICES = (
CHOICES = [
(STATUS_ACTIVE, 'Active'),
(STATUS_RESERVED, 'Reserved'),
(STATUS_DEPRECATED, 'Deprecated'),
)
]
CSS_CLASSES = {
STATUS_ACTIVE: 'primary',
@ -66,6 +68,7 @@ class IPRangeStatusChoices(ChoiceSet):
#
class IPAddressStatusChoices(ChoiceSet):
key = 'ipam.IPAddress.status'
STATUS_ACTIVE = 'active'
STATUS_RESERVED = 'reserved'
@ -73,13 +76,13 @@ class IPAddressStatusChoices(ChoiceSet):
STATUS_DHCP = 'dhcp'
STATUS_SLAAC = 'slaac'
CHOICES = (
CHOICES = [
(STATUS_ACTIVE, 'Active'),
(STATUS_RESERVED, 'Reserved'),
(STATUS_DEPRECATED, 'Deprecated'),
(STATUS_DHCP, 'DHCP'),
(STATUS_SLAAC, 'SLAAC'),
)
]
CSS_CLASSES = {
STATUS_ACTIVE: 'primary',
@ -161,16 +164,17 @@ class FHRPGroupAuthTypeChoices(ChoiceSet):
#
class VLANStatusChoices(ChoiceSet):
key = 'ipam.VLAN.status'
STATUS_ACTIVE = 'active'
STATUS_RESERVED = 'reserved'
STATUS_DEPRECATED = 'deprecated'
CHOICES = (
CHOICES = [
(STATUS_ACTIVE, 'Active'),
(STATUS_RESERVED, 'Reserved'),
(STATUS_DEPRECATED, 'Deprecated'),
)
]
CSS_CLASSES = {
STATUS_ACTIVE: 'primary',

View File

@ -86,6 +86,7 @@ DEVELOPER = getattr(configuration, 'DEVELOPER', False)
DOCS_ROOT = getattr(configuration, 'DOCS_ROOT', os.path.join(os.path.dirname(BASE_DIR), 'docs'))
EMAIL = getattr(configuration, 'EMAIL', {})
EXEMPT_VIEW_PERMISSIONS = getattr(configuration, 'EXEMPT_VIEW_PERMISSIONS', [])
FIELD_CHOICES = getattr(configuration, 'FIELD_CHOICES', {})
HTTP_PROXIES = getattr(configuration, 'HTTP_PROXIES', None)
INTERNAL_IPS = getattr(configuration, 'INTERNAL_IPS', ('127.0.0.1', '::1'))
LOGGING = getattr(configuration, 'LOGGING', {})

View File

@ -1,7 +1,21 @@
from django.conf import settings
class ChoiceSetMeta(type):
"""
Metaclass for ChoiceSet
"""
def __new__(mcs, name, bases, attrs):
# Extend static choices with any configured choices
if 'key' in attrs:
try:
attrs['CHOICES'].extend(settings.FIELD_CHOICES[attrs['key']])
except KeyError:
pass
return super().__new__(mcs, name, bases, attrs)
def __call__(cls, *args, **kwargs):
# Django will check if a 'choices' value is callable, and if so assume that it returns an iterable
return getattr(cls, 'CHOICES', ())

View File

@ -6,6 +6,7 @@ from utilities.choices import ChoiceSet
#
class VirtualMachineStatusChoices(ChoiceSet):
key = 'virtualization.VirtualMachine.status'
STATUS_OFFLINE = 'offline'
STATUS_ACTIVE = 'active'
@ -14,14 +15,14 @@ class VirtualMachineStatusChoices(ChoiceSet):
STATUS_FAILED = 'failed'
STATUS_DECOMMISSIONING = 'decommissioning'
CHOICES = (
CHOICES = [
(STATUS_OFFLINE, 'Offline'),
(STATUS_ACTIVE, 'Active'),
(STATUS_PLANNED, 'Planned'),
(STATUS_STAGED, 'Staged'),
(STATUS_FAILED, 'Failed'),
(STATUS_DECOMMISSIONING, 'Decommissioning'),
)
]
CSS_CLASSES = {
STATUS_OFFLINE: 'warning',