How To Upload Image and Text from React to Django with JSON and Proper Encoding.
Frank Ezenwanne
Posted on May 25, 2022
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.
Highlights.
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.
For pure Django, then Django Tutorials 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
andMEDIA_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 andonChange
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
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. 😊
Posted on May 25, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
May 25, 2022