Bläddra i källkod

Closes #8779: Enable the use of ChoiceSet by plugins

jeremystretch 4 år sedan
förälder
incheckning
5f8af6ad66

+ 67 - 0
docs/plugins/development/models.md

@@ -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
+    )
+```

+ 1 - 1
netbox/circuits/choices.py

@@ -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'

+ 4 - 4
netbox/dcim/choices.py

@@ -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'

+ 4 - 4
netbox/ipam/choices.py

@@ -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'

+ 10 - 8
netbox/utilities/choices.py

@@ -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'] = []

+ 1 - 1
netbox/virtualization/choices.py

@@ -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'