Django + HTMX CRUD приложение


Введение

Прошли времена написания Ajax-запросов с помощью javascript, просто добавьте несколько параметров в теги HTML-контента, и вы будете готовы к отправке запросов на ваш бэкэнд. Итак, мы возвращаемся в прошлое и корректируем наше представление об API и рендеринге на стороне клиента/сервера. Мы обращаемся к модели Hypermedia для использования серверной обработки данных. Давайте намочим наши ноги в этой древней, но революционной методологии разработки с помощью HTMX.

Да, HTMX можно использовать для API/вызовов на стороне сервера непосредственно в HTML. Мы будем изучать основы HTMX, создавая базовое CRUD-приложение.

Что такое HTMX?

Первый вопрос, который может возникнуть, — что и почему HTMX? HTMX — это замечательная библиотека, это библиотека javascript, но подождите. Это библиотека javascript, разработанная для того, чтобы позволить нам писать меньше или вообще не писать javascript. Она служит для отправки AJAX-запросов без написания javascript. Она использует встроенные функции браузера прямо из HTML.

Таким образом, мы можем использовать HTMX для создания интерактивных шаблонов в нашем Django-приложении. Мы можем динамически вызывать и получать данные с сервера, используя простые HTML-атрибуты, такие как hx-get, hx-post и т.д. Мы рассмотрим их в этой статье.

Вы можете ознакомиться с исходным кодом, использованным в этой статье, на этом репозитории GitHub.

Настройка проекта Django

Мы создадим проект Django с нуля и разработаем базовое приложение для блога. Мы создадим довольно простой проект с парой приложений, таких как user для аутентификации и article для CRUD части нашего приложения блога.

Для настройки проекта django мы можем выполнить следующие команды, чтобы быстро начать работу с базовым проектом django.

mkdir htmx_blog
python3 -m venv .venv
source .venv/bin/activate
pip install django
django-admin startproject htmx_blog .
Войти в полноэкранный режим Выйти из полноэкранного режима

У меня есть базовая модель пользователя, которую я использую для простой системы аутентификации в некоторых базовых проектах django, вы можете определить свое собственное приложение пользователя или получить его отсюда.

Таким образом, мы будем использовать модель пользователя для модели статьи, которую мы определим далее. Создав базовую функциональность регистрации, вы можете приступать к работе!

Создание приложения для статей

Для работы с htmx нам понадобится как минимум приложение, поскольку модели, представления и URL мы определим позже, когда будем настраивать htmx.

django-admin startapp article
Войдите в полноэкранный режим Выход из полноэкранного режима

После создания приложения вы можете добавить ярлыки этих приложений в конфиг INSTALLED_APPS в файле settings.py. Приложение user и приложение article должны быть добавлены в установленные приложения, чтобы django мог выбрать их для различных контекстов, связанных с проектом.

# htmx_blog/settings.py

INSTALLED_APPS = [
    ...
    ...
    ...

    'article',  
    'user',
]
Вход в полноэкранный режим Выход из полноэкранного режима

Мы закончили с базовой настройкой, нам также потребуется еще несколько конфигураций для правильной работы проекта.

Шаблоны настроек и статические файлы

Шаблоны будут играть важную роль в htmx-части, поэтому не менее важно правильно их настроить, прежде чем приступать к htmx и рендерингу данных на стороне клиента.

Мне нравится хранить все шаблоны в одной папке в BASE_DIR с отдельными подпапками для конкретных приложений. Также для более крупного проекта можно использовать одну папку static с подпапками css, js и images.

mkdir templates static
Вход в полноэкранный режим Выйдите из полноэкранного режима

Далее настройте созданные статические и шаблоны в настройках.


TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, "templates")],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

STATIC_URL = 'static/'
STATICFILES_DIRS = [str(BASE_DIR/ "static")]
STATIC_ROOT = BASE_DIR / "staticfiles"
Войти в полноэкранный режим Выйти из полноэкранного режима

Начальная миграция

Запустите команду миграции для модели пользователя и модели по умолчанию в проекте django.

python manage.py makemigrations
python manage.py migrate
Войти в полноэкранный режим Выйти из полноэкранного режима

Итак, этот проект также будет включать в себя маршруты аутентификации, простой регистрации и входа/выхода. Мы будем использовать модель Django User по умолчанию, создав абстрактного пользователя на случай, если нам потребуются дополнительные атрибуты.

Настройка HTMX

Нам не нужно много настраивать для использования HTMX, поскольку это библиотека javascript, мы можем вызвать ее через CDN или установить вручную и связать статические файлы javascript. В любом случае, оба варианта одинаково хороши, вам может нравиться один, мне может нравиться другой.

Если у вас уже есть базовый шаблон, вы можете просто поместить приведенный ниже скрипт в тег head шаблона. Это сделает доступными атрибуты htmx.

<script src="https://unpkg.com/htmx.org@1.8.0"></script>
Вход в полноэкранный режим Выход из полноэкранного режима

Если у вас нет базового шаблона, вы можете создать его, создав HTML-файл в каталоге templates. Название может быть любым, но будьте внимательны, так как оно может быть другим. В качестве шаблона для этого проекта я выберу base.html. Он будет выглядеть примерно следующим образом:

<!-- tempaltes/base.html -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTMX Blog</title>
    {% load static %}
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <script src="https://unpkg.com/htmx.org@1.8.0"></script>
</head>
<body >
        <nav>
        <h2>HTMX Blog</h2>
        <div class="navbar">
          {% if user.is_authenticated %}
            <a class="nav-item nav-link" href="{% url 'logout' %}"><button class="btn btn-link">Logout</button></a>
          {% else %}
            <a class="nav-item nav-link" href="{% url 'login' %}"><button class="btn btn-link">Login</button></a>
            <a class="nav-item nav-link" href="{% url 'register' %}"><button class="btn btn-link">Register</button></a>
          {% endif %}
        </div>
        </nav>

    {% block body %}
    {% endblock %}
</body>
</html>
Вход в полноэкранный режим Выход из полноэкранного режима

У меня есть панель навигации с моими представлениями аутентификации пользователя, просто кнопка входа или регистрации, если пользователь не вошел в систему, и кнопка выхода из системы, если пользователь аутентифицирован. Мы добавили файл скрипта htmx из CDN непосредственно перед концом тега head. Мы также включили файл CSS bootstrap для создания достойного пользовательского интерфейса, который мы будем создавать в этом посте.

Это один из способов внедрения htmx в HTML-шаблон, вы даже можете загрузить файл javascript с htmx cdn. Далее, его можно загрузить или вставить в локальную папку и использовать как статический файл или внедрить непосредственно в HTML-шаблон.

Определение моделей

Мы начнем учебник с определения модели создаваемого приложения. Здесь мы создадим простую модель Article с несколькими параметрами, такими как title, content, author и т.д.

from django.db import models
from user.models import Profile

class Article(models.Model):
    Article_Status = (
        ("DRAFT", "Draft"),
        ("PUBLISHED", "Published"),
    )
    title = models.CharField(max_length=128, unique=True)
    content = models.TextField()
    author = models.ForeignKey(Profile, on_delete=models.CASCADE)
    status = models.CharField(
        max_length=16,
        choices=Article_Status,
        default=Article_Status[0],
    )

    def __str__(self):
        return self.title
Вход в полноэкранный режим Выйти из полноэкранного режима

В приведенной выше модели Article, у нас есть несколько полей, таких как title простое символьное поле, content как текстовое поле, поскольку это будет большой текст, как тело поста, author который является внешним ключом к модели пользователя. У нас также есть статус, который определяется как символьное поле, но с несколькими вариантами, такими как draft или published, мы можем далее изменить этот статус как public или private. Но это просто и легко для понимания.

Имя ссылки на объект для этой модели — это название, которое мы определили в методе dunder string. Итак, простая модель создана, теперь мы можем перенести изменения в базу данных для добавления таблиц и атрибутов.

python manage.py makemigrations
python manage.py migrate
Вход в полноэкранный режим Выйти из полноэкранного режима

Это позволит выполнить миграцию в базу данных, т.е. преобразовать класс модели python в таблицы и атрибуты базы данных. Итак, после успешного завершения процесса миграции мы можем перейти к основной части этой статьи — разработке представлений. В следующем разделе мы будем использовать модели в наших представлениях для представления данных на шаблонах.

Создание формы статьи

Прежде чем погрузиться в раздел представлений, нам понадобится несколько вещей, таких как форма статьи, которая будет формой на основе модели Django. Она очень поможет нам в создании или обновлении полей для модели статьи. Мы можем определить форму в файле python под названием forms.py, не обязательно хранить ваши формы в forms.py, но если у вас много форм и моделей, это становится хорошей практикой для организации компонентов нашего приложения. Итак, я создам новый файл внутри приложения article под названием forms.py и определю ArticleForm.

# article/forms.py

from django import forms
from .models import Article


class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        exclude = (
            "created",
            "updated",
            "author",
        )
        widgets = {
            "title": forms.TextInput(
                attrs={
                    "class": "form-control",
                    "style": "max-width: 450px; align: center;",
                    "placeholder": "Title",
                }
            ),
            "content": forms.Textarea(
                attrs={
                    "class": "form-control",
                    "style": "max-width: 900px;",
                    "placeholder": "Content",
                }
            ),
        }
Вход в полноэкранный режим Выход из полноэкранного режима

Итак, формы наследуются от [ModelForm], что позволяет нам создавать формы на основе нашей модели. Итак, мы указываем имя модели, которое в нашем случае Article, и далее мы можем иметь кортежи exclude или fields. Чтобы исключить определенные поля в фактической форме, просто разберите кортеж этих атрибутов, а если вы хотите выбрать только несколько атрибутов, вы можете указать кортеж fields и упомянуть необходимые поля для формы.

Таким образом, если у нас есть много вещей, которые должны быть включены в форму, мы можем указать только атрибуты, которые должны быть исключены с помощью кортежа exclude. А если у нас много полей, которые нужно исключить, мы можем использовать кортеж fields, чтобы указать, какие атрибуты использовать в форме.

Рассмотрим пример: Для приведенной выше формы ArticleForm, если мы хотим указать обязательные поля, которые должны быть включены в форму, то мы можем использовать кортеж fields, как показано ниже, остальные поля не будут отображаться в форме.

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = (
            "title",
            "content",
            "status",
        )
Вход в полноэкранный режим Выйти из полноэкранного режима

Можно использовать оба варианта, все зависит от того, сколько полей нужно исключить или включить в форму.

Мы также указали атрибут widgets, который дает немного больше контроля над тем, как нам нужно отобразить форму в шаблоне. Так, я указал тип ввода, который нужно отобразить, например, простой текстовый ввод для заголовка, текстовую область для содержимого и т.д. Классная вещь в том, что он может автоматически установить их, зная тип поля в модели, но иногда это может быть немного нежелательно, особенно при сложных отношениях и атрибутах.

Создание представлений

Давайте начнем создавать представления для создания, чтения, обновления и удаления статей из базы данных. Я буду использовать представления, основанные на функциях, просто потому, что мы понимаем, как HTMX и Django могут быть интегрированы, поэтому нам нужно погрузиться глубже и понять фактическое течение процесса.

Создание представления

Итак, создание статей кажется хорошим способом начать. Мы можем создать простое представление на основе функций, которое изначально загрузит пустую форму ArticleForm, и если запрос будет GET, мы отобразим форму в шаблоне create.html. Если запрос будет POST, что произойдет после отправки формы, мы проверим форму, прикрепим текущего пользователя как автора статьи и сохраним экземпляр for, который создаст запись статьи, и этот объект будет отображен в шаблоне detail.

from django.shortcuts import render
from .models import Article
from .forms import ArticleForm

def createArticle(request):
    form = ArticleForm()
    context = {
        'form': form,
    }
    return render(request, 'articles/create.html', context)
Вход в полноэкранный режим Выход из полноэкранного режима

Рендеринг формы

Мы создаем пустой экземпляр ArticleForm и отображаем его в шаблоне. Таким образом, в шаблоне create.html будет отображена пустая форма.

<!-- templates/articles/create.html -->

{% extends 'base.html' %}

{% block body %}
<div hx-target="this" hx-swap="outerHTML">
  <form>
    {% csrf_token %}
    {{ form.as_p }}
    <button hx-post="." class="btn btn-success"
      type="submit">Save</button>
  </form>
</div>
{% endblock %}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь, наследуя от базового шаблона, мы создаем тег формы в HTML с {{ form}} для отображения полей формы, наконец, у нас есть элемент button для отправки формы. Мы использовали атрибут hx-post. Подробнее об этом через минуту. Итак, мы создаем шаблон для отображения формы статьи.

Мы использовали атрибут hx-post, который отправит запрос POST на текущий URL, представленный hx-post=".". Вы могли заметить атрибуты div, hx-target и hx-swap, это одни из многих атрибутов, предоставляемых библиотекой htmx для управления реактивностью выполняемых запросов. hx-target позволяет нам указать элемент или тег, в который будут выводиться данные. hx-swap используется для указания целевого DOM, например innerHTML, outerHTML и т.д. Вы можете ознакомиться с различными вариантами в документации htmx. Указывая hx-swap как outerHTML, мы хотим заменить весь элемент на входящий контент из запроса, который мы отправим с помощью триггеров близлежащего запроса.

Нам нужно сопоставить представление с URL, чтобы получить представление о запросе и разобранном содержимом.

Мы создадим маршрут create/ и привяжем его к представлению createArticle с именем article-create.

# article/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('create/', views.createArticle, name='article-create'), 
]
Вход в полноэкранный режим Выход из полноэкранного режима

Этот URL будет сопоставлен с глобальным URL в проекте, здесь мы можем просто указать префикс для URL в приложении article и включить эти URL.

# htmx_blog/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('user/', include('user.urls'), name='auth'),
    path('', include('article.urls'), name='home'),
]
Войти в полноэкранный режим Выход из полноэкранного режима

Не стесняйтесь добавить любой другой шаблон URL, например, приложение article находится по адресу /, то есть 127.0.01.:8000/, вы можете добавить любое другое имя, например 127.0.0.1:8000/article/, добавив path('article/', include('article.urls')).

Итак, наконец, мы отправляем запрос GET на 127.0.0.1:8000/create/ и выводим форму. Поскольку у нас есть запрос POST, встроенный в кнопку внутри формы, мы отправим запрос POST на тот же URL -> 127.0.0.1:8000/create/.

Отправка формы

Давайте обработаем запрос POST в представлении create.

from django.shortcuts import render
from .models import Article
from .forms import ArticleForm

def createArticle(request):
    form = ArticleForm(request.POST or None)
    if request.method == 'POST':
        if form.is_valid():
            form.instance.author = request.user
            article = form.save()
            return render(request, 'articles/detail.html', {'article': article})
    context = {
        'form': form,
    }
    return render(request, 'articles/create.html', context)
Войдите в полноэкранный режим Выход из полноэкранного режима

Простое объяснение

  • Создайте экземпляр формы ArticleForm с данными запроса или пустой -> ArticleForm(request.POST или None).
  • Если это POST запрос, проверьте и создайте статью, отобразите объект статьи в шаблоне detail.html.
  • Если это GET-запрос, выведите пустую форму в шаблоне create.html.

Есть несколько изменений в представлении, вместо инициализации формы пустой, т.е. ArticleForm(), мы инициализируем с помощью ArticleForm(request.POST или None). Это означает, что если у нас есть что-то в дикте request.POST, то мы инициализируем форму этими данными, иначе это будет пустой экземпляр формы.

Далее мы проверяем, является ли запрос POST, если да, то мы проверяем, действительна ли форма, т.е. не пусты ли поля формы, удовлетворяются ли какие-либо другие ограничения на атрибуты модели или нет. Если данные формы действительны, мы прикрепляем автора как текущего зарегистрированного пользователя/пользователя, который отправил запрос. Наконец, мы сохраняем форму, которая, в свою очередь, создает запись статьи в базе данных. Затем мы отображаем созданную статью в шаблоне detail.html, который еще не создан.

Итак, атрибут htmx-post сработал, и он отправит пост-запрос на тот же URL, то есть 127.0.0.1:8000/create, и это снова вызовет представление createArticle, на этот раз у нас будут данные request.POST. Итак, мы проверим и сохраним форму.

Детальный вид

Представление деталей используется для просмотра деталей статьи. Оно будет отображаться после создания или обновления статьи. Это довольно просто, нам нужен id или primary key(pk) статьи и отображение title и content статьи в шаблоне.

Мы передаем первичный ключ вместе с запросом в качестве параметра представления, pk будет передан через URL. Мы получаем объект Article с id как разобранный pk и, наконец, выводим шаблон detail.html с объектом article. К context['article'] можно получить доступ из шаблона для отображения специфических атрибутов, таких как title, content и т.д.

# article/views.py

def detailArticle(request, pk):
    article = Article.objects.get(id=pk)
    context = {'article': article}
    return render(request, 'articles/detail.html', context)

Вход в полноэкранный режим Выход из полноэкранного режима

Теперь мы можем привязать представление к URL и разобрать необходимый параметр pk для представления.

from django.urls import path
from . import views

urlpatterns = [
    path('create/', views.createArticle, name='article-create'), 
    path('<int:pk>', views.detailArticle, name='article-detail'), 
]
Вход в полноэкранный режим Выход из полноэкранного режима

Мы разобрали pk как int для параметра URL, поэтому для статьи с id=4, URL будет, 127.0.0.1:8000/4/.

Нам нужно создать шаблон для отображения контекста из представления detailArticle. Поэтому мы создаем detail.html в папке templates/articles. Мы наследуем базовый шаблон и отображаем article.title и article.content с помощью фильтра шаблона linebreaks, чтобы правильно отобразить содержимое.

<!-- templates/articles/detail.html -->


{% extends 'base.html' %}
{% block body %}
<div id="article-card">
  <h2>{{ article.title }}
  <p>{{ article.content|linebreaks|safe }}</p>
<div>
{% endblock %}

Вход в полноэкранный режим Выход из полноэкранного режима

Итак, теперь мы можем использовать представление createArticle, а также представление detailArticle, оба настроены правильно, поэтому (CR) или CRUD завершен. Мы можем добавить listArticle для вывода списка всех статей автора (вошедшего пользователя).

Просмотр списка

Представление списка статей во многом схоже с представлением деталей, так как оно возвращает список статей, а не одну статью.

Так, в представлении listArticle мы вернем все статьи с автором в виде пользователя, отправившего запрос/зарегистрировавшегося пользователя. Мы разберем этот список объектов в шаблоне как base.html или articles/list.html.

# article/views.py


def listArticle(request):
    articles = Article.objects.filter(author=request.user.id)
    context = {
        'articles': articles,
    }
    return render(request, 'base.html', context)
Вход в полноэкранный режим Выход из полноэкранного режима

Мы добавим URL маршрут для этого как / маршрут, который находится на 127.0.0.1:8000/ это базовый URL для приложения статей и маршрут для представления listArticle. Таким образом, мы будем отображать список статей на главной странице.

# article/urls.py


from django.urls import path
from . import views

urlpatterns = [
    path('<int:pk>', views.detailArticle, name='article-detail'), 
    path('create/', views.createArticle, name='article-create'), 
    path('', views.listArticle, name='article-list'), 
]
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте создадим шаблон для представления списка, который будет перебирать статьи и отображать соответствующие данные, такие как заголовок и ссылку на статью.

<!-- templates/articles/list.html -->

<ul id="article-list">
  {% for article in articles %}
  <li>
    <div class="card" style="width: 18rem;">
      <div class="card-body">
        <h5 class="card-title">{{ article.title }}</h5>
        <p class="card-text">{{ article.content|truncatewords:5  }}</p>
        <a href="{% url 'article-detail' article.id %}" class="card-link">Read more</a>
      </div>
    </div>
  </li>
  {% endfor %}
</ul>
Вход в полноэкранный режим Выход из полноэкранного режима

Мы использовали фильтр шаблона truncatewords:5 для отображения содержимого статей только до первых 5 слов, поскольку это всего лишь представление списка, мы не хотим отображать здесь все детали статьи.

Мы можем использовать этот шаблон для рендеринга в файле base.html.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTMX Blog</title>
    {% load static %}
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <script src="https://unpkg.com/htmx.org@1.8.0"></script>
</head>
<body hx-target="this" hx-swap="outerHTML" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
        <nav>
        <h2>HTMX Blog</h2>
        <div class="navbar">
          {% if user.is_authenticated %}
            <a class="nav-item nav-link" href="{% url 'article-list' %}"><button class="btn btn-link">Home</button></a>
            <a class="nav-item nav-link" href="{% url 'logout' %}"><button class="btn btn-link">Logout</button></a>
          {% else %}
            <a class="nav-item nav-link" href="{% url 'login' %}"><button class="btn btn-link">Login</button></a>
            <a class="nav-item nav-link" href="{% url 'register' %}"><button class="btn btn-link">Register</button></a>
          {% endif %}
        </div>
        </nav>

    {% block body %}
    <a href="{% url 'article-create' %}"><button class="btn btn-success" >Create</button></a>
    {% include 'articles/list.html' %}
    {% endblock %}
</body>
</html>
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь мы включили шаблон list.html на главную страницу, а также добавили кнопку create в качестве ссылки на URL article-create.

Вид удаления

Для удаления статьи мы будем просто полагаться на htmx для отправки запроса, и по этому запросу мы удалим текущую статью и выведем обновленный список статей.

В представлении deleteArticle мы будем принимать два параметра: запрос, который по умолчанию используется для представления на основе функций Django, и первичный ключ pk. Мы снова разберем pk из URL. Мы удалим объект article и получим последний список статей. Наконец, отобразим обновленный список статей в базовом шаблоне, который является нашим представлением списка.

# article/views.py


def deleteArticle(request, pk):
    Article.objects.get(id=pk).delete()
    articles = Article.objects.filter(author=request.user)
    context = {'article': articles}
    return render(request, "base.html", context)

Вход в полноэкранный режим Выход из полноэкранного режима

Мы добавим deleteArticle в шаблоны URL и назовем его article-delete с URL delete/<int:pk>. Это позволит нам отправить запрос на URL 127.0.0.1:8000/delete/4 для удаления статьи с id 4.

# article/urls.py


from django.urls import path
from . import views

urlpatterns = [
    path('', views.listArticle, name='article-list'), 
    path('<int:pk>', views.detailArticle, name='article-detail'), 
    path('create/', views.createArticle, name='article-create'), 
    path('delete/<int:pk>', views.deleteArticle, name='article-delete'), 
]
Вход в полноэкранный режим Выход из полноэкранного режима

В представлении удаления шаблон важен, поскольку мы хотим отправить запрос соответствующим образом на определенный URL. Для этого у нас будет форма, но в ней не будет никаких входов как таковых, только кнопка, которая указывает на удаление текущей статьи. Мы добавим атрибут hx-delete в качестве URL в представление deleteArticle с id статьи. Это отправит запрос на URL article-delete, который, в свою очередь, вызовет представление с заданным id и удалит статью.

Мы добавили атрибут hx-confirm для показа всплывающего окна подтверждения удаления статьи. Как вы видите, мы добавили небольшой скрипт для добавления csrf_token в HTML, это важно для отправки формы с действительным CSRFToken.

<!-- templates/article/delete.html -->

<script>
  document.body.addEventListener('htmx:configRequest', (event) => {
    event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
  })
</script>
<div >
  <form method="post" >
  {% csrf_token %}
    <button class="btn btn-danger"
      hx-delete="{% url 'article-delete' article.id %}"
      hx-confirm="Are you sure, You want to delete this article?"
      type="submit">
      Delete
    </button>
  </form>
</div>
Войти в полноэкранный режим Выход из полноэкранного режима

У вас есть вопрос, как нам получить доступ к article.id? Мы не рендерим шаблон delete.html из представления, поэтому нет контекста для передачи. Мы включим этот фрагмент в шаблон представления подробностей, чтобы иметь возможность удалить текущую статью.

Мы изменим шаблон articles/detail.html и включим в него шаблон delete.html. Это включает простое добавление HTML-шаблона в указанное место. Таким образом, мы, по сути, внедрим форму удаления в шаблон detail.

{% extends 'base.html' %}
{% block body %}
<div hx-target="this" hx-swap="outerHTML">
  <h2>{{ article.title }}
  {% include 'articles/delete.html' %}
  <p>{{ article.content|linebreaks|safe }}</p>
<div>
{% endblock %}
Вход в полноэкранный режим Выйти из полноэкранного режима

Таким образом, у нас будет красивая опция удаления статьи в разделе подробностей, ее можно разместить где угодно, но помните, что нам нужно добавить hx-target="this" и hx-swap="outerHTML" в div, чтобы правильно поменять HTML-контент после выполнения запроса.

Обновление вида

Теперь мы можем перейти к последней части CRUD, а именно Update. Это будет похоже на createArticle с парой изменений. Мы будем анализировать параметры типа pk для этого представления, так как мы хотим обновить конкретную статью. Поэтому нам нужно будет получить первичный ключ статьи из URL slug.

Внутри представления updateArticle мы сначала получим объект статьи из разобранного первичного ключа. Здесь у нас будет два вида запросов, один будет для получения form с текущими данными статьи, а следующий запрос будет PUT для фактического сохранения изменений в статье.

Первый запрос прост, поскольку нам нужно разобрать данные формы с экземпляром объекта article. Мы вызовем ArticleForm с экземпляром article, который загрузит данные статьи в форму, готовую к отображению в шаблоне. Таким образом, после отправки запроса GET мы отобразим шаблон с формой, предварительно заполненной значениями атрибутов статьи.

# article/views.py


def updateArticle(request, pk):
    article = Article.objects.get(id=pk)
    form = ArticleForm(instance=article)
    context = {
        'form': form,
        'article': article,
    }
    return render(request, 'articles/update.html', context)
Вход в полноэкранный режим Выход из полноэкранного режима

Мы создадим шаблон в папке templates/articles/ под названием update.html, который будет содержать простую форму для отображения полей формы и кнопку для отправки запроса PUT. Мы отобразим form, а затем добавим элемент button с атрибутом hx-put для отправки запроса PUT для сохранения изменений в записи статьи. Мы разберем article.id для параметра первичного ключа представления.

<!-- templates/articles/update.html -->

<div hx-target="this" hx-swap="outerHTML">
  <form>
    {% csrf_token %}
    {{ form.as_p }}
    <button hx-put="{% url 'article-update' article.id %}"
      type="submit">Update</button>
  </form>
</div>
Вход в полноэкранный режим Выход из полноэкранного режима

Нам еще предстоит связать updateArticle с URL-адресами. Мы добавим представление updateArticle в URL с именем article-update и update/<int:pk в качестве шаблона slug. Этот шаблон URL вызовет updateArticle, когда мы отправим HTTP запрос на 127.0.0.1:8000/update/4 для обновления статьи с id 4.

# article/urls.py


from django.urls import path
from . import views

urlpatterns = [
    path('', views.listArticle, name='article-list'), 
    path('<int:pk>', views.detailArticle, name='article-detail'), 
    path('create/', views.createArticle, name='article-create'), 
    path('delete/<int:pk>', views.deleteArticle, name='article-delete'), 
    path('update/<int:pk>', views.updateArticle, name='article-update'), 
]
Вход в полноэкранный режим Выход из полноэкранного режима

Это еще не все, нам нужно будет также обработать запрос PUT, т.е. когда данные формы были изменены, и мы собираемся сохранить изменения в данных формы. Итак, мы проверим тип метода запроса. Если это запрос PUT, нам придется выполнить несколько действий.

# article/views.py


from django.http import QueryDict

def updateArticle(request, pk):
    article = Article.objects.get(id=pk)
    if request.method == 'PUT':
        qd = QueryDict(request.body)
        form = ArticleForm(instance=article, data=qd)
        if form.is_valid():
            article = form.save()
            return render(request, 'articles/detail.html', {'article': article})
    form = ArticleForm(instance=article)
    context = {
        'form': form,
        'article': article,
    }
    return render(request, 'articles/update.html', context)
Войти в полноэкранный режим Выйти из полноэкранного режима

В приведенном выше представлении updateArticle мы должны проверить наличие запроса PUT, если мы отправляем запрос PUT, экземпляр формы должен быть загружен из объекта запроса. Мы используем request.body для доступа к данным, отправленным в запросе. Входящие данные, полученные из объекта request.body, не являются допустимым форматом для разбора их в экземпляр формы, поэтому мы разберем их с помощью QueryDict. Это позволит нам модифицировать объект request.body в действительные сериализуемые данные python.

Итак, мы импортируем QueryDict из модуля django.http. Мы разбираем данные как параметр QueryDict и сохраняем их в переменной. Затем нам нужно получить ArticleForm для получения данных в соответствии с деталями формы, поэтому мы анализируем экземпляр, а также параметр data. Экземпляр — это объект article, а данные — это полученные данные формы, которые мы сохранили в qd как QueryDict(request.body). Это загрузит новые данные формы, после чего мы сможем проверить ее.

После проверки данных формы мы можем сохранить форму, что приведет к обновлению записи статьи. Таким образом, мы можем отобразить обновленную статью в представлении detail с обновленным объектом article в качестве контекста.

Таким образом, это настроит и представление обновления, и теперь мы можем создавать, читать, обновлять и удалять экземпляр статьи с помощью HTMX в шаблонах и представлениях на основе функций Django без написания javascript.

Резюме

Мы смогли создать базовое CRUD-приложение в Django с помощью HTMX. Мы использовали простые представления на основе функций, чтобы продемонстрировать внутренние детали того, как мы можем работать с HTMX и обрабатывать запросы из шаблонов. Создав простые отдельные шаблоны, мы можем соединить их вместе, чтобы сделать полностью функциональную и отзывчивую веб-страницу. Пользовательский интерфейс не очень хорош, но целью этого руководства было создание базового CRUD-приложения для работы с бэкендом с помощью HTMX, так что, надеюсь, вы получили хорошее представление о том, как HTMX может быть интегрирован в Django-приложение.

В целом, HTMX — это отличная библиотека, которую можно использовать для улучшения или даже создания нового веб-приложения, чтобы сделать сайт отзывчивым и без написания javascript.

Django HTMX CRUD Application Demo GIF

Вы можете ознакомиться с исходным кодом этого проекта и блога на репозитории htmx-blog GitHub.

Заключение

Из этого поста мы смогли понять основы HTMX и то, как мы можем интегрировать его в Django-приложение. Надеюсь, вам понравился этот пост, если у вас есть какие-либо вопросы или отзывы, пожалуйста, дайте мне знать в комментариях или на моих социальных страницах. Спасибо за прочтение. Счастливого кодинга 🙂

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