Django表单集
一、今日学习内容概述
学习模块 | 重要程度 | 主要内容 |
---|---|---|
表单集基础 | ⭐⭐⭐⭐⭐ | 表单集定义、基本用法 |
内联表单集 | ⭐⭐⭐⭐⭐ | 内联表单、关联数据 |
表单集验证 | ⭐⭐⭐⭐ | 自定义验证、错误处理 |
动态表单集 | ⭐⭐⭐⭐ | 动态添加删除表单 |
二、基本模型定义
python"># models.py
from django.db import modelsclass Author(models.Model):name = models.CharField('姓名', max_length=100)email = models.EmailField('邮箱')bio = models.TextField('简介', blank=True)def __str__(self):return self.nameclass 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.titleclass 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 基本表单集
python"># forms.py
from django import forms
from django.forms import formset_factory, modelformset_factory
from .models import Book, Chapterclass BookForm(forms.ModelForm):class Meta:model = Bookfields = ['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 = Chapterfields = ['title', 'content', 'order']# 创建章节表单集
ChapterFormSet = modelformset_factory(Chapter,form=ChapterForm,extra=1,can_delete=True
)
3.2 内联表单集
python"># 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 = Authorfields = ['name', 'email', 'bio']AuthorBooksFormSet = inlineformset_factory(Author,Book,form=BookForm,extra=1,can_delete=True
)
四、视图实现
python"># 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, Authordef 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 dynamicallyfunction addForm() {const forms = formsetContainer.querySelectorAll('.book-form');const formNum = forms.length;const newForm = forms[0].cloneNode(true);// Clear form valuesnewForm.querySelectorAll('input').forEach(input => {input.value = '';updateFormIndex(input, formNum);});formsetContainer.appendChild(newForm);totalForms.value = formNum + 1;}
});
</script>
六、表单集流程图
七、验证与错误处理
python"># 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
)
八、实用工具函数
python"># 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_formsetdef 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
九、常见问题和解决方案
- 表单集验证失败
python">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;});
}
- 文件上传处理
python">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大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!