每天40分玩转Django:Django表单集

Django表单集

一、今日学习内容概述

学习模块 重要程度 主要内容
表单集基础 ⭐⭐⭐⭐⭐ 表单集定义、基本用法
内联表单集 ⭐⭐⭐⭐⭐ 内联表单、关联数据
表单集验证 ⭐⭐⭐⭐ 自定义验证、错误处理
动态表单集 ⭐⭐⭐⭐ 动态添加删除表单

二、基本模型定义

python 复制代码
# 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 基本表单集

python 复制代码
# 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 内联表单集

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 = Author
        fields = ['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, 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
    })

五、模板实现

html 复制代码
<!-- 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>

六、表单集流程图

七、验证与错误处理

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_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

九、常见问题和解决方案

  1. 表单集验证失败
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
  1. 动态表单处理
javascript 复制代码
// 动态添加和删除表单的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;
    });
}
  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'])

通过本章学习,你应该能够:

  1. 理解表单集的工作原理
  2. 使用内联表单集处理关联数据
  3. 实现动态表单添加和删除
  4. 处理表单集验证和错误

怎么样今天的内容还满意吗?再次感谢朋友们的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

相关推荐
云上的阿七1 小时前
《云计算能不能真正实现按需付费?》
开发语言·云计算·perl
Yhame.5 小时前
深入理解 Java 中的 ArrayList 和 List:泛型与动态数组
java·开发语言
ROCKY_8175 小时前
Mysql复习(二)
数据库·mysql·oracle
Dovir多多6 小时前
Python数据处理——re库与pydantic的使用总结与实战,处理采集到的思科ASA防火墙设备信息
网络·python·计算机网络·安全·网络安全·数据分析
mazo_command7 小时前
【MATLAB课设五子棋教程】(附源码)
开发语言·matlab
IT猿手7 小时前
多目标应用(一):多目标麋鹿优化算法(MOEHO)求解10个工程应用,提供完整MATLAB代码
开发语言·人工智能·算法·机器学习·matlab
青春男大7 小时前
java栈--数据结构
java·开发语言·数据结构·学习·eclipse
88号技师7 小时前
几款性能优秀的差分进化算法DE(SaDE、JADE,SHADE,LSHADE、LSHADE_SPACMA、LSHADE_EpSin)-附Matlab免费代码
开发语言·人工智能·算法·matlab·优化算法
Zer0_on7 小时前
数据结构栈和队列
c语言·开发语言·数据结构
一只小bit7 小时前
数据结构之栈,队列,树
c语言·开发语言·数据结构·c++