AJAX-запросы в Django на примере простейшего приложения сбора и показа сообщений

Предисловие

Веб-разработчики часто сталкиваются с необходимостью динамически обновлять страницы без полной перезагрузки. С этим хорошо справляется технология ассинхронного обмена данными 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 для отображения всех элементов:


{% load static %}

{% for message in messages %}

Отправитель: {{ message.sender_name }}

Сообщение: {{ message.message }}

Отправлено: {{ message.sent_at }}


{% endfor %}

И send_message.html:


{% if repeat %}
    
Вы уже отправляли форму, хотите отправить повторно?
{% endif %}

Отправить сообщение

{% csrf_token %} {{ form.as_p }}

По пути 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/. Нас ждет такой результат:

localhost/get_data/

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.

Получаем object { messages: ...}

Получаем object { messages: …}

Нам нужно, чтобы метод обновлял все содержимое блока #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-запрос с сохранением текущих данных на клиенте. Об этом могу написать позже, если статья вам понравиться, и вы захотите продолжения!

© Habrahabr.ru