Danioss
Danioss

Reputation: 3105

Django Rest Framework + Angular 2 - uploading multiple files

I'm using Django Rest Framework as my backend and Angular 2 for my frontend. I've got this page in Angular, where I create a form:

createResourcesForm() {
  this.resourcesForm = this.formBuilder.group({
    resources: this.formBuilder.array([
      this.formBuilder.group({
        title: ['', Validators.compose([Validators.required])],
        file: ['', Validators.compose([])],
      })
    ])
  })
}

As you can see, the form consists of FormArray, where every element has two inputs: title and file - a text input and a file input respectively. On submitting the form I'm trying to send the data to Django but I get an error Missing filename. Request should include a Content-Disposition header with a filename parameter.. I could set it easily but I'm expecting to receive a list of {title, file}, so how to set multiple file names? Any other idea how I could do this?

The error in Django Rest Framework comes from parse method in FileUploadParser.

I'm not pasting any Python code here because it's a standard ListCreateAPIView, nothing special about it. Here is my serializer:

class ResourceCreateSerializer2(serializers.Serializer):
    author_pk = serializers.IntegerField(required=False)
    author_first_name = serializers.CharField(max_length=50, required=False)
    author_last_name = serializers.CharField(max_length=50, required=False)
    resources = ResourceWithoutAuthorSerializer(many=True)

class ResourceWithoutAuthorSerializer(serializers.ModelSerializer):
    instruments = InstrumentSerializer(many=True)

    class Meta:
        model = MusicResource
        fields = ['title', 'file', 'instruments']

Don't mind the other fields, they are being sent just fine (as the file does). One more thing to say - I'm adding a content-type header in Angular just before sending the data.

UPDATE 1 Here is my method for uploading files (Angular 2):

get value() {
  let formData = new FormData();

  for (let i = 0; i < this.resources.length; i++) {
    let resource = this.resources.value[i];
    let fileName = resource.file;
    let fileInputs = $('input[type="file"]').filter(function () {
      return this.value == fileName;
    });

    if (fileInputs.length == 0) {
      return null;
    }

    let fileInput = <HTMLInputElement>fileInputs[0];

    formData.append('resources-' + i + '-title', resource.title);
    formData.append('resources-' + i + '-file', fileInput.files[0], fileInput.files[0].name);

    for (let j = 0; j < this.instrumentsSelect.value.length; j++) {
      formData.append('resources-' + i + '-instruments', this.instrumentsSelect.value[j]);
    }
  }

  return formData;
}

then

this.musicResourcesService.addMusicResource(toSend).subscribe(
  data => console.log('successfuly added resources'),
  err => console.log('MusicResourcesAddComponent', 'onMusicResourceFormSubmit', err)
);

addMusicResource(data) {
  let headers = new Headers({});
  headers.append('Content-Type', 'multipart/form-data');
  headers.append('Accept', 'application/json');
  let options = new RequestOptions({headers});

  return this.api.post('resources/resources/list_create/', data, true, options);
}

public post(url: any, payload: any, noToken?, options?: any): Observable<any> {
  const provider = noToken ? this.http : this.authHttp;
  const fulLUrl = this.conf.getAPIUrl() + url;
  return provider.post(fulLUrl, payload, options)
    .delay(100)
    .map(this.extractData)
    .catch(this.handleError).share();
}

Upvotes: 0

Views: 1018

Answers (1)

Danioss
Danioss

Reputation: 3105

I did not like @Robert's answer and did not receive any other idea so after hours of reseraching it turns out that I was missing two things:

  1. The parser should have been set to MultiPartParser, not FileUploadParser
  2. There is no need to set the Content-Type header manually, it will get filled automatically along with the boundary which I was missing

Also, to make sure Django receives all the data and understands it, I had to change

formData.append('resources-' + i + '-title', resource.title);

and similar lines to

formData.append('resources[' + i + ']title', resource.title);

Upvotes: 1

Related Questions