mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Add REST API endpoint for custom fields
This commit is contained in:
@ -6,6 +6,7 @@ from users.api.nested_serializers import NestedUserSerializer
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'NestedConfigContextSerializer',
|
'NestedConfigContextSerializer',
|
||||||
|
'NestedCustomFieldSerializer',
|
||||||
'NestedExportTemplateSerializer',
|
'NestedExportTemplateSerializer',
|
||||||
'NestedImageAttachmentSerializer',
|
'NestedImageAttachmentSerializer',
|
||||||
'NestedJobResultSerializer',
|
'NestedJobResultSerializer',
|
||||||
@ -13,6 +14,14 @@ __all__ = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class NestedCustomFieldSerializer(WritableNestedSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.CustomField
|
||||||
|
fields = ['id', 'url', 'name']
|
||||||
|
|
||||||
|
|
||||||
class NestedConfigContextSerializer(WritableNestedSerializer):
|
class NestedConfigContextSerializer(WritableNestedSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='extras-api:configcontext-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='extras-api:configcontext-detail')
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ from dcim.api.nested_serializers import (
|
|||||||
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site
|
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.models import (
|
from extras.models import (
|
||||||
ConfigContext, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag,
|
ConfigContext, CustomField, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag,
|
||||||
)
|
)
|
||||||
from extras.utils import FeatureQuery
|
from extras.utils import FeatureQuery
|
||||||
from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField, ValidatedModelSerializer
|
from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField, ValidatedModelSerializer
|
||||||
@ -24,6 +24,27 @@ from virtualization.models import Cluster, ClusterGroup
|
|||||||
from .nested_serializers import *
|
from .nested_serializers import *
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Custom fields
|
||||||
|
#
|
||||||
|
|
||||||
|
class CustomFieldSerializer(ValidatedModelSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail')
|
||||||
|
content_types = ContentTypeField(
|
||||||
|
queryset=ContentType.objects.filter(FeatureQuery('export_templates').get_query()),
|
||||||
|
many=True
|
||||||
|
)
|
||||||
|
type = ChoiceField(choices=CustomFieldTypeChoices)
|
||||||
|
filter_logic = ChoiceField(choices=CustomFieldFilterLogicChoices, required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CustomField
|
||||||
|
fields = [
|
||||||
|
'id', 'url', 'content_types', 'type', 'name', 'label', 'description', 'required', 'filter_logic',
|
||||||
|
'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choices',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Export templates
|
# Export templates
|
||||||
#
|
#
|
||||||
|
@ -5,6 +5,9 @@ from . import views
|
|||||||
router = OrderedDefaultRouter()
|
router = OrderedDefaultRouter()
|
||||||
router.APIRootView = views.ExtrasRootView
|
router.APIRootView = views.ExtrasRootView
|
||||||
|
|
||||||
|
# Custom fields
|
||||||
|
router.register('custom-fields', views.CustomFieldViewSet)
|
||||||
|
|
||||||
# Export templates
|
# Export templates
|
||||||
router.register('export-templates', views.ExportTemplateViewSet)
|
router.register('export-templates', views.ExportTemplateViewSet)
|
||||||
|
|
||||||
|
@ -12,17 +12,26 @@ from rq import Worker
|
|||||||
|
|
||||||
from extras import filters
|
from extras import filters
|
||||||
from extras.choices import JobResultStatusChoices
|
from extras.choices import JobResultStatusChoices
|
||||||
from extras.models import ConfigContext, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag
|
from extras.models import ConfigContext, CustomField, ExportTemplate, ImageAttachment, ObjectChange, JobResult, Tag
|
||||||
from extras.reports import get_report, get_reports, run_report
|
from extras.reports import get_report, get_reports, run_report
|
||||||
from extras.scripts import get_script, get_scripts, run_script
|
from extras.scripts import get_script, get_scripts, run_script
|
||||||
from netbox.api.views import ModelViewSet
|
from netbox.api.views import ModelViewSet
|
||||||
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
||||||
from netbox.api.metadata import ContentTypeMetadata
|
from netbox.api.metadata import ContentTypeMetadata
|
||||||
from utilities.exceptions import RQWorkerNotRunningException
|
from utilities.exceptions import RQWorkerNotRunningException
|
||||||
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.utils import copy_safe_request
|
from utilities.utils import copy_safe_request
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class ExtrasRootView(APIRootView):
|
||||||
|
"""
|
||||||
|
Extras API root view
|
||||||
|
"""
|
||||||
|
def get_view_name(self):
|
||||||
|
return 'Extras'
|
||||||
|
|
||||||
|
|
||||||
class ConfigContextQuerySetMixin:
|
class ConfigContextQuerySetMixin:
|
||||||
"""
|
"""
|
||||||
Used by views that work with config context models (device and virtual machine).
|
Used by views that work with config context models (device and virtual machine).
|
||||||
@ -46,18 +55,17 @@ class ConfigContextQuerySetMixin:
|
|||||||
return self.queryset.annotate_config_context_data()
|
return self.queryset.annotate_config_context_data()
|
||||||
|
|
||||||
|
|
||||||
class ExtrasRootView(APIRootView):
|
|
||||||
"""
|
|
||||||
Extras API root view
|
|
||||||
"""
|
|
||||||
def get_view_name(self):
|
|
||||||
return 'Extras'
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Custom fields
|
# Custom fields
|
||||||
#
|
#
|
||||||
|
|
||||||
|
class CustomFieldViewSet(ModelViewSet):
|
||||||
|
metadata_class = ContentTypeMetadata
|
||||||
|
queryset = CustomField.objects.all()
|
||||||
|
serializer_class = serializers.CustomFieldSerializer
|
||||||
|
filterset_class = filters.CustomFieldFilterSet
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldModelViewSet(ModelViewSet):
|
class CustomFieldModelViewSet(ModelViewSet):
|
||||||
"""
|
"""
|
||||||
Include the applicable set of CustomFields in the ModelViewSet context.
|
Include the applicable set of CustomFields in the ModelViewSet context.
|
||||||
|
@ -74,6 +74,13 @@ class CustomFieldModelFilterSet(django_filters.FilterSet):
|
|||||||
self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf)
|
self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomFieldFilterSet(django_filters.FilterSet):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CustomField
|
||||||
|
fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'default', 'weight']
|
||||||
|
|
||||||
|
|
||||||
class ExportTemplateFilterSet(BaseFilterSet):
|
class ExportTemplateFilterSet(BaseFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -13,6 +13,7 @@ from django.utils.safestring import mark_safe
|
|||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.utils import FeatureQuery
|
from extras.utils import FeatureQuery
|
||||||
from utilities.forms import CSVChoiceField, DatePicker, LaxURLField, StaticSelect2, add_blank_choice
|
from utilities.forms import CSVChoiceField, DatePicker, LaxURLField, StaticSelect2, add_blank_choice
|
||||||
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.validators import validate_regex
|
from utilities.validators import validate_regex
|
||||||
|
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ class CustomFieldModel(models.Model):
|
|||||||
raise ValidationError(f"Missing required custom field '{cf.name}'.")
|
raise ValidationError(f"Missing required custom field '{cf.name}'.")
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldManager(models.Manager):
|
class CustomFieldManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
||||||
use_in_migrations = True
|
use_in_migrations = True
|
||||||
|
|
||||||
def get_for_model(self, model):
|
def get_for_model(self, model):
|
||||||
@ -193,7 +194,7 @@ class CustomField(models.Model):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# A selection field must have at least two choices defined
|
# A selection field must have at least two choices defined
|
||||||
if self.type == CustomFieldTypeChoices.TYPE_SELECT and len(self.choices) < 2:
|
if self.type == CustomFieldTypeChoices.TYPE_SELECT and self.choices and len(self.choices) < 2:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'choices': "Selection fields must specify at least two choices."
|
'choices': "Selection fields must specify at least two choices."
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user