Closes #20295: Make cable terminations REST API endpoint read-only (#20394)

This commit is contained in:
Jeremy Stretch 2025-09-19 13:54:51 -04:00 committed by GitHub
parent f0ae0da1c7
commit 12818f1786
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 41 additions and 394 deletions

View File

@ -21698,191 +21698,6 @@
"description": "" "description": ""
} }
} }
},
"post": {
"operationId": "dcim_cable_terminations_create",
"description": "Post a list of cable termination objects.",
"tags": [
"dcim"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CableTerminationRequest"
}
},
"multipart/form-data": {
"schema": {
"$ref": "#/components/schemas/CableTerminationRequest"
}
}
},
"required": true
},
"security": [
{
"cookieAuth": []
},
{
"tokenAuth": []
}
],
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CableTermination"
}
}
},
"description": ""
}
}
},
"put": {
"operationId": "dcim_cable_terminations_bulk_update",
"description": "Put a list of cable termination objects.",
"tags": [
"dcim"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CableTerminationRequest"
}
}
},
"multipart/form-data": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CableTerminationRequest"
}
}
}
},
"required": true
},
"security": [
{
"cookieAuth": []
},
{
"tokenAuth": []
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CableTermination"
}
}
}
},
"description": ""
}
}
},
"patch": {
"operationId": "dcim_cable_terminations_bulk_partial_update",
"description": "Patch a list of cable termination objects.",
"tags": [
"dcim"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CableTerminationRequest"
}
}
},
"multipart/form-data": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CableTerminationRequest"
}
}
}
},
"required": true
},
"security": [
{
"cookieAuth": []
},
{
"tokenAuth": []
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CableTermination"
}
}
}
},
"description": ""
}
}
},
"delete": {
"operationId": "dcim_cable_terminations_bulk_destroy",
"description": "Delete a list of cable termination objects.",
"tags": [
"dcim"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CableTerminationRequest"
}
}
},
"multipart/form-data": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CableTerminationRequest"
}
}
}
},
"required": true
},
"security": [
{
"cookieAuth": []
},
{
"tokenAuth": []
}
],
"responses": {
"204": {
"description": "No response body"
}
}
} }
}, },
"/api/dcim/cable-terminations/{id}/": { "/api/dcim/cable-terminations/{id}/": {
@ -21923,142 +21738,6 @@
"description": "" "description": ""
} }
} }
},
"put": {
"operationId": "dcim_cable_terminations_update",
"description": "Put a cable termination object.",
"parameters": [
{
"in": "path",
"name": "id",
"schema": {
"type": "integer"
},
"description": "A unique integer value identifying this cable termination.",
"required": true
}
],
"tags": [
"dcim"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CableTerminationRequest"
}
},
"multipart/form-data": {
"schema": {
"$ref": "#/components/schemas/CableTerminationRequest"
}
}
},
"required": true
},
"security": [
{
"cookieAuth": []
},
{
"tokenAuth": []
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CableTermination"
}
}
},
"description": ""
}
}
},
"patch": {
"operationId": "dcim_cable_terminations_partial_update",
"description": "Patch a cable termination object.",
"parameters": [
{
"in": "path",
"name": "id",
"schema": {
"type": "integer"
},
"description": "A unique integer value identifying this cable termination.",
"required": true
}
],
"tags": [
"dcim"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PatchedCableTerminationRequest"
}
},
"multipart/form-data": {
"schema": {
"$ref": "#/components/schemas/PatchedCableTerminationRequest"
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"tokenAuth": []
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CableTermination"
}
}
},
"description": ""
}
}
},
"delete": {
"operationId": "dcim_cable_terminations_destroy",
"description": "Delete a cable termination object.",
"parameters": [
{
"in": "path",
"name": "id",
"schema": {
"type": "integer"
},
"description": "A unique integer value identifying this cable termination.",
"required": true
}
],
"tags": [
"dcim"
],
"security": [
{
"cookieAuth": []
},
{
"tokenAuth": []
}
],
"responses": {
"204": {
"description": "No response body"
}
}
} }
}, },
"/api/dcim/cables/": { "/api/dcim/cables/": {
@ -204463,7 +204142,8 @@
"readOnly": true "readOnly": true
}, },
"cable": { "cable": {
"type": "integer" "type": "integer",
"readOnly": true
}, },
"cable_end": { "cable_end": {
"enum": [ "enum": [
@ -204473,16 +204153,16 @@
"type": "string", "type": "string",
"description": "* `A` - A\n* `B` - B", "description": "* `A` - A\n* `B` - B",
"x-spec-enum-id": "1db84f9b93b261c8", "x-spec-enum-id": "1db84f9b93b261c8",
"readOnly": true,
"title": "End" "title": "End"
}, },
"termination_type": { "termination_type": {
"type": "string" "type": "string",
"readOnly": true
}, },
"termination_id": { "termination_id": {
"type": "integer", "type": "integer",
"maximum": 9223372036854775807, "readOnly": true
"minimum": 0,
"format": "int64"
}, },
"termination": { "termination": {
"nullable": true, "nullable": true,
@ -204514,40 +204194,6 @@
"url" "url"
] ]
}, },
"CableTerminationRequest": {
"type": "object",
"description": "Adds support for custom fields and tags.",
"properties": {
"cable": {
"type": "integer"
},
"cable_end": {
"enum": [
"A",
"B"
],
"type": "string",
"description": "* `A` - A\n* `B` - B",
"x-spec-enum-id": "1db84f9b93b261c8",
"title": "End"
},
"termination_type": {
"type": "string"
},
"termination_id": {
"type": "integer",
"maximum": 9223372036854775807,
"minimum": 0,
"format": "int64"
}
},
"required": [
"cable",
"cable_end",
"termination_id",
"termination_type"
]
},
"Circuit": { "Circuit": {
"type": "object", "type": "object",
"description": "Adds support for custom fields and tags.", "description": "Adds support for custom fields and tags.",
@ -226099,34 +225745,6 @@
} }
} }
}, },
"PatchedCableTerminationRequest": {
"type": "object",
"description": "Adds support for custom fields and tags.",
"properties": {
"cable": {
"type": "integer"
},
"cable_end": {
"enum": [
"A",
"B"
],
"type": "string",
"description": "* `A` - A\n* `B` - B",
"x-spec-enum-id": "1db84f9b93b261c8",
"title": "End"
},
"termination_type": {
"type": "string"
},
"termination_id": {
"type": "integer",
"maximum": 9223372036854775807,
"minimum": 0,
"format": "int64"
}
}
},
"PatchedCircuitGroupRequest": { "PatchedCircuitGroupRequest": {
"type": "object", "type": "object",
"description": "Adds support for custom fields and tags.", "description": "Adds support for custom fields and tags.",

View File

@ -1,10 +1,8 @@
from django.contrib.contenttypes.models import ContentType
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers from rest_framework import serializers
from dcim.choices import * from dcim.choices import *
from dcim.constants import *
from dcim.models import Cable, CablePath, CableTermination from dcim.models import Cable, CablePath, CableTermination
from netbox.api.fields import ChoiceField, ContentTypeField from netbox.api.fields import ChoiceField, ContentTypeField
from netbox.api.serializers import BaseModelSerializer, GenericObjectSerializer, NetBoxModelSerializer from netbox.api.serializers import BaseModelSerializer, GenericObjectSerializer, NetBoxModelSerializer
@ -51,9 +49,11 @@ class TracedCableSerializer(BaseModelSerializer):
class CableTerminationSerializer(NetBoxModelSerializer): class CableTerminationSerializer(NetBoxModelSerializer):
termination_type = ContentTypeField( termination_type = ContentTypeField(
queryset=ContentType.objects.filter(CABLE_TERMINATION_MODELS) read_only=True,
)
termination = serializers.SerializerMethodField(
read_only=True,
) )
termination = serializers.SerializerMethodField(read_only=True)
class Meta: class Meta:
model = CableTermination model = CableTermination
@ -61,6 +61,8 @@ class CableTerminationSerializer(NetBoxModelSerializer):
'id', 'url', 'display', 'cable', 'cable_end', 'termination_type', 'termination_id', 'id', 'url', 'display', 'cable', 'cable_end', 'termination_type', 'termination_id',
'termination', 'created', 'last_updated', 'termination', 'created', 'last_updated',
] ]
read_only_fields = fields
brief_fields = ('id', 'url', 'display', 'cable', 'cable_end', 'termination_type', 'termination_id')
@extend_schema_field(serializers.JSONField(allow_null=True)) @extend_schema_field(serializers.JSONField(allow_null=True))
def get_termination(self, obj): def get_termination(self, obj):

View File

@ -16,7 +16,7 @@ from extras.api.mixins import ConfigContextQuerySetMixin, RenderConfigMixin
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 netbox.api.pagination import StripCountAnnotationsPaginator from netbox.api.pagination import StripCountAnnotationsPaginator
from netbox.api.viewsets import NetBoxModelViewSet, MPTTLockedMixin from netbox.api.viewsets import NetBoxModelViewSet, MPTTLockedMixin, NetBoxReadOnlyModelViewSet
from netbox.api.viewsets.mixins import SequentialBulkCreatesMixin from netbox.api.viewsets.mixins import SequentialBulkCreatesMixin
from utilities.api import get_serializer_for_model from utilities.api import get_serializer_for_model
from utilities.query_functions import CollateAsChar from utilities.query_functions import CollateAsChar
@ -563,7 +563,7 @@ class CableViewSet(NetBoxModelViewSet):
filterset_class = filtersets.CableFilterSet filterset_class = filtersets.CableFilterSet
class CableTerminationViewSet(NetBoxModelViewSet): class CableTerminationViewSet(NetBoxReadOnlyModelViewSet):
metadata_class = ContentTypeMetadata metadata_class = ContentTypeMetadata
queryset = CableTermination.objects.all() queryset = CableTermination.objects.all()
serializer_class = serializers.CableTerminationSerializer serializer_class = serializers.CableTerminationSerializer

View File

@ -2376,6 +2376,33 @@ class CableTest(APIViewTestCases.APIViewTestCase):
] ]
class CableTerminationTest(
APIViewTestCases.GetObjectViewTestCase,
APIViewTestCases.ListObjectsViewTestCase,
):
model = CableTermination
brief_fields = ['cable', 'cable_end', 'display', 'id', 'termination_id', 'termination_type', 'url']
@classmethod
def setUpTestData(cls):
device1 = create_test_device('Device 1')
device2 = create_test_device('Device 2')
interfaces = []
for device in (device1, device2):
for i in range(0, 10):
interfaces.append(Interface(device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED, name=f'eth{i}'))
Interface.objects.bulk_create(interfaces)
cables = (
Cable(a_terminations=[interfaces[0]], b_terminations=[interfaces[10]], label='Cable 1'),
Cable(a_terminations=[interfaces[1]], b_terminations=[interfaces[11]], label='Cable 2'),
Cable(a_terminations=[interfaces[2]], b_terminations=[interfaces[12]], label='Cable 3'),
)
for cable in cables:
cable.save()
class ConnectedDeviceTest(APITestCase): class ConnectedDeviceTest(APITestCase):
@classmethod @classmethod