如何基于主表单选定供应商动态限制子表单产品下拉选项
基于选定供应商动态限制采购订单子表单产品选项的实现方案
嘿,这个需求在采购系统里太常见了!咱们得结合后端数据接口和前端动态更新来实现,同时还要加后端验证防止恶意提交,一步步来:
1. 后端新增AJAX接口,返回供应商对应的产品列表
首先我们需要一个接口,接收供应商ID,返回该供应商旗下的所有产品。在views.py里添加这个函数:
from django.http import JsonResponse from .models import Products def get_supplier_products(request): supplier_id = request.GET.get('supplier_id') if supplier_id: # 过滤出该供应商的产品,返回ID、编码和名称(方便下拉显示) products = Products.objects.filter(supplier_ref_id=supplier_id).values('id', 'product_code', 'name') product_options = [{'id': p['id'], 'text': f"{p['product_code']} - {p['name']}"} for p in products] return JsonResponse({'products': product_options}) # 没传供应商ID就返回空列表 return JsonResponse({'products': []})
然后在项目的urls.py里给这个接口加路由:
path('get-supplier-products/', views.get_supplier_products, name='get_supplier_products'),
2. 优化子表单,添加CSS类方便前端定位
为了让前端能快速找到所有子表单的产品下拉框,我们自定义PoLine的表单类,给product_code字段加个CSS类:
# forms.py from django.forms import ModelForm, Select from .models import PoLines class PoLineForm(ModelForm): class Meta: model = PoLines fields = ('product_code', 'product_price', 'item_quantity') widgets = { 'product_code': Select(attrs={'class': 'product-code-select'}), } # 用自定义表单生成formset,替换原来的po_line_formset po_line_formset = modelformset_factory( PoLines, form=PoLineForm, extra=1 )
如果你的父子表单关联更紧密,推荐改用inlineformset_factory(因为PoLines和PurchaseOrders是外键关系),这样更符合Django的父子表单设计:
from django.forms import inlineformset_factory po_line_formset = inlineformset_factory( PurchaseOrders, PoLines, form=PoLineForm, fields=('product_code', 'product_price', 'item_quantity'), extra=1, can_delete=False )
3. 前端实现动态更新下拉选项
在你的模板new_po.html里添加JavaScript(用jQuery简化操作,如果你不用jQuery也可以用原生JS),监听供应商下拉框的变化,请求接口更新产品选项:
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script> <script> $(document).ready(function() { // 获取主表单的供应商下拉框(注意ID要和你的表单prefix对应,这里是purchase_orders前缀) const supplierSelect = $('#id_purchase_orders-supplier_ref'); // 监听供应商选择变化 supplierSelect.change(function() { const supplierId = $(this).val(); // 获取所有子表单的产品下拉框 const productSelects = $('.product-code-select'); if (supplierId) { // 发送AJAX请求获取产品列表 $.ajax({ url: "{% url 'get_supplier_products' %}", data: { 'supplier_id': supplierId }, dataType: 'json', success: function(response) { // 更新每个产品下拉框 productSelects.each(function() { const $select = $(this); $select.empty(); // 添加默认空选项 $select.append('<option value="">---------</option>'); // 遍历添加产品选项 $.each(response.products, function(index, product) { $select.append(`<option value="${product.id}">${product.text}</option>`); }); }); } }); } else { // 没选供应商时清空所有产品下拉框 productSelects.each(function() { $(this).empty().append('<option value="">---------</option>'); }); } }); // 处理动态添加的子表单行(如果允许用户添加更多行) // 假设你的formset有添加按钮,类名为add-row,需要根据实际情况调整 $(document).on('click', '.add-row', function() { const newProductSelect = $(this).closest('.formset-row').find('.product-code-select'); const supplierId = supplierSelect.val(); if (supplierId) { // 给新添加的下拉框加载对应供应商的产品 $.ajax({ url: "{% url 'get_supplier_products' %}", data: { 'supplier_id': supplierId }, dataType: 'json', success: function(response) { newProductSelect.empty(); newProductSelect.append('<option value="">---------</option>'); $.each(response.products, function(index, product) { newProductSelect.append(`<option value="${product.id}">${product.text}</option>`); }); } }); } }); }); </script>
4. 后端添加验证,防止恶意提交
前端的限制可以被绕过,所以必须在后端验证每个提交的产品是否属于选定的供应商。修改views.py里的POST处理逻辑:
elif request.method == 'POST': poForm = NewPoForm(request.POST, prefix='purchase_orders') formset = po_line_formset(request.POST) if poForm.is_valid() and formset.is_valid(): # 额外验证:检查所有产品是否属于当前选定的供应商 selected_supplier = poForm.cleaned_data['supplier_ref'] is_valid = True for form in formset: if form.cleaned_data.get('product_code'): product = form.cleaned_data['product_code'] if product.supplier_ref != selected_supplier: form.add_error('product_code', '该产品不属于您选定的供应商') is_valid = False if not is_valid: # 验证失败,返回表单页面显示错误 return render(request, template_name, { 'poForm': poForm, 'formset': formset, 'helper': helper, 'title': title_message, 'date': date.today().strftime("%Y-%m-%d"), 'next_po': next_po + 1, }) # 验证通过,执行保存逻辑 print('DATA IS CORRECT') po = po_form_save(poForm, formset) formset_save(formset, po) messages.success(request, f"Purchase Order Successfully added.") return redirect('purchase_orders')
这样一套流程下来,就能完美实现你要的功能:选完供应商后,子表单的产品下拉只显示该供应商的产品,同时后端也做了安全验证。
内容的提问来源于stack exchange,提问作者CSL619




