mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-27 01:36:11 -06:00
9608 add AutoSchema fix for bulk operations
This commit is contained in:
parent
7120a0384c
commit
85c91cbfbf
99
netbox/core/api/schema.py
Normal file
99
netbox/core/api/schema.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from drf_spectacular.extensions import (
|
||||||
|
OpenApiSerializerFieldExtension,
|
||||||
|
OpenApiViewExtension,
|
||||||
|
)
|
||||||
|
from drf_spectacular.openapi import AutoSchema
|
||||||
|
from drf_spectacular.plumbing import (
|
||||||
|
ComponentRegistry,
|
||||||
|
ResolvedComponent,
|
||||||
|
build_basic_type,
|
||||||
|
build_object_type,
|
||||||
|
is_serializer,
|
||||||
|
)
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import extend_schema
|
||||||
|
|
||||||
|
BULK_ACTIONS = ["bulk_destroy", "bulk_partial_update", "bulk_update"]
|
||||||
|
|
||||||
|
|
||||||
|
class FixTimeZoneSerializerField(OpenApiSerializerFieldExtension):
|
||||||
|
target_class = 'timezone_field.rest_framework.TimeZoneSerializerField'
|
||||||
|
|
||||||
|
def map_serializer_field(self, auto_schema, direction):
|
||||||
|
return build_basic_type(OpenApiTypes.STR)
|
||||||
|
|
||||||
|
|
||||||
|
class ChoiceFieldFix(OpenApiSerializerFieldExtension):
|
||||||
|
target_class = 'netbox.api.fields.ChoiceField'
|
||||||
|
|
||||||
|
def map_serializer_field(self, auto_schema, direction):
|
||||||
|
if direction == 'request':
|
||||||
|
return build_basic_type(OpenApiTypes.STR)
|
||||||
|
|
||||||
|
elif direction == "response":
|
||||||
|
return build_object_type(
|
||||||
|
properties={
|
||||||
|
"value": build_basic_type(OpenApiTypes.STR),
|
||||||
|
"label": build_basic_type(OpenApiTypes.STR),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NetBoxAutoSchema(AutoSchema):
|
||||||
|
"""
|
||||||
|
Overrides to spectaculars AutoSchema to fix following issues:
|
||||||
|
1. bulk serializers cause operation_id conflicts with non-bulk ones
|
||||||
|
2. bulk operations don't have filter params
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_bulk_action(self):
|
||||||
|
if hasattr(self.view, "action") and self.view.action in BULK_ACTIONS:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_operation(self, path, path_regex, path_prefix, method, registry: ComponentRegistry):
|
||||||
|
operation = super().get_operation(path, path_regex, path_prefix, method, registry)
|
||||||
|
return operation
|
||||||
|
|
||||||
|
def get_operation_id(self):
|
||||||
|
"""
|
||||||
|
Fix: bulk serializers cause operation_id conflicts with non-bulk ones
|
||||||
|
bulk operations cause id conflicts in spectacular resulting in numerous:
|
||||||
|
Warning: operationId "xxx" has collisions [xxx]. "resolving with numeral suffixes"
|
||||||
|
code is modified from drf_spectacular.openapi.AutoSchema.get_operation_id
|
||||||
|
"""
|
||||||
|
if self.is_bulk_action:
|
||||||
|
tokenized_path = self._tokenize_path()
|
||||||
|
# replace dashes as they can be problematic later in code generation
|
||||||
|
tokenized_path = [t.replace('-', '_') for t in tokenized_path]
|
||||||
|
|
||||||
|
if self.method == 'GET' and self._is_list_view():
|
||||||
|
# this shouldn't happen, but keeping it here to follow base code
|
||||||
|
action = 'list'
|
||||||
|
else:
|
||||||
|
# action = self.method_mapping[self.method.lower()]
|
||||||
|
# use bulk name so partial_update -> bulk_partial_update
|
||||||
|
action = self.view.action.lower()
|
||||||
|
|
||||||
|
if not tokenized_path:
|
||||||
|
tokenized_path.append('root')
|
||||||
|
|
||||||
|
if re.search(r'<drf_format_suffix\w*:\w+>', self.path_regex):
|
||||||
|
tokenized_path.append('formatted')
|
||||||
|
|
||||||
|
return '_'.join(tokenized_path + [action])
|
||||||
|
|
||||||
|
# if not bulk - just return normal id
|
||||||
|
return super().get_operation_id()
|
||||||
|
|
||||||
|
def get_filter_backends(self):
|
||||||
|
# Fix: bulk operations don't have filter params
|
||||||
|
if self.is_bulk_action:
|
||||||
|
return []
|
||||||
|
return super().get_filter_backends()
|
@ -6,3 +6,4 @@ class CoreConfig(AppConfig):
|
|||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from . import data_backends, search
|
from . import data_backends, search
|
||||||
|
from core.api import schema # noqa: E402
|
||||||
|
@ -10,7 +10,6 @@ class DCIMConfig(AppConfig):
|
|||||||
def ready(self):
|
def ready(self):
|
||||||
from . import signals, search
|
from . import signals, search
|
||||||
from .models import CableTermination
|
from .models import CableTermination
|
||||||
from . import schema # noqa: E402
|
|
||||||
|
|
||||||
# Register denormalized fields
|
# Register denormalized fields
|
||||||
denormalized.register(CableTermination, '_device', {
|
denormalized.register(CableTermination, '_device', {
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
from drf_spectacular.extensions import (
|
|
||||||
OpenApiSerializerFieldExtension,
|
|
||||||
OpenApiViewExtension,
|
|
||||||
)
|
|
||||||
from drf_spectacular.plumbing import build_basic_type, build_object_type
|
|
||||||
from drf_spectacular.types import OpenApiTypes
|
|
||||||
from drf_spectacular.utils import extend_schema
|
|
||||||
|
|
||||||
|
|
||||||
class FixTimeZoneSerializerField(OpenApiSerializerFieldExtension):
|
|
||||||
target_class = 'timezone_field.rest_framework.TimeZoneSerializerField'
|
|
||||||
|
|
||||||
def map_serializer_field(self, auto_schema, direction):
|
|
||||||
return build_basic_type(OpenApiTypes.STR)
|
|
||||||
|
|
||||||
|
|
||||||
class ChoiceFieldFix(OpenApiSerializerFieldExtension):
|
|
||||||
target_class = 'netbox.api.fields.ChoiceField'
|
|
||||||
|
|
||||||
def map_serializer_field(self, auto_schema, direction):
|
|
||||||
if direction == 'request':
|
|
||||||
return build_basic_type(OpenApiTypes.STR)
|
|
||||||
|
|
||||||
elif direction == "response":
|
|
||||||
return build_object_type(
|
|
||||||
properties={
|
|
||||||
"value": build_basic_type(OpenApiTypes.STR),
|
|
||||||
"label": build_basic_type(OpenApiTypes.STR),
|
|
||||||
}
|
|
||||||
)
|
|
@ -558,7 +558,7 @@ REST_FRAMEWORK = {
|
|||||||
'rest_framework.renderers.JSONRenderer',
|
'rest_framework.renderers.JSONRenderer',
|
||||||
'netbox.api.renderers.FormlessBrowsableAPIRenderer',
|
'netbox.api.renderers.FormlessBrowsableAPIRenderer',
|
||||||
),
|
),
|
||||||
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
|
'DEFAULT_SCHEMA_CLASS': 'core.api.schema.NetBoxAutoSchema',
|
||||||
'DEFAULT_VERSION': REST_FRAMEWORK_VERSION,
|
'DEFAULT_VERSION': REST_FRAMEWORK_VERSION,
|
||||||
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
|
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
|
||||||
'SCHEMA_COERCE_METHOD_NAMES': {
|
'SCHEMA_COERCE_METHOD_NAMES': {
|
||||||
|
Loading…
Reference in New Issue
Block a user