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

Closes #12194: Add pre-defined custom field choices (#13219)

* 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
This commit is contained in:
Jeremy Stretch
2023-07-28 11:24:21 -04:00
committed by GitHub
parent 9d3bb585a2
commit cf1b1a83eb
27 changed files with 121940 additions and 100 deletions

View File

@@ -8,11 +8,49 @@ from utilities.forms import widgets
from utilities.utils import get_viewname
__all__ = (
'DynamicChoiceField',
'DynamicModelChoiceField',
'DynamicModelMultipleChoiceField',
'DynamicMultipleChoiceField',
)
#
# Choice fields
#
class DynamicChoiceField(forms.ChoiceField):
def get_bound_field(self, form, field_name):
bound_field = BoundField(form, self, field_name)
data = bound_field.value()
if data is not None:
self.choices = [
choice for choice in self.choices if choice[0] == data
]
return bound_field
class DynamicMultipleChoiceField(forms.MultipleChoiceField):
def get_bound_field(self, form, field_name):
bound_field = BoundField(form, self, field_name)
data = bound_field.value()
if data is not None:
self.choices = [
choice for choice in self.choices if choice[0] in data
]
return bound_field
#
# Model choice fields
#
class DynamicModelChoiceMixin:
"""
Override `get_bound_field()` to avoid pre-populating field choices with a SQL query. The field will be

View File

@@ -2,6 +2,7 @@ from django import forms
__all__ = (
'ArrayWidget',
'ChoicesWidget',
'ClearableFileInput',
'MarkdownWidget',
'NumberWithOptions',
@@ -54,3 +55,15 @@ class ArrayWidget(forms.Textarea):
if value is None or not len(value):
return None
return '\n'.join(value)
class ChoicesWidget(forms.Textarea):
"""
Render each key-value pair of a dictionary on a new line within a textarea for easy editing.
"""
def format_value(self, value):
if not value:
return None
if type(value) is list:
return '\n'.join([f'{k},{v}' for k, v in value])
return value

View File

@@ -462,6 +462,9 @@ class APIViewTestCases:
if type(field) is GQLDynamic:
# Dynamic fields must specify a subselection
fields_string += f'{field_name} {{ id }}\n'
# TODO: Improve field detection logic to avoid nested ArrayFields
elif field_name == 'extra_choices':
continue
elif inspect.isclass(field.type) and issubclass(field.type, GQLUnion):
# Union types dont' have an id or consistent values
continue

View File

@@ -129,13 +129,18 @@ class ModelTestCase(TestCase):
model_dict[key] = str(value)
else:
field = instance._meta.get_field(key)
# Convert ArrayFields to CSV strings
if type(instance._meta.get_field(key)) is ArrayField:
model_dict[key] = ','.join([str(v) for v in value])
if type(field) is ArrayField:
if type(field.base_field) is ArrayField:
# Handle nested arrays (e.g. choice sets)
model_dict[key] = '\n'.join([f'{k},{v}' for k, v in value])
else:
model_dict[key] = ','.join([str(v) for v in value])
# JSON
if type(instance._meta.get_field(key)) is JSONField and value is not None:
if type(field) is JSONField and value is not None:
model_dict[key] = json.dumps(value)
return model_dict