You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何基于主表单选定供应商动态限制子表单产品下拉选项

基于选定供应商动态限制采购订单子表单产品选项的实现方案

嘿,这个需求在采购系统里太常见了!咱们得结合后端数据接口前端动态更新来实现,同时还要加后端验证防止恶意提交,一步步来:

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

火山引擎 最新活动