mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'feature' into 15277-object-types
This commit is contained in:
@@ -3,23 +3,26 @@ import sys
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.core.exceptions import FieldDoesNotExist
|
||||
from django.core.exceptions import (
|
||||
FieldDoesNotExist, FieldError, MultipleObjectsReturned, ObjectDoesNotExist, ValidationError,
|
||||
)
|
||||
from django.db.models.fields.related import ManyToOneRel, RelatedField
|
||||
from django.http import JsonResponse
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import status
|
||||
from rest_framework.serializers import Serializer
|
||||
from rest_framework.utils import formatting
|
||||
|
||||
from netbox.api.fields import RelatedObjectCountField
|
||||
from netbox.api.exceptions import GraphQLTypeNotFound, SerializerNotFound
|
||||
from utilities.utils import count_related
|
||||
from .utils import dynamic_import
|
||||
from .utils import count_related, dict_to_filter_params, dynamic_import
|
||||
|
||||
__all__ = (
|
||||
'get_annotations_for_serializer',
|
||||
'get_graphql_type_for_model',
|
||||
'get_prefetches_for_serializer',
|
||||
'get_related_object_by_attrs',
|
||||
'get_serializer_for_model',
|
||||
'get_view_name',
|
||||
'is_api_request',
|
||||
@@ -93,7 +96,7 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None):
|
||||
"""
|
||||
model = serializer_class.Meta.model
|
||||
|
||||
# If specific fields are not specified, default to all
|
||||
# If fields are not specified, default to all
|
||||
if not fields_to_include:
|
||||
fields_to_include = serializer_class.Meta.fields
|
||||
|
||||
@@ -118,7 +121,9 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None):
|
||||
# for the related object.
|
||||
if serializer_field:
|
||||
if issubclass(type(serializer_field), Serializer):
|
||||
for subfield in get_prefetches_for_serializer(type(serializer_field)):
|
||||
# Determine which fields to prefetch for the nested object
|
||||
subfields = serializer_field.Meta.brief_fields if serializer_field.nested else None
|
||||
for subfield in get_prefetches_for_serializer(type(serializer_field), subfields):
|
||||
prefetch_fields.append(f'{field_name}__{subfield}')
|
||||
|
||||
return prefetch_fields
|
||||
@@ -144,6 +149,48 @@ def get_annotations_for_serializer(serializer_class, fields_to_include=None):
|
||||
return annotations
|
||||
|
||||
|
||||
def get_related_object_by_attrs(queryset, attrs):
|
||||
"""
|
||||
Return an object identified by either a dictionary of attributes or its numeric primary key (ID). This is used
|
||||
for referencing related objects when creating/updating objects via the REST API.
|
||||
"""
|
||||
if attrs is None:
|
||||
return None
|
||||
|
||||
# Dictionary of related object attributes
|
||||
if isinstance(attrs, dict):
|
||||
params = dict_to_filter_params(attrs)
|
||||
try:
|
||||
return queryset.get(**params)
|
||||
except ObjectDoesNotExist:
|
||||
raise ValidationError(
|
||||
_("Related object not found using the provided attributes: {params}").format(params=params))
|
||||
except MultipleObjectsReturned:
|
||||
raise ValidationError(
|
||||
_("Multiple objects match the provided attributes: {params}").format(params=params)
|
||||
)
|
||||
except FieldError as e:
|
||||
raise ValidationError(e)
|
||||
|
||||
# Integer PK of related object
|
||||
try:
|
||||
# Cast as integer in case a PK was mistakenly sent as a string
|
||||
pk = int(attrs)
|
||||
except (TypeError, ValueError):
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Related objects must be referenced by numeric ID or by dictionary of attributes. Received an "
|
||||
"unrecognized value: {value}"
|
||||
).format(value=attrs)
|
||||
)
|
||||
|
||||
# Look up object by PK
|
||||
try:
|
||||
return queryset.get(pk=pk)
|
||||
except ObjectDoesNotExist:
|
||||
raise ValidationError(_("Related object not found using the provided numeric ID: {id}").format(id=pk))
|
||||
|
||||
|
||||
def rest_api_server_error(request, *args, **kwargs):
|
||||
"""
|
||||
Handle exceptions and return a useful error message for REST API requests.
|
||||
|
@@ -36,7 +36,7 @@
|
||||
{% elif 'data-clipboard' in field.field.widget.attrs %}
|
||||
<div class="input-group">
|
||||
{{ field }}
|
||||
<button type="button" title="{% trans "Copy to clipboard" %}" class="btn btn-outline-dark copy-content" data-clipboard-target="#{{ field.id_for_label }}">
|
||||
<button type="button" title="{% trans "Copy to clipboard" %}" class="btn copy-content" data-clipboard-target="#{{ field.id_for_label }}">
|
||||
<i class="mdi mdi-content-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@@ -19,7 +19,7 @@
|
||||
<div class="dropdown-menu-columns">
|
||||
<div class="dropdown-menu-column pb-2">
|
||||
{% for group, items in groups %}
|
||||
<div class="text-uppercase fw-bold fs-5 ps-3 pt-3 pb-1">
|
||||
<div class="text-uppercase text-secondary fw-bold fs-5 ps-3 pt-3 pb-1">
|
||||
{{ group.label }}
|
||||
</div>
|
||||
{% for item, buttons in items %}
|
||||
|
@@ -1,6 +1,8 @@
|
||||
<div class="input-group">
|
||||
{% include 'django/forms/widgets/number.html' %}
|
||||
<button type="button" class="btn btn-outline-dark dropdown-toggle" data-bs-toggle="dropdown"></button>
|
||||
<button type="button" class="btn" data-bs-toggle="dropdown">
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
{% for value, label in widget.options %}
|
||||
<li>
|
||||
|
Reference in New Issue
Block a user