You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Django中实现客户与服务订单分表存储及表单提交?

解决Django客户与服务订单分离存储的问题

问题背景

原本的Django应用将客户与服务订单字段存储在同一张表中,现在需要将客户数据拆分到独立的Clientes表,避免重复存储同一客户信息。需求是:在同一页面展示客户和服务订单的所有字段,提交时如果客户不存在则同时创建客户和服务订单;如果客户已存在,则仅创建关联该客户的服务订单。当前修改视图后只能显示客户字段,服务订单字段消失,需实现正确的表单处理逻辑。


当前现有代码

1. 新增订单视图(NovaOS)

@login_required
def NovaOS(request):
    if request.method == 'POST':
        form = OrdemServicoForm(request.POST)

        if form.is_valid():
            OS = form.save(commit=False)
            OS.user = request.user
            OS.save()
            return redirect('/os/list/')
    else:
        form = OrdemServicoForm()
        return render(request, 'serviceorders/addos.html', {'form': form})

2. 客户表单(forms.py)

class ClienteForm(forms.ModelForm):
    class Meta:
        model = Clientes
        fields = '__all__'
        widgets = { #Dados do cliente
                    'STATUSCliente':                forms.Select(attrs={'class': 'form-control', 'placeholder': 'Status'}),
                    'cliente_razaosocial':          forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Razão Social', 'autofocus': True}),
                    'cliente_cpfcnpj':              forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'CPF/CNPJ'}),
                    'cliente_rgie':                 forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'RG/I.E.'}),
                    'cliente_email':                forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'E-mail'}),
                    'cliente_telefone_fixo':        forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Tel. fixo', 'data-mask':"(00)0000-0000"}),
                    'cliente_telefone_celular':     forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Tel. celular', 'data-mask':"(00)00000-0000"}),
                    'cliente_endereco':             forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Endereço'}),
                    'cliente_endereco_num':         forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Número'}), 
                    'cliente_endereco_CEP':         forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'CEP'}),
                    'cliente_endereco_Bairro':      forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Bairro'}),
                    'cliente_endereco_Cidade':      forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Cidade'}),
                    'cliente_endereco_UF':          forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'UF'}),
                    'cliente_endereco_Complemento': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Complemento'}),

        }

        labels = {
            'cliente_razaosocial': 'Nome/Razão social:',
            'cliente_cpfcnpj': 'CPF/CNPJ:',
            'cliente_rgie': 'RG/IE:',
            'cliente_email': 'E-mail:',
            'cliente_telefone_fixo': 'Fixo:',
            'cliente_telefone_celular': 'Celular:',
            'cliente_endereco': 'Endereço:',
            'cliente_endereco_num': 'Num:',
            'cliente_endereco_Complemento': 'Complemento:',
            'cliente_endereco_CEP': 'CEP:',
            'cliente_endereco_Bairro': 'Bairro:',
            'cliente_endereco_Cidade': 'Cidade:',
            'cliente_endereco_UF': 'UF:',
        }

3. 客户模型(models.py)

class Clientes(models.Model):
    STATUSCliente = (
        ('Ativo', 'Ativo'),        
        ('Inativo', 'Inativo'),
    )

    #Dados Cliente
    cliente_razaosocial          = models.CharField(max_length=100)
    cliente_cpfcnpj              = models.CharField(max_length=20)
    cliente_rgie                 = models.CharField(max_length=20)
    cliente_email                = models.CharField(max_length=50)
    cliente_telefone_fixo        = models.CharField(max_length=20)
    cliente_telefone_celular     = models.CharField(max_length=20)
    cliente_endereco             = models.CharField(max_length=100)
    cliente_endereco_num         = models.CharField(max_length=10)
    cliente_endereco_CEP         = models.CharField(max_length=10)
    cliente_endereco_Bairro      = models.CharField(max_length=30)
    cliente_endereco_Cidade      = models.CharField(max_length=30)
    cliente_endereco_UF          = models.CharField(max_length=20)
    cliente_endereco_Complemento = models.CharField(max_length=50)  
    STATUSCliente = models.CharField(max_length=10, choices=STATUSCliente, default='Ativo')

    def __str__(self):
        return self.cliente_razaosocial

实现方案

1. 完善服务订单模型的外键关联

首先确保OrdemServico模型中添加指向Clientes的外键,这样服务订单才能关联到对应的客户:

from django.contrib.auth.models import User

class OrdemServico(models.Model):
    cliente = models.ForeignKey(Clientes, on_delete=models.CASCADE, related_name='ordens_servico')
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    # 添加你的服务订单其他字段,比如服务类型、描述等
    servico_tipo = models.CharField(max_length=50)
    servico_descricao = models.TextField()
    # ... 其他字段

    def __str__(self):
        return f"OS #{self.id} - {self.cliente.cliente_razaosocial}"

2. 修改视图,同时处理两个表单

更新NovaOS视图,同时实例化客户表单和服务订单表单,提交时验证两个表单的有效性,通过CPF/CNPJ判断客户是否存在,存在则复用,不存在则创建新客户,最后关联到服务订单:

@login_required
def NovaOS(request):
    if request.method == 'POST':
        cliente_form = ClienteForm(request.POST)
        os_form = OrdemServicoForm(request.POST)
        
        if cliente_form.is_valid() and os_form.is_valid():
            # 用CPF/CNPJ作为唯一标识查找客户,不存在则创建
            cpfcnpj = cliente_form.cleaned_data['cliente_cpfcnpj']
            cliente, created = Clientes.objects.get_or_create(
                cliente_cpfcnpj=cpfcnpj,
                defaults=cliente_form.cleaned_data
            )
            
            # 保存服务订单,关联客户和当前登录用户
            os_instance = os_form.save(commit=False)
            os_instance.cliente = cliente
            os_instance.user = request.user
            os_instance.save()
            
            return redirect('/os/list/')
    else:
        # GET请求时初始化两个空表单
        cliente_form = ClienteForm()
        os_form = OrdemServicoForm()
    
    # 把两个表单传递给模板
    return render(request, 'serviceorders/addos.html', {
        'cliente_form': cliente_form,
        'os_form': os_form
    })

3. 更新模板,渲染两个表单

修改addos.html模板,同时渲染客户表单和服务订单表单的字段:

<form method="post">
    {% csrf_token %}
    
    <div class="section">
        <h3>Dados do Cliente</h3>
        {{ cliente_form.as_p }}
    </div>

    <div class="section">
        <h3>Dados da Ordem de Serviço</h3>
        {{ os_form.as_p }}
    </div>

    <button type="submit" class="btn btn-primary">Salvar Ordem</button>
</form>

4. 可选优化:自动填充已有客户信息

如果想提升用户体验,可以添加AJAX功能,当用户输入CPF/CNPJ时自动查询并填充已有客户的信息:

  • 前端监听cliente_cpfcnpj输入框的blur事件,发送AJAX请求到后端接口
  • 后端接口根据CPF/CNPJ返回客户数据,前端用JavaScript填充表单字段

示例后端接口:

from django.http import JsonResponse

def get_cliente_by_cpfcnpj(request):
    cpfcnpj = request.GET.get('cpfcnpj')
    try:
        cliente = Clientes.objects.get(cliente_cpfcnpj=cpfcnpj)
        data = {
            'cliente_razaosocial': cliente.cliente_razaosocial,
            'cliente_email': cliente.cliente_email,
            'cliente_telefone_celular': cliente.cliente_telefone_celular,
            # 其他需要填充的字段
        }
        return JsonResponse(data)
    except Clientes.DoesNotExist:
        return JsonResponse({}, status=404)

内容的提问来源于stack exchange,提问作者Elinton Soares

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 15:52:06