Подключение React приложения к Django API
24.07.2024
18.12.2024
13 минут
151
0
0
0
0
Введение
В данной статье я опишу процесс объединения React и Django под одним проектом. Мы настроим API для общения обоих между собой, а так же подключим TailwindCSS библиотеку, дабы упростить создание сайта ещё сильнее.
Весь код статьи можно скачать с github, ветка называется simple-integration-react-django https://github.com/DmRafaule/SearchResultParser/tree/simple-integration-react-django
Создание базового проекта на Django
Корневая директория будет называться SearchResultParser. В ней создайте новый django проект. Он тоже будет иметь название SearchResultParser. После установки базового проекта, можно перейти к созданию первого приложения для данного проекта.
Добавление приложения к Django проекту
Игнорируй всё ниже описанное в данной главе, если ты хочешь просто подключить react к djanog и создать своё приложение.
Перед тем как ты перейдёшь по ссылке в заголовке, ты должен знать, что название у приложения будет Main, а название модели в ней будет Result, для админки модель будет называться ResultAdmin. И ты должен будешь добавить следующие поля в неё:
- user (CharField)
- file (FileField)
- time_created (DateTimeField)
Настройка API для общения между React и Django
Установка APIs
pip install djangorestframework django-cors-headers
В файле settings.py, необходимо будет добавить ново установленные приложения, coreheaders и rest_framework в INSTALLED_APP, а так же добавить одну мидлвари corsheaders.middleware.CorsMiddleware в MIDDLEWARE список.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
'rest_framework',
'Main.apps.MainConfig',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
]
Добавь следующие строчки внизу файла settings.py
CORS_ORIGIN_WHITELIST = [
'http://localhost:3000'
]
Создание cериализатора для модели
Чтобы фронтенд мог работать с моделями, необходимо их как-то получать. Для этого я создам сериализатор(экспортёр), который превратит отправляемые бэкендом записи баз данных, в json файлы.
Создай новый файл в директории приложения Main.
cd Main
touch serializers.py
Добавь следующий контент:
from rest_framework import serializers
from .models import Result
class ResultSerializer(serializers.ModelSerializer):
class Meta:
model = Result
fields = (‘id’, 'user', 'file', 'time_created')
Подготовка и настройка APIs
В файле Main/views.py добавим новое представление для API. Файл будет выглядеть так:
from django.shortcuts import render
from rest_framework import viewsets
from .serializers import ResultSerializer
from .models import Result
class ResultView(viewsets.ModelViewSet):
serializer_class = ResultSerializer
queryset = Result.objects.all()
def home(request):
return render(request, 'Main/home.html')
В файле SearchResultParser/urls.py нужно будет создать роутер и подключить новые пути.
Вот новое содержимое файла:
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from Main import views
router = routers.DefaultRouter()
router.register(r'results', views.ResultView, 'result')
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
path('', include('Main.urls'))
]
После всего этого у тебя появятся следующие адреса для навигации:
- localhost:8000/api/results – Здесь ты можешь просмотреть все записи в твоей базе данных
- localhost:8000/api/results/1/ - Так ты можешь просмотреть одну единственную запись, 1 это id записи


Создание базового проекта для React
Я создал react проект в корневой директории, где .venv и назвал его searchresultparser-frontend.
Устанавливаем и подключаем TailwindCSS к проекту (опционально)
Опционально, если хотите использовать что-нибудь другое.
Заходим в директорию react проекта:
cd searchresultparser-frontend
Установим пакет
npm install -D tailwindcss
Создадим файл конфигурации (tailwind.config.js) и отредактируем его
npx tailwindcss init
Замени содержание файла searchresultparser-frontend/tailwind.config.js на
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
Добавим базовые директивы в searchresultparser-frontend/src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
Начнём сборку и запустим проект
npm run start
Теперь библиотека tailwindcss готова к использованию.
Создадим базовый интерфейс на React
Модальное окно
В директории searchresultparser-frontend/src/ создайте новый файл.
touch Modal.js
Данный файл описывает, появление и вид модального окна. Которое нам потребуется в будущем для отправки различных запросов к API нашего сайта. Вставьте следующий код в файл Modal.js:
import React from "react";
const Modal = ({ isOpen, onClose, children }) => {
if (!isOpen) return null;
return (
{children}
Close
);
};
export default Modal;
Страница приложения
Следующий «кусочек» кода описывает базовый интерфейс для общения с сервером и выполнения таких базовых операций как CRUD. Откройте файл App.js и вместо кода по умолчанию вставьте следующий код:
import React, { useState } from 'react';
import Modal from './Modal';
var items =[
{
id: 1,
user: "dima",
file: "0020kfjewl01232",
time_created: "11.07.24"
},
{
id: 2,
user: "artur",
file: "ert0k355wl01232",
time_created: "12.07.24"
},
{
id: 3,
user: "pasha",
file: "aaaakfje23ddf32",
time_created: "13.07.24"
},
];
let currentChildren = null
function App() {
const [isModal, setOpen] = React.useState(false);
const renderItems = () => {
return items.map((item) => (
{item.user+'__'+item.file}
editItem(item)} className="bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform">Edit
deleteItem(item)} className="bg-orange-300 pl-3 pr-3 hover:bg-orange-50 active:scale-150 hover:cursor-pointer transition-transform">Delete
));
}
const handleClose = () => {
setOpen(false);
};
const handleOpen = () => {
setOpen(true);
};
const deleteItem = (item) => {
handleOpen();
currentChildren =
DELETE ITEM
Are you sure ?
};
const createItem = () => {
handleOpen();
currentChildren =
ADD NEW ITEM
};
const editItem = (item) => {
handleOpen();
currentChildren =
EDIT ITEM
};
return (
Add new item
{renderItems()}
);
}
export default App;

Опять же, не особо зацикливайтесь на данном «кусочке» кода, мы его всё равно в следующей главе будем менять и препарировать. Вот так.
Соединение React и Django
Axios или fetch ?
После того как с фронтендом и бэкендом было покончено, нам необходимо их как-то связать. Для это предлагаю использовать javascript библиотеку axios.
Почему не fetch, а axios ?
Лично я выбрал axios потому что мне понравился его синтаксис, он просто элегантней и ещё одно, он поддерживает старые браузеры и их версии. Вот. Но я так же прикреплю таблицу их сравнения, что бы вы смогли сами решить что из этого лучше.
Фичи | fetch | axios |
---|---|---|
Доступность | Встроенный | Требуется установка |
Синтакс | Подробный, более шаблонный | Краткий, удобный для пользователя |
Обратка JSON | Ручная | Автоматическая |
Обработка ошибок | Ручная | Встроенная |
Наличие перехватчиков | Не доступно | Доступно |
Закрытие запросов | Не доступно | Доступно |
Поддержка браузеров | Только современные браузеры | Поддержка старых версий доступна |
Установим и настроим axios.
npm install axios
После установки в файл package.json можно добавить путь до нашего сервера. Вставьте это значение:
"proxy": "http://localhost:8000",
Это вообще не обязательно, но позволяет не писать лишний раз полный адрес вашего сервера. Меньше кода, меньше ошибок.
Теперь осталось только указать, где и откуда мы собираемся обращаться к API нашего приложения.
Пошагово, с комментариями меняем код для App.js
Импорты и переменные состояния
Во-первых, импортируем функцию из react: useEffect . Во-вторых, импортируем сам axios. В-третьих, уберём var items. Итого, до определения функции App у нас будет только 3 импорта, вот так:
import React, { useEffect, useState, useCallback } from 'react';
import Modal from './Modal';
import axios from "axios";
Все последующие куски кода будут вставляться в функцию App
function App() {
…
}
В начале, объявляем необходимые переменные и привязываем к ним функции для их изменения.
const [isModal, setOpen] = useState(false);
const [isUpdate, setUpdateTrigger] = useState(true);
const [itemsData, setItemsData] = useState(null);
const [formData, setFormData] = useState({
username: "",
datetime: "",
file: null,
})
const [formBody, setFormBody] = useState(null);
Модальное окно
Для появления и скрытия модального окна, добавим следующие функции:
const handleClose = () => {
setOpen(false);
};
const handleOpen =() => {
setOpen(true);
};
Они работают таким образом, что при изменении переменной isModal, модальное окно будет либо появляться, либо исчезать.
Отрисовка списка
Давай сразу вставим код, который будет отрисовывать наш список. Этот код мы вставим в конце функции App.
return (
Add new item
{/* Place for a list of displaying items */}
{itemsData ? (itemsData.map((item) => (
{item.user+'__'+item.file}
editItem(item)} className="bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform">Edit
deleteItem(item)} className="bg-orange-300 pl-3 pr-3 hover:bg-orange-50 active:scale-150 hover:cursor-pointer transition-transform">Delete
))) : (Loading...
)
}
{/* Place for modal window */}
);
Отрисовка модального окна, CRUD
Как видно из шаблона отрисовки списка, есть такие функции как:
- createItem
- editItem
- deleteItem
Идея этих функций проста. При нажатии одной из кнопок, к которым данные функции присоединены, будет вставлена соответствующая форма в компонент Modal.
const deleteItem = (item) => {
handleOpen();
setFormBody(
DELETE ITEM
Are you sure ?
)
};

const createItem = () => {
handleOpen();
setFormBody(
ADD NEW ITEM
)
};

const editItem = (item) => {
handleOpen();
setFormBody(
EDIT ITEM
)
};

Вставь их выше return.
Обновление данных форм
В данных формах за обновление данных отвечает функция handleChange
const handleChange = (e) => {
if (e.target.name === 'file') {
formData.file = e.target.files[0]
setFormData(formData)
} else if (e.target.name === 'username') {
formData.username = e.target.value
setFormData(formData)
} else if (e.target.name === 'date') {
formData.datetime = e.target.value
setFormData(formData)
}
}
Общение с сервером
За непосредственную отправку запросов на сервер отвечают функции:
- editingSubmit
- creationSubmit
- deletionSubmit
Они и отвечают за правильное использование axios библиотеки или fetch API
Функция creationSubmit отправляет POST запросы по адресу /api/results/ . Она сохраняет и отправляет введённые данные и триггерит обновление списка.
const creationSubmit = (form_el) =>{
form_el.preventDefault()
var form_data = new FormData();
form_data.append("user", formData.username)
form_data.append("time_created", formData.datetime)
form_data.append("file", formData.file)
// Use the fetch API to send the form data to the server
axios.post('/api/results/', form_data)
.then(data => {
console.log('Success:', data);
setUpdateTrigger(true)
})
.catch(error => {
console.error('Error:', error);
});
handleClose()
};
const creationSubmit = (form_el) =>{
form_el.preventDefault()
var form_data = new FormData();
form_data.append("user", formData.username)
form_data.append("time_created", formData.datetime)
form_data.append("file", formData.file)
fetch(`http://localhost:8000/api/results/`,{
method: 'POST',
headers: {
'Accept': 'application/json',
},
body: form_data
})
.then( response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
console.log('Success');
setUpdateTrigger(true)
})
.catch(error => {
console.error('There was a problem with your fetch operation:', error);
});
handleClose()
};

Функция editingSubmit отправляет PUT запросы по адресу /api/results/{ID} . Она сохраняет и отправляет введённые данные и триггерит обновление списка.
const editingSubmit = (form_el) => {
console.log(form_el)
form_el.preventDefault()
var form_data = new FormData();
form_data.append("user", formData.username)
form_data.append("time_created", formData.datetime)
form_data.append("file", formData.file)
// Use the fetch API to send the form data to the server
axios.put(`/api/results/${form_el.target.id}/`, form_data)
.then(data => {
console.log('Success:', data);
setUpdateTrigger(true)
})
.catch(error => {
console.error('Error:', error);
});
handleClose()
}
const editingSubmit = (form_el) => {
console.log(form_el)
form_el.preventDefault()
var form_data = new FormData();
form_data.append("user", formData.username)
form_data.append("time_created", formData.datetime)
form_data.append("file", formData.file)
fetch(`http://localhost:8000/api/results/${form_el.target.id}/`,{
method: 'PUT',
headers: {
'Accept': 'application/json',
},
body: form_data
})
.then( response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
console.log('Success');
setUpdateTrigger(true)
})
.catch(error => {
console.error('There was a problem with your fetch operation:', error);
});
handleClose()
}

Функция deletionSubmit отправляет DELETE запросы по адресу /api/results/{ID} . Тем самым удаляя из списка соответствующую запись по ID. Так же в конце триггерит обновление списка.
const deletionSubmit = (item) => {
axios.delete(`/api/results/${item.id}/`)
.then((res) => setUpdateTrigger(true));
handleClose()
}
const deletionSubmit = (item) => {
fetch(`http://localhost:8000/api/results/${item.id}/`,{
method: "DELETE",
headers: {
'Accept': 'application/json',
}
})
.then( response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
setUpdateTrigger(true)
})
.catch(error => {
console.error('There was a problem with your fetch operation:', error);
});
handleClose()
}

Обновление списка
До этого момента я писал о том что функции триггерят обновление списка. В чём дело, а дело в функции useEffect. В моём случае эта функция вызывает handleUpdate, только тогда когда переменная состояния isUpdate (смотри выше) изменена. И меняется она в функциях подтверждения удаления, изменения и добавления записей в базу данных.
const handleUpdate = () => {
axios.get('/api/results/')
.then(response => {
setItemsData(response.data); // Save the data in state
})
.catch(error => {
console.error('There was an error fetching the data!', error);
});
setUpdateTrigger(false)
};
const handleUpdate = () => {
fetch('http://localhost:8000/api/results/',{
method: 'GET',
headers: {
'Accept': 'application/json',
},
})
.then( response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
const dataArray = Array.isArray(data) ? data : [data];
setItemsData(dataArray); // Save the data in state
})
.catch(error => {
console.error('There was a problem with your fetch operation:', error);
});
setUpdateTrigger(false)
};
Вставь вызов функции useEffect перед самым началом return.
useEffect(handleUpdate, [isUpdate]);
Если использовать useEffect без списка зависимостей (т. е. isUpdate в нашем случае ), react будет отправлять запросы об обновлении списка постоянно, что конечно сильно нагружает сервер.
На этом всё. React приложение готово и оно успешно обменивается запросами с сервером.
Готовый код для App.js
Заключение
Конечно, я не рассказал о том как деплоить данный проект на сервер, или то как react будет взаимодействовать с шаблонами от django. Конечно, для них будут отдельные статьи и всё со временем уляжеться. Ну а пока вот так.
Весь код статьи можно скачать с github, ветка называется simple-integration-react-django https://github.com/DmRafaule/SearchResultParser/tree/simple-integration-react-django
Комментарии
(0)
Отправить
Сейчас тут пусто. Буть первым (o゚v゚)ノ