How to upload image from Ionic to Django using Django Rest Framework ?

In this tutorial, i will show you how to take an image using Capacitor Camera plugin and upload it to our Django backend from our ionic application.
You will find the repository source code here.

Take or choose an image using Capacitor Camera plugin

Let’s imagine we would allow our user to set or change his profile picture. First we will add a new page

ionic g page UploadPicture

Then as always, i will move the new created page in the pages folder and modiy the app-routing.module.ts to reflect this change

 { path: 'upload-picture', loadChildren: './pages/upload-picture/upload-picture.module#UploadPicturePageModule' }

Ok now we can edit the upload-picture.page.ts file to include the capacitor Camera plugin which will be used to take or select a photo

import { Component, OnInit } from '@angular/core';
import { Plugins, CameraResultType,CameraSource } from '@capacitor/core';
const { Camera } = Plugins;
@Component({
  selector: 'app-upload-picture',
  templateUrl: './upload-picture.page.html',
  styleUrls: ['./upload-picture.page.scss'],
})
export class UploadPicturePage implements OnInit {
  constructor() { }
  ngOnInit() {
  }
}

And then implement a method which will ask the user if he wants to choose or take a photo (default behaviour of the capacitor Camera plugin)

async chooseOrTakePicture() {
    const image = await Plugins.Camera.getPhoto({
      quality: 90,
      allowEditing: false,
      resultType: CameraResultType.Base64,
      //source: CameraSource.Camera
    }).catch((error)=>{
      console.log(error)
    })
    // variable image should contain our base64 image
  }

You can find all the options available for the Camerag plugin on the Capacitor documentation page

Convert a base64 image into a blob in our Ionic application

The capacitor Camera plugin should return the image in base64 format because we ask for it using the line

resultType: CameraResultType.Base64,

It is better to transform the image into a binary file because sometimes some weird conversion errors can occured between javascript base64 and python base64 format.

To transform a base64 image to a blob we can use this code

  public b64toBlob(b64Data, contentType = '', sliceSize = 512) {
    const byteCharacters = atob(b64Data);
    const byteArrays = [];
 
    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
      const slice = byteCharacters.slice(offset, offset + sliceSize);
 
      const byteNumbers = new Array(slice.length);
      for (let i = 0; i < slice.length; i++) {
        byteNumbers[i] = slice.charCodeAt(i);
      }
 
      const byteArray = new Uint8Array(byteNumbers);
      byteArrays.push(byteArray);
    }
 
    const blob = new Blob(byteArrays, { type: contentType });
    return blob;
  }

So now our method will look like

  async chooseOrTakePicture() {
    const image = await Plugins.Camera.getPhoto({
      quality: 90,
      allowEditing: false,
      resultType: CameraResultType.Base64,
      //source: CameraSource.Camera
    }).catch((error)=>{
      console.log(error)
    })
    // variable image should contain our base64 image
    if (image){
      // convert base64 image to blob
      let blob = this.b64toBlob(image.base64String)
      
    }
   
  }

Upload image by using a form from our Ionic application to Django

Now that we have our binary image we will use a standard angular form to send data to our Django backend. We will first need to check if network is available and if so, we can send the form

 if (this.apiService.networkConnected){
        //Create a form to send the file
        const formData = new FormData();
        //Generate a fake filename
        let name =Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 10);
        formData.append('file', blob, name+.${image.format});
        formData.append('name', name);
        this.apiService.uploadPhoto(formData).subscribe((value)=>{
          //server return value
        })
      }
      else{
        this.apiService.showNoNetwork()
      }

The form will be composed of the file and a fake name that we randomly generate.

We need to create the uploadPhoto method into our usual django-api service and will use the fetch method to send the form. We also need to define our new api endpoint which will be called uploadphotobinary

 getUploadPhotoUrlBinary= this.virtualHostName + this.apiPrefix + "/uploadphotobinary/"
uploadPhoto(formData) {
 
  var myHeaders = new Headers();
  myHeaders.append("Authorization", "Bearer "+ this.tokenSSO);

  var requestOptions = {
      method: 'POST',
      headers: myHeaders,
      body: formData
  };
  return Observable.create(observer => {
  fetch(this.getUploadPhotoUrlBinary, requestOptions)
  .then(response => response.json())
  .then(result => {
      
      observer.next(result)
  })
  .catch(error => {console.log('error', error);
      observer.next()});
  })
}

Receive and save an image using Django Rest Framework

To receive and save the image in our Django backend, we first need to declare the model that will be used to save photo. So we can add the following models to our models.py file

class Photo(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)
    file = models.ImageField(upload_to='dossiers/(%Y_%m)/', null=True, blank=True)
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)

Now we declare our new endpoint in the urls.py file

 url(r'^uploadphotobinary/$', PhotoUploadView.as_view()),

Then we declare the PhotoUploadView in our views.py file

//Adding some imports
from rest_framework.views import APIView
from rest_framework.parsers import FileUploadParser
from rest_framework.response import Response
from rest_framework import status
class PhotoUploadView(APIView):
    parser_class = (FileUploadParser,)

    def post(self, request, *args, **kwargs):
      print("=== DANS POST METHOD")
      print(request.data)
      file_serializer = PhotoUploadSerializer(data=request.data)

      if file_serializer.is_valid():
          file_serializer.save()
          return Response(file_serializer.data, status=status.HTTP_201_CREATED)
      else:
          print(file_serializer.errors)
          return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST)

and we declare the new PhotoUploadSerializer in our serializers.py file

class PhotoUploadSerializer(ModelSerializer):
    class Meta:
        model = Photo
        fields = '__all__'

Christophe Surbier