Преглед на файлове

Add REST API endpoint for custom fields

Jeremy Stretch преди 5 години
родител
ревизия
a05fe69043

+ 9 - 0
netbox/extras/api/nested_serializers.py

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

+ 22 - 1
netbox/extras/api/serializers.py

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

+ 3 - 0
netbox/extras/api/urls.py

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

+ 17 - 9
netbox/extras/api/views.py

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

+ 7 - 0
netbox/extras/filters.py

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

+ 3 - 2
netbox/extras/models/customfields.py

@@ -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."
             })
             })