From ea9de37dd1abfef2342ba2764b8e2fe0508136dc Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 12 Mar 2020 10:48:17 -0400 Subject: [PATCH 1/3] Remove FieldChoicesViewSet --- netbox/circuits/api/urls.py | 3 -- netbox/circuits/api/views.py | 13 +-------- netbox/dcim/api/urls.py | 3 -- netbox/dcim/api/views.py | 31 +------------------- netbox/extras/api/urls.py | 3 -- netbox/extras/api/views.py | 14 +-------- netbox/ipam/api/urls.py | 3 -- netbox/ipam/api/views.py | 16 +---------- netbox/secrets/api/urls.py | 3 -- netbox/secrets/api/views.py | 10 +------ netbox/tenancy/api/urls.py | 3 -- netbox/tenancy/api/views.py | 10 +------ netbox/utilities/api.py | 46 ------------------------------ netbox/virtualization/api/urls.py | 3 -- netbox/virtualization/api/views.py | 13 +-------- 15 files changed, 7 insertions(+), 167 deletions(-) diff --git a/netbox/circuits/api/urls.py b/netbox/circuits/api/urls.py index cd3015d0a..01fbfb62c 100644 --- a/netbox/circuits/api/urls.py +++ b/netbox/circuits/api/urls.py @@ -14,9 +14,6 @@ class CircuitsRootView(routers.APIRootView): router = routers.DefaultRouter() router.APIRootView = CircuitsRootView -# Field choices -router.register('_choices', views.CircuitsFieldChoicesViewSet, basename='field-choice') - # Providers router.register('providers', views.ProviderViewSet) diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index 75f7e0e3e..363392a4d 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -8,21 +8,10 @@ from circuits.models import Provider, CircuitTermination, CircuitType, Circuit from extras.api.serializers import RenderedGraphSerializer from extras.api.views import CustomFieldModelViewSet from extras.models import Graph -from utilities.api import FieldChoicesViewSet, ModelViewSet +from utilities.api import ModelViewSet from . import serializers -# -# Field choices -# - -class CircuitsFieldChoicesViewSet(FieldChoicesViewSet): - fields = ( - (serializers.CircuitSerializer, ['status']), - (serializers.CircuitTerminationSerializer, ['term_side']), - ) - - # # Providers # diff --git a/netbox/dcim/api/urls.py b/netbox/dcim/api/urls.py index 5a915becc..f989d817c 100644 --- a/netbox/dcim/api/urls.py +++ b/netbox/dcim/api/urls.py @@ -14,9 +14,6 @@ class DCIMRootView(routers.APIRootView): router = routers.DefaultRouter() router.APIRootView = DCIMRootView -# Field choices -router.register('_choices', views.DCIMFieldChoicesViewSet, basename='field-choice') - # Sites router.register('regions', views.RegionViewSet) router.register('sites', views.SiteViewSet) diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index d044d6198..f61041b58 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -26,7 +26,7 @@ from extras.api.views import CustomFieldModelViewSet from extras.models import Graph from ipam.models import Prefix, VLAN from utilities.api import ( - get_serializer_for_model, IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ModelViewSet, ServiceUnavailable, + get_serializer_for_model, IsAuthenticatedOrLoginNotRequired, ModelViewSet, ServiceUnavailable, ) from utilities.utils import get_subquery from virtualization.models import VirtualMachine @@ -34,35 +34,6 @@ from . import serializers from .exceptions import MissingFilterException -# -# Field choices -# - -class DCIMFieldChoicesViewSet(FieldChoicesViewSet): - fields = ( - (serializers.CableSerializer, ['length_unit', 'status', 'termination_a_type', 'termination_b_type', 'type']), - (serializers.ConsolePortSerializer, ['type', 'connection_status']), - (serializers.ConsolePortTemplateSerializer, ['type']), - (serializers.ConsoleServerPortSerializer, ['type']), - (serializers.ConsoleServerPortTemplateSerializer, ['type']), - (serializers.DeviceSerializer, ['face', 'status']), - (serializers.DeviceTypeSerializer, ['subdevice_role']), - (serializers.FrontPortSerializer, ['type']), - (serializers.FrontPortTemplateSerializer, ['type']), - (serializers.InterfaceSerializer, ['type', 'mode']), - (serializers.InterfaceTemplateSerializer, ['type']), - (serializers.PowerFeedSerializer, ['phase', 'status', 'supply', 'type']), - (serializers.PowerOutletSerializer, ['type', 'feed_leg']), - (serializers.PowerOutletTemplateSerializer, ['type', 'feed_leg']), - (serializers.PowerPortSerializer, ['type', 'connection_status']), - (serializers.PowerPortTemplateSerializer, ['type']), - (serializers.RackSerializer, ['outer_unit', 'status', 'type', 'width']), - (serializers.RearPortSerializer, ['type']), - (serializers.RearPortTemplateSerializer, ['type']), - (serializers.SiteSerializer, ['status']), - ) - - # Mixins class CableTraceMixin(object): diff --git a/netbox/extras/api/urls.py b/netbox/extras/api/urls.py index d699cd22e..8d8463bad 100644 --- a/netbox/extras/api/urls.py +++ b/netbox/extras/api/urls.py @@ -14,9 +14,6 @@ class ExtrasRootView(routers.APIRootView): router = routers.DefaultRouter() router.APIRootView = ExtrasRootView -# Field choices -router.register('_choices', views.ExtrasFieldChoicesViewSet, basename='field-choice') - # Custom field choices router.register('_custom_field_choices', views.CustomFieldChoicesViewSet, basename='custom-field-choice') diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index aa9e380ba..7e547dafd 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -15,22 +15,10 @@ from extras.models import ( ) from extras.reports import get_report, get_reports from extras.scripts import get_script, get_scripts, run_script -from utilities.api import FieldChoicesViewSet, IsAuthenticatedOrLoginNotRequired, ModelViewSet +from utilities.api import IsAuthenticatedOrLoginNotRequired, ModelViewSet from . import serializers -# -# Field choices -# - -class ExtrasFieldChoicesViewSet(FieldChoicesViewSet): - fields = ( - (serializers.ExportTemplateSerializer, ['template_language']), - (serializers.GraphSerializer, ['type', 'template_language']), - (serializers.ObjectChangeSerializer, ['action']), - ) - - # # Custom field choices # diff --git a/netbox/ipam/api/urls.py b/netbox/ipam/api/urls.py index c4d68f9c0..ff0ea32a8 100644 --- a/netbox/ipam/api/urls.py +++ b/netbox/ipam/api/urls.py @@ -14,9 +14,6 @@ class IPAMRootView(routers.APIRootView): router = routers.DefaultRouter() router.APIRootView = IPAMRootView -# Field choices -router.register('_choices', views.IPAMFieldChoicesViewSet, basename='field-choice') - # VRFs router.register('vrfs', views.VRFViewSet) diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 262ca7908..4b50ac7de 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -10,26 +10,12 @@ from rest_framework.response import Response from extras.api.views import CustomFieldModelViewSet from ipam import filters from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF -from utilities.api import FieldChoicesViewSet, ModelViewSet +from utilities.api import ModelViewSet from utilities.constants import ADVISORY_LOCK_KEYS from utilities.utils import get_subquery from . import serializers -# -# Field choices -# - -class IPAMFieldChoicesViewSet(FieldChoicesViewSet): - fields = ( - (serializers.AggregateSerializer, ['family']), - (serializers.PrefixSerializer, ['family', 'status']), - (serializers.IPAddressSerializer, ['family', 'status', 'role']), - (serializers.VLANSerializer, ['status']), - (serializers.ServiceSerializer, ['protocol']), - ) - - # # VRFs # diff --git a/netbox/secrets/api/urls.py b/netbox/secrets/api/urls.py index 70abcfe29..7ae2ae9ac 100644 --- a/netbox/secrets/api/urls.py +++ b/netbox/secrets/api/urls.py @@ -14,9 +14,6 @@ class SecretsRootView(routers.APIRootView): router = routers.DefaultRouter() router.APIRootView = SecretsRootView -# Field choices -router.register('_choices', views.SecretsFieldChoicesViewSet, basename='field-choice') - # Secrets router.register('secret-roles', views.SecretRoleViewSet) router.register('secrets', views.SecretViewSet) diff --git a/netbox/secrets/api/views.py b/netbox/secrets/api/views.py index 367dc9bd0..1795e6c0a 100644 --- a/netbox/secrets/api/views.py +++ b/netbox/secrets/api/views.py @@ -11,7 +11,7 @@ from rest_framework.viewsets import ViewSet from secrets import filters from secrets.exceptions import InvalidKey from secrets.models import Secret, SecretRole, SessionKey, UserKey -from utilities.api import FieldChoicesViewSet, ModelViewSet +from utilities.api import ModelViewSet from . import serializers ERR_USERKEY_MISSING = "No UserKey found for the current user." @@ -20,14 +20,6 @@ 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 5762f9a0d..645cc2edc 100644 --- a/netbox/tenancy/api/urls.py +++ b/netbox/tenancy/api/urls.py @@ -14,9 +14,6 @@ class TenancyRootView(routers.APIRootView): router = routers.DefaultRouter() router.APIRootView = TenancyRootView -# Field choices -router.register('_choices', views.TenancyFieldChoicesViewSet, basename='field-choice') - # Tenants router.register('tenant-groups', views.TenantGroupViewSet) router.register('tenants', views.TenantViewSet) diff --git a/netbox/tenancy/api/views.py b/netbox/tenancy/api/views.py index ab82c3cf5..148058a33 100644 --- a/netbox/tenancy/api/views.py +++ b/netbox/tenancy/api/views.py @@ -4,20 +4,12 @@ from extras.api.views import CustomFieldModelViewSet from ipam.models import IPAddress, Prefix, VLAN, VRF from tenancy import filters from tenancy.models import Tenant, TenantGroup -from utilities.api import FieldChoicesViewSet, ModelViewSet +from utilities.api import ModelViewSet from utilities.utils import get_subquery from virtualization.models import VirtualMachine from . import serializers -# -# Field choices -# - -class TenancyFieldChoicesViewSet(FieldChoicesViewSet): - fields = () - - # # Tenant Groups # diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index 43062af69..25501a182 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -371,49 +371,3 @@ class ModelViewSet(_ModelViewSet): logger = logging.getLogger('netbox.api.views.ModelViewSet') logger.info(f"Deleting {instance} (PK: {instance.pk})") return super().perform_destroy(instance) - - -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().__init__(*args, **kwargs) - - # Compile a dict of all fields in this view - self._fields = OrderedDict() - for serializer_class, field_list in self.fields: - for field_name in field_list: - - model_name = serializer_class.Meta.model._meta.verbose_name - key = ':'.join([model_name.lower().replace(' ', '-'), field_name]) - serializer = serializer_class() - choices = [] - - for k, v in serializer.get_fields()[field_name].choices.items(): - if type(v) in [list, tuple]: - for k2, v2 in v: - choices.append({ - 'value': k2, - 'label': v2, - }) - else: - choices.append({ - 'value': k, - 'label': v, - }) - 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" diff --git a/netbox/virtualization/api/urls.py b/netbox/virtualization/api/urls.py index a94e043b2..c237f1e68 100644 --- a/netbox/virtualization/api/urls.py +++ b/netbox/virtualization/api/urls.py @@ -14,9 +14,6 @@ class VirtualizationRootView(routers.APIRootView): router = routers.DefaultRouter() router.APIRootView = VirtualizationRootView -# Field choices -router.register('_choices', views.VirtualizationFieldChoicesViewSet, basename='field-choice') - # Clusters router.register('cluster-types', views.ClusterTypeViewSet) router.register('cluster-groups', views.ClusterGroupViewSet) diff --git a/netbox/virtualization/api/views.py b/netbox/virtualization/api/views.py index 415fc6289..2a1d7c3a9 100644 --- a/netbox/virtualization/api/views.py +++ b/netbox/virtualization/api/views.py @@ -2,24 +2,13 @@ from django.db.models import Count from dcim.models import Device, Interface from extras.api.views import CustomFieldModelViewSet -from utilities.api import FieldChoicesViewSet, ModelViewSet +from utilities.api import ModelViewSet from utilities.utils import get_subquery from virtualization import filters from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine from . import serializers -# -# Field choices -# - -class VirtualizationFieldChoicesViewSet(FieldChoicesViewSet): - fields = ( - (serializers.VirtualMachineSerializer, ['status']), - (serializers.InterfaceSerializer, ['type']), - ) - - # # Clusters # From a53f85418774b53088c72186562ca475cc07db03 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 12 Mar 2020 10:48:53 -0400 Subject: [PATCH 2/3] Remove tests for API _choices endpoints --- netbox/circuits/tests/test_api.py | 15 +---- netbox/dcim/tests/test_api.py | 75 +------------------------ netbox/extras/tests/test_api.py | 25 +-------- netbox/ipam/tests/test_api.py | 27 +-------- netbox/secrets/tests/test_api.py | 7 --- netbox/tenancy/tests/test_api.py | 7 --- netbox/utilities/testing/utils.py | 27 --------- netbox/virtualization/tests/test_api.py | 15 +---- 8 files changed, 5 insertions(+), 193 deletions(-) diff --git a/netbox/circuits/tests/test_api.py b/netbox/circuits/tests/test_api.py index b1b6d9e14..b5f8758e7 100644 --- a/netbox/circuits/tests/test_api.py +++ b/netbox/circuits/tests/test_api.py @@ -6,7 +6,7 @@ from circuits.choices import * from circuits.models import Circuit, CircuitTermination, CircuitType, Provider from dcim.models import Site from extras.models import Graph -from utilities.testing import APITestCase, choices_to_dict +from utilities.testing import APITestCase class AppTest(APITestCase): @@ -18,19 +18,6 @@ class AppTest(APITestCase): self.assertEqual(response.status_code, 200) - def test_choices(self): - - url = reverse('circuits-api:field-choice-list') - response = self.client.get(url, **self.header) - - self.assertEqual(response.status_code, 200) - - # Circuit - self.assertEqual(choices_to_dict(response.data.get('circuit:status')), CircuitStatusChoices.as_dict()) - - # CircuitTermination - self.assertEqual(choices_to_dict(response.data.get('circuit-termination:term_side')), CircuitTerminationSideChoices.as_dict()) - class ProviderTest(APITestCase): diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index ddb9c0b52..d57aaa7d5 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -14,7 +14,7 @@ from dcim.models import ( ) from ipam.models import IPAddress, VLAN from extras.models import Graph -from utilities.testing import APITestCase, choices_to_dict +from utilities.testing import APITestCase from virtualization.models import Cluster, ClusterType @@ -27,79 +27,6 @@ class AppTest(APITestCase): self.assertEqual(response.status_code, 200) - def test_choices(self): - - url = reverse('dcim-api:field-choice-list') - response = self.client.get(url, **self.header) - - self.assertEqual(response.status_code, 200) - - # Cable - self.assertEqual(choices_to_dict(response.data.get('cable:length_unit')), CableLengthUnitChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('cable:status')), CableStatusChoices.as_dict()) - content_types = ContentType.objects.filter(CABLE_TERMINATION_MODELS) - cable_termination_choices = { - "{}.{}".format(ct.app_label, ct.model): str(ct) for ct in content_types - } - self.assertEqual(choices_to_dict(response.data.get('cable:termination_a_type')), cable_termination_choices) - self.assertEqual(choices_to_dict(response.data.get('cable:termination_b_type')), cable_termination_choices) - self.assertEqual(choices_to_dict(response.data.get('cable:type')), CableTypeChoices.as_dict()) - - # Console ports - self.assertEqual(choices_to_dict(response.data.get('console-port:type')), ConsolePortTypeChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('console-port:connection_status')), dict(CONNECTION_STATUS_CHOICES)) - self.assertEqual(choices_to_dict(response.data.get('console-port-template:type')), ConsolePortTypeChoices.as_dict()) - - # Console server ports - self.assertEqual(choices_to_dict(response.data.get('console-server-port:type')), ConsolePortTypeChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('console-server-port-template:type')), ConsolePortTypeChoices.as_dict()) - - # Device - self.assertEqual(choices_to_dict(response.data.get('device:face')), DeviceFaceChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('device:status')), DeviceStatusChoices.as_dict()) - - # Device type - self.assertEqual(choices_to_dict(response.data.get('device-type:subdevice_role')), SubdeviceRoleChoices.as_dict()) - - # Front ports - self.assertEqual(choices_to_dict(response.data.get('front-port:type')), PortTypeChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('front-port-template:type')), PortTypeChoices.as_dict()) - - # Interfaces - self.assertEqual(choices_to_dict(response.data.get('interface:type')), InterfaceTypeChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('interface:mode')), InterfaceModeChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('interface-template:type')), InterfaceTypeChoices.as_dict()) - - # Power feed - self.assertEqual(choices_to_dict(response.data.get('power-feed:phase')), PowerFeedPhaseChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('power-feed:status')), PowerFeedStatusChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('power-feed:supply')), PowerFeedSupplyChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('power-feed:type')), PowerFeedTypeChoices.as_dict()) - - # Power outlets - self.assertEqual(choices_to_dict(response.data.get('power-outlet:type')), PowerOutletTypeChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('power-outlet:feed_leg')), PowerOutletFeedLegChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('power-outlet-template:type')), PowerOutletTypeChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('power-outlet-template:feed_leg')), PowerOutletFeedLegChoices.as_dict()) - - # Power ports - self.assertEqual(choices_to_dict(response.data.get('power-port:type')), PowerPortTypeChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('power-port:connection_status')), dict(CONNECTION_STATUS_CHOICES)) - self.assertEqual(choices_to_dict(response.data.get('power-port-template:type')), PowerPortTypeChoices.as_dict()) - - # Rack - self.assertEqual(choices_to_dict(response.data.get('rack:type')), RackTypeChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('rack:width')), RackWidthChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('rack:status')), RackStatusChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('rack:outer_unit')), RackDimensionUnitChoices.as_dict()) - - # Rear ports - self.assertEqual(choices_to_dict(response.data.get('rear-port:type')), PortTypeChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('rear-port-template:type')), PortTypeChoices.as_dict()) - - # Site - self.assertEqual(choices_to_dict(response.data.get('site:status')), SiteStatusChoices.as_dict()) - class RegionTest(APITestCase): diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py index 6871b2654..b04b216ba 100644 --- a/netbox/extras/tests/test_api.py +++ b/netbox/extras/tests/test_api.py @@ -7,12 +7,10 @@ from rest_framework import status from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Platform, Rack, RackGroup, RackRole, Region, Site from extras.api.views import ScriptViewSet -from extras.choices import * -from extras.constants import GRAPH_MODELS from extras.models import ConfigContext, Graph, ExportTemplate, Tag from extras.scripts import BooleanVar, IntegerVar, Script, StringVar from tenancy.models import Tenant, TenantGroup -from utilities.testing import APITestCase, choices_to_dict +from utilities.testing import APITestCase class AppTest(APITestCase): @@ -24,27 +22,6 @@ class AppTest(APITestCase): self.assertEqual(response.status_code, 200) - def test_choices(self): - - url = reverse('extras-api:field-choice-list') - response = self.client.get(url, **self.header) - - self.assertEqual(response.status_code, 200) - - # ExportTemplate - self.assertEqual(choices_to_dict(response.data.get('export-template:template_language')), TemplateLanguageChoices.as_dict()) - - # Graph - content_types = ContentType.objects.filter(GRAPH_MODELS) - graph_type_choices = { - "{}.{}".format(ct.app_label, ct.model): str(ct) for ct in content_types - } - self.assertEqual(choices_to_dict(response.data.get('graph:type')), graph_type_choices) - self.assertEqual(choices_to_dict(response.data.get('graph:template_language')), TemplateLanguageChoices.as_dict()) - - # ObjectChange - self.assertEqual(choices_to_dict(response.data.get('object-change:action')), ObjectChangeActionChoices.as_dict()) - class GraphTest(APITestCase): diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index 99a7eaca4..8bdf7fd06 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -7,7 +7,7 @@ from rest_framework import status from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site from ipam.choices import * from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF -from utilities.testing import APITestCase, choices_to_dict, disable_warnings +from utilities.testing import APITestCase, disable_warnings class AppTest(APITestCase): @@ -19,31 +19,6 @@ class AppTest(APITestCase): self.assertEqual(response.status_code, 200) - def test_choices(self): - - url = reverse('ipam-api:field-choice-list') - response = self.client.get(url, **self.header) - - self.assertEqual(response.status_code, 200) - - # Aggregate - # self.assertEqual(choices_to_dict(response.data.get('aggregate:family')), ) - - # Prefix - # self.assertEqual(choices_to_dict(response.data.get('prefix:family')), ) - self.assertEqual(choices_to_dict(response.data.get('prefix:status')), PrefixStatusChoices.as_dict()) - - # IPAddress - # self.assertEqual(choices_to_dict(response.data.get('ip-address:family')), ) - self.assertEqual(choices_to_dict(response.data.get('ip-address:role')), IPAddressRoleChoices.as_dict()) - self.assertEqual(choices_to_dict(response.data.get('ip-address:status')), IPAddressStatusChoices.as_dict()) - - # VLAN - self.assertEqual(choices_to_dict(response.data.get('vlan:status')), VLANStatusChoices.as_dict()) - - # Service - self.assertEqual(choices_to_dict(response.data.get('service:protocol')), ServiceProtocolChoices.as_dict()) - class VRFTest(APITestCase): diff --git a/netbox/secrets/tests/test_api.py b/netbox/secrets/tests/test_api.py index df32ad7f2..339c370d8 100644 --- a/netbox/secrets/tests/test_api.py +++ b/netbox/secrets/tests/test_api.py @@ -19,13 +19,6 @@ class AppTest(APITestCase): self.assertEqual(response.status_code, 200) - def test_choices(self): - - url = reverse('secrets-api:field-choice-list') - response = self.client.get(url, **self.header) - - self.assertEqual(response.status_code, 200) - class SecretRoleTest(APITestCase): diff --git a/netbox/tenancy/tests/test_api.py b/netbox/tenancy/tests/test_api.py index 1767c8f28..8da3d7594 100644 --- a/netbox/tenancy/tests/test_api.py +++ b/netbox/tenancy/tests/test_api.py @@ -14,13 +14,6 @@ class AppTest(APITestCase): self.assertEqual(response.status_code, 200) - def test_choices(self): - - url = reverse('tenancy-api:field-choice-list') - response = self.client.get(url, **self.header) - - self.assertEqual(response.status_code, 200) - class TenantGroupTest(APITestCase): diff --git a/netbox/utilities/testing/utils.py b/netbox/utilities/testing/utils.py index 38ec6e196..fd8c70f05 100644 --- a/netbox/utilities/testing/utils.py +++ b/netbox/utilities/testing/utils.py @@ -36,33 +36,6 @@ def create_test_user(username='testuser', permissions=None): return user -def choices_to_dict(choices_list): - """ - Convert a list of field choices to a dictionary suitable for direct comparison with a ChoiceSet. For example: - - [ - { - "value": "choice-1", - "label": "First Choice" - }, - { - "value": "choice-2", - "label": "Second Choice" - } - ] - - Becomes: - - { - "choice-1": "First Choice", - "choice-2": "Second Choice - } - """ - return { - choice['value']: choice['label'] for choice in choices_list - } - - @contextmanager def disable_warnings(logger_name): """ diff --git a/netbox/virtualization/tests/test_api.py b/netbox/virtualization/tests/test_api.py index 719954c10..7aa4e929f 100644 --- a/netbox/virtualization/tests/test_api.py +++ b/netbox/virtualization/tests/test_api.py @@ -5,7 +5,7 @@ from rest_framework import status from dcim.choices import InterfaceModeChoices from dcim.models import Interface from ipam.models import IPAddress, VLAN -from utilities.testing import APITestCase, choices_to_dict, disable_warnings +from utilities.testing import APITestCase, disable_warnings from virtualization.choices import * from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -19,19 +19,6 @@ class AppTest(APITestCase): self.assertEqual(response.status_code, 200) - def test_choices(self): - - url = reverse('virtualization-api:field-choice-list') - response = self.client.get(url, **self.header) - - self.assertEqual(response.status_code, 200) - - # VirtualMachine - self.assertEqual(choices_to_dict(response.data.get('virtual-machine:status')), VirtualMachineStatusChoices.as_dict()) - - # Interface - self.assertEqual(choices_to_dict(response.data.get('interface:type')), VMInterfaceTypeChoices.as_dict()) - class ClusterTypeTest(APITestCase): From ef5c20dc6fd3bf6366332ef498c80cea0970cdea Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 12 Mar 2020 11:14:27 -0400 Subject: [PATCH 3/3] Update documentation --- docs/api/overview.md | 76 +++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 51 deletions(-) diff --git a/docs/api/overview.md b/docs/api/overview.md index 1d8a91084..81e4caa25 100644 --- a/docs/api/overview.md +++ b/docs/api/overview.md @@ -187,37 +187,6 @@ GET /api/ipam/prefixes/13980/?brief=1 The brief format is supported for both lists and individual objects. -### Static Choice Fields - -Some model fields, such as the `status` field in the above example, utilize static integers corresponding to static choices. The available choices can be retrieved from the read-only `_choices` endpoint within each app. A specific `model:field` tuple may optionally be specified in the URL. - -Each choice includes a human-friendly label and its corresponding numeric value. For example, `GET /api/ipam/_choices/prefix:status/` will return: - -``` -[ - { - "value": 0, - "label": "Container" - }, - { - "value": 1, - "label": "Active" - }, - { - "value": 2, - "label": "Reserved" - }, - { - "value": 3, - "label": "Deprecated" - } -] -``` - -Thus, to set a prefix's status to "Reserved," it would be assigned the integer `2`. - -A request for `GET /api/ipam/_choices/` will return choices for _all_ fields belonging to models within the IPAM app. - ## Pagination API responses which contain a list of objects (for example, a request to `/api/dcim/devices/`) will be paginated to avoid unnecessary overhead. The root JSON object will contain the following attributes: @@ -280,27 +249,32 @@ A list of objects retrieved via the API can be filtered by passing one or more q GET /api/ipam/prefixes/?status=1 ``` -The choices available for fixed choice fields such as `status` are exposed in the API under a special `_choices` endpoint for each NetBox app. For example, the available choices for `Prefix.status` are listed at `/api/ipam/_choices/` under the key `prefix:status`: +The choices available for fixed choice fields such as `status` can be retrieved by sending an `OPTIONS` API request for the desired endpoint: + +```no-highlight +$ curl -s -X OPTIONS \ +-H "Content-Type: application/json" \ +-H "Accept: application/json; indent=4" \ +http://localhost:8000/api/ipam/prefixes/ | jq ".actions.POST.status.choices" +[ + { + "value": "container", + "display_name": "Container" + }, + { + "value": "active", + "display_name": "Active" + }, + { + "value": "reserved", + "display_name": "Reserved" + }, + { + "value": "deprecated", + "display_name": "Deprecated" + } +] -``` -"prefix:status": [ - { - "label": "Container", - "value": 0 - }, - { - "label": "Active", - "value": 1 - }, - { - "label": "Reserved", - "value": 2 - }, - { - "label": "Deprecated", - "value": 3 - } -], ``` For most fields, when a filter is passed multiple times, objects matching _any_ of the provided values will be returned. For example, `GET /api/dcim/sites/?name=Foo&name=Bar` will return all sites named "Foo" _or_ "Bar". The exception to this rule is ManyToManyFields which may have multiple values assigned. Tags are the most common example of a ManyToManyField. For example, `GET /api/dcim/sites/?tag=foo&tag=bar` will return only sites tagged with both "foo" _and_ "bar".