Uploading Image and Text from React to Django with JSON and Proper Encoding.
Tags: django, react, webdev, json
Published: 2022-05-25 14:34:12

Uploading only text as JSON from React to Django is quite straightforward. Django serializers easily convert JSON to python native data types. JSON is just like python dictionary in strings(quotes).

But then, how can we send images along with text?. Well, sending image files fixed in the react component state by `e.target.files[0]` did not work.

A little research brought up the idea of encoding the images as base64!!😈

Okay..okay, Don't let that scare you.

Two or three lines on the frontend, we are done. We don't even need to install anything. Same thing on the backend, 2 lines and we just need to pip install a little package.

We don't even need any header on the frontend, except you are sending an authorization token. We don't even need parsers in the API.


## **<u>Highlights.</u>**
_NB: This is not a tutorial on setting up Django with React. A good tutorial on that would be a YouTube series by Brad Traversy (Traversy Media), [Full stack React and Django](https://www.youtube.com/playlist?list=PLillGF-RfqbbRA-CIUxlxkUpbq0IFkX60). <br>For pure Django, then [Django Tutorials](https://www.youtube.com/playlist?list=PL-osiE80TeTtoQCKZ03TU5fNfx2UY6U4p) by Corey Schafer._

We are gonna be using a Post model as an example (Like a Blog Post).


 **THE BACKEND (DJANGO REST)**

- Create the Post Model
- Adding `MEDIA_URL` and `MEDIA_ROOT`  to settings.
- Pip installing `drf_extra_fields` (the only installation)
- Create the serializers
- Create the API
- Set up the url.


**THE FRONTEND ( REACT )**

- Set up the `PostCreate ` component with state and `onChange ` attribute.

- Adding the Image change handler, conversion of the image to base64 with `FileReader`.

- Setting up axios and sending the JSON data with the base64 representation.


**NOW LET'S GO INTO DETAILS**<br><br>



## THE BACKEND 

**1. Create the Post model**

We will start by creating the Post model

```
from django.db import models
from django_resized import ResizedImageField
from django.utils.text import slugify
from django.utils import timezone
from django.urls import reverse
from django.contrib.auth.models import User


class Post(models.Model):
    title = models.CharField(max_length=150)
    slug = models.SlugField(blank=True)
    file = models.ImageField(null=True,upload_to = 'post_pics',blank=True)
    date_posted = models.DateTimeField(default = timezone.now)
    content = models.TextField()

    def __str__(self):
        return f'Post : {self.title}'

    def save(self, force_insert=True ,*args , **kwargs):
         if not self.slug:
            super().save(*args,**kwargs)
            pk=str(self.pk)
            slug_field = slugify(self.title) + pk
            self.slug = slug_field
            return super().save(*args,**kwargs)
         return super().save(*args,**kwargs)

    def get_absolute_url(self):
	    return reverse('post-detail',kwargs ={"slug":self.slug})

```
The image field takes null=True to allow image upload to be optional.




**2.) Adding MEDIA_URL and MEDIA_ROOT to settings.**

Next, we'll add MEDIA_ROOT AND MEDIA_URL to Django settings.py to enable us to create a local storage location for the uploaded images.


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




**3.) Pip installing drf_extra_fields (the only installation)**

 This is the only installation we will be doing in this tutorial. We need to use the `Base64ImageField `in the package to accept the base64 data. 

```
pip install drf_extra_fields
```



**4.) Create the Serializer class**

```
from rest_framework import serializers
from .models import Post, Review

from drf_extra_fields.fields import Base64ImageField

class PostSerializer(serializers.ModelSerializer):
    file = Base64ImageField()

    class Meta:
        model=Post
        fields= ('title','file','content')
```

 Notice how the file field was set to be the `Base64ImageField`. The field will receive the base64 data and will allow for conversion  back to an image.




**5.) Create the API**

```
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from .models import Post
from .serializers import PostSerializer, 


class PostAPI(APIView):
    permission_classes = [IsAuthenticated]
    def post(self,request,*args,**kwargs):
        serializer = PostSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        instance = serializer.save()
        response = {        "title":instance.title,"content":instance.content,"date":instance.date_posted.strftime("%a %H:%M %d/%m/%y"),"file":instance.file.url,
            "url":instance.get_absolute_url()
        }
        return Response(response)
```

The api gets the JSON data from the frontend, passes into the serializer, which validates and saves the data with the base64 being converted back to an image. Finally it accesses the properties of the saved instance including the url of the saved image and sends it back. I am not sending back the image to the frontend, but rather, a link to the saved image on my local drive.

You might be thinking why not send back as base 64.. Well, that would mean I can't open up the storage location and view. Also, in the frontend, I'll have to convert again from base64. So I didn't bother. So I think it is better this way.




**6.) Set up the url.**

```
from django.urls import path
from .api import PostAPI


urlpatterns=[
    path('api/create',PostAPI.as_view()),]

```

Here, we set up the URL necessary to link the react request to the api.


That's it for the Backend..



## **THE FRONTEND (REACT)**

**1. Setting up the `PostCreate` component with state and `onChange ` attribute.**

```
import React, {Component} from "react"
import axios from "axios"

class PostCreate extends Component{
    
    state = {
        title: "",
        content: "",
        file: ""
    }
   

    onchange=(e) =>{
        this.setState({[e.target.name] : e.target.value})
    }

    render(){
 
        const { title, content, file} = this.state

        return(
            <div className = 'create-form'>
                <h4 align="center" className = 'createpost-heading'>Start A New Topic</h4>
                <span className ="create-post-requirement">A Title is Enough To Start.. </span>
                <form onSubmit={this.onsubmit}>
                    <div className = 'form-field'>
                        <span className= "asterik-field">*</span>
                        <label className= "post-create-label-first" htmlFor = "id_title">Title</label><br/>
                        <input id = "id_title"
                        className = 'user-field'
                        type ='text'
                        name ='title'
                        placeholder=' Title of Post'
                        size = '110'
                        maxLength = '100'
                        value = {title} 
                        onChange={this.onchange}
                        />
                        
                    </div><br/>


                    <div id="post-create-text" className = 'form-field'>
                        <label className= "post-create-label" htmlFor = "id_content">Write Something</label>
                        <textarea id = 'id_content'
                        className = 'content-write'
                        type ='text'
                        name ='content'
                        placeholder=' Write post content'
                        rows = '7'
                        cols = '25'
                        value = {content}
                        onChange={this.onchange}>
                        </textarea>
                    </div> <br/>

                    <div id="post-create-image" className = 'form-field'>
                        <label className= "post-create-label" htmlFor = "id_postimage">Upload A Game Pic</label>
                        <input id = "id_postimage" 
                        className = 'post-image-field' 
                        type ='file' 
                        accept = 'image/*'
                        name = 'file' 
                        />
                    </div><br/>

                    <button type = "submit" className = 'form-button'>Submit</button><br/>
                    
                </form>
		</div>
            
        )
    }
}


export default PostCreate
```

Here, we have created the component for post creation and put in the fields. We have also set the `onChange ` handler for the title and content fields.




**2.) Adding the Image change handler and conversion of the image to base64 with `FileReader`.**

  Now let's set up the handler for the image field. You'll see the base64 encoding by `FileReader ` in action here.😈

 

```
imageChange = (e) =>{
        const file = e.target.files[0]
        const reader = new FileReader()
        reader.onload = () => {
            this.setState({file : reader.result})
        }

        if(file){
            reader.readAsDataURL(file)
        }

    }
```


Now, what happens here is very simple. The first line gets the uploaded file under the file variable. The next line creates a `FileReader` object. Let's visit the last block before the `reader.onload`. The last block calls `reader.readAsDataURL` on the file and converts it to base64 format. The `reader.onload` runs an arrow function when `reader.readAsDataURL` is triggered to handle a file i.e just like an event Listener. The arrow function simply sets the state with the base64 file.




**3.)Setting up axios and sending the JSON data with the base64 representation.**

We are gonna be setting up the axios in the `onSubmithandler ` function, so that the axios request is triggered on submission.

 

```
onsubmit = (e) =>{
        e.preventDefault();
        const {title,content,file} = this.state
        const token = localStorage.token
        let config={}

        if(token){
        
            config = {
                "headers": {"Authorization":`Token ${token}`
                  } 
            }
        }

        const body = {title,content,file}
        console.log(body)
        axios
        .post("api/create",body,config)

        .then(
            (res) => {
                console.log(res)
            }
        )
       .catch(
            (err)=>{
                console.log(err.response)
           }
       )
    }
```

Token was used for authentication explaining the token setting in the header. Using `JSON.stringify` wasn't necessary on the body before sending. The operation is quite simple. After preventing default submission with `e.preventDefault` , the fields were extracted from the state, and token, from `localStorage`. The axios request comes in to finish the job by sending the data and handling success with `.then`, and failure with `.catch`


**VERSIONS OF TOOLS** 

```
 Python == 3.7.2,
 Django == 3.2.13,
 drf-extra-fields == 3.4.0,
"react": "^18.0.0",
"react-dom": "^18.0.0",
"webpack": "^5.70.0",
"webpack-cli": "^4.9.2",
"axios": "^0.27.1",
"react-router-dom": "^6.3.0",
"@babel/core": "^7.17.9",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"babel-loader": "^8.2.4",
"babel-plugin-transform-class-properties": "^6.24.1"
```


And that's a wrap! I hope you enjoyed the article. I'd love to read/hear your comments. 😊