1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

169 lines
5.8 KiB
Python
Raw Normal View History

import platform
import sys
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields.related import ManyToOneRel, RelatedField
from django.http import JsonResponse
from django.urls import reverse
from rest_framework import status
from rest_framework.serializers import Serializer
from rest_framework.utils import formatting
2016-03-01 11:23:03 -05:00
from netbox.api.fields import RelatedObjectCountField
from netbox.api.exceptions import GraphQLTypeNotFound, SerializerNotFound
from utilities.utils import count_related
from .utils import dynamic_import
__all__ = (
'get_annotations_for_serializer',
'get_graphql_type_for_model',
'get_prefetches_for_serializer',
'get_serializer_for_model',
'get_view_name',
'is_api_request',
'rest_api_server_error',
)
2018-06-19 14:57:03 -04:00
def get_serializer_for_model(model, prefix=''):
"""
Dynamically resolve and return the appropriate serializer for a model.
"""
app_name, model_name = model._meta.label.split('.')
# Serializers for Django's auth models are in the users app
if app_name == 'auth':
app_name = 'users'
# Account for changes using Proxy model
if app_name == 'users':
if model_name == 'NetBoxUser':
model_name = 'User'
elif model_name == 'NetBoxGroup':
model_name = 'Group'
serializer_name = f'{app_name}.api.serializers.{prefix}{model_name}Serializer'
2018-06-19 14:57:03 -04:00
try:
return dynamic_import(serializer_name)
except AttributeError:
raise SerializerNotFound(
f"Could not determine serializer for {app_name}.{model_name} with prefix '{prefix}'"
)
2018-06-19 14:57:03 -04:00
def get_graphql_type_for_model(model):
"""
Return the GraphQL type class for the given model.
"""
app_name, model_name = model._meta.label.split('.')
2021-06-29 13:15:10 -04:00
# Object types for Django's auth models are in the users app
if app_name == 'auth':
app_name = 'users'
class_name = f'{app_name}.graphql.types.{model_name}Type'
try:
return dynamic_import(class_name)
except AttributeError:
raise GraphQLTypeNotFound(f"Could not find GraphQL type for {app_name}.{model_name}")
def is_api_request(request):
"""
Return True of the request is being made via the REST API.
"""
api_path = reverse('api-root')
return request.path_info.startswith(api_path) and request.content_type == 'application/json'
def get_view_name(view, suffix=None):
"""
Derive the view name from its associated model, if it has one. Fall back to DRF's built-in `get_view_name`.
"""
if hasattr(view, 'queryset'):
# Determine the model name from the queryset.
name = view.queryset.model._meta.verbose_name
name = ' '.join([w[0].upper() + w[1:] for w in name.split()]) # Capitalize each word
else:
# Replicate DRF's built-in behavior.
name = view.__class__.__name__
name = formatting.remove_trailing_string(name, 'View')
name = formatting.remove_trailing_string(name, 'ViewSet')
name = formatting.camelcase_to_spaces(name)
if suffix:
name += ' ' + suffix
return name
def get_prefetches_for_serializer(serializer_class, fields_to_include=None):
"""
Compile and return a list of fields which should be prefetched on the queryset for a serializer.
"""
model = serializer_class.Meta.model
# If specific fields are not specified, default to all
if not fields_to_include:
fields_to_include = serializer_class.Meta.fields
prefetch_fields = []
for field_name in fields_to_include:
serializer_field = serializer_class._declared_fields.get(field_name)
# Determine the name of the model field referenced by the serializer field
model_field_name = field_name
if serializer_field and serializer_field.source:
model_field_name = serializer_field.source
# If the serializer field does not map to a discrete model field, skip it.
try:
field = model._meta.get_field(model_field_name)
if isinstance(field, (RelatedField, ManyToOneRel, GenericForeignKey)):
prefetch_fields.append(field.name)
except FieldDoesNotExist:
continue
# If this field is represented by a nested serializer, recurse to resolve prefetches
# for the related object.
if serializer_field:
if issubclass(type(serializer_field), Serializer):
for subfield in get_prefetches_for_serializer(type(serializer_field)):
prefetch_fields.append(f'{field_name}__{subfield}')
return prefetch_fields
def get_annotations_for_serializer(serializer_class, fields_to_include=None):
"""
Return a mapping of field names to annotations to be applied to the queryset for a serializer.
"""
annotations = {}
# If specific fields are not specified, default to all
if not fields_to_include:
fields_to_include = serializer_class.Meta.fields
model = serializer_class.Meta.model
for field_name, field in serializer_class._declared_fields.items():
if field_name in fields_to_include and type(field) is RelatedObjectCountField:
related_field = model._meta.get_field(field.relation).field
annotations[field_name] = count_related(related_field.model, related_field.name)
return annotations
def rest_api_server_error(request, *args, **kwargs):
"""
Handle exceptions and return a useful error message for REST API requests.
"""
type_, error, traceback = sys.exc_info()
data = {
'error': str(error),
'exception': type_.__name__,
'netbox_version': settings.VERSION,
'python_version': platform.python_version(),
}
return JsonResponse(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR)