diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index a5c139c08..08da93aa0 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -1,9 +1,14 @@ from rest_framework import serializers -from dcim.api.serializers import NestedSiteSerializer -from extras.models import ACTION_CHOICES, Graph, GRAPH_TYPE_CHOICES, ExportTemplate, TopologyMap, UserAction +from django.core.exceptions import ObjectDoesNotExist + +from dcim.api.serializers import NestedDeviceSerializer, NestedRackSerializer, NestedSiteSerializer +from dcim.models import Device, Rack, Site +from extras.models import ( + ACTION_CHOICES, ExportTemplate, Graph, GRAPH_TYPE_CHOICES, ImageAttachment, TopologyMap, UserAction, +) from users.api.serializers import NestedUserSerializer -from utilities.api import ChoiceFieldSerializer +from utilities.api import ChoiceFieldSerializer, ContentTypeFieldSerializer # @@ -71,6 +76,52 @@ class WritableTopologyMapSerializer(serializers.ModelSerializer): fields = ['id', 'name', 'slug', 'site', 'device_patterns', 'description'] +# +# Image attachments +# + +class ImageAttachmentSerializer(serializers.ModelSerializer): + parent = serializers.SerializerMethodField() + + class Meta: + model = ImageAttachment + fields = ['id', 'parent', 'name', 'image', 'image_height', 'image_width', 'created'] + + def get_parent(self, obj): + + # Static mapping of models to their nested serializers + if isinstance(obj.parent, Device): + serializer = NestedDeviceSerializer + elif isinstance(obj.parent, Rack): + serializer = NestedRackSerializer + elif isinstance(obj.parent, Site): + serializer = NestedSiteSerializer + else: + raise Exception("Unexpected type of parent object for ImageAttachment") + + return serializer(obj.parent, context={'request': self.context['request']}).data + + +class WritableImageAttachmentSerializer(serializers.ModelSerializer): + content_type = ContentTypeFieldSerializer() + + class Meta: + model = ImageAttachment + fields = ['id', 'content_type', 'object_id', 'name', 'image'] + + def validate(self, data): + + # Validate that the parent object exists + try: + data['content_type'].get_object_for_this_type(id=data['object_id']) + except ObjectDoesNotExist: + raise serializers.ValidationError( + "Invalid parent object: {} ID {}".format(data['content_type'], data['object_id']) + ) + + return data + + # # User actions # diff --git a/netbox/extras/api/urls.py b/netbox/extras/api/urls.py index 1623dcdeb..85ed93a24 100644 --- a/netbox/extras/api/urls.py +++ b/netbox/extras/api/urls.py @@ -23,6 +23,9 @@ router.register(r'export-templates', views.ExportTemplateViewSet) # Topology maps router.register(r'topology-maps', views.TopologyMapViewSet) +# Image attachments +router.register(r'image-attachments', views.ImageAttachmentViewSet) + # Recent activity router.register(r'recent-activity', views.RecentActivityViewSet) diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index fab1ccdb5..d5b05fab4 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -6,7 +6,7 @@ from django.http import HttpResponse from django.shortcuts import get_object_or_404 from extras import filters -from extras.models import ExportTemplate, Graph, TopologyMap, UserAction +from extras.models import ExportTemplate, Graph, ImageAttachment, TopologyMap, UserAction from utilities.api import WritableSerializerMixin from . import serializers @@ -81,6 +81,12 @@ class TopologyMapViewSet(WritableSerializerMixin, ModelViewSet): return response +class ImageAttachmentViewSet(WritableSerializerMixin, ModelViewSet): + queryset = ImageAttachment.objects.all() + serializer_class = serializers.ImageAttachmentSerializer + write_serializer_class = serializers.WritableImageAttachmentSerializer + + class RecentActivityViewSet(ReadOnlyModelViewSet): """ List all UserActions to provide a log of recent activity. diff --git a/netbox/extras/migrations/0005_add_imageattachment.py b/netbox/extras/migrations/0005_add_imageattachment.py index 23ed8b786..478762079 100644 --- a/netbox/extras/migrations/0005_add_imageattachment.py +++ b/netbox/extras/migrations/0005_add_imageattachment.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.6 on 2017-03-30 21:09 +# Generated by Django 1.10.6 on 2017-04-03 15:55 from __future__ import unicode_literals from django.db import migrations, models diff --git a/netbox/extras/models.py b/netbox/extras/models.py index cdf2af31c..9b31c3db4 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -384,7 +384,7 @@ class ImageAttachment(models.Model): """ content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() - obj = GenericForeignKey('content_type', 'object_id') + parent = GenericForeignKey('content_type', 'object_id') image = models.ImageField(upload_to=image_upload, height_field='image_height', width_field='image_width') image_height = models.PositiveSmallIntegerField() image_width = models.PositiveSmallIntegerField() diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index ca4384f08..b7dd61d0f 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -1,9 +1,10 @@ from django.conf import settings +from django.contrib.contenttypes.models import ContentType from rest_framework import authentication, exceptions from rest_framework.exceptions import APIException from rest_framework.permissions import DjangoModelPermissions, SAFE_METHODS -from rest_framework.serializers import Field +from rest_framework.serializers import Field, ValidationError from users.models import Token @@ -79,6 +80,21 @@ class ChoiceFieldSerializer(Field): return self._choices.get(data) +class ContentTypeFieldSerializer(Field): + """ + Represent a ContentType as '.' + """ + def to_representation(self, obj): + return "{}.{}".format(obj.app_label, obj.model) + + def to_internal_value(self, data): + app_label, model = data.split('.') + try: + return ContentType.objects.get_by_natural_key(app_label=app_label, model=model) + except ContentType.DoesNotExist: + raise ValidationError("Invalid content type") + + class WritableSerializerMixin(object): """ Allow for the use of an alternate, writable serializer class for write operations (e.g. POST, PUT).