Предисловие
Веб-разработчики часто сталкиваются с необходимостью динамически обновлять страницы без полной перезагрузки. С этим хорошо справляется технология ассинхронного обмена данными AJAX, однако я не нашел на просторах интернета простого мануала использования AJAX и решил создать его сам. В этой статье я собираюсь подробно показать взаимодействие фронтенда с AJAX и бекенда с Django, ограничившись минимумом кода. Статья больше рассчитана на новичков и станет отличной базой для дальнейшего развития в теме.
Часть 1. Подготовка проекта
Для демонстрации я создам небольшой Django проект, который имеет всего лишь одно приложение, обрабатывающее сообщение от пользователя. Вы можете скачать проект с GitHub отсюда или создать что-то подобное самостоятельно. Познакомимся с структурой приложения поближе.
Структура приложения
app/
│
│ ├── app/
│ └── #Конфигурация проекта
│ ├── modules/ #Здесь хранятся приложения
│ └── messages_app/
│ ├── models.py #Хранит модель Message (Имя отправителя, Текст, Время отправки)
│ ├── forms.py #Хранит форму модели Message (Имя отправителя, Текст сообщения)
│ ├── urls.py #Два адреса, обрабатывают views
│ ├── views.py #Может отправить на шаблон отображения сообщений (messages.html) или на шаблон отправки сообщения (send_message.html)
│ └── # Другие настройки по умолчанию
│ ├── templates/ #Здесь хранится весь фронт
│ ├── messages.html # Обрабатывает все сообщения
│ ├── send_message.html # Отправка сообщения для сохранение его в модель Message
│ └── src/
│ └── js/
│ └── ajax.js/ #Здесь будет хранится код JS, пока файл пуст
Все django-приложения будут храниться в modules, а html-шаблоны в templates в папке проекта, код JS будет хранится в templates/src/js.
Дальше я подробно разберу файлы внутри структуры проекта: приложения и шаблоны. Если вы в состоянии разобраться самостоятельно, то можете смело пропустить этот шаг и перейти ко второй части.
Рассмотрим единственную модель приложения:
#/modules/models.py
from django.db import models
class Message(models.Model):
sender_name = models.CharField(max_length=100) # Имя отправителя
message = models.TextField() # Текст сообщения
sent_at = models.DateTimeField(auto_now_add=True) # Время отправки
Я уже создал два тестовых сообщения для отображения. Эта модель используется во views.py, для отображения всех элементов и для создания нового элемента:
#/modules/views.py
from django.shortcuts import render
from .models import Message
from .forms import MessageForm
# Create your views here.
def get_all_mesages(request):
messages = Message.objects.all()
return render(request, 'messages_app/messages.html', {'messages': messages} )
def send_message(request):
if request.method == 'POST':
form = MessageForm(request.POST)
if form.is_valid():
form.save() # Сохраняем форму в базе данных
return render(request, 'messages_app/send_message.html', {'form': form, 'repeat': True})
else:
form = MessageForm()
return render(request, 'messages_app/send_message.html', {'form': form})
Во views используется форма из forms.py:
#/modules/forms.py
from django import forms
from .models import Message
class MessageForm(forms.ModelForm):
class Meta:
model = Message
fields = ['sender_name', 'message']
HTML-шаблоны
Используемые шаблоны - message.html для отображения всех элементов:
<!-- /templates/message.html -->
{% load static %}
<div id="messages-container">
{% for message in messages %}
<hr>
<p>Отправитель: {{ message.sender_name }}</p>
<p>Сообщение: {{ message.message }}</p>
<p>Отправлено: {{ message.sent_at }}</p>
<hr>
{% endfor %}
</div>
<script src="{% static 'messages_app/src/js/ajax.js' %}"></script>
И send_message.html:
<!-- /templates/send_message.html -->
{% if repeat %}
<div> Вы уже отправляли форму, хотите отправить повторно?</div>
{% endif %}
<h1>Отправить сообщение</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Отправить</button>
</form>
По пути templates/src/js/ находится ajax.js, который сейчас пуст. В дальнейшем мы заполним его кодом.
Часть 2. Внедрение AJAX
Внутри AJAX-запроса используется стандартный HTTP-запрос, такой же, как и обычный GET или POST, однако нам, для эффективного использования AJAX, данные нужно завернуть в JSON-формат. Для этого мы будем использовать JsonResponse на бекенде - перейдем во views.py и создадим еще один метод, который будет заворачивать экземпляры модели в JSON.
#/modules/views.py
#...Ранее добавленные импорты и методы
from django.http import JsonResponse
def ajax_messages(request):
messages = Message.objects.all() #Получим все сообщения из модели
#Обработаем модель
messages_data = []
for message in messages:
messages_data.append({
'sender_name': message.sender_name,
'message': message.message,
'sent_at': message.sent_at.strftime('%Y-%m-%d %H:%M:%S'), # Преобразуем дату в строку
})
return JsonResponse({'messages': messages_data})
Добавим отображение метода в urls.py
#/modules/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.get_all_mesages, name='get_all_messages'),
path('send/', views.send_message, name='send_message'),
#ajax
path('get_data/', views.ajax_messages, name='get_data')
]
Попробуем перейти по ссылке https:localhost/get_data/. Нас ждет такой результат:
Теперь мы можем получать по этому адресу данные из модели в JSON-форматe. Далее создадим AJAX-запрос - для этого на странице отображения напишем следующий JS код и поместим его в ajax.js
// templates/src/js/ajax.js
let get_data_url = 'get_data/' // Переход на ссылку с ajax запросом
function draw_messages(messages) { // То что мы будем делать с нашими данными
console.log(messages) // Сейчас мы просто хотим вывести их в консоль
}
function ajax_get(){
return fetch(get_data_url, {
method: 'GET',
}).then(response => response.json()) // Преобразуем полученный ответ в JSON
.then(data => draw_messages(data)) // Обрабатываем данные, полученные в ответе с помощью функции draw_message [3 строка]
.catch(error => console.error('Ошибка:', error)); // Если возникли ошибки, выводим их в консоль
}
Мы создали асинхронный GET-запрос который обращается к адресу /get_data/. Уже сейчас, если в консоли браузера на странице отображения сообщений мы введем ajax_get() - получим тот самый ответ в json - элементы модели Message.
Нам нужно, чтобы метод обновлял все содержимое блока #messages-container, для этого изменим метод draw_messages внутри ajax.js
// templates/src/js/ajax.js
let get_data_url = 'get_data/' // Переход на ссылку с ajax запросом
const messagesContainer = document.querySelector('#messages-container'); // Сюда помещаются наши статьи
function draw_messages(messages) {
// Очистим контейнер перед добавлением новых сообщений
messagesContainer.innerHTML = '';
// Для каждого сообщения в массиве messages создаем HTML
messages.forEach(message => {
const messageElement = document.createElement('div');
// Создаем элементы для отображения данных сообщения
const senderName = document.createElement('p');
senderName.textContent = `Отправитель: ${message.sender_name}`;
const messageText = document.createElement('p');
messageText.textContent = `Сообщение: ${message.message}`;
const sentAt = document.createElement('p');
sentAt.textContent = `Отправлено: ${message.sent_at}`;
// Создаем разделитель
const hr = document.createElement('hr');
// Добавляем все элементы в messageElement
messageElement.appendChild(senderName);
messageElement.appendChild(messageText);
messageElement.appendChild(sentAt);
messageElement.appendChild(hr);
// Вставляем messageElement в контейнер
messagesContainer.appendChild(messageElement);
});
}
function ajax_get() {
fetch(get_data_url, {
method: 'GET', // Используем метод 'GET'
})
.then(response => response.json()) // Преобразуем ответ в JSON
.then(data => {
draw_messages(data.messages); // Передаем полученные сообщения в draw_messages
})
.catch(error => console.error('Ошибка:', error)); // Обрабатываем ошибки
}
При AJAX-запросе мы полностью очищаем #messages-container и обновляем содержимым из GET-запроса.
Эта простая и местами примитивная реализация поможет понять всю суть процесса, однако сейчас все равно чего-то не хватает - добавим интервал в 1 секунду для ajax_get(), чтобы данные обновлялись автоматически, и нам не пришлось перезагружать страницу. Для этого добавим в конец ajax.js:
// templates/src/js/ajax.js
// ...Переменные и методы
setInterval(ajax_get, 1000);
Проверить результат можно просто: откройте страницу с отображением сообщений и страницу с отправкой. Заполните форму отправки и вернитесь на страницу с сообщениями, она автоматически обновится.
В этой статье мы рассмотрели простой GET-запрос. Однако такой подход не подойдет, если объем данных в модели будет слишком большим. В таком случае нужно реализовать POST-запрос с сохранением текущих данных на клиенте. Об этом могу написать позже, если статья вам понравиться, и вы захотите продолжения!
Комментарии (5)
Ipatov_e
01.12.2024 13:38Видимо, опечатка в коде:
let get_data_url = 'get_data/'
Этот кусок кода как будто не отсюда. Лучше константой объявить. А так же в стиль написания должен быть camelCase, как у всех остальных дальше по коду.
const getDataUrl = 'get_data/';
Neychychyen Автор
01.12.2024 13:38Согласен, лучше всего и правильнее передать ее вместе с контекстом в
ajax_messages (ИЗ /modules/views.py), но я решил не
обременять статью излишними объяснениями и дополнениями в коде и просто скопировал ссылку из бекенда, чтобы сосредоточить читателя на самом методе.
ilya_chch
01.12.2024 13:38js в
templates
- не самое хорошее решение. для этого естьstatic
.Django-приложения в
modules
. зачемmodules
? при условии, что в корне проекта будетapp
,modules
,templates
иmanage.py
, создание дополнительного неймспейса, кажется, не дает никакого выйгрыша.app
в названии приложений.-
Про HTMX уже говорили. Я согласен, что он был бы тут очень кстати. Но, как вариант, можно рассмотреть Alpine Ajax
еще как вариант использовать django-render-block, чтобы было удобнее работать с htmx/alpine
Sergey_Kh
Такое можно делать с использованием htmx буквально одной строчкой в шаблоне, не нужно столько js кода.
Neychychyen Автор
Спасибо, не знал про htmx, но похоже зря, интересная библиотека