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

Closes #8779: Enable the use of ChoiceSet by plugins

This commit is contained in:
jeremystretch
2022-03-02 11:43:28 -05:00
parent 638d89e73b
commit 5f8af6ad66
6 changed files with 87 additions and 18 deletions

View File

@ -109,3 +109,70 @@ The example above will enable export templates and tags, but no other NetBox fea
::: netbox.models.features.TagsMixin
::: netbox.models.features.WebhooksMixin
## Choice Sets
For model fields which support the selection of one or more values from a predefined list of choices, NetBox provides the `ChoiceSet` utility class. This can be used in place of a regular choices tuple to provide enhanced functionality, namely dynamic configuration and colorization.
To define choices for a model field, subclass `ChoiceSet` and define a tuple named `CHOICES`, of which each member is a two- or three-element tuple. These elements are:
* The database value
* The corresponding human-friendly label
* The assigned color (optional)
!!! note
Authors may find it useful to declare each of the database values as constants on the class, and reference them within `CHOICES` members. This convention allows the values to be referenced from outside the class, however it is not strictly required.
### Dynamic Configuration
To enable dynamic configuration for a ChoiceSet subclass, define its `key` as a string specifying the model and field name to which it applies. For example:
```python
from utilities.choices import ChoiceSet
class StatusChoices(ChoiceSet):
key = 'MyModel.status'
```
To extend or replace the default values for this choice set, a NetBox administrator can then reference it under the [`FIELD_CHOICES`](../../configuration/optional-settings.md#field_choices) configuration parameter. For example, the `status` field on `MyModel` in `my_plugin` would be referenced as:
```python
FIELD_CHOICES = {
'my_plugin.MyModel.status': (
# Custom choices
)
}
```
### Example
```python
# choices.py
from utilities.choices import ChoiceSet
class StatusChoices(ChoiceSet):
key = 'MyModel.status'
STATUS_FOO = 'foo'
STATUS_BAR = 'bar'
STATUS_BAZ = 'baz'
CHOICES = (
(STATUS_FOO, 'Foo', 'red'),
(STATUS_BAR, 'Bar', 'green'),
(STATUS_BAZ, 'Baz', 'blue'),
)
```
```python
# models.py
from django.db import models
from .choices import StatusChoices
class MyModel(models.Model):
status = models.CharField(
max_length=50,
choices=StatusChoices,
default=StatusChoices.STATUS_FOO
)
```

View File

@ -6,7 +6,7 @@ from utilities.choices import ChoiceSet
#
class CircuitStatusChoices(ChoiceSet):
key = 'circuits.Circuit.status'
key = 'Circuit.status'
STATUS_DEPROVISIONING = 'deprovisioning'
STATUS_ACTIVE = 'active'

View File

@ -6,7 +6,7 @@ from utilities.choices import ChoiceSet
#
class SiteStatusChoices(ChoiceSet):
key = 'dcim.Site.status'
key = 'Site.status'
STATUS_PLANNED = 'planned'
STATUS_STAGING = 'staging'
@ -60,7 +60,7 @@ class RackWidthChoices(ChoiceSet):
class RackStatusChoices(ChoiceSet):
key = 'dcim.Rack.status'
key = 'Rack.status'
STATUS_RESERVED = 'reserved'
STATUS_AVAILABLE = 'available'
@ -130,7 +130,7 @@ class DeviceFaceChoices(ChoiceSet):
class DeviceStatusChoices(ChoiceSet):
key = 'dcim.Device.status'
key = 'Device.status'
STATUS_OFFLINE = 'offline'
STATUS_ACTIVE = 'active'
@ -1175,7 +1175,7 @@ class CableLengthUnitChoices(ChoiceSet):
#
class PowerFeedStatusChoices(ChoiceSet):
key = 'dcim.PowerFeed.status'
key = 'PowerFeed.status'
STATUS_OFFLINE = 'offline'
STATUS_ACTIVE = 'active'

View File

@ -17,7 +17,7 @@ class IPAddressFamilyChoices(ChoiceSet):
#
class PrefixStatusChoices(ChoiceSet):
key = 'ipam.Prefix.status'
key = 'Prefix.status'
STATUS_CONTAINER = 'container'
STATUS_ACTIVE = 'active'
@ -37,7 +37,7 @@ class PrefixStatusChoices(ChoiceSet):
#
class IPRangeStatusChoices(ChoiceSet):
key = 'ipam.IPRange.status'
key = 'IPRange.status'
STATUS_ACTIVE = 'active'
STATUS_RESERVED = 'reserved'
@ -55,7 +55,7 @@ class IPRangeStatusChoices(ChoiceSet):
#
class IPAddressStatusChoices(ChoiceSet):
key = 'ipam.IPAddress.status'
key = 'IPAddress.status'
STATUS_ACTIVE = 'active'
STATUS_RESERVED = 'reserved'
@ -134,7 +134,7 @@ class FHRPGroupAuthTypeChoices(ChoiceSet):
#
class VLANStatusChoices(ChoiceSet):
key = 'ipam.VLAN.status'
key = 'VLAN.status'
STATUS_ACTIVE = 'active'
STATUS_RESERVED = 'reserved'

View File

@ -8,7 +8,9 @@ class ChoiceSetMeta(type):
def __new__(mcs, name, bases, attrs):
# Extend static choices with any configured choices
replace_key = attrs.get('key')
if key := attrs.get('key'):
app = attrs['__module__'].split('.', 1)[0]
replace_key = f'{app}.{key}'
extend_key = f'{replace_key}+' if replace_key else None
if replace_key and replace_key in settings.FIELD_CHOICES:
# Replace the stock choices

View File

@ -6,7 +6,7 @@ from utilities.choices import ChoiceSet
#
class VirtualMachineStatusChoices(ChoiceSet):
key = 'virtualization.VirtualMachine.status'
key = 'VirtualMachine.status'
STATUS_OFFLINE = 'offline'
STATUS_ACTIVE = 'active'