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

如何在Django的Order模型中通过OrderItem字段计算并保存总价

Alright, let's get your Order.total field automatically calculating the sum of price * quantity from related OrderItems—here's how to make this work reliably, whether you're using the admin or updating items elsewhere in your app:

1. Admin-Specific Fix (For Backend Operations)

Since you're using TabularInline in the Django admin, overriding the save_formset method in your OrderAdmin ensures that after saving all inline OrderItems, we recalculate and update the order's total.

Update your admin.py with this modified OrderAdmin:

from django.contrib import admin
from .models import Order, OrderItem
from pizza_app.models import UserTypes
from django.db.models import Sum, F, DecimalField

class OrderItemInline(admin.TabularInline):
    model = OrderItem

class OrderAdmin(admin.ModelAdmin):
    list_display = ['id','name','total' ,'mobile_no','address','status', 'created']
    list_editable = ['status']
    list_per_page = 10
    list_filter = ['status']
    readonly_fields= ['total']
    search_fields = ('id','mobile_no','name',)
    inlines = [OrderItemInline]

    def get_form(self, request, obj=None, **kwargs):
        form = super(OrderAdmin,self).get_form(request, obj,**kwargs)
        form.base_fields['Assigned_to'].queryset = form.base_fields['Assigned_to'].queryset.filter(staff_status='Delivery Boy')
        return form

    # New method to recalculate total after saving OrderItems
    def save_formset(self, request, form, formset, change):
        # Save all inline OrderItem instances first
        formset.save()
        # Grab the current Order object
        order = form.instance
        # Calculate total by summing (price * quantity) for all items
        total_calculated = order.items.aggregate(
            total_sum=Sum(F('price') * F('quantity'), output_field=DecimalField())
        )['total_sum'] or 0  # Fallback to 0 if no items exist
        # Update and save the order's total
        order.total = total_calculated
        order.save()

admin.site.register(Order, OrderAdmin)

This works because when you save an Order in the admin, Django first saves the Order itself, then saves each related OrderItem. The save_formset hook runs after all inline forms are saved, so we can safely compute the full sum of item totals.

2. Universal Fix (Works Everywhere)

To ensure the total updates even when OrderItems are added/modified/deleted outside the admin (like via an API or custom views), use Django's model signals.

Add this to your models.py:

from django.db import models
from pizza_app.models import MenuVariant
from django.urls import reverse
from django.db.models import Sum, F, DecimalField
from pizza_app.models import UserTypes
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver

class Order(models.Model):
    name = models.CharField(max_length=60)
    email = models.EmailField(max_length=60,default=None,blank=True)
    mobile_no = models.CharField(max_length=13, default=None)
    address = models.CharField(max_length=150)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    status_choices = (
        ('In Queue', 'In Queue'),
        ('Processing', 'Processing'),
        ('Ready', 'Ready'),
        ('Delivered', 'Delivered'),
        ('Paid', 'Paid'),
        ('Cancelled', 'Cancelled'),
    )
    status = models.CharField(max_length=15, choices=status_choices, default=status_choices[0][0])
    total = models.DecimalField(max_digits=10,decimal_places=2,default=0)
    Assigned_to = models.OneToOneField(UserTypes, on_delete=models.CASCADE,default=None, blank=True, null=True)

    class Meta:
        ordering = ('created', )

    def __str__(self):
        return 'Order {}'.format(self.id)

class OrderItem(models.Model):
    order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE )
    product = models.ForeignKey(MenuVariant, related_name='order_items', on_delete=models.CASCADE)
    size = models.CharField(max_length=20, default=None)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    quantity = models.PositiveIntegerField(default=1)

    def __str__(self):
        return '{}'.format(self.id)

# Signals to update Order.total when OrderItem is saved or deleted
@receiver(post_save, sender=OrderItem)
@receiver(post_delete, sender=OrderItem)
def update_order_total(sender, instance, **kwargs):
    order = instance.order
    # Calculate total sum of (price * quantity)
    total_calculated = order.items.aggregate(
        total_sum=Sum(F('price') * F('quantity'), output_field=DecimalField())
    )['total_sum'] or 0
    # Update and save the order
    order.total = total_calculated
    order.save()

This pair of signals triggers every time an OrderItem is created, updated, or deleted. It recalculates the order's total immediately, so your total field is always in sync with the actual items in the order.

Key Notes:

  • Why not just a pre_save signal on Order? When creating a new Order in the admin, Django saves the Order first (before any OrderItems are added), so a pre_save signal would set the total to 0 and never update it. Listening to OrderItem changes or using save_formset fixes this.
  • Fallback to 0: The or 0 ensures that empty orders have a total of 0 instead of None, matching your field's default value.
  • Decimal Precision: Using output_field=DecimalField() avoids floating-point calculation errors, since your price and total fields use Decimal types.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 05:20:34