Error uploading image using postman in django rest

2019-08-01 00:11发布

问题:

I'm trying to create an endpoint to upload images(using postman) to a specific folder using django rest framework. This is my settings for the folder,

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

This is my model,

class UserMedia(models.Model):
    user = models.OneToOneField(User, related_name='medias', on_delete=models.CASCADE, )
    profile_image_web = models.FileField(null=True)
    profile_image_android = models.FileField(null=True)
    profile_image_ios = models.FileField(null=True)
    thumbnail = models.FileField(null=True)

This is the Serializer,

class UserMediaSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserMedia
        fields = (
            'profile_image_web', 'profile_image_ios', 'profile_image_android', 'thumbnail',
        )

This is the api,

class CreateUpdateUserMedia(views.APIView):
    parser_classes = (MultiPartParser, FormParser)

    def post(self, request, **kwargs):
        serializer = UserMediaSerializer(request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Now when I try to upload ONE image corresponding to one of the fields using POSTMAN, this is the error I get.

'Cannot call `.is_valid()` as no `data=` keyword argument was '
AssertionError: Cannot call `.is_valid()` as no `data=` keyword argument was passed when instantiating the serializer instance.

Which is perfectly understandable, but I dont know how to fix it.

Here are my questions,

  1. How do I correctly upload images using django rest framework.

  2. I don't expect this api to be called with 4 images together, but 4 times using one image at a time, how do I pass the relevant name and modify the serializer accordingly.

  3. How do I provide a subpath to the root media directory.

  4. Finally I want the serializer to display the full image url, how do I do that?

回答1:

You are using serializer = UserMediaSerializer(request.data) but you should call it by serializer = UserMediaSerializer(data=request.data)

For uploading images in Django rest framework you should either upload images on S3 and pass s3 url in DRF API or use base64 field in serializer and send base64 encoded value of image in API

import uuid
import base64
import imghdr

from django.utils.translation import ugettext_lazy as _
from django.core.files.base import ContentFile
from rest_framework import serializers


ALLOWED_IMAGE_TYPES = (
    "jpeg",
    "jpg",
    "png",
    "gif"
)


class Base64ImageField(serializers.ImageField):
    """
        A django-rest-framework field for handling image-uploads through raw post data.
        It uses base64 for en-/decoding the contents of the file.
        """

    def to_internal_value(self, base64_data):
        # Check if this is a base64 string
        if not base64_data:
            return None

        if isinstance(base64_data, basestring):
            # Try to decode the file. Return validation error if it fails.
            try:
                decoded_file = base64.b64decode(base64_data)
            except TypeError:
                raise serializers.ValidationError(_("Please upload a valid image."))
            # Generate file name:
            file_name = str(uuid.uuid4())[:12]  # 12 characters are more than enough.
            # Get the file name extension:
            file_extension = self.get_file_extension(file_name, decoded_file)
            if file_extension not in ALLOWED_IMAGE_TYPES:
                raise serializers.ValidationError(_("The type of the image couldn't been determined."))
            complete_file_name = file_name + "." + file_extension
            data = ContentFile(decoded_file, name=complete_file_name)
            return super(Base64ImageField, self).to_internal_value(data)
        raise serializers.ValidationError('This is not an base64 string')

    def to_representation(self, value):
        # Return url including domain name.
        return value.name

    def get_file_extension(self, filename, decoded_file):
        extension = imghdr.what(filename, decoded_file)
        extension = "jpg" if extension == "jpeg" else extension
        return extension

Updated

You should use ImageField (not FileField) for images.

You can use above field directly in serializer like any other field.

class UserMediaSerializer(serializers.ModelSerializer):
    profile_image_web = Base64ImageField(required=False)
    class Meta:
        model = models.UserMedia
        fields = ('profile_image_web', 'profile_image_ios', 'profile_image_android', 'thumbnail')