5

I'm trying to upload multiple images with django rest api. I followed the following approach. But when I select one or more files and try to send them to the server as form data, I get the following error message:

AttributeError at /api/photo/ 'bytes' object has no attribute 'name'

Model:

class Photo(models.Model):
    image = models.ImageField(upload_to='audio_stories/')

Serializer:

class FileListSerializer ( serializers.Serializer ) :
    image = serializers.ListField(
                child=serializers.FileField( max_length=100000,
                                         allow_empty_file=False,
                                        use_url=False )
                                )
    def create(self, validated_data):
        image=validated_data.pop('image')
        for img in image:
            photo=Photo.objects.create(image=img,**validated_data)
        return photo

View:

class PhotoViewSet(viewsets.ModelViewSet):
    serializer_class = FileListSerializer
    parser_classes = (MultiPartParser, FormParser,)
    queryset=Photo.objects.all()

URL

router.register('api/photo', PhotoViewSet, 'photocreate')

I dont know how to approach the error, as i have nothing in my code that relates to "name"?

2
  • How are you uploading the photos? Commented Nov 11, 2020 at 21:14
  • @Paul via Postman. Commented Nov 11, 2020 at 21:44

2 Answers 2

3

The error seemed to be in the serializer. I had to set the use_url=True.

Serializer:

class FileListSerializer ( serializers.Serializer ) :
    image = serializers.ListField(
                child=serializers.FileField( max_length=100000,
                                         allow_empty_file=False,
                                        use_url=True )
                                )
    def create(self, validated_data):
        image=validated_data.pop('image')
        for img in image:
            photo=Photo.objects.create(image=img,**validated_data)
        return photo

Extended Answer

The above answer works but produces an large null array. In order to make the code work I had to seperate my two models in Story and Story_Media. Each instance of Story Media contains a single image and provides a FK to the Story.

class Story (models.Model):
    title = models.CharField(max_length=100, blank=True)
    description = models.TextField(blank=True)
    date_posted = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return f'{self.id} Story'

class Story_Media (models.Model):
    story = models.ForeignKey(Story,on_delete=models.CASCADE, null=True, related_name = 'story_media', related_query_name = 'story_media')
    file = models.FileField(upload_to='story_media/',  null=True, validators=[validate_file_extension_image])
    isTitlePicture = models.BooleanField(blank=False, null=True)

    def __str__(self):
        return f'{self.id} Media'

In my serializer, a new Sotry_Media instance is created for each image included in the incoming data. In my case, it was necessary to create a story even if no images were uploaded, so the two conditions are included.

# Story  Serializer_Media_Serializer
class Story_Media_Serializer (serializers.ModelSerializer):

    class Meta:
        model = Story_Media
        fields =  ('id','isTitlePicture', 'file',)


# Story  Serializer
class StoryCreateUpdateSerializer (serializers.ModelSerializer):
    story_media = Story_Media_Serializer(many=True, required = False)


    class Meta:
        model = Story
        fields =  ('title','description', )

    def create(self, validated_data):
        current_user = self.context["request"].user

        # Story  contains images
        if 'story_media' in validated_data:
            story_media = validated_data.pop('story_media')
            story_instance = Story.objects.create(author=current_user, **validated_data)
            for img in story_media:
                Story_Media.objects.create(**img, story=story_instance)
            return story_instance

        # Story  is not containing images
        if 'story_media'not in validated_data:
            story_instance = Story.objects.create(author=current_user, **validated_data)
            return story_instance

class StoryCreateUpdateViewSet(viewsets.ModelViewSet):
    serializer_class = StoryCreateUpdateSerializer

    http_method_names = ['post','delete','put','patch', 'head']

    queryset = Story.objects.all()

    permission_classes = [
        permissions.IsAuthenticated, PostOwnerPermssion
    ]

API Post

Sign up to request clarification or add additional context in comments.

7 Comments

I was stuck on the same issue . Your approach helped me a lot. I tried out your way , its working fine but while uploading files through postman , it is showing a huge null array . Do you know why it is happening ?
yes, i encountered the same problem with the null ary and solved it. let me share my code when i am back at my pc.
can you help me up ? Thanks
@Mitesh i extended my answer.
can you please update your viewset ? thanks
|
0

Maybe you should consider implementing like i did which is working for me

Model

class Photo(models.Model):
    title = models.CharField(max_length=255, blank=True)
    image = models.ImageField(upload_to='hotel_photos')
    hotel = models.ForeignKey(
        Hotels, on_delete=models.CASCADE, null=True, blank=True,)

    class Meta:
        verbose_name_plural = 'Photos'

    def __str__(self):
        """Prints the name of the Photo"""
        return f'{self.hotel} photos'

Serializer

class PhotoSerializer(ModelSerializer):
    class Meta:
        model = Photo
        fields = (
            "image",
            "hotel",
        )

Helper function

def modify_input_for_multiple_files(hotel, image):
    dict = {}
    dict['hotel'] = hotel
    dict['image'] = image
    return dict

Views.py

class PhotoUploadView(ListCreateAPIView):
    """
    This API view handles multiple images upload form
    It will also display images for specific hotel
    """
    parser_classes = (MultiPartParser, FormParser)
    # http_method_names = ['get', 'post', 'head']

    def get(self, request):
        all_images = Photo.objects.all()
        permission_classes = (IsAdminOrOwner,)
        serializer_class = PhotoSerializer(all_images, many=True)
        return JsonResponse(serializer.data, safe=False)

    def post(self, request, *args, **kwargs):
        hotel = request.data.get('hotel')
        images = dict((request.data).lists())['image']
        flag = 1
        arr = []
        for img_name in images:
            modified_data = modify_input_for_multiple_files(hotel, img_name)
            file_serializer = PhotoSerializer(data=modified_data)

            if file_serializer.is_valid():
                file_serializer.save()
                arr.append(file_serializer.data)
            else:
                flag = 0

        if flag == 1:
            return Response(arr, status=status.HTTP_201_CREATED)
        else:
            return Response(arr, status=status.HTTP_400_BAD_REQUEST)

Hope this can help you or anyone stuck. In case of any question shoot it please

2 Comments

Thanks for your answer. I try to add multiple files that are strored in a file list. It appears to me that your code assumes for each file an individual form?
The code assumes that you are adding photos to another model such as a blog photos, user photos etc. But you can easily customize the code to just upload photos without necessarily tying to another model

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.