Django 5 для начинающих

Прогресс по курсу:  9/1004

11.2 Создание древовидных комментариев, добавление JavaScript
2 из 2 шагов пройдено

Работа с JavaScript

В папке templates, создадим папку js, для хранения скриптов, а в ней два файла: comments.js и backend.js.


Добавим в файл backend.js следующий JavaScript код:

const getCookie = (name) => {
  let cookieValue = null;
  if (document.cookie && document.cookie !== "") {
    const cookies = document.cookie.split(";");
    for (let i = 0; i < cookies.length; i++) {
      const cookie = cookies[i].trim();
      // Does this cookie string begin with the name we want?
      if (cookie.substring(0, name.length + 1) === name + "=") {
        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
        break;
      }
    }
  }
  return cookieValue;
};

const csrftoken = getCookie("csrftoken");

В этом коде мы определяем функцию getCookie, которая принимает имя куки (cookie) и возвращает ее значение.

Эта функции проверяет, есть ли у нас сохраненные куки, и если да, то эти куки разделяются по символу ; и перебираются в цикле.

Для каждого элемента, функция проверяет, начинается ли ее строковое представление с имени куки, которое мы ищем. Если да, то значение куки декодируется с помощью decodeURIComponent и сохраняется в переменной cookieValue, после чего цикл прерывается.

В конце функция возвращает значение cookieValue. После этого, определяется переменная csrftoken, которая получает значение куки с именем csrftoken, вызвав функцию getCookie. Посмотреть в документации.


Теперь добавим в файл comments.js следующий JavaScript код для добавления комментариев:

const commentForm = document.forms.commentForm;
const commentFormContent = commentForm.content;
const commentFormParentInput = commentForm.parent;
const commentFormSubmit = commentForm.commentSubmit;
const commentPostId = commentForm.getAttribute('data-post-id');

commentForm.addEventListener('submit', createComment);

replyUser()

function replyUser() {
  document.querySelectorAll('.btn-reply').forEach(e => {
    e.addEventListener('click', replyComment);
  });
}

function replyComment() {
  const commentUsername = this.getAttribute('data-comment-username');
  const commentMessageId = this.getAttribute('data-comment-id');
  commentFormContent.value = `${commentUsername}, `;
  commentFormParentInput.value = commentMessageId;
}
async function createComment(event) {
    event.preventDefault();
    commentFormSubmit.disabled = true;
    commentFormSubmit.innerText = "Ожидаем ответа сервера";
    try {
        const response = await fetch(`/post/${commentPostId}/comments/create/`, {
            method: 'POST',
            headers: {
                'X-CSRFToken': csrftoken,
                'X-Requested-With': 'XMLHttpRequest',
            },
            body: new FormData(commentForm),
        });
        const comment = await response.json();

        let commentTemplate = `<ul id="comment-thread-${comment.id}">
                                <li class="card border-0">
                                    <div class="row">
                                        <div class="col-md-2">
                                            <img src="${comment.avatar}" style="width: 120px;height: 120px;object-fit: cover;" alt="${comment.author}"/>
                                        </div>
                                        <div class="col-md-10">
                                            <div class="card-body">
                                                <h6 class="card-title">
                                                    <a href="${comment.get_absolute_url}">${comment.author}</a>
                                                </h6>
                                                <p class="card-text">
                                                    ${comment.content}
                                                </p>
                                                <a class="btn btn-sm btn-dark btn-reply" href="#commentForm" data-comment-id="${comment.id}" data-comment-username="${comment.author}">Ответить</a>
                                                <hr/>
                                                <time>${comment.time_create}</time>
                                            </div>
                                        </div>
                                    </div>
                                </li>
                            </ul>`;
        if (comment.is_child) {
            document.querySelector(`#comment-thread-${comment.parent_id}`).insertAdjacentHTML("beforeend", commentTemplate);
        }
        else {
            document.querySelector('.nested-comments').insertAdjacentHTML("beforeend", commentTemplate)
        }
        commentForm.reset()
        commentFormSubmit.disabled = false;
        commentFormSubmit.innerText = "Добавить комментарий";
        commentFormParentInput.value = null;
        replyUser();
    }
    catch (error) {
        console.log(error)
    }
}

Этот код реализует функционал добавления комментариев к статье на сайте, выполняя следующие функции:

  • Создает переменные для хранения формы комментария, содержания, родительского элемента комментария, кнопки отправки и ID статьи.
  • Назначает функцию createComment для события отправки формы комментария.
  • Функция replyUser добавляет обработчики события для кнопок Ответить, которые позволяют пользователю ответить на другой комментарий.
  • Назначает функцию replyComment для кнопки Ответить каждого комментария, чтобы ответить на комментарий.
  • Определяет функцию createComment для отправки данных формы комментария на сервер. Она использует fetch API, чтобы отправить POST запрос на сервер для создания нового комментария.
  • Она также добавляет заголовки X-CSRFToken и X-Requested-With с текущим значением csrftoken и XMLHttpRequest, соответственно.
  • Далее мы получаем ответ от сервера в виде JSON, от представления CommentCreateView, который добавляем в переменную comment.
  • В переменной commentTemplate мы создаем шаблон, и добавляем полученные данные.
  • В функции также проверяется условие, является ли комментарий вложенным или нет, и в зависимости от условия выводим в определенном элементе.


Заключительным шагом мы подключим статику в настройках, для этого в settings.py добавим путь для статичных файлов:

STATIC_URL = '/static/'
STATIC_ROOT = (BASE_DIR / 'static')
STATICFILES_DIRS = [BASE_DIR / 'templates/js/']


Теперь давайте запустим сервер и проверим работу комментариев:

Затем нажмём кнопку Добавить комментарий:

Теперь попробуем ответить сами себе:

Мы видим что наши древовидные комментарии прекрасно работают.

В админ панели комментарии будут выглядеть следующим образом:

На этом мы закончим разработку комментариев, в следующем разделе мы добавим теги к нашим записям.


Будьте вежливы и соблюдайте наши принципы сообщества. Пожалуйста, не оставляйте решения и подсказки в комментариях, для этого есть отдельный форум.
Оставить комментарий

Спорный момент с JavaScript. Не уверен, что оно здесь нужно. Намного интереснее было бы, если бы учились интегрировать в проект готовые скрипты. Что-то вроде: вот то, что сделали фронтэндеры, давайте все это подключим и т.д. А то получается много копирования без понимания сути происходящего. Уважаемые авторы! Очень хороший курс, единственный качественный на этой платформе, но хотелось бы как-то поменьше внимания уделять второстепенному и больше основам. Потому как, когда начинаешь проект делать самостоятельно вылезают неожиданные ошибки. Например, с представлениями на основе классов: поди разберись, какой метод переопределить, чтобы получить желаемый результат.

@Кирилл_Семенихин, Смотрите какая ситуация, обычно когда в команде фронтэндеры, то мы пишем апи для взаимодейтвия с фронтом. А в данном случае хотелось показать как мы можем взаимодействовать с JS на примере.

По поводу представления на основе классов, все методы например для ListView можем посмотреть здесь. И уже понять какой метод за что отвечает, чтобы переопределить его.

@Илья_Перминов, А можно поподробнее про фронт? То есть, в основном, все происходит через API, например, rest-framework? Просто, начинающему трудно все уложить в голове. Я прохожу курс от Практикума и там тоже: сначала прошли django, потом rest-framework и я так и не понял: одно исключает другое? Это разные подходы? Более старый и более перспективный? На степике есть бесплатный курс по FAST API. (Это еще кто??!!?!) Может, стоит пройти для расширения портфолио? Не понятно тогда, зачем всеми этими templates шаблонами заниматься, в какую сторону идти? Я пока хочу делать упор на бэкенде и не идти в фуллстек. (И правильно ли это?)

Спасибо.

@Кирилл_Семенихин, В основном все крупные проекты работают через API. Это два разных подхода. FAST API это фреймворк по созданию API на питоне. Курс по нему стоит пройти, кстати мы начали писать новый курс как раз по данному фреймворку. Можете написать мне в телеграм, контакт есть в описании курса, я попробую вам ответить на остальные вопросы.