每天40分玩转Django:Django表单集
Django表单集
一、今日学习内容概述
学习模块 | 重要程度 | 主要内容 |
---|---|---|
表单集基础 | ⭐⭐⭐⭐⭐ | 表单集定义、基本用法 |
内联表单集 | ⭐⭐⭐⭐⭐ | 内联表单、关联数据 |
表单集验证 | ⭐⭐⭐⭐ | 自定义验证、错误处理 |
动态表单集 | ⭐⭐⭐⭐ | 动态添加删除表单 |
二、基本模型定义
# models.py
from django.db import models
class Author(models.Model):
name = models.CharField('姓名', max_length=100)
email = models.EmailField('邮箱')
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'
)
isbn = models.CharField('ISBN', max_length=13)
published_date = models.DateField('出版日期')
price = models.DecimalField('价格', max_digits=10, decimal_places=2)
def __str__(self):
return self.title
class Chapter(models.Model):
book = models.ForeignKey(
Book,
on_delete=models.CASCADE,
related_name='chapters'
)
title = models.CharField('章节标题', max_length=200)
content = models.TextField('内容')
order = models.PositiveIntegerField('排序')
class Meta:
ordering = ['order']
def __str__(self):
return self.title
三、表单集实现
3.1 基本表单集
# forms.py
from django import forms
from django.forms import formset_factory, modelformset_factory
from .models import Book, Chapter
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ['title', 'isbn', 'published_date', 'price']
widgets = {
'published_date': forms.DateInput(attrs={'type': 'date'})
}
# 创建基本表单集
BookFormSet = modelformset_factory(
Book,
form=BookForm,
extra=2,
can_delete=True
)
# 创建章节表单
class ChapterForm(forms.ModelForm):
class Meta:
model = Chapter
fields = ['title', 'content', 'order']
# 创建章节表单集
ChapterFormSet = modelformset_factory(
Chapter,
form=ChapterForm,
extra=1,
can_delete=True
)
3.2 内联表单集
# forms.py
from django.forms import inlineformset_factory
# 创建内联表单集
BookChapterFormSet = inlineformset_factory(
Book,
Chapter,
form=ChapterForm,
extra=3,
can_delete=True,
min_num=1,
validate_min=True
)
class AuthorBooksForm(forms.ModelForm):
class Meta:
model = Author
fields = ['name', 'email', 'bio']
AuthorBooksFormSet = inlineformset_factory(
Author,
Book,
form=BookForm,
extra=1,
can_delete=True
)
四、视图实现
# views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from .forms import BookFormSet, ChapterFormSet, BookChapterFormSet
from .models import Book, Author
def manage_books(request):
if request.method == 'POST':
formset = BookFormSet(request.POST)
if formset.is_valid():
formset.save()
messages.success(request, '书籍信息保存成功!')
return redirect('book_list')
else:
formset = BookFormSet()
return render(request, 'books/manage_books.html', {
'formset': formset
})
def edit_book_chapters(request, book_id):
book = get_object_or_404(Book, id=book_id)
if request.method == 'POST':
formset = BookChapterFormSet(request.POST, instance=book)
if formset.is_valid():
formset.save()
messages.success(request, '章节信息保存成功!')
return redirect('book_detail', book_id=book.id)
else:
formset = BookChapterFormSet(instance=book)
return render(request, 'books/edit_chapters.html', {
'book': book,
'formset': formset
})
def author_books(request, author_id):
author = get_object_or_404(Author, id=author_id)
if request.method == 'POST':
form = AuthorBooksForm(request.POST, instance=author)
formset = AuthorBooksFormSet(request.POST, instance=author)
if form.is_valid() and formset.is_valid():
form.save()
formset.save()
messages.success(request, '作者和图书信息保存成功!')
return redirect('author_detail', author_id=author.id)
else:
form = AuthorBooksForm(instance=author)
formset = AuthorBooksFormSet(instance=author)
return render(request, 'books/author_books.html', {
'form': form,
'formset': formset
})
五、模板实现
<!-- templates/books/manage_books.html -->
{% extends "base.html" %}
{% block content %}
<div class="container mt-4">
<h2>管理图书</h2>
<form method="post">
{% csrf_token %}
{{ formset.management_form }}
<div class="formset-container">
{% for form in formset %}
<div class="card mb-3 book-form">
<div class="card-body">
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
<div class="row">
<div class="col-md-6">
{{ form.title.label_tag }}
{{ form.title }}
</div>
<div class="col-md-3">
{{ form.isbn.label_tag }}
{{ form.isbn }}
</div>
<div class="col-md-3">
{{ form.price.label_tag }}
{{ form.price }}
</div>
</div>
{% if form.can_delete %}
<div class="form-check mt-2">
{{ form.DELETE }}
<label class="form-check-label">删除此书</label>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
<button type="submit" class="btn btn-primary">保存</button>
</form>
</div>
{% endblock %}
<!-- JavaScript for dynamic forms -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const formsetContainer = document.querySelector('.formset-container');
const totalForms = document.querySelector('#id_form-TOTAL_FORMS');
function updateFormIndex(element, index) {
element.id = element.id.replace('-__prefix__-', `-${index}-`);
element.name = element.name.replace('-__prefix__-', `-${index}-`);
}
// Add form dynamically
function addForm() {
const forms = formsetContainer.querySelectorAll('.book-form');
const formNum = forms.length;
const newForm = forms[0].cloneNode(true);
// Clear form values
newForm.querySelectorAll('input').forEach(input => {
input.value = '';
updateFormIndex(input, formNum);
});
formsetContainer.appendChild(newForm);
totalForms.value = formNum + 1;
}
});
</script>
六、表单集流程图
七、验证与错误处理
# forms.py
class BaseBookFormSet(forms.BaseModelFormSet):
def clean(self):
"""自定义表单集验证"""
super().clean()
# 验证ISBN唯一性
isbns = []
for form in self.forms:
if form.cleaned_data and not form.cleaned_data.get('DELETE', False):
isbn = form.cleaned_data.get('isbn')
if isbn in isbns:
raise forms.ValidationError('ISBN不能重复')
isbns.append(isbn)
# 使用自定义基类
BookFormSet = modelformset_factory(
Book,
form=BookForm,
formset=BaseBookFormSet,
extra=2,
can_delete=True
)
八、实用工具函数
# utils.py
def copy_formset_instance(formset):
"""复制表单集实例"""
new_formset = formset.__class__(
queryset=formset.queryset,
initial=[form.initial for form in formset.forms],
prefix=formset.prefix
)
return new_formset
def get_changed_data(formset):
"""获取表单集中已更改的数据"""
changed_objects = []
for form in formset.forms:
if form.has_changed() and not form.cleaned_data.get('DELETE', False):
changed_objects.append(form.instance)
return changed_objects
九、常见问题和解决方案
- 表单集验证失败
def handle_formset_errors(formset):
"""处理表单集错误"""
errors = []
for i, form in enumerate(formset):
if form.errors:
errors.append(f'表单 {i + 1}: {form.errors}')
if formset.non_form_errors():
errors.append(f'整体错误: {formset.non_form_errors()}')
return errors
- 动态表单处理
// 动态添加和删除表单的JavaScript代码
function initDynamicFormset() {
const addButton = document.getElementById('add-form');
const formContainer = document.querySelector('.formset-container');
const totalForms = document.getElementById('id_form-TOTAL_FORMS');
addButton.addEventListener('click', function() {
const forms = formContainer.getElementsByClassName('dynamic-form');
const formCount = forms.length;
const newForm = forms[0].cloneNode(true);
// 更新表单索引
newForm.innerHTML = newForm.innerHTML.replace(
/form-(\d+)/g,
`form-${formCount}`
);
formContainer.appendChild(newForm);
totalForms.value = formCount + 1;
});
}
- 文件上传处理
def handle_formset_files(request, formset):
"""处理表单集中的文件上传"""
for form in formset:
if form.is_valid() and form.cleaned_data.get('file'):
handle_uploaded_file(request.FILES['file'])
通过本章学习,你应该能够:
- 理解表单集的工作原理
- 使用内联表单集处理关联数据
- 实现动态表单添加和删除
- 处理表单集验证和错误
怎么样今天的内容还满意吗?再次感谢朋友们的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!