38

In django rest framework, I am able to upload single file using danialfarid/ng-file-upload

views.py:

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

    def perform_create(self, serializer):
        serializer.save(blogs=Blogs.objects.latest('created_at'),
                   image=self.request.data.get('image'))

serializers.py:

class PhotoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Photo

models.py:

class Photo(models.Model):
    blogs = models.ForeignKey(Blogs, related_name='blogs_img')
    image = models.ImageField(upload_to=content_file_name)

When I try to upload multiple file. I get in

chrome developer tools: Request Payload

------WebKitFormBoundaryjOsYUxPLKB1N69Zn
Content-Disposition: form-data; name="image[0]"; filename="datacable.jpg"
Content-Type: image/jpeg


------WebKitFormBoundaryjOsYUxPLKB1N69Zn
Content-Disposition: form-data; name="image[1]"; filename="datacable2.jpg"
Content-Type: image/jpeg

Response:

{"image":["No file was submitted."]}

I don't know how to write serializer for uploading multiple file.

2
  • might i ask how you created the request to have an array of images available on the api side? Commented Jun 27, 2017 at 10:52
  • As mentioned in post you can see I am using api for multiple file upload is danialfarid/ng-file-upload Commented Jun 27, 2017 at 18:17

8 Answers 8

43

I manage to solve this issue and I hope it will help community

serializers.py:

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):
        blogs=Blogs.objects.latest('created_at')
        image=validated_data.pop('image')
        for img in image:
            photo=Photo.objects.create(image=img,blogs=blogs,**validated_data)
        return photo

class PhotoSerializer(serializers.ModelSerializer):

    class Meta:
        model = Photo
        read_only_fields = ("blogs",)

views.py:

class PhotoViewSet(viewsets.ModelViewSet):
    serializer_class = FileListSerializer
    parser_classes = (MultiPartParser, FormParser,)
    queryset=Photo.objects.all()
Sign up to request clarification or add additional context in comments.

6 Comments

I tried to replicate this solution as is but am getting an error 'bytes' object has no attribute 'name' during POST and GET. Thing is though that the file was uploaded and the entries have been made in the database but the response has an error.
I think you are using browseable api . where this error comes , but in actual program this problem does not come
No as I copy pasted your answer directly. Could it be the package versions. Am using Django==1.11 and djangorestframework==3.6.2. Also, I notice you are not making use of the PhotoSerializer anywhere despite you declaring it in your answer because the serializer_class that you make use of is FileListSerializer in the PhotoViewSet.
wait I will clarify it and update tonight. Now It is not possible .I am using django 1.9.x and rest is 3.5.x
Instead of putting create method in serializer, put it in the views. 2 serializer are not at all required, removing the create method and putting the rest of FileListSerializer in PhotoSerializer would be enough. The solution really worked for me.
|
11

I dont know it very well, but this is working... This is for my viewset.

def perform_create(self, serializer):
    obj = serializer.save()
    for f in self.request.data.getlist('files'):
        mf = MyFile.objects.create(file=f)
        obj.files.add(mf)

1 Comment

getlist is a saviour. I was getting the data from request and that was making the data unrecognisable.
1

Here is how you upload multiple files on blogs api:

models.py

class Blogs(models.Model):
    ...
 

class Photo(models.Model):
    blogs = models.ForeignKey(Blogs, related_name='blogs_img')
    image = models.ImageField(upload_to=content_file_name)

serializers.py

class PhotoSerializer(serializers.ModelSerializer):

    class Meta:
        model = Photo
        fields = ['blogs', 'image',]


class BlogsSerializer(serializers.ModelSerializer):
    photos = serializers.SerializerMethodField()

    def get_photos(self, obj):
        photos = Photo.objects.filter(blogs=obj)
        return PhotoSerializer(photos, many=True, read_only=False).data

    class Meta:
        model = Blogs
        fields = [
            ...
            'photos',
    ]

views.py

class BlogsViewSet(viewsets.ModelViewSet):
    serializer_class = BlogsSerializer
    queryset = Blogs.objects.all()

    def create(self, request, *args, **kwargs):
        instance_data = request.data
        data = {key: value for key, value in instance_data.items()}
        serializer = self.get_serializer(data=data)
        serializer.is_valid(raise_exception=True)
        instance = serializer.save()

        if request.FILES:
            photos = dict((request.FILES).lists()).get('photos', None)
            if photos:
                for photo in photos:
                    photo_data = {}
                    photo_data["blogs"] = instance.pk
                    photo_data["image"] = photo
                    photo_serializer = PhotoSerializer(data=photo_data)
                    photo_serializer.is_valid(raise_exception=True)
                    photo_serializer.save()

        return Response(serializer.data)

1 Comment

what are you doing here? instance_data = request.data data = {key: value for key, value in instance_data.items()}
1

I have solved the issue with this solution

models.py:

class Product(models.Model):
   title = models.CharField(max_length=255)
   description = models.CharField(max_length=255)

class Images(models.Model):
   product = model.ForeignKey('Product', related_name="images", on_delete=models.CASCADE)
   image = models.ImageField(upload_to=upload_path)

serializers.py

class ImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Images
        fields = '__all__'

views.py

class ImagesViewSet(ModelViewSet):
    queryset = models.Images.objects.all()
    serializer_class = serializers.ImageSerializer
    
    # overwrite create method from the CreateModelMixin
    def create(self, request, *args, **kwargs):
        data = request.data
        images = data.getlist('image')
        
        # if no images call parent method it will return error
        if not images:
            return super().create(request, *args, **kwargs)

        # verify only without creating the images
        serializer_lst = []
        for image in images:
            data['image'] = image
            serializer = self.get_serializer(data=data)
            serializer.is_valid(raise_exception=True)
            serializer_lst.append(serializer)
        
        serializers_data = [] # this is to collect data for all created images and return as list in the response
        for serializer in serializer_lst:
            self.perform_create(serializer)
            serializers_data.append(serializer.data)
            headers = self.get_success_headers(serializer.data)
        
        return Response(serializers_data, status=status.HTTP_201_CREATED, headers=headers)

Comments

0

It took me a while to find out an effective solution to this, I would like to share with you what finally worked for me. I am using reactjs and DRF.

Here is my model :

class MediaFile(models.Model):
    article = models.ForeignKey(Articles, on_delete=models.CASCADE, null=True)
    caption = models.CharField(max_length=500, null=True, blank=True)
    file = models.FileField('photo of article', upload_to=set_filename,
                            blank=True, null=True, default='')
    added = models.DateTimeField(auto_now_add=True)

The views are standard viewsets.ModelViewSet

class MediaFilesViewSet(viewsets.ModelViewSet):
    serializer_class = FileListSerializer
    parser_classes = (parsers.MultiPartParser, parsers.FormParser,)
    queryset=MediaFile.objects.all()

In ArticleSerializer I added:

def create(self, validated_data):
    request = self.context.get('request')
    user = request.user
    instance = Articles.objects.create(**validated_data)
    instance.publisher = user
    instance.save()
    images = request.FILES
    if images:
        try:
            for f in images.getlist('mediafiles'):
                instance.mediafile_set.get_or_create(file=f, caption=f.name)
                instance.save()
        except Exception as e:
            print(e)
    return instance

The FRONTEND structure:

postChangeImage = (event) =>{
    this.setState({
        is_images: true
    });
    let files = event.target.files;
    const files_array = Object.values(files);

    this.setState(
        {imagefile: files}
    );

    console.log('IMAGES ARRAY', files_array);
    files_array.map(value => {
        const urls = URL.createObjectURL(value);
        this.setState((prevState)=>(
            {postfolio:[urls, ...prevState.postfolio]}
            ));
    });

};

and POSTING:

for (let i=0; i< this.state.imagefile.length; i++) {
                    form_data.append(`mediafiles`, this.state.imagefile[i]);
                }

2 Comments

instance.mediafile_set.get_or_create(file=f, caption=f.name) .... where is this method
The article model has a 1 to many relationship with Mediafile so you can access it with mediafiles_set
0

The best answer to this question did not work for me, but Charles' suggestion worked very well. In my case, I needed to upload multiple files and assign them to a specific batch. Each batch is assigned to a particular user.

Below is more context using ReactJS to make the POST request, along with the serializers used and Postman window:

api.py

from convert_files.models import File
from rest_framework import viewsets, permissions
from rest_framework.parsers import MultiPartParser, JSONParser
from .serializers import BatchSerializer

class BatchViewSet(viewsets.ModelViewSet):
    permission_classes = [
        permissions.IsAuthenticated
    ]

    def perform_create(self, serializer):
        obj = serializer.save(owner=self.request.user)
        for f in self.request.data.getlist('files'):
            mf = File.objects.create(office_file=f)
            obj.files.add(mf)

    parser_classes = (MultiPartParser, JSONParser, )

    serializer_class = BatchSerializer

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

    def get_queryset(self):
        return self.request.user.batches.all()

serializers.py

from rest_framework import serializers
from convert_files.models import File, Batch

class FileSerializer(serializers.ModelSerializer):
    class Meta:
        model = File
        fields = '__all__'


class BatchSerializer(serializers.ModelSerializer):
    files = FileSerializer(many=True, required = False)

    class Meta:
        model = Batch
        fields =  '__all__'

models.py

from django.db import models
from django.conf import settings
from django.contrib.auth.models import User

from .extra import ContentTypeRestrictedFileField

def make_upload_path(instance, filename):
    """Generates upload path for FileField"""
    return settings.OFFICE_OUTPUT_FILES_URL + "/%s" % (filename)

class Batch(models.Model):
    name = models.CharField(max_length=100, blank=True)
    description = models.TextField(blank=True)
    date_posted = models.DateTimeField(default=datetime.datetime.now)
    owner = models.ForeignKey(User, related_name="batches", 
                            on_delete=models.CASCADE, null=True)

class File(models.Model):
    name = models.CharField(max_length=100, blank=True)
    office_file = ContentTypeRestrictedFileField(
        upload_to           = make_upload_path,
        content_types       = ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                            'application/vnd.ms-excel','application/msword',
                            'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
        max_upload_size     = 10485760,
    )

    files = models.ForeignKey(Batch, on_delete=models.CASCADE, null=True, 
                                    related_name='files', related_query_name='files')

FileUpload.js

import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { addBatch } from '../actions/batches';

function FileUpload() {

  const dispatch = useDispatch();
  let formData = new FormData()

  const onDrop = useCallback((acceptedFiles) => {
    for (var i = 0; i < acceptedFiles.length; i++) {
      formData.append("files", acceptedFiles[i], acceptedFiles[i].name)
    }
    dispatch(addBatch(formData));
  })

...

Postman

Image of POST request in Postman for Multiple File Upload to DRF

Comments

0

Working with "dictionary (array) of images"

Ok, so today I tried Arindam's solution.. it worked perfectly, but after a while, I figgured out that my frontend (port 3000) makes a GET request to itself looking for an image that is not there, and not at the backend(port 8000).. (eg. GET http://localhost:3000/media/images/products/default.png - Returns 404: Not found).. What worked for me was to change the code around a bit and this is my solution..

in models.py

class Product(models.Model):
    title = models.CharField(max_length=255)
    description = models.CharField(max_length=255)
    price = models.FloatField()
    quantity = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)
    active = models.BooleanField(default=False)
    slug = models.SlugField(max_length=255, unique=True)
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)
...

class ProductImage(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='images')
    image = models.ImageField("Image", upload_to=upload_to, default='products/default.png')

in serializers.py

...
class ProductImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = ProductImage
        fields = ['id', 'image', 'product']
        extra_kwargs = {
        'product': {'required': False},
        }

class ProductSerializer(serializers.ModelSerializer):
    images = ProductImageSerializer(many=True, required=False)

    class Meta:
        model = Product
        fields = ['id', 'title', 'description', 'images', 'price', 'quantity', 'active', 'slug', 'created_at', 'modified_at']
        read_only_fields = ['slug']
        #lookup_field = 'slug'

    def create(self, validated_data):        
        product = Product.objects.create(**validated_data)
        try:
            # try to get and save images (if any)
            images_data = dict((self.context['request'].FILES).lists()).get('images', None)
            for image in images_data:
                ProductImage.objects.create(product=product, image=image)
        except:
            # if no images are available - create using default image
            ProductImage.objects.create(product=product)
        return product
    

in views.py

class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    permission_classes = [permissions.AllowAny]
    serializer_class = ProductSerializer
    parser_classes = [MultiPartParser, FormParser]
    #lookup_field = 'slug'

edit: in settings.py

import os
...
BASE_DIR = Path(__file__).resolve().parent.parent
...
MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/')
MEDIA_URL = '/media/'

I am posting this here to help someone (or me again in the future) with multiple files upload or multiple images upload as I spent 2 days looking up tutorials and answeres online to help me solve this issue... I may not be doing everything perfectly as I just recently started exploring Django REST Framework (and Python), but I hope it helps.

1 Comment

Thanks a lot. It saved me. but as in your code it's adding the files field with file null and id and all. what if I want to keep the whole files field null if no images? it will be great help if you answer me thanks
0

I wanted to suggest another way based on list_serializer_class. And using perform_create to add the primary key.Perhaps someone will find this method useful.

models.py

class PrimaryModel(models.Model):
    title = models.CharField(
        max_length=100
        )

class File(models.Model):
    file = models.FileField(
        upload_to=directory_path,
        verbose_name='File'
        )
    primary_model = models.ForeignKey(
        PrimaryModel,
        on_delete=models.CASCADE,
        related_name="files",
        )

serializers.py

class FileListSerializer(serializers.ListSerializer):

    def create(self, validated_data):
        files = [File(**item) for item in validated_data]
        return File.objects.bulk_create(files)


class FileSerializer(serializers.ModelSerializer):

    class Meta:
        model = File
        exclude = ["primary_model"]
        list_serializer_class = FileListSerializer

views.py

from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from django.http import Http404
from django.db import transaction
from django.core.exceptions import ValidationError


...

class FileUpload(generics.CreateAPIView):
    permission_classes = [IsAuthenticated]
    serializer_class = FileSerializer
    queryset = File.objects.all()

    def get_object(self):
        try:
            id = self.kwargs.get('pk', None)
            obj = PrimaryModel.objects.get(id=id)
            return obj
        except PrimaryModel.DoesNotExist:
            raise Http404

    def perform_create(self, serializer):
        primary_model = self.get_object()
        return serializer.save(primary_model=primary_model)

    @transaction.atomic
    def create(self, request, *args, **kwargs):
        files = request.FILES.getlist("files")
        if len(files)==0:
            return Response(status=status.HTTP_400_BAD_REQUEST)
        data_list = [{"file": file} for file in files]
        serializer = self.get_serializer(data=data_list, many=True)
        serializer.is_valid(raise_exception=True)
        try:
            self.perform_create(serializer)
            return Response(
                serializer.data, status=status.HTTP_201_CREATED)
        except ValidationError as e:
            return Response(
                e, status=status.HTTP_400_BAD_REQUEST)

urls.py

...

urlpatterns = [
    path(
        'primary_model/<int:pk>/upload_files/',
        views.FileUpload.as_view(),
        name='upload_files',
        ),
]

Comments

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.