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

news2025/3/13 8:34:09

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大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MVCC实现原理以及解决脏读、不可重复读、幻读问题

MVCC实现原理以及解决脏读、不可重复读、幻读问题 MVCC是什么&#xff1f;有什么作用&#xff1f;MVCC的实现原理行隐藏的字段undo log日志版本链Read View MVCC在RC下避免脏读MVCC在RC造成不可重复读、丢失修改MVCC在RR下解决不可重复读问题RR下仍然存在幻读的问题 MVCC是什么…

自学记录鸿蒙API 13:实现人脸比对Core Vision Face Comparator

完成了文本识别和人脸检测的项目后&#xff0c;我发现人脸比对是一个更有趣的一个小技术玩意儿。我决定整一整&#xff0c;也就是对HarmonyOS Next最新版本API 13中的Core Vision Face Comparator API的学习&#xff0c;这项技术能够对人脸进行高精度比对&#xff0c;并给出相似…

代码解析:安卓VHAL的AIDL参考实现

以下内容基于安卓14的VHAL代码。 总体架构 参考实现采用双层架构。上层是 DefaultVehicleHal&#xff0c;实现了 VHAL AIDL 接口&#xff0c;并提供适用于所有硬件设备的通用 VHAL 逻辑。下层是 FakeVehicleHardware&#xff0c;实现了 IVehicleHardware 接口。此类可模拟与实…

通过 Ansys Electronics Desktop 中的高级仿真优化 IC 设计

半导体行业继续通过日益复杂的集成电路 (IC) 设计突破技术界限。随着工艺节点缩小和电路密度达到前所未有的水平&#xff0c;电磁效应对设备性能和可靠性变得越来越重要。现代 IC 设计面临着来自复杂的布局相关耦合机制、信号完整性问题和功率分布问题的挑战&#xff0c;这些问…

Kafka数据迁移全解析:同集群和跨集群

文章目录 一、同集群迁移二、跨集群迁移 Kafka两种迁移场景&#xff0c;分别是同集群数据迁移、跨集群数据迁移。 一、同集群迁移 应用场景&#xff1a; broker 迁移 主要使用的场景是broker 上线,下线,或者扩容等.基于同一套zookeeper的操作。 实践&#xff1a; 将需要新添加…

【OpenGL ES】GLSL基础语法

1 前言 本文将介绍 GLSL 中数据类型、数组、结构体、宏、运算符、向量运算、矩阵运算、函数、流程控制、精度限定符、变量限定符&#xff08;in、out、inout&#xff09;、函数参数限定符等内容&#xff0c;另外提供了一个 include 工具&#xff0c;方便多文件管理 glsl 代码&a…

ffmpeg之播放一个yuv视频

播放YUV视频的步骤 初始化SDL库&#xff1a; 目的&#xff1a;确保SDL库正确初始化&#xff0c;以便可以使用其窗口、渲染和事件处理功能。操作&#xff1a;调用 SDL_Init(SDL_INIT_VIDEO) 来初始化SDL的视频子系统。 创建窗口用于显示YUV视频&#xff1a; 目的&#xff1a;…

复习打卡大数据篇——Hadoop MapReduce

目录 1. MapReduce基本介绍 2. MapReduce原理 1. MapReduce基本介绍 什么是MapReduce MapReduce是一个分布式运算程序的编程框架&#xff0c;核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序&#xff0c;并发运行在Hadoop集群上。 MapRed…

小程序配置文件 —— 13 全局配置 - window配置

全局配置 - window配置 这里讲解根目录 app.json 中的 window 字段&#xff0c;window 字段用于设置小程序的状态栏、导航条、标题、窗口背景色&#xff1b; 状态栏&#xff1a;顶部位置&#xff0c;有网络信号、时间信息、电池信息等&#xff1b;导航条&#xff1a;有一个当…

el-pagination 为什么只能展示 10 条数据(element-ui@2.15.13)

好的&#xff0c;我来帮你分析前端为什么只能展示 10 条数据&#xff0c;以及如何解决这个问题。 问题分析&#xff1a; pageSize 的值&#xff1a; 你的 el-pagination 组件中&#xff0c;pageSize 的值被设置为 10&#xff1a;<el-pagination:current-page"current…

单片机与MQTT协议

MQTT 协议简述 MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输协议&#xff09;&#xff0c;是一种基于发布 / 订阅&#xff08;publish/subscribe&#xff09;模式的 “轻量级” 通讯协议&#xff0c;该协议构建于 TCP/IP 协议上&#xf…

Debian-linux运维-docker安装和配置

腾讯云搭建docker官方文档&#xff1a;https://cloud.tencent.com/document/product/213/46000 阿里云安装Docker官方文档&#xff1a;https://help.aliyun.com/zh/ecs/use-cases/install-and-use-docker-on-a-linux-ecs-instance 天翼云常见docker源配置指导&#xff1a;htt…

使用Docker-compose部署SpringCloud项目

docker编写dockfile遇到的问题&#xff1a; 需要在docker-compose.yml文件下执行命令 docker-compose.yml文件格式的问题 1和2处空2格&#xff0c;3处空1格&#xff0c;4为本地配置文件目录&#xff0c;5为docker容器的目录&#xff0c;version为自己安装的docker-compose版本 …

KG4Diagnosis 分层多代理的医疗诊断框架,结合大模型与知识图谱构建,覆盖362种常见疾病

KG4Diagnosis 分层多代理的医疗诊断框架&#xff0c;结合大模型与知识图谱构建&#xff0c;覆盖362种常见疾病 论文大纲理解1. 提出背景是什么&#xff1f;2. 概念的性质是什么&#xff1f;是什么导致这个性质&#xff1f;3. 请举一个正例、一个反例&#xff0c;对比4. 请使用类…

【LLM综述】29种大模型Prompt Engineering技术

note 从零样本&#xff08;Zero-shot&#xff09;提示到最新进展的各种提示技术&#xff0c;包括推理和逻辑链&#xff08;Chain-of-Thought, CoT&#xff09;提示、自动链式思考&#xff08;Auto-CoT&#xff09;提示、自我一致性&#xff08;Self-Consistency&#xff09;提…

【黑马头条训练营】day02-黑马头条-App端文章展示

目录 描述app端首页从请求到数据显示的全部流程 描述文章微服务的组成及首页展示业务与实现 自己编写文章微服务关键逻辑 描述app端首页从请求到数据显示的全部流程 浏览器请求我们的app端 会通过nginx请求到我们app前端 app端输入手机号和密码 点击登录 请求 会到我们的…

DBeaver 咋手动配置sqlite 驱动

目录 1 问题2 下载 1 问题 离线安装了DBeaver 数据库软件&#xff0c;现在需要使用这个数据库打开sqlite 数据库&#xff0c;但是提示没有 驱动&#xff0c;那么我们就需要手动下载驱动&#xff0c;在这个软件里面导入 2 下载 https://repo1.maven.org/maven2/org/xerial/sql…

Linux 的历史与发展:从诞生到未来

Linux 的历史与发展&#xff1a;从诞生到未来 1. 起源之前&#xff1a;操作系统的历史背景 在 Linux 问世之前&#xff0c;操作系统的发展经历了多个重要阶段&#xff0c;这些阶段为 Linux 的诞生奠定了基础&#xff1a; 1940-1950 年代&#xff1a;计算机初期 早期计算机如 [[…

八爪鱼easyspider:

参考我的上一篇博客&#xff1a; scraper插件与软件&#xff0c; 主八爪鱼&#xff0c;easyspider 1&#xff0c;八爪鱼&#xff1a; 同时注意数据横向还是纵向&#xff0c;但是不好操作 二&#xff0c;easyspider&#xff1a; 其中1/2是不需要用户登入的&#xff0c;第3个…

算法基础一:冒泡排序

一、冒泡排序 1、定义 冒泡排序&#xff08;英语&#xff1a;Bubble Sort&#xff09;是一种简单的排序算法。它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序&#xff08;如从大到小、首字母从A到Z&#xff09;错误就把他们交换过来。 …