如何在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_formsetfixes this. - Fallback to 0: The
or 0ensures that empty orders have a total of 0 instead ofNone, matching your field's default value. - Decimal Precision: Using
output_field=DecimalField()avoids floating-point calculation errors, since yourpriceandtotalfields use Decimal types.
内容的提问来源于stack exchange,提问作者M_FarhanZia

