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

Django试剂订单管理:动态字段添加与订单合并实现问询

Hey there! Let's work through your reagent order tracking system problem step by step—focusing on fixing model relationships first, then handling forms and order merging logic.

1. Model Structure Overhaul (The Core Fix)

The root issue here is that your current model setup mixes order metadata, product details, and manufacturer info together, leading to duplicate data and merge headaches. Let's split things into clean, reusable models:

a. Manufacturer & Product Models (Fix Data Duplication)

First, isolate manufacturer details (since their delivery address and payment account are fixed) and standardize product data:

from django.db import models
from django.contrib.auth.models import User

class Manufacturer(models.Model):
    name = models.CharField(max_length=255, unique=True)
    delivery_address = models.TextField()
    payment_account = models.CharField(max_length=255)
    contact_person = models.CharField(max_length=100, blank=True)

    def __str__(self):
        return self.name

class Product(models.Model):
    name = models.CharField(max_length=255)
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
    spec = models.CharField(max_length=100, blank=True)  # e.g., "100mL"
    reference_price = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)

    class Meta:
        # Prevent duplicate products from the same manufacturer
        unique_together = ('name', 'manufacturer', 'spec')

    def __str__(self):
        return f"{self.name} ({self.spec}) - {self.manufacturer.name}"

This way, you only enter manufacturer and product details once, and users select from pre-existing entries (no more messy, inconsistent product data).

b. Order & OrderItem Models (Separate Orders from Line Items)

Split orders into a parent Order (tracking user/submission status) and child OrderItem (tracking individual reagents in an order):

class Order(models.Model):
    STATUS_CHOICES = [
        ('submitted', 'Submitted'),
        ('processed', 'Processed'),
        ('merged', 'Merged into Invoice'),
    ]
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    submitted_at = models.DateTimeField(auto_now_add=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='submitted')
    # Add user-specific delivery address here ONLY if it's different from the manufacturer's
    # user_delivery_address = models.TextField(blank=True)

class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items')
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField()
    actual_price = models.DecimalField(max_digits=10, decimal_places=2)

    def __str__(self):
        return f"{self.quantity}x {self.product.name}"

c. Invoice & InvoiceItem Models (Handle Merged Orders)

Create an Invoice model to group eligible OrderItems together, tied to a single manufacturer:

class Invoice(models.Model):
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    invoice_number = models.CharField(max_length=50, unique=True, blank=True)
    total_amount = models.DecimalField(max_digits=12, decimal_places=2, editable=False)

    def save(self, *args, **kwargs):
        # Auto-calculate total when saving the invoice
        if not self.invoice_number:
            # Generate a simple invoice number (customize as needed)
            self.invoice_number = f"INV-{self.created_at.strftime('%Y%m%d')}-{self.pk}"
        self.total_amount = sum(item.quantity * item.actual_price for item in self.invoice_items.all())
        super().save(*args, **kwargs)

class InvoiceItem(models.Model):
    invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE, related_name='invoice_items')
    order_item = models.ForeignKey(OrderItem, on_delete=models.CASCADE)

    def __str__(self):
        return f"{self.order_item.product.name} (from Order #{self.order_item.order.pk})"

2. Form Implementation (Fix Duplicate Data Entry)

You mentioned concerns about formsets—inline formsets are exactly what you need here, because they let users add multiple reagent line items to a single order, no repeated order metadata.

User Order Submission Form

from django import forms
from .models import Order, OrderItem

class OrderItemForm(forms.ModelForm):
    class Meta:
        model = OrderItem
        fields = ['product', 'quantity', 'actual_price']
        widgets = {
            'product': forms.Select(attrs={'class': 'form-control'}),
            'quantity': forms.NumberInput(attrs={'min': 1, 'class': 'form-control'}),
            'actual_price': forms.NumberInput(attrs={'step': '0.01', 'class': 'form-control'}),
        }

# Create an inline formset for OrderItems tied to an Order
OrderItemFormSet = forms.inlineformset_factory(
    Order,
    OrderItem,
    form=OrderItemForm,
    extra=1,  # Show 1 line item by default
    can_delete=True,  # Let users remove unwanted line items
    min_num=1,  # Require at least 1 line item
    validate_min=True
)

class OrderForm(forms.ModelForm):
    class Meta:
        model = Order
        fields = []  # No extra fields—user and status are set automatically

# In your view:
def submit_order(request):
    if request.method == 'POST':
        order_form = OrderForm(request.POST)
        formset = OrderItemFormSet(request.POST)
        
        if order_form.is_valid() and formset.is_valid():
            # Save the order first (without committing to DB yet)
            order = order_form.save(commit=False)
            order.user = request.user
            order.save()
            
            # Save all line items tied to the new order
            formset.instance = order
            formset.save()
            
            # Redirect to order confirmation page
            return redirect('order_detail', order_id=order.pk)
    else:
        order_form = OrderForm()
        formset = OrderItemFormSet()
    
    return render(request, 'submit_order.html', {
        'order_form': order_form,
        'formset': formset
    })

In your template, you can render the formset with a loop, and add a "Add More Reagent" button using JavaScript to dynamically add new line items (plenty of simple Django formset JS examples out there).

3. Order Merging Logic (For Admins)

Now, merging orders becomes straightforward—you just need to group OrderItems by their manufacturer, then link them to a new Invoice:

Admin Merge View Example

from django.contrib import messages
from itertools import groupby

def merge_orders(request):
    if request.method == 'POST':
        order_ids = request.POST.getlist('selected_orders')
        pending_orders = Order.objects.filter(id__in=order_ids, status='submitted').prefetch_related('items__product__manufacturer')
        
        # Validate all selected orders belong to the same manufacturer
        manufacturers = set()
        for order in pending_orders:
            for item in order.items.all():
                manufacturers.add(item.product.manufacturer.id)
        
        if len(manufacturers) != 1:
            messages.error(request, "Only orders from the same manufacturer can be merged.")
            return redirect('merge_orders')
        
        # Create the invoice
        target_manufacturer = Manufacturer.objects.get(id=next(iter(manufacturers)))
        new_invoice = Invoice.objects.create(manufacturer=target_manufacturer)
        
        # Link all eligible OrderItems to the invoice
        for order in pending_orders:
            for item in order.items.all():
                InvoiceItem.objects.create(invoice=new_invoice, order_item=item)
            # Update order status to mark as merged
            order.status = 'merged'
            order.save()
        
        messages.success(request, f"Successfully merged orders into Invoice #{new_invoice.invoice_number}")
        return redirect('invoice_detail', invoice_id=new_invoice.pk)
    
    # Get all pending orders, grouped by manufacturer for easy selection
    pending_orders = Order.objects.filter(status='submitted').prefetch_related('items__product__manufacturer')
    # Sort orders by manufacturer first to use groupby
    sorted_orders = sorted(pending_orders, key=lambda o: o.items.first().product.manufacturer.id if o.items.exists() else '')
    grouped_orders = groupby(sorted_orders, key=lambda o: o.items.first().product.manufacturer if o.items.exists() else None)
    
    return render(request, 'merge_orders.html', {'grouped_orders': grouped_orders})

In the admin template, you can display orders grouped by manufacturer, with checkboxes to select which ones to merge into a single invoice.

Quick Extra Tips

  • Product Data Standardization: Add an admin page for Product and Manufacturer so lab admins can pre-enter all common reagents—users just select from the list instead of typing, eliminating messy data.
  • Dynamic Product Addition: If users need to request a reagent not in the system, add a small modal form to the order page that lets them submit a new product request (which an admin can approve later).

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:27:04