mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 01:48:38 -06:00
Merge pull request #4359 from netbox-community/3416-remove-API-choices
Closes #3416: Remove API _choices endpoints
This commit is contained in:
commit
16bc262a4f
@ -187,37 +187,6 @@ GET /api/ipam/prefixes/13980/?brief=1
|
|||||||
|
|
||||||
The brief format is supported for both lists and individual objects.
|
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
|
## 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:
|
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
|
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".
|
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".
|
||||||
|
@ -14,9 +14,6 @@ class CircuitsRootView(routers.APIRootView):
|
|||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.APIRootView = CircuitsRootView
|
router.APIRootView = CircuitsRootView
|
||||||
|
|
||||||
# Field choices
|
|
||||||
router.register('_choices', views.CircuitsFieldChoicesViewSet, basename='field-choice')
|
|
||||||
|
|
||||||
# Providers
|
# Providers
|
||||||
router.register('providers', views.ProviderViewSet)
|
router.register('providers', views.ProviderViewSet)
|
||||||
|
|
||||||
|
@ -8,21 +8,10 @@ from circuits.models import Provider, CircuitTermination, CircuitType, Circuit
|
|||||||
from extras.api.serializers import RenderedGraphSerializer
|
from extras.api.serializers import RenderedGraphSerializer
|
||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
from extras.models import Graph
|
from extras.models import Graph
|
||||||
from utilities.api import FieldChoicesViewSet, ModelViewSet
|
from utilities.api import ModelViewSet
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Field choices
|
|
||||||
#
|
|
||||||
|
|
||||||
class CircuitsFieldChoicesViewSet(FieldChoicesViewSet):
|
|
||||||
fields = (
|
|
||||||
(serializers.CircuitSerializer, ['status']),
|
|
||||||
(serializers.CircuitTerminationSerializer, ['term_side']),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Providers
|
# Providers
|
||||||
#
|
#
|
||||||
|
@ -6,7 +6,7 @@ from circuits.choices import *
|
|||||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from extras.models import Graph
|
from extras.models import Graph
|
||||||
from utilities.testing import APITestCase, choices_to_dict
|
from utilities.testing import APITestCase
|
||||||
|
|
||||||
|
|
||||||
class AppTest(APITestCase):
|
class AppTest(APITestCase):
|
||||||
@ -18,19 +18,6 @@ class AppTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
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):
|
class ProviderTest(APITestCase):
|
||||||
|
|
||||||
|
@ -14,9 +14,6 @@ class DCIMRootView(routers.APIRootView):
|
|||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.APIRootView = DCIMRootView
|
router.APIRootView = DCIMRootView
|
||||||
|
|
||||||
# Field choices
|
|
||||||
router.register('_choices', views.DCIMFieldChoicesViewSet, basename='field-choice')
|
|
||||||
|
|
||||||
# Sites
|
# Sites
|
||||||
router.register('regions', views.RegionViewSet)
|
router.register('regions', views.RegionViewSet)
|
||||||
router.register('sites', views.SiteViewSet)
|
router.register('sites', views.SiteViewSet)
|
||||||
|
@ -26,7 +26,7 @@ from extras.api.views import CustomFieldModelViewSet
|
|||||||
from extras.models import Graph
|
from extras.models import Graph
|
||||||
from ipam.models import Prefix, VLAN
|
from ipam.models import Prefix, VLAN
|
||||||
from utilities.api import (
|
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 utilities.utils import get_subquery
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
@ -34,35 +34,6 @@ from . import serializers
|
|||||||
from .exceptions import MissingFilterException
|
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
|
# Mixins
|
||||||
|
|
||||||
class CableTraceMixin(object):
|
class CableTraceMixin(object):
|
||||||
|
@ -14,7 +14,7 @@ from dcim.models import (
|
|||||||
)
|
)
|
||||||
from ipam.models import IPAddress, VLAN
|
from ipam.models import IPAddress, VLAN
|
||||||
from extras.models import Graph
|
from extras.models import Graph
|
||||||
from utilities.testing import APITestCase, choices_to_dict
|
from utilities.testing import APITestCase
|
||||||
from virtualization.models import Cluster, ClusterType
|
from virtualization.models import Cluster, ClusterType
|
||||||
|
|
||||||
|
|
||||||
@ -27,79 +27,6 @@ class AppTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
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):
|
class RegionTest(APITestCase):
|
||||||
|
|
||||||
|
@ -14,9 +14,6 @@ class ExtrasRootView(routers.APIRootView):
|
|||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.APIRootView = ExtrasRootView
|
router.APIRootView = ExtrasRootView
|
||||||
|
|
||||||
# Field choices
|
|
||||||
router.register('_choices', views.ExtrasFieldChoicesViewSet, basename='field-choice')
|
|
||||||
|
|
||||||
# Custom field choices
|
# Custom field choices
|
||||||
router.register('_custom_field_choices', views.CustomFieldChoicesViewSet, basename='custom-field-choice')
|
router.register('_custom_field_choices', views.CustomFieldChoicesViewSet, basename='custom-field-choice')
|
||||||
|
|
||||||
|
@ -15,22 +15,10 @@ from extras.models import (
|
|||||||
)
|
)
|
||||||
from extras.reports import get_report, get_reports
|
from extras.reports import get_report, get_reports
|
||||||
from extras.scripts import get_script, get_scripts, run_script
|
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
|
from . import serializers
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Field choices
|
|
||||||
#
|
|
||||||
|
|
||||||
class ExtrasFieldChoicesViewSet(FieldChoicesViewSet):
|
|
||||||
fields = (
|
|
||||||
(serializers.ExportTemplateSerializer, ['template_language']),
|
|
||||||
(serializers.GraphSerializer, ['type', 'template_language']),
|
|
||||||
(serializers.ObjectChangeSerializer, ['action']),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Custom field choices
|
# Custom field choices
|
||||||
#
|
#
|
||||||
|
@ -7,12 +7,10 @@ from rest_framework import status
|
|||||||
|
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Platform, Rack, RackGroup, RackRole, Region, Site
|
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Platform, Rack, RackGroup, RackRole, Region, Site
|
||||||
from extras.api.views import ScriptViewSet
|
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.models import ConfigContext, Graph, ExportTemplate, Tag
|
||||||
from extras.scripts import BooleanVar, IntegerVar, Script, StringVar
|
from extras.scripts import BooleanVar, IntegerVar, Script, StringVar
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from utilities.testing import APITestCase, choices_to_dict
|
from utilities.testing import APITestCase
|
||||||
|
|
||||||
|
|
||||||
class AppTest(APITestCase):
|
class AppTest(APITestCase):
|
||||||
@ -24,27 +22,6 @@ class AppTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
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):
|
class GraphTest(APITestCase):
|
||||||
|
|
||||||
|
@ -14,9 +14,6 @@ class IPAMRootView(routers.APIRootView):
|
|||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.APIRootView = IPAMRootView
|
router.APIRootView = IPAMRootView
|
||||||
|
|
||||||
# Field choices
|
|
||||||
router.register('_choices', views.IPAMFieldChoicesViewSet, basename='field-choice')
|
|
||||||
|
|
||||||
# VRFs
|
# VRFs
|
||||||
router.register('vrfs', views.VRFViewSet)
|
router.register('vrfs', views.VRFViewSet)
|
||||||
|
|
||||||
|
@ -10,26 +10,12 @@ from rest_framework.response import Response
|
|||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
from ipam import filters
|
from ipam import filters
|
||||||
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
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.constants import ADVISORY_LOCK_KEYS
|
||||||
from utilities.utils import get_subquery
|
from utilities.utils import get_subquery
|
||||||
from . import serializers
|
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
|
# VRFs
|
||||||
#
|
#
|
||||||
|
@ -7,7 +7,7 @@ from rest_framework import status
|
|||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
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):
|
class AppTest(APITestCase):
|
||||||
@ -19,31 +19,6 @@ class AppTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
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):
|
class VRFTest(APITestCase):
|
||||||
|
|
||||||
|
@ -14,9 +14,6 @@ class SecretsRootView(routers.APIRootView):
|
|||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.APIRootView = SecretsRootView
|
router.APIRootView = SecretsRootView
|
||||||
|
|
||||||
# Field choices
|
|
||||||
router.register('_choices', views.SecretsFieldChoicesViewSet, basename='field-choice')
|
|
||||||
|
|
||||||
# Secrets
|
# Secrets
|
||||||
router.register('secret-roles', views.SecretRoleViewSet)
|
router.register('secret-roles', views.SecretRoleViewSet)
|
||||||
router.register('secrets', views.SecretViewSet)
|
router.register('secrets', views.SecretViewSet)
|
||||||
|
@ -11,7 +11,7 @@ from rest_framework.viewsets import ViewSet
|
|||||||
from secrets import filters
|
from secrets import filters
|
||||||
from secrets.exceptions import InvalidKey
|
from secrets.exceptions import InvalidKey
|
||||||
from secrets.models import Secret, SecretRole, SessionKey, UserKey
|
from secrets.models import Secret, SecretRole, SessionKey, UserKey
|
||||||
from utilities.api import FieldChoicesViewSet, ModelViewSet
|
from utilities.api import ModelViewSet
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
ERR_USERKEY_MISSING = "No UserKey found for the current user."
|
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."
|
ERR_PRIVKEY_INVALID = "Invalid private key."
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Field choices
|
|
||||||
#
|
|
||||||
|
|
||||||
class SecretsFieldChoicesViewSet(FieldChoicesViewSet):
|
|
||||||
fields = ()
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Secret Roles
|
# Secret Roles
|
||||||
#
|
#
|
||||||
|
@ -19,13 +19,6 @@ class AppTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
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):
|
class SecretRoleTest(APITestCase):
|
||||||
|
|
||||||
|
@ -14,9 +14,6 @@ class TenancyRootView(routers.APIRootView):
|
|||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.APIRootView = TenancyRootView
|
router.APIRootView = TenancyRootView
|
||||||
|
|
||||||
# Field choices
|
|
||||||
router.register('_choices', views.TenancyFieldChoicesViewSet, basename='field-choice')
|
|
||||||
|
|
||||||
# Tenants
|
# Tenants
|
||||||
router.register('tenant-groups', views.TenantGroupViewSet)
|
router.register('tenant-groups', views.TenantGroupViewSet)
|
||||||
router.register('tenants', views.TenantViewSet)
|
router.register('tenants', views.TenantViewSet)
|
||||||
|
@ -4,20 +4,12 @@ from extras.api.views import CustomFieldModelViewSet
|
|||||||
from ipam.models import IPAddress, Prefix, VLAN, VRF
|
from ipam.models import IPAddress, Prefix, VLAN, VRF
|
||||||
from tenancy import filters
|
from tenancy import filters
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from utilities.api import FieldChoicesViewSet, ModelViewSet
|
from utilities.api import ModelViewSet
|
||||||
from utilities.utils import get_subquery
|
from utilities.utils import get_subquery
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Field choices
|
|
||||||
#
|
|
||||||
|
|
||||||
class TenancyFieldChoicesViewSet(FieldChoicesViewSet):
|
|
||||||
fields = ()
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Tenant Groups
|
# Tenant Groups
|
||||||
#
|
#
|
||||||
|
@ -14,13 +14,6 @@ class AppTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
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):
|
class TenantGroupTest(APITestCase):
|
||||||
|
|
||||||
|
@ -371,49 +371,3 @@ class ModelViewSet(_ModelViewSet):
|
|||||||
logger = logging.getLogger('netbox.api.views.ModelViewSet')
|
logger = logging.getLogger('netbox.api.views.ModelViewSet')
|
||||||
logger.info(f"Deleting {instance} (PK: {instance.pk})")
|
logger.info(f"Deleting {instance} (PK: {instance.pk})")
|
||||||
return super().perform_destroy(instance)
|
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"
|
|
||||||
|
@ -36,33 +36,6 @@ def create_test_user(username='testuser', permissions=None):
|
|||||||
return user
|
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
|
@contextmanager
|
||||||
def disable_warnings(logger_name):
|
def disable_warnings(logger_name):
|
||||||
"""
|
"""
|
||||||
|
@ -14,9 +14,6 @@ class VirtualizationRootView(routers.APIRootView):
|
|||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.APIRootView = VirtualizationRootView
|
router.APIRootView = VirtualizationRootView
|
||||||
|
|
||||||
# Field choices
|
|
||||||
router.register('_choices', views.VirtualizationFieldChoicesViewSet, basename='field-choice')
|
|
||||||
|
|
||||||
# Clusters
|
# Clusters
|
||||||
router.register('cluster-types', views.ClusterTypeViewSet)
|
router.register('cluster-types', views.ClusterTypeViewSet)
|
||||||
router.register('cluster-groups', views.ClusterGroupViewSet)
|
router.register('cluster-groups', views.ClusterGroupViewSet)
|
||||||
|
@ -2,24 +2,13 @@ from django.db.models import Count
|
|||||||
|
|
||||||
from dcim.models import Device, Interface
|
from dcim.models import Device, Interface
|
||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
from utilities.api import FieldChoicesViewSet, ModelViewSet
|
from utilities.api import ModelViewSet
|
||||||
from utilities.utils import get_subquery
|
from utilities.utils import get_subquery
|
||||||
from virtualization import filters
|
from virtualization import filters
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Field choices
|
|
||||||
#
|
|
||||||
|
|
||||||
class VirtualizationFieldChoicesViewSet(FieldChoicesViewSet):
|
|
||||||
fields = (
|
|
||||||
(serializers.VirtualMachineSerializer, ['status']),
|
|
||||||
(serializers.InterfaceSerializer, ['type']),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Clusters
|
# Clusters
|
||||||
#
|
#
|
||||||
|
@ -5,7 +5,7 @@ from rest_framework import status
|
|||||||
from dcim.choices import InterfaceModeChoices
|
from dcim.choices import InterfaceModeChoices
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from ipam.models import IPAddress, VLAN
|
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.choices import *
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||||
|
|
||||||
@ -19,19 +19,6 @@ class AppTest(APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
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):
|
class ClusterTypeTest(APITestCase):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user