diff --git a/netbox/circuits/api/urls.py b/netbox/circuits/api/urls.py index 25df44bfd..3fb4eda0a 100644 --- a/netbox/circuits/api/urls.py +++ b/netbox/circuits/api/urls.py @@ -16,6 +16,9 @@ class CircuitsRootView(routers.APIRootView): router = routers.DefaultRouter() router.APIRootView = CircuitsRootView +# Field choices +router.register(r'_choices', views.CircuitsFieldChoicesViewSet, base_name='field-choice') + # Providers router.register(r'providers', views.ProviderViewSet) diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index 685fa8f9e..9defb5060 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -11,10 +11,20 @@ from circuits.models import Provider, CircuitTermination, CircuitType, Circuit from extras.models import Graph, GRAPH_TYPE_PROVIDER from extras.api.serializers import RenderedGraphSerializer from extras.api.views import CustomFieldModelViewSet -from utilities.api import WritableSerializerMixin +from utilities.api import FieldChoicesViewSet, WritableSerializerMixin from . import serializers +# +# Field choices +# + +class CircuitsFieldChoicesViewSet(FieldChoicesViewSet): + fields = ( + (CircuitTermination, ['term_side']), + ) + + # # Providers # diff --git a/netbox/dcim/api/urls.py b/netbox/dcim/api/urls.py index 6f16310e5..a03432c61 100644 --- a/netbox/dcim/api/urls.py +++ b/netbox/dcim/api/urls.py @@ -16,6 +16,9 @@ class DCIMRootView(routers.APIRootView): router = routers.DefaultRouter() router.APIRootView = DCIMRootView +# Field choices +router.register(r'_choices', views.DCIMFieldChoicesViewSet, base_name='field-choice') + # Sites router.register(r'regions', views.RegionViewSet) router.register(r'sites', views.SiteViewSet) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 56d4221da..af41a2a83 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -20,11 +20,27 @@ from dcim import filters from extras.api.serializers import RenderedGraphSerializer from extras.api.views import CustomFieldModelViewSet from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE -from utilities.api import IsAuthenticatedOrLoginNotRequired, ServiceUnavailable, WritableSerializerMixin +from utilities.api import IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ServiceUnavailable, WritableSerializerMixin from .exceptions import MissingFilterException from . import serializers +# +# Field choices +# + +class DCIMFieldChoicesViewSet(FieldChoicesViewSet): + fields = ( + (Device, ['face', 'status']), + (ConsolePort, ['connection_status']), + (Interface, ['form_factor']), + (InterfaceConnection, ['connection_status']), + (InterfaceTemplate, ['form_factor']), + (PowerPort, ['connection_status']), + (Rack, ['type', 'width']), + ) + + # # Regions # diff --git a/netbox/extras/api/urls.py b/netbox/extras/api/urls.py index da76e67bd..cc278644d 100644 --- a/netbox/extras/api/urls.py +++ b/netbox/extras/api/urls.py @@ -16,6 +16,9 @@ class ExtrasRootView(routers.APIRootView): router = routers.DefaultRouter() router.APIRootView = ExtrasRootView +# Field choices +router.register(r'_choices', views.ExtrasFieldChoicesViewSet, base_name='field-choice') + # Graphs router.register(r'graphs', views.GraphViewSet) diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index bd1d33fa3..72569dae1 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -10,12 +10,27 @@ from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404 from extras import filters -from extras.models import ExportTemplate, Graph, ImageAttachment, ReportResult, TopologyMap, UserAction +from extras.models import CustomField, ExportTemplate, Graph, ImageAttachment, ReportResult, TopologyMap, UserAction from extras.reports import get_report, get_reports -from utilities.api import WritableSerializerMixin +from utilities.api import FieldChoicesViewSet, WritableSerializerMixin from . import serializers +# +# Field choices +# + +class ExtrasFieldChoicesViewSet(FieldChoicesViewSet): + fields = ( + (CustomField, ['type']), + (Graph, ['type']), + ) + + +# +# Custom fields +# + class CustomFieldModelViewSet(ModelViewSet): """ Include the applicable set of CustomFields in the ModelViewSet context. @@ -46,6 +61,10 @@ class CustomFieldModelViewSet(ModelViewSet): return super(CustomFieldModelViewSet, self).get_queryset().prefetch_related('custom_field_values__field') +# +# Graphs +# + class GraphViewSet(WritableSerializerMixin, ModelViewSet): queryset = Graph.objects.all() serializer_class = serializers.GraphSerializer @@ -53,12 +72,20 @@ class GraphViewSet(WritableSerializerMixin, ModelViewSet): filter_class = filters.GraphFilter +# +# Export templates +# + class ExportTemplateViewSet(WritableSerializerMixin, ModelViewSet): queryset = ExportTemplate.objects.all() serializer_class = serializers.ExportTemplateSerializer filter_class = filters.ExportTemplateFilter +# +# Topology maps +# + class TopologyMapViewSet(WritableSerializerMixin, ModelViewSet): queryset = TopologyMap.objects.select_related('site') serializer_class = serializers.TopologyMapSerializer @@ -85,12 +112,20 @@ class TopologyMapViewSet(WritableSerializerMixin, ModelViewSet): return response +# +# Image attachments +# + class ImageAttachmentViewSet(WritableSerializerMixin, ModelViewSet): queryset = ImageAttachment.objects.all() serializer_class = serializers.ImageAttachmentSerializer write_serializer_class = serializers.WritableImageAttachmentSerializer +# +# Reports +# + class ReportViewSet(ViewSet): _ignore_model_permissions = True exclude_from_schema = True @@ -162,6 +197,10 @@ class ReportViewSet(ViewSet): return Response(serializer.data) +# +# User activity +# + class RecentActivityViewSet(ReadOnlyModelViewSet): """ List all UserActions to provide a log of recent activity. diff --git a/netbox/ipam/api/urls.py b/netbox/ipam/api/urls.py index e6b1bb13d..ca046cd93 100644 --- a/netbox/ipam/api/urls.py +++ b/netbox/ipam/api/urls.py @@ -16,6 +16,9 @@ class IPAMRootView(routers.APIRootView): router = routers.DefaultRouter() router.APIRootView = IPAMRootView +# Field choices +router.register(r'_choices', views.IPAMFieldChoicesViewSet, base_name='field-choice') + # VRFs router.register(r'vrfs', views.VRFViewSet) diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 1c56efa8f..dabb518ae 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -12,10 +12,24 @@ from django.shortcuts import get_object_or_404 from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from ipam import filters from extras.api.views import CustomFieldModelViewSet -from utilities.api import WritableSerializerMixin +from utilities.api import FieldChoicesViewSet, WritableSerializerMixin from . import serializers +# +# Field choices +# + +class IPAMFieldChoicesViewSet(FieldChoicesViewSet): + fields = ( + (Aggregate, ['family']), + (Prefix, ['family', 'status']), + (IPAddress, ['family', 'status', 'role']), + (VLAN, ['status']), + (Service, ['protocol']), + ) + + # # VRFs # diff --git a/netbox/secrets/api/urls.py b/netbox/secrets/api/urls.py index 3b1e7d3d0..2a24c445a 100644 --- a/netbox/secrets/api/urls.py +++ b/netbox/secrets/api/urls.py @@ -16,6 +16,9 @@ class SecretsRootView(routers.APIRootView): router = routers.DefaultRouter() router.APIRootView = SecretsRootView +# Field choices +router.register(r'_choices', views.SecretsFieldChoicesViewSet, base_name='field-choice') + # Secrets router.register(r'secret-roles', views.SecretRoleViewSet) router.register(r'secrets', views.SecretViewSet) diff --git a/netbox/secrets/api/views.py b/netbox/secrets/api/views.py index 52a77b87c..07897aa19 100644 --- a/netbox/secrets/api/views.py +++ b/netbox/secrets/api/views.py @@ -12,7 +12,7 @@ from django.http import HttpResponseBadRequest from secrets import filters from secrets.exceptions import InvalidKey from secrets.models import Secret, SecretRole, SessionKey, UserKey -from utilities.api import WritableSerializerMixin +from utilities.api import FieldChoicesViewSet, WritableSerializerMixin from . import serializers @@ -22,6 +22,14 @@ ERR_PRIVKEY_MISSING = "Private key was not provided." ERR_PRIVKEY_INVALID = "Invalid private key." +# +# Field choices +# + +class SecretsFieldChoicesViewSet(FieldChoicesViewSet): + fields = () + + # # Secret Roles # diff --git a/netbox/tenancy/api/urls.py b/netbox/tenancy/api/urls.py index a3ce7774a..a36a1ec3d 100644 --- a/netbox/tenancy/api/urls.py +++ b/netbox/tenancy/api/urls.py @@ -16,6 +16,9 @@ class TenancyRootView(routers.APIRootView): router = routers.DefaultRouter() router.APIRootView = TenancyRootView +# Field choices +router.register(r'_choices', views.TenancyFieldChoicesViewSet, base_name='field-choice') + # Tenants router.register(r'tenant-groups', views.TenantGroupViewSet) router.register(r'tenants', views.TenantViewSet) diff --git a/netbox/tenancy/api/views.py b/netbox/tenancy/api/views.py index 3c930bf73..c1f7d990d 100644 --- a/netbox/tenancy/api/views.py +++ b/netbox/tenancy/api/views.py @@ -5,10 +5,18 @@ from rest_framework.viewsets import ModelViewSet from extras.api.views import CustomFieldModelViewSet from tenancy import filters from tenancy.models import Tenant, TenantGroup -from utilities.api import WritableSerializerMixin +from utilities.api import FieldChoicesViewSet, WritableSerializerMixin from . import serializers +# +# Field choices +# + +class TenancyFieldChoicesViewSet(FieldChoicesViewSet): + fields = () + + # # Tenant Groups # diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index 6806af73a..2da24a23e 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -1,12 +1,16 @@ from __future__ import unicode_literals +from collections import OrderedDict from django.conf import settings from django.contrib.contenttypes.models import ContentType +from django.http import Http404 from rest_framework.compat import is_authenticated from rest_framework.exceptions import APIException from rest_framework.permissions import BasePermission +from rest_framework.response import Response from rest_framework.serializers import Field, ModelSerializer, ValidationError +from rest_framework.viewsets import ViewSet WRITE_OPERATIONS = ['create', 'update', 'partial_update', 'delete'] @@ -94,6 +98,55 @@ class ContentTypeFieldSerializer(Field): raise ValidationError("Invalid content type") +# +# Views +# + +class FieldChoicesViewSet(ViewSet): + """ + Expose the built-in numeric values which represent static choices for a model's field. + """ + permission_classes = [IsAuthenticatedOrLoginNotRequired] + fields = [] + + def __init__(self, *args, **kwargs): + super(FieldChoicesViewSet, self).__init__(*args, **kwargs) + + # Compile a dict of all fields in this view + self._fields = OrderedDict() + for cls, field_list in self.fields: + for field_name in field_list: + model_name = cls._meta.verbose_name.lower().replace(' ', '-') + key = ':'.join([model_name, field_name]) + choices = [] + for k, v in cls._meta.get_field(field_name).choices: + if type(v) in [list, tuple]: + for k2, v2 in v: + choices.append({ + 'value': v2, + 'label': k2, + }) + else: + choices.append({ + 'value': v, + 'label': k, + }) + self._fields[key] = choices + + def list(self, request): + return Response(self._fields) + + def retrieve(self, request, pk): + + if pk not in self._fields: + raise Http404 + + return Response(self._fields[pk]) + + def get_view_name(self): + return "Field Choices" + + # # Mixins # diff --git a/netbox/virtualization/api/urls.py b/netbox/virtualization/api/urls.py index 34ecf4431..45db6aa6a 100644 --- a/netbox/virtualization/api/urls.py +++ b/netbox/virtualization/api/urls.py @@ -16,6 +16,9 @@ class VirtualizationRootView(routers.APIRootView): router = routers.DefaultRouter() router.APIRootView = VirtualizationRootView +# Field choices +router.register(r'_choices', views.VirtualizationFieldChoicesViewSet, base_name='field-choice') + # Clusters router.register(r'cluster-types', views.ClusterTypeViewSet) router.register(r'cluster-groups', views.ClusterGroupViewSet) diff --git a/netbox/virtualization/api/views.py b/netbox/virtualization/api/views.py index bae8d5f90..2b7ce4b60 100644 --- a/netbox/virtualization/api/views.py +++ b/netbox/virtualization/api/views.py @@ -4,12 +4,22 @@ from rest_framework.viewsets import ModelViewSet from dcim.models import Interface from extras.api.views import CustomFieldModelViewSet -from utilities.api import WritableSerializerMixin +from utilities.api import FieldChoicesViewSet, WritableSerializerMixin from virtualization import filters from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine from . import serializers +# +# Field choices +# + +class VirtualizationFieldChoicesViewSet(FieldChoicesViewSet): + fields = ( + (VirtualMachine, ['status']), + ) + + # # Clusters #