如何用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'), ]
核心逻辑说明
- 动态添加表单:点击"添加更多书籍"按钮时,HTMX发送请求获取新的书籍表单片段并追加到页面,同时更新隐藏字段记录表单数量。
- 实时验证:作者输入时,HTMX延迟500ms发送验证请求,实时反馈输入是否合法。
- 批量保存:提交表单时,先保存作者,再遍历所有书籍表单,将书籍与新创建的作者关联后保存。
- 删除表单:点击删除按钮直接移除当前书籍表单,并同步更新表单数量。
内容的提问来源于stack exchange,提问作者Tomek
相关产品推荐
相关产品推荐

