假设我有一个用于根据购物车内容将订单保存到数据库的视图:
def cart_checkout(request):
order = Order()
order.first_name = 'x'
order.last_name = 'y'
order.address = 'z'
order.save()
cart = Cart(request)
for product_id, product_quantity in cart:
product = Product.objects.get(pk=product_id)
order_item = OrderItem()
order_item.order = order
order_item.name = product.name
order_item.price = product.price
order_item.amount = product_quantity
order_item.save()
order.update_total_price() # updates the Order total price field with the sum of order items prices
order.save()
return HttpResponse('Checked-out!')
如您所见,我在此视图中两次调用order.save()
:首先创建一个Order
实例,可以在OrderItems
循环中附加for
,然后根据订单中的订单项更新订单总价。如果我删除了第一个.save()
,则第二个错误会告诉我order
需要先保存。
两次调用.save()
方法对我来说似乎还不够干。有办法只做一次吗?
请注意,我没有继承ModelForm
的子类,因此无法使用.save(commit=False)
。另外,我不想只在save()
方法中隐藏update_total_price()
方法。
Models.py:
from django.db import models
from .mixins import DisplayNameMixin
class Product(DisplayNameMixin, models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=6, decimal_places=2)
amount = models.IntegerField()
class Order(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
address = models.CharField(max_length=255)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
def update_total_price(self):
order_items = self.orderitem_set.all()
self.total_price = sum([
x.price * x.amount
for x in order_items
])
class OrderItem(models.Model):
order = models.ForeignKey('Order', on_delete=models.CASCADE)
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=6, decimal_places=2)
amount = models.IntegerField()
答案 0 :(得分:2)
我认为,您不禁将订单保存两次,因为您需要具有order_id才能创建OrderItems,然后使用商品的金额更新订单。 我有一些建议。
您可以将total_price设为计算所得的属性,这样就不必保存订单:
class Order(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
address = models.CharField(max_length=255)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
@property
def total_price(self):
return sum([
x.price * x.amount
for x in self.orderitem_set.all()
])
答案 1 :(得分:1)
从数据库理论的角度来看,您的数据库结构是错误的。首先需要对其进行标准化。
为什么错了?
Order.total_price
是冗余表列。可以通过聚合找到该信息。在数据库级别,没有保护措施可以防止数据库用户(在您的情况下为Django应用)输入受到破坏的数据。因此,您的数据库可以同时告知两个不同的总价格(Order.total_price != SUM(OrderItem.price * OrderItem.amount)
)。
因此,要安抚数据库规范化问题,您需要删除total_price
字段,并在需要访问时使用Django聚合/注释(https://docs.djangoproject.com/en/3.0/topics/db/aggregation/)。
说,将total_price
放在Order
表中可能是完全有道理的。那个原因通常是性能。有时SQL查询的复杂性(按聚合列进行过滤非常烦人。)
但是有价格。这个价格就是您数据库的非规范化。对于您而言,您要付出违反DRY原则的责任。
只需确保您在同一事务中同时呼叫两个save()
。
答案 2 :(得分:1)
要扩展petraszd's answer(即删除total_price
字段)和engin_ipek's answer(即添加total_price
作为计算的属性),您可以尝试将{ {1}} cached property ,以避免传递相同的值,只要传递相同的total_price
实例即可。
如petraszd所述,如果您使用汇总来计算总价,则可能会使计算的成本降低一些。 Order
和price
中的adding the products。