You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Django+HTMX在单页同时创建作者并关联保存书籍?

在Django中用HTMX实现作者与关联书籍的动态创建

1. 模型定义

首先定义关联的Author和Book模型,通过外键建立两者的关联关系:

# models.py
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    bio = models.TextField(blank=True)

    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
    publication_year = models.IntegerField()

    def __str__(self):
        return self.title

2. 表单设置

创建两个表单类,给书籍表单设置前缀避免多表单字段冲突:

# forms.py
from django import forms
from .models import Author, Book

class AuthorForm(forms.ModelForm):
    class Meta:
        model = Author
        fields = ['name', 'bio']

class BookForm(forms.ModelForm):
    class Meta:
        model = Book
        fields = ['title', 'publication_year']

3. 视图函数

主视图:处理页面加载与数据提交

负责渲染初始页面,以及表单提交时批量保存作者和关联书籍:

# views.py
from django.shortcuts import render
from django.http import HttpResponse
from .models import Author
from .forms import AuthorForm, BookForm

def author_books_create(request):
    if request.method == 'POST':
        author_form = AuthorForm(request.POST)
        if author_form.is_valid():
            # 先保存作者实例
            author = author_form.save()
            # 获取页面中书籍表单的数量
            book_count = int(request.POST.get('book_count', 0))
            # 遍历所有书籍表单并关联作者保存
            for i in range(book_count):
                book_form = BookForm(request.POST, prefix=f'book-{i}')
                if book_form.is_valid():
                    book = book_form.save(commit=False)
                    book.author = author
                    book.save()
            return HttpResponse('作者及关联书籍已成功保存!')
        else:
            # 验证失败时重新渲染页面并保留错误信息
            book_count = int(request.POST.get('book_count', 0))
            book_forms = [BookForm(request.POST, prefix=f'book-{i}') for i in range(book_count)]
            return render(request, 'author_books_create.html', {
                'author_form': author_form,
                'book_forms': book_forms,
                'book_count': book_count
            })
    # GET请求:渲染初始页面,默认加载1个书籍表单
    else:
        author_form = AuthorForm()
        book_forms = [BookForm(prefix='book-0')]
        return render(request, 'author_books_create.html', {
            'author_form': author_form,
            'book_forms': book_forms,
            'book_count': len(book_forms)
        })

HTMX视图:动态生成书籍表单

响应HTMX请求,返回新的书籍表单片段:

def add_book_form(request):
    book_count = int(request.GET.get('book_count', 0))
    # 用递增的前缀创建新表单
    form = BookForm(prefix=f'book-{book_count}')
    return render(request, 'book_form_partial.html', {'form': form, 'book_index': book_count})

可选:实时验证作者表单

通过HTMX实现作者输入的实时验证:

def validate_author(request):
    form = AuthorForm(request.POST)
    if form.is_valid():
        return HttpResponse('<span style="color: green;">作者信息格式正确</span>')
    else:
        error_msg = ' '.join([err for field in form.errors.values() for err in field])
        return HttpResponse(f'<span style="color: red;">{error_msg}</span>')

4. 模板实现

主模板

加载HTMX库,构建完整页面并配置HTMX交互:

<!-- templates/author_books_create.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>创建作者及书籍</title>
    <script src="https://unpkg.com/htmx.org@1.9.6"></script>
    <style>
        .book-form {
            border: 1px solid #eee;
            padding: 10px;
            margin: 10px 0;
        }
    </style>
</head>
<body>
    <h1>创建作者及关联书籍</h1>
    <form method="post">
        {% csrf_token %}
        
        <div id="author-form">
            <h3>作者信息</h3>
            {{ author_form.as_p }}
            <!-- 实时验证作者输入 -->
            <div hx-post="{% url 'validate_author' %}" 
                 hx-trigger="keyup changed delay:500ms" 
                 hx-target="#author-validation" 
                 hx-include="#author-form">
            </div>
            <div id="author-validation"></div>
        </div>

        <h3>书籍信息</h3>
        <div id="books-container">
            {% for form in book_forms %}
                {% include 'book_form_partial.html' with form=form book_index=forloop.counter0 %}
            {% endfor %}
        </div>
        
        <!-- 动态添加书籍表单按钮 -->
        <button type="button" 
                hx-get="{% url 'add_book_form' %}?book_count={{ book_count }}" 
                hx-target="#books-container" 
                hx-swap="beforeend">
            添加更多书籍
        </button>
        <!-- 隐藏字段记录当前书籍表单数量 -->
        <input type="hidden" name="book_count" id="book-count" value="{{ book_count }}">
        
        <br><br>
        <button type="submit">保存所有数据</button>
    </form>

    <script>
        // 监听HTMX交换完成事件,更新书籍表单数量
        document.body.addEventListener('htmx:afterSwap', function() {
            const bookCount = document.querySelectorAll('.book-form').length;
            document.getElementById('book-count').value = bookCount;
        });
    </script>
</body>
</html>

书籍表单片段模板

独立的书籍表单单元,包含删除功能:

<!-- templates/book_form_partial.html -->
<div class="book-form" id="book-form-{{ book_index }}">
    {{ form.as_p }}
    <button type="button" onclick="this.parentElement.remove(); updateBookCount();">
        删除这本书
    </button>
</div>

<script>
    function updateBookCount() {
        const bookCount = document.querySelectorAll('.book-form').length;
        document.getElementById('book-count').value = bookCount;
    }
</script>

5. URL配置

将视图映射到对应URL:

# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('author-books/create/', views.author_books_create, name='author_books_create'),
    path('author-books/add-book-form/', views.add_book_form, name='add_book_form'),
    path('author-books/validate-author/', views.validate_author, name='validate_author'),
]

核心逻辑说明

  1. 动态添加表单:点击"添加更多书籍"按钮时,HTMX发送请求获取新的书籍表单片段并追加到页面,同时更新隐藏字段记录表单数量。
  2. 实时验证:作者输入时,HTMX延迟500ms发送验证请求,实时反馈输入是否合法。
  3. 批量保存:提交表单时,先保存作者,再遍历所有书籍表单,将书籍与新创建的作者关联后保存。
  4. 删除表单:点击删除按钮直接移除当前书籍表单,并同步更新表单数量。

内容的提问来源于stack exchange,提问作者Tomek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 23:50:44