Загрузка изображений и текста из React в Django с помощью JSON и правильной кодировки.

Загрузка только текста в формате 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"
Вход в полноэкранный режим Выйти из полноэкранного режима

Вот и все! Надеюсь, вам понравилась статья. Буду рад прочитать/выслушать ваши комментарии. 😊

Оцените статью
Procodings.ru
Добавить комментарий