diff --git a/CHANGELOG.md b/CHANGELOG.md index d8eaf376b..7992414c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ v2.5.8 (FUTURE) * [#2961](https://github.com/digitalocean/netbox/issues/2961) - Prevent exception when exporting inventory items belonging to unnamed devices * [#2962](https://github.com/digitalocean/netbox/issues/2962) - Increase ExportTemplate `mime_type` field length * [#2966](https://github.com/digitalocean/netbox/issues/2966) - Accept `null` cable length_unit via API +* [#2972](https://github.com/digitalocean/netbox/issues/2972) - Improve ContentTypeField serializer to elegantly handle invalid data * [#2976](https://github.com/digitalocean/netbox/issues/2976) - Add delete button to tag view --- diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index 530372fb9..b65a7841b 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -8,7 +8,7 @@ from django.db.models import ManyToManyField from django.http import Http404 from rest_framework.exceptions import APIException from rest_framework.permissions import BasePermission -from rest_framework.relations import PrimaryKeyRelatedField +from rest_framework.relations import PrimaryKeyRelatedField, RelatedField from rest_framework.response import Response from rest_framework.serializers import Field, ModelSerializer, ValidationError from rest_framework.viewsets import ModelViewSet as _ModelViewSet, ViewSet @@ -101,19 +101,30 @@ class ChoiceField(Field): return data -class ContentTypeField(Field): +class ContentTypeField(RelatedField): """ Represent a ContentType as '.' """ - def to_representation(self, obj): - return "{}.{}".format(obj.app_label, obj.model) + default_error_messages = { + "does_not_exist": "Invalid content type: {content_type}", + "invalid": "Invalid value. Specify a content type as '.'.", + } + + # Can't set this as an attribute because it raises an exception when the field is read-only + def get_queryset(self): + return ContentType.objects.all() def to_internal_value(self, data): - app_label, model = data.split('.') try: + app_label, model = data.split('.') return ContentType.objects.get_by_natural_key(app_label=app_label, model=model) - except ContentType.DoesNotExist: - raise ValidationError("Invalid content type") + except ObjectDoesNotExist: + self.fail('does_not_exist', content_type=data) + except (TypeError, ValueError): + self.fail('invalid') + + def to_representation(self, obj): + return "{}.{}".format(obj.app_label, obj.model) class TimeZoneField(Field):