Загрузка только текста в формате JSON из React в Django довольно проста. Сериализаторы Django легко конвертируют JSON в родные типы данных python. JSON – это просто как словарь python в строках (кавычках).
Но как же тогда мы можем отправлять изображения вместе с текстом? Отправка файлов изображений, зафиксированных в состоянии компонента react по e.target.files[0]
, не работала.
Небольшое исследование натолкнуло на идею кодировать изображения в base64!!!😈.
Ладно… ладно, пусть это вас не пугает.
Две-три строчки на фронтенде – и готово. Нам даже не нужно ничего устанавливать. То же самое на бэкенде, 2 строчки и нам просто нужно установить небольшой пакет.
Нам даже не нужен никакой заголовок на фронтенде, за исключением того, что вы отправляете токен авторизации. Нам даже не нужны парсеры в API.
Основные моменты.
NB: Это не учебник по настройке Django с React. Хорошим учебником по этому вопросу будет серия YouTube Брэда Траверси (Traversy Media), Full stack React and Django.
Для чистого Django, тогда Django Tutorials by Corey Schafer.
В качестве примера мы будем использовать модель Post (как запись в блоге).
БЭКЭНД (DJANGO REST)
- Создаем модель Post
- Добавление
MEDIA_URL
иMEDIA_ROOT
в настройки. - Pip установка
drf_extra_fields
(единственная установка) - Создайте сериализаторы
- Создайте API
- Настройте url.
ФРОНТЕНД (REACT)
-
Настройка компонента
PostCreate
с состоянием и атрибутомonChange
. -
Добавление обработчика изменения изображения, преобразование изображения в base64 с помощью
FileReader
. -
Настройка axios и отправка данных JSON с представлением base64.
ТЕПЕРЬ ПЕРЕЙДЕМ К ДЕТАЛЯМ
БЭКЕНД
1. Создание модели Post
Мы начнем с создания модели Post
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})
Поле image принимает значение null=True, чтобы загрузка изображения была необязательной.
2.) Добавление MEDIA_URL и MEDIA_ROOT в настройки.
Далее мы добавим MEDIA_ROOT и MEDIA_URL в файл Django settings.py, чтобы мы могли создать локальное место хранения для загруженных изображений.
MEDIA_ROOT = os.path.join(BASE_DIR,'media')
MEDIA_URL = '/media/'
3.) Pip-установка drf_extra_fields (единственная установка)
Это единственная установка, которую мы будем выполнять в этом руководстве. Нам нужно использовать Base64ImageField
в пакете для приема данных base64.
pip install drf_extra_fields
4.) Создайте класс Serializer
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')
Обратите внимание, что поле файла было задано как Base64ImageField
. Это поле будет получать данные в формате base64 и позволит преобразовать их обратно в изображение.
5.) Создайте 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)
API получает JSON-данные с фронтенда, передает их в сериализатор, который проверяет и сохраняет данные с преобразованием base64 в изображение. Наконец, он получает доступ к свойствам сохраненного экземпляра, включая url сохраненного изображения, и отправляет его обратно. Я отправляю обратно не изображение на фронтенд, а ссылку на сохраненное изображение на моем локальном диске.
Вы можете подумать, почему бы не отправить обратно как базу 64… Ну, это означает, что я не могу открыть место хранения и просмотреть. Кроме того, во фронтенде мне придется снова конвертировать из base64. Так что я не стал заморачиваться. Поэтому я думаю, что так будет лучше.
6.) Настройте url.
from django.urls import path
from .api import PostAPI
urlpatterns=[
path('api/create',PostAPI.as_view()),]
Здесь мы устанавливаем URL, необходимый для связи запроса react с api.
Вот и все для бэкенда.
ФРОНТЕНД (REACT)
1. Настройка компонента PostCreate
с состоянием и атрибутом onChange
.
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
Здесь мы создали компонент для создания поста и поместили в него поля. Мы также установили обработчик onChange
для полей title и content.
2.) Добавление обработчика изменения изображения и преобразование изображения в base64 с помощью FileReader
.
Теперь давайте настроим обработчик для поля image. Вы увидите кодирование base64 с помощью FileReader
в действии здесь.😈.
imageChange = (e) =>{
const file = e.target.files[0]
const reader = new FileReader()
reader.onload = () => {
this.setState({file : reader.result})
}
if(file){
reader.readAsDataURL(file)
}
}
Теперь, что здесь происходит, очень просто. Первая строка получает загруженный файл в переменную file. Следующая строка создает объект FileReader
. Давайте посетим последний блок перед reader.onload
. Последний блок вызывает reader.readAsDataURL
на файле и конвертирует его в формат base64. reader.onload
запускает стрелочную функцию, когда reader.readAsDataURL
срабатывает для обработки файла, т.е. как слушатель событий. Функция стрелки просто устанавливает состояние с файлом base64.
3.)Настройка axios и отправка данных JSON с представлением base64.
Мы будем настраивать axios в функции onSubmithandler
, чтобы запрос axios срабатывал при отправке.
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)
}
)
}
Для аутентификации использовался токен, объясняющий установку токена в заголовке. Использование JSON.stringify
в теле перед отправкой было необязательным. Операция довольно проста. После предотвращения отправки по умолчанию с помощью e.preventDefault
, поля были извлечены из состояния, а токен – из localStorage
. Запрос axios завершает работу, отправляя данные и обрабатывая успех с помощью .then
, а неудачу с помощью .catch
.
ВЕРСИИ ИНСТРУМЕНТОВ
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"
Вот и все! Надеюсь, вам понравилась статья. Буду рад прочитать/выслушать ваши комментарии. 😊