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

Django表单集

一、知识要点概览表

类别知识点掌握程度要求
基础概念FormSet、ModelFormSet深入理解
内联表单集InlineFormSet、BaseInlineFormSet熟练应用
表单集验证clean方法、验证规则熟练应用
自定义配置extra、max_num、can_delete理解应用
动态管理JavaScript动态添加/删除表单掌握使用

二、基础模型和表单设置

# models.py
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField()
    bio = models.TextField()

    def __str__(self):
        return self.name

class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    isbn = models.CharField(max_length=13)
    publication_date = models.DateField()
    price = models.DecimalField(max_digits=10, decimal_places=2)

    def __str__(self):
        return self.title

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

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

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

三、基本表单集实现

1. 创建表单集

# forms.py
from django.forms import modelformset_factory, formset_factory

# 创建Book模型的表单集
BookFormSet = modelformset_factory(
    Book,
    form=BookForm,
    extra=2,  # 额外空表单数量
    max_num=5,  # 最大表单数量
    can_delete=True  # 允许删除
)

# 创建自定义表单集
class BaseBookFormSet(forms.BaseModelFormSet):
    def clean(self):
        super().clean()
        titles = []
        for form in self.forms:
            if form.cleaned_data:
                title = form.cleaned_data.get('title')
                if title in titles:
                    raise forms.ValidationError("书籍标题不能重复")
                titles.append(title)

# 使用自定义表单集基类
BookFormSet = modelformset_factory(
    Book,
    form=BookForm,
    formset=BaseBookFormSet,
    extra=2
)

2. 视图实现

# views.py
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import BookFormSet, AuthorForm

class BookFormSetView(View):
    template_name = 'books/book_formset.html'
    
    def get(self, request):
        formset = BookFormSet(queryset=Book.objects.none())
        return render(request, self.template_name, {'formset': formset})
    
    def post(self, request):
        formset = BookFormSet(request.POST)
        if formset.is_valid():
            instances = formset.save()
            messages.success(request, f'成功保存{len(instances)}本书籍信息')
            return redirect('book_list')
        return render(request, self.template_name, {'formset': formset})

def manage_books(request, author_id):
    author = get_object_or_404(Author, id=author_id)
    
    if request.method == 'POST':
        formset = BookFormSet(
            request.POST,
            queryset=Book.objects.filter(author=author)
        )
        if formset.is_valid():
            books = formset.save(commit=False)
            for book in books:
                book.author = author
                book.save()
            # 处理删除的书籍
            for obj in formset.deleted_objects:
                obj.delete()
            return redirect('author_detail', pk=author.pk)
    else:
        formset = BookFormSet(queryset=Book.objects.filter(author=author))
    
    return render(request, 'books/manage_books.html', {
        'formset': formset,
        'author': author
    })

四、内联表单集实现

1. 创建内联表单集

# forms.py
from django.forms import inlineformset_factory

# 创建Author-Book内联表单集
BookInlineFormSet = inlineformset_factory(
    Author,  # 父模型
    Book,    # 子模型
    form=BookForm,
    extra=2,
    max_num=5,
    can_delete=True
)

# 自定义内联表单集
class BaseBookInlineFormSet(forms.BaseInlineFormSet):
    def clean(self):
        super().clean()
        total_price = 0
        for form in self.forms:
            if form.cleaned_data and not form.cleaned_data.get('DELETE', False):
                price = form.cleaned_data.get('price', 0)
                total_price += price
                
        if total_price > 1000:
            raise forms.ValidationError(
                "所有书籍总价不能超过1000"
            )

# 使用自定义内联表单集
BookInlineFormSet = inlineformset_factory(
    Author,
    Book,
    form=BookForm,
    formset=BaseBookInlineFormSet,
    extra=2
)

2. 视图实现

# views.py
from django.views.generic.edit import UpdateView
from .forms import BookInlineFormSet

class AuthorBooksUpdateView(UpdateView):
    model = Author
    form_class = AuthorForm
    template_name = 'books/author_books_form.html'
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        if self.request.POST:
            context['book_formset'] = BookInlineFormSet(
                self.request.POST,
                instance=self.object
            )
        else:
            context['book_formset'] = BookInlineFormSet(
                instance=self.object
            )
        return context
    
    def form_valid(self, form):
        context = self.get_context_data()
        book_formset = context['book_formset']
        if book_formset.is_valid():
            self.object = form.save()
            book_formset.instance = self.object
            book_formset.save()
            return redirect('author_detail', pk=self.object.pk)
        return self.render_to_response(self.get_context_data(form=form))

五、表单集模板实现

<!-- templates/books/author_books_form.html -->
{% extends 'base.html' %}
{% load static %}

{% block content %}
<div class="container">
    <h1>编辑作者及其书籍</h1>
    
    <form method="post">
        {% csrf_token %}
        
        <div class="author-form">
            <h2>作者信息</h2>
            {{ form.as_p }}
        </div>
        
        <div class="books-formset">
            <h2>书籍信息</h2>
            {{ book_formset.management_form }}
            
            <div id="book-forms">
                {% for book_form in book_formset %}
                <div class="book-form">
                    {{ book_form.non_field_errors }}
                    <div class="form-row">
                        <div class="form-group">
                            {{ book_form.title.label_tag }}
                            {{ book_form.title }}
                            {{ book_form.title.errors }}
                        </div>
                        <div class="form-group">
                            {{ book_form.isbn.label_tag }}
                            {{ book_form.isbn }}
                            {{ book_form.isbn.errors }}
                        </div>
                        <div class="form-group">
                            {{ book_form.price.label_tag }}
                            {{ book_form.price }}
                            {{ book_form.price.errors }}
                        </div>
                        {% if book_form.instance.pk %}
                            {{ book_form.DELETE }}
                        {% endif %}
                    </div>
                </div>
                {% endfor %}
            </div>
            
            <button type="button" id="add-book" class="btn btn-secondary">
                添加书籍
            </button>
        </div>
        
        <button type="submit" class="btn btn-primary mt-3">
            保存
        </button>
    </form>
</div>

{% block extra_js %}
<script>
$(document).ready(function() {
    // 获取表单总数
    const totalForms = $('#id_book_set-TOTAL_FORMS');
    
    // 添加新书籍表单
    $('#add-book').click(function() {
        const formCount = parseInt(totalForms.val());
        const newForm = $('#book-forms .book-form:first').clone(true);
        
        // 更新表单索引
        newForm.find(':input').each(function() {
            const name = $(this).attr('name').replace('-0-', '-' + formCount + '-');
            const id = 'id_' + name;
            $(this).attr({'name': name, 'id': id}).val('');
        });
        
        // 更新标签的for属性
        newForm.find('label').each(function() {
            const newFor = $(this).attr('for').replace('-0-', '-' + formCount + '-');
            $(this).attr('for', newFor);
        });
        
        // 添加新表单到DOM
        $('#book-forms').append(newForm);
        totalForms.val(formCount + 1);
    });
});
</script>
{% endblock %}
{% endblock %}

六、表单集处理流程图

在这里插入图片描述

七、高级用法示例

1. 工厂函数自定义

def get_book_formset(extra=1, max_num=None):
    return modelformset_factory(
        Book,
        form=BookForm,
        extra=extra,
        max_num=max_num,
        validate_max=True,
        can_delete=True,
        widgets={
            'title': forms.TextInput(attrs={'class': 'form-control'}),
            'isbn': forms.TextInput(attrs={'class': 'form-control'}),
            'price': forms.NumberInput(attrs={'class': 'form-control'})
        }
    )

# 在视图中使用
def manage_books_dynamic(request):
    BookFormSet = get_book_formset(
        extra=2,
        max_num=10
    )
    if request.method == 'POST':
        formset = BookFormSet(request.POST)
        if formset.is_valid():
            formset.save()
            return redirect('book_list')
    else:
        formset = BookFormSet()
    return render(request, 'books/manage_books.html', {'formset': formset})

2. 条件验证

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)
        
        # 检查总价格
        total_price = sum(
            form.cleaned_data.get('price', 0)
            for form in self.forms
            if form.cleaned_data and not form.cleaned_data.get('DELETE', False)
        )
        if total_price > 1000:
            raise forms.ValidationError('所有书籍总价不能超过1000')

3. 动态表单处理

# views.py
from django.http import JsonResponse

class DynamicBookFormView(View):
    def post(self, request):
        if request.is_ajax():
            formset = BookFormSet(request.POST)
            if formset.is_valid():
                instances = formset.save()
                return JsonResponse({
                    'status': 'success',
                    'message': f'成功保存{len(instances)}本书籍'
                })
            else:
                errors = []
                for form in formset:
                    for field, error in form.errors.items():
                        errors.append(f"{field}: {error}")
                return JsonResponse({
                    'status': 'error',
                    'errors': errors
                })
        return JsonResponse({'status': 'error', 'message': '非法请求'})

这就是关于Django表单集的详细内容。通过学习这些内容,你将能够理解和使用Django的表单集系统,实现复杂的表单处理逻辑。如果有任何问题,欢迎随时提出!


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/945459.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

抽奖2(信奥)

【问题描述】 公司举办年会&#xff0c;为了活跃气氛&#xff0c;设置了摇奖环节。参加聚会的每位员工都有一张带有号码的抽奖券。现在&#xff0c;主持人从小到依次公布了n个不同的获奖号码&#xff0c;小谢看着自己抽奖券上的号码win&#xff0c;无比紧张。请编写一个程序&am…

JWT包中的源码分析【Golang】

前言 最近在学web编程的途中&#xff0c;经过学长提醒&#xff0c;在进行登陆&#xff08;Login&#xff09;操作之后&#xff0c;识别是否登陆的标识应该要放入authorization中&#xff0c;正好最近也在学鉴权&#xff0c;就顺便来看看源码了。 正文 1. 代码示例 在进行分…

Airbnb/Booking 系统设计(high level architecture)

原文地址 CodeKarle: Airbnb System Design | Booking.com System Design B站搜 “Airbnb System Design” 有视频版本 需求&#xff1a; 功能性需求 系统用户包括商家和客人。 Hotel - 商家&#xff08;拥有hotel的人&#xff09; onboarding - 商家可以入住系统。 update…

windows系统安装完Anaconda之后怎么激活自己的虚拟环境并打开jupyter

1.在win主菜单中找到Anaconda安装文件夹并打开终端 文件夹内有所有安装后的Anaconda的应用软件和终端窗口启动窗口 点击Anaconda Prompt&#xff08;Anaconda&#xff09;就会打开类似cmd的命令终端窗口&#xff0c;默认打开的路径是用户名下的路径 2.激活虚拟环境 使用命令…

C++进阶(三)--多态

目录 一、多态的基本概念 1.什么是多态 二、多态的定义及实现 1.虚函数 2.虚函数的重写 3.虚函数重写的⼀些其他问题 协变(了解) 析构函数的重写 C11 override和final 4.重载、覆盖(重写)、隐藏(重定义)的对比 三、抽象类 1.纯虚函数 2.接口继承和实现继承 四、多…

2025经典的软件测试面试题(答案+文档)

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 以下是软件测试相关的面试题及答案&#xff0c;希望对各位能有帮助&#xff01; 1、测试分为哪几个阶段? 一般来说分为5个阶段&#xff1a;单元测试、集成测试…

海南省首套数据资产化系列团体标准正式发布

近日&#xff0c;首套数据资产化系列团体标准正式发布。本次系列涵盖《数据资产 数据治理规范》、《数据资产数据质量评价规范》、《数据资产 数据评估定价办法》和《数据资产 入表流程规范化标准》四项团体标准&#xff0c;通过海南省人工智能学会面向行业发布&#xff0c;自2…

突发!GitLab(国际版)将停止对中国区用户提供 GitLab.com 账号服务

消息称&#xff1a; 目前&#xff0c;为了更加严格的遵循中国网络数据安全管理的相关要求&#xff0c;GitLab SaaS&#xff08;国际版&#xff09;已逐步停止向国内用户提供服务与支持&#xff0c;国内用户亦无法注册或使用 GitLab SaaS&#xff08;国际版&#xff09;。自您的…

LLaVA模型讲解与总结

系列文章目录 第一章&#xff1a;LoRA微调系列笔记 第二章&#xff1a;Llama系列关键知识总结 第三章&#xff1a;LLaVA模型讲解与总结 文章目录 系列文章目录LLaVA&#xff08;Large Language and Vision Assistant&#xff09;Q: 这篇论文试图解决什么问题&#xff1f;Q: 论…

【linux内核分析-存储】EXT4源码分析之“创建文件”原理

EXT4源码分析之“文件创建”原理&#xff0c;详细的介绍文件创建的核心流程&#xff0c;并对EXT4中关于文件创建的关键函数进行了分析。 文章目录 0.前言1.创建文件概览1.1关键流程1.2 关键步骤说明 2.源码分析2.1 入口函数ext4_create2.2 分配inode关键函数 ext4_new_inode_st…

自学记录鸿蒙 API 13:骨骼点检测应用Core Vision Skeleton Detection

骨骼点检测技术是一项强大的AI能力&#xff0c;能够从图片中识别出人体的关键骨骼点位置&#xff0c;如头部、肩部、手肘等。这些信息在人体姿态分析、动作捕捉、健身指导等场景中有着广泛应用。我决定深入学习HarmonyOS Next最新版本API 13中的Skeleton Detection API&#xf…

使用ArcGIS Pro自带的Notebook计算多个遥感指数

在之前的分享中&#xff0c;我们介绍了如何使用ArcPy将GEE下载的遥感影像转为单波段文件。基于前面创建的单波段文件&#xff0c;我们可以一次性计算多种遥感指数&#xff0c;例如NDVI、EVI、NDSI等。我这里直接在ArcGIS Pro中自带的Notebook进行的运行。如下图所示&#xff0c…

XGPT用户帮助手册

文章目录 2024 更新日志2024.12.272024.12.29 摘要 本文详细介绍了XGPT软件的功能及发展历程。XGPT是一款融合了当前最先进人工智能技术的多模态智能软件&#xff0c;专为国内用户优化设计。除了强大的智能问答功能外&#xff0c;XGPT还结合日常办公和科学研究的需求&#xff0…

面试提问:Redis为什么快?

Redis为什么快&#xff1f; 引言 Redis是一个高性能的开源内存数据库&#xff0c;以其快速的读写速度和丰富的数据结构支持而闻名。本文将探讨Redis快速处理数据的原因&#xff0c;帮助大家更好地理解Redis的内部机制和性能优化技术。 目录 完全基于内存高效的内存数据结构…

强化学习——贝尔曼公式

文章目录 前言一、Return的重要性二、State Value三、贝尔曼公式总结 前言 一、Return的重要性 在不同策略下&#xff0c;最终得到的return都会有所不同。因此&#xff0c;return可以用来评估策略。 return的计算公式在基础概念中已经给出&#xff0c;通过包含 γ {\gamma} γ与…

使用MFC编写一个paddleclas预测软件

目录 写作目的 环境准备 下载编译环境 解压预编译库 准备训练文件 模型文件 图像文件 路径整理 准备预测代码 创建预测应用 新建mfc应用 拷贝文档 配置环境 界面布局 添加回cpp文件 修改函数 报错1解决 报错2未解决 修改infer代码 修改MFCPaddleClasDlg.cp…

CSS特效032:2025庆新春,孔明灯向上旋转飘移效果

CSS常用示例100专栏目录 本专栏记录的是经常使用的CSS示例与技巧&#xff0c;主要包含CSS布局&#xff0c;CSS特效&#xff0c;CSS花边信息三部分内容。其中CSS布局主要是列出一些常用的CSS布局信息点&#xff0c;CSS特效主要是一些动画示例&#xff0c;CSS花边是描述了一些CSS…

3D云展厅对文物保护有什么意义?

在文化遗产保护领域&#xff0c;3D云展厅技术的应用正成为一股新兴力量&#xff0c;它不仅改变了文物展示的方式&#xff0c;也为文物保护工作带来了深远的影响。 下面&#xff0c;由【圆桌3D云展厅平台】为大家介绍一下3D云展厅对文物保护意义的详细探讨。 1. 减少物理接触&a…

spring入门程序

安装eclipse https://blog.csdn.net/qq_36437991/article/details/131644570 新建maven项目 安装依赖包 pom.xml <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation&quo…

vue 修改vant样式NoticeBar中的图标,不用插槽可以直接用图片

使用文档中是可以直接使用图片链接的 :left-icon"require(../../assets/newImages/noticeImg.png)" <html> .... <NoticeBarmode""color"#C6C6C6"background""v-if"global_info.site_bulletin":left-icon"r…