Skip to content

Commit

Permalink
Closes #12194: Add pre-defined custom field choices (#13219)
Browse files Browse the repository at this point in the history
* Initial work on custom field choice sets

* Rename choices to extra_choices (prep for #12194)

* Remove CustomField.choices

* Add & update tests

* Clean up table columns

* Add order_alphanetically boolean for choice sets

* Introduce ArrayColumn for choice lists

* Show dependent custom fields on choice set view

* Update custom fields documentation

* Introduce ArrayWidget for more convenient editing of choices

* Incorporate PR feedback

* Misc cleanup

* Initial work on predefined choices for custom fields

* Misc cleanup

* Add IATA airport codes

* #13241: Add support for custom field choice labels

* Restore ArrayColumn

* Misc cleanup

* Change extra_choices back to a nested ArrayField to preserve choice ordering

* Hack to bypass GraphQL API test utility absent support for nested ArrayFields
  • Loading branch information
jeremystretch authored Jul 28, 2023
1 parent 9d3bb58 commit cf1b1a8
Show file tree
Hide file tree
Showing 27 changed files with 121,940 additions and 100 deletions.
14 changes: 12 additions & 2 deletions docs/models/extras/customfieldchoiceset.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
# Custom Field Choice Sets

Single- and multi-selection [custom fields documentation](../../customization/custom-fields.md) must define a set of valid choices from which the user may choose when defining the field value. These choices are defined as sets that may be reused among multiple custom fields.
Single- and multi-selection [custom fields](../../customization/custom-fields.md) must define a set of valid choices from which the user may choose when defining the field value. These choices are defined as sets that may be reused among multiple custom fields.

A choice set must define a base choice set and/or a set of arbitrary extra choices.

## Fields

### Name

The human-friendly name of the choice set.

### Base Choices

The set of pre-defined choices to include. Available sets are listed below. This is an optional setting.

* IATA airport codes
* ISO 3166 - Two-letter country codes
* UN/LOCODE - Five-character location identifiers

### Extra Choices

The list of valid choices, entered as a comma-separated list.
A set of custom choices that will be appended to the base choice set (if any).

### Order Alphabetically

Expand Down
8 changes: 6 additions & 2 deletions netbox/extras/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,16 @@ def get_data_type(self, obj):

class CustomFieldChoiceSetSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfieldchoiceset-detail')
base_choices = ChoiceField(
choices=CustomFieldChoiceSetBaseChoices,
required=False
)

class Meta:
model = CustomFieldChoiceSet
fields = [
'id', 'url', 'display', 'name', 'description', 'extra_choices', 'order_alphabetically', 'choices_count',
'created', 'last_updated',
'id', 'url', 'display', 'name', 'description', 'base_choices', 'extra_choices', 'order_alphabetically',
'choices_count', 'created', 'last_updated',
]


Expand Down
21 changes: 21 additions & 0 deletions netbox/extras/api/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.contrib.contenttypes.models import ContentType
from django.http import Http404
from django.shortcuts import get_object_or_404
from django_rq.queues import get_connection
from rest_framework import status
from rest_framework.decorators import action
Expand Down Expand Up @@ -63,6 +64,26 @@ class CustomFieldChoiceSetViewSet(NetBoxModelViewSet):
serializer_class = serializers.CustomFieldChoiceSetSerializer
filterset_class = filtersets.CustomFieldChoiceSetFilterSet

@action(detail=True)
def choices(self, request, pk):
"""
Provides an endpoint to iterate through each choice in a set.
"""
choiceset = get_object_or_404(self.queryset, pk=pk)
choices = choiceset.choices

# Enable filtering
if q := request.GET.get('q'):
q = q.lower()
choices = [c for c in choices if q in c[0].lower() or q in c[1].lower()]

# Paginate data
if page := self.paginate_queryset(choices):
data = [
{'value': c[0], 'label': c[1]} for c in page
]
return self.get_paginated_response(data)


#
# Custom links
Expand Down
13 changes: 13 additions & 0 deletions netbox/extras/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ class CustomFieldVisibilityChoices(ChoiceSet):
)


class CustomFieldChoiceSetBaseChoices(ChoiceSet):

IATA = 'IATA'
ISO_3166 = 'ISO_3166'
UN_LOCODE = 'UN_LOCODE'

CHOICES = (
(IATA, 'IATA (Airport codes)'),
(ISO_3166, 'ISO 3166 (Country codes)'),
(UN_LOCODE, 'UN/LOCODE (Location codes)'),
)


#
# CustomLinks
#
Expand Down
9 changes: 9 additions & 0 deletions netbox/extras/data/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .iata import IATA
from .iso_3166 import ISO_3166
from .un_locode import UN_LOCODE

CHOICE_SETS = {
'IATA': IATA,
'ISO_3166': ISO_3166,
'UN_LOCODE': UN_LOCODE,
}
Loading

0 comments on commit cf1b1a8

Please sign in to comment.