如何在Django邮件模板中添加动态新增的组件字段内容
问题描述
我正在开发一个表单,填写完成后会发送邮件,邮件需包含表单中的所有信息。表单设置了“添加组件”按钮,点击后会新增“组件选择”和“组件数量”字段,请问如何获取这些动态新增字段的数据并写入邮件?
现有代码
views.py
def home(request): if request.method == 'POST': form = Form(request.POST) if form.is_valid(): type = form.cleaned_data['type'] serialNumber = form.cleaned_data['serialNumber'] itemNumber = form.cleaned_data['itemNumber'] date = form.cleaned_data['date'] phase = form.cleaned_data['phase'] capacity = form.cleaned_data['capacity'] centerQuantity = form.cleaned_data['centerQuantity'] componentQuantity = form.cleaned_data['componentQuantity'] componentSelection = form.cleaned_data['componentSelection'] commentary = form.cleaned_data['commentary'] html = render_to_string('emails/email.html', { 'type': type, 'serialNumber': serialNumber, 'item': itemNumber, 'date': date, 'phase': phase, 'capacity': capacity, 'centerQuantity': centerQuantity, 'componentQuantity': componentQuantity, 'componentSelection': componentSelection, 'commentary': commentary, } ) send_mail('ATENÇÃO', 'Message', 'no-reply@xxx.net', ['test@xxx.net'], html_message=html, fail_silently=False) return redirect('home') else: form = Form() return render(request, 'template.html', { 'form': form })
HTML模板(template.html)
<body> <div> <h2> 发动机信息</h2> <table> <form action="." method="POST" hx-post="."> {% csrf_token %} <tbody> <tr> <th>查询类型</th> <td>{{form.type}}</td> </tr> <tr> <th>序列号</th> <td>{{form.serialNumber}}</td> <th>或物料编号</th> <td>{{form.itemNumber}}</td> </tr> <tr> <th>生产日期</th> <td>{{form.date}}</td> <th>相位</th> <td>{{form.phase}}</td> <th>功率</th> <td>{{form.capacity}}</td> <th>中心数量</th> <td>{{form.centerQuantity}}</td> </tr> </tbody> </table> </div> </div> <div id="component-form"> <h2>组件</h2> <div class="parts-form"> <table id="component"> <tbody> <tr> <th>选择组件</th> <td>{{form.componentSelection}}</td> </tr> <tr> <th>组件数量</th> <td>{{form.componentQuantity}}</td> </tr> </tbody> <div id="form-added"></div> </table> </div> <button id="add-more" type="button">添加组件</button> <table> <tbody> <tr> <th>备注</th> <td>{{form.commentary}}</td> </tr> </tbody> </table> <input type="submit" id="button" value="提交"> </form> </div> <script> const AddMoreBtn = document.getElementById('add-more') AddMoreBtn.addEventListener('click', add_new_form) function add_new_form(event) { if (event) { event.preventDefault() } //id const newId = document.getElementsByClassName('parts-form') const idIncrement = newId.length + 1 // const formAdd = document.getElementById('form-added') //add new empty form var componentForm = document.getElementById('component').cloneNode(true) componentForm.setAttribute('class', 'parts-form') //id increment componentForm.setAttribute('id', `form-${idIncrement}`) formAdd.append(componentForm) } </script> </body> </html>
forms.py
from django import forms material_type = ( ('material', '按物料查询'), ('serial', '按序列号查询'), ) component_type = ( ('AIR FILTER', '空气滤清器'), ('ALUMINIUM FAN', '铝风扇'), ('BEARING CAP DE', '轴承端盖') ) class Form(forms.Form): type = forms.ChoiceField(choices=material_type, initial="选择查询类型") serialNumber = forms.CharField(label='序列号', required=False) itemNumber = forms.IntegerField(label='物料编号', required=False) date = forms.DateField(label='生产日期',widget=forms.DateInput(attrs={'placeholder':'示例: 10/05/2022'})) phase = forms.CharField(widget=forms.TextInput(attrs={'placeholder': '示例: 单相'})) capacity = forms.CharField(widget=forms.TextInput(attrs={'placeholder': '示例: 15cv'})) centerQuantity = forms.IntegerField(label='中心数量') componentSelection = forms.ChoiceField(choices=component_type, label='组件选择') componentQuantity = forms.IntegerField(label='组件数量') componentSelection1 = forms.ChoiceField(choices=component_type, label='组件选择', required=False) componentQuantity1 = forms.IntegerField(label='组件数量', required=False) commentary = forms.CharField(widget=forms.Textarea(attrs={"rows":8, "cols":80}))
email.html
<h2 id="title">提交信息</h2> <br> <p>查询类型: {{type}}</p> <p>序列号: {{serialNumber}}</p> <p>物料编号: {{item}}</p> <p>生产日期: {{date}}</p> <p>功率: {{capacity}}</p> <p>中心数量: {{centerQuantity}}</p> <p>组件数量: {{componentQuantity}}</p> <p>组件选择: {{componentSelection}}</p> <p>备注: {{commentary}}</p> <style> #title{ margin-bottom: 2px; } </style>
解决方案
当前核心问题是:动态克隆的表单字段与原字段name属性完全一致,Django无法区分多组组件输入;同时表单定义仅固定两组组件字段,无法支持无限新增。以下是具体修改步骤:
1. 修改前端JS,给动态字段设置唯一name属性
克隆字段时要修改name为带索引的格式(如componentSelection_1、componentQuantity_1),让后端能识别多组数据:
const AddMoreBtn = document.getElementById('add-more') AddMoreBtn.addEventListener('click', add_new_form) function add_new_form(event) { if (event) { event.preventDefault() } // 获取已有组件表单数量 const formCount = document.querySelectorAll('.parts-form').length; const formAdd = document.getElementById('form-added'); // 克隆原表单结构 const componentForm = document.getElementById('component').cloneNode(true); componentForm.setAttribute('class', 'parts-form'); componentForm.setAttribute('id', `form-${formCount + 1}`); // 修改克隆字段的name属性,添加索引 const selectField = componentForm.querySelector('select'); const quantityField = componentForm.querySelector('input[type="number"]'); selectField.setAttribute('name', `componentSelection_${formCount}`); quantityField.setAttribute('name', `componentQuantity_${formCount}`); // 清空克隆字段的默认值 selectField.value = ''; quantityField.value = ''; formAdd.append(componentForm); }
2. 修改后端视图,收集所有动态组件数据
无需在forms.py预定义多组字段,直接从request.POST提取所有带索引的组件数据:
def home(request): if request.method == 'POST': form = Form(request.POST) if form.is_valid(): # 提取基础字段数据 type = form.cleaned_data['type'] serialNumber = form.cleaned_data['serialNumber'] itemNumber = form.cleaned_data['itemNumber'] date = form.cleaned_data['date'] phase = form.cleaned_data['phase'] capacity = form.cleaned_data['capacity'] centerQuantity = form.cleaned_data['centerQuantity'] commentary = form.cleaned_data['commentary'] # 收集所有动态组件数据 components = [] # 遍历POST中所有组件相关字段 for key in request.POST.keys(): if key.startswith('componentSelection_'): # 获取索引 idx = key.split('_')[-1] selection = request.POST.get(key) quantity = request.POST.get(f'componentQuantity_{idx}') if selection and quantity: # 只收集有值的组件 components.append({ 'selection': selection, 'quantity': quantity }) # 加入原表单的第一个组件 original_selection = form.cleaned_data['componentSelection'] original_quantity = form.cleaned_data['componentQuantity'] if original_selection and original_quantity: components.insert(0, { 'selection': original_selection, 'quantity': original_quantity }) # 传递数据到邮件模板 html = render_to_string('emails/email.html', { 'type': type, 'serialNumber': serialNumber, 'item': itemNumber, 'date': date, 'phase': phase, 'capacity': capacity, 'centerQuantity': centerQuantity, 'components': components, 'commentary': commentary, }) send_mail('注意', '表单提交信息', 'no-reply@xxx.net', ['test@xxx.net'], html_message=html, fail_silently=False) return redirect('home') else: form = Form() return render(request, 'template.html', { 'form': form })
3. 修改邮件模板,渲染所有组件数据
使用循环遍历所有组件数据,展示在邮件中:
<h2 id="title">提交信息</h2> <br> <p>查询类型: {{type}}</p> <p>序列号: {{serialNumber}}</p> <p>物料编号: {{item}}</p> <p>生产日期: {{date}}</p> <p>相位: {{phase}}</p> <p>功率: {{capacity}}</p> <p>中心数量: {{centerQuantity}}</p> <h3>组件列表</h3> {% for comp in components %} <p>组件: {{comp.selection}} | 数量: {{comp.quantity}}</p> {% endfor %} <p>备注: {{commentary}}</p> <style> #title{ margin-bottom: 2px; } </style>
可选:用Django表单集规范处理(进阶)
如果需要更严谨的表单验证,可使用Django表单集:
- 在
forms.py定义单个组件表单:
class ComponentForm(forms.Form): selection = forms.ChoiceField(choices=component_type, label='组件选择') quantity = forms.IntegerField(label='组件数量')
- 视图中创建表单集:
from django.forms import formset_factory ComponentFormSet = formset_factory(ComponentForm, extra=1) def home(request): if request.method == 'POST': main_form = Form(request.POST) component_formset = ComponentFormSet(request.POST, prefix='components') if main_form.is_valid() and component_formset.is_valid(): # 处理主表单数据 # ... # 获取表单集数据 components = [] for form in component_formset: if form.cleaned_data: components.append({ 'selection': form.cleaned_data['selection'], 'quantity': form.cleaned_data['quantity'] }) # 发送邮件... else: main_form = Form() component_formset = ComponentFormSet(prefix='components') return render(request, 'template.html', { 'form': main_form, 'component_formset': component_formset })
- 修改前端模板渲染表单集,同时调整JS逻辑处理表单集的隐藏字段(如
form-TOTAL_FORMS),这种方式自带验证,适合复杂场景。
内容的提问来源于stack exchange,提问作者Bruno
相关产品推荐
相关产品推荐

