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:
@ -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
|
||||
)
|
||||
```
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -8,14 +8,16 @@ class ChoiceSetMeta(type):
|
||||
def __new__(mcs, name, bases, attrs):
|
||||
|
||||
# Extend static choices with any configured choices
|
||||
replace_key = attrs.get('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
|
||||
attrs['CHOICES'] = settings.FIELD_CHOICES[replace_key]
|
||||
elif extend_key and extend_key in settings.FIELD_CHOICES:
|
||||
# Extend the stock choices
|
||||
attrs['CHOICES'].extend(settings.FIELD_CHOICES[extend_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
|
||||
attrs['CHOICES'] = settings.FIELD_CHOICES[replace_key]
|
||||
elif extend_key and extend_key in settings.FIELD_CHOICES:
|
||||
# Extend the stock choices
|
||||
attrs['CHOICES'].extend(settings.FIELD_CHOICES[extend_key])
|
||||
|
||||
# Define choice tuples and color maps
|
||||
attrs['_choices'] = []
|
||||
|
@ -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'
|
||||
|
Reference in New Issue
Block a user