Django 1.10+ 中发票计算的 "Best Practice" 是什么?
What is the "Best Practice" for invoice calculations in Django 1.10+?
如果一个有两个模型:
class Invoice(models.Model):
class Meta:
ordering = ('-date_added', )
number = models.CharField(max_length=10,)
comments = models.TextField(blank=True, help_text="Notes about this invoice." )
total = models.DecimalField(max_digits=9, decimal_places=2, default="0" )
date_added = models.DateTimeField(_('date added'), auto_now_add=True)
date_modified = models.DateTimeField(_('date modified'), auto_now=True)
def __unicode__(self):
return "%s: total %s" % (self.number, self.total)
class Part(models.Model):
for_invoice = models.ForeignKey(Invoice)
description = models.CharField(max_length=200, blank=True, help_text=_("Briefly describe the part.") )
supplier = models.CharField(max_length=100, blank=True, help_text=_("Supplier Name.") )
supplier_number = models.CharField(max_length=100, blank=False, help_text=_("Supplier's order number.") )
qty = models.DecimalField(max_digits=3, decimal_places=0, blank=False, null=False, help_text=_("How many are needed?") )
cost = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, help_text=_("Price paid per unit") )
line_total = models.DecimalField(max_digits=9, decimal_places=2, default="0")
date_added = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
def __unicode__(self):
return "%s: total %s" % (self.for_invoice, self.line_total)
我看到的第一个选择是可以将 "line_total" 或 "total" 实现为计算模型字段。但如果你这样做,那么你永远无法按 "line_total" 或 "total" 对更改列表进行排序,而用户希望能够这样做。所以我把它们作为模型上的保存字段。
阅读 Django 1.10 文档我看到 4 个地方可以定义代码来计算和更新 "total" 和 "line_total" 字段:
- ModelAdmin.save_model(请求、对象、表单、更改)和ModelAdmin.save_formset(请求、表单、表单集、更改)
- ModelAdmin.save_related(请求、表单、表单集、更改)
- model.save(self, *args, **kwargs))
- 保存信号
在我看来,保存信号级别太低 - 绑定到单个模型保存事件,它永远无法访问请求或会话。那么 "annoying" 尝试汇总所有 "Part" 记录?
同样,model.save 方法似乎也过于精细。如果我可以使用 Invoice.save 方法,那将是 "handy",因为它可以通过 Invoice.part_set.all() 轻松访问所有相关部分,遍历该查询集将是更新 line_total,然后是主总。但同样,此时是否会保存所有 new/changed 部分记录?而且它无法访问 HttpRequest。
我认为这同样适用于管理员 save_model。但是,我有一个模糊的记忆,可以确保先保存相关部分。我在管理员的发票 add/edit 页面上使用内嵌方式列出了零件。
我刚刚在 ModelAdmin.save_related 中添加了!我忘了那个。由于此时将保存主发票,也许这是更新所有部分记录的最佳位置,然后更新父合计?
提前致谢!
感谢 Alasdair 的建议,但是,该客户不想要完整的自定义解决方案。所以我坚持管理调整。
我正在使用选项 2 进行测试:管理员的 save_related 功能。代码如下:
from decimal import Decimal
def save_related(self, request, form, formsets, change):
total = Decimal('0.00')
for myform in formsets:
for mf in myform.forms:
# Skip empty rows (if any):
if len(mf.cleaned_data) > 0:
# Update item line total from item quantity and price:
mf.instance.line_total = mf.instance.qty * mf.instance.cost
# Add line item total to grand total:
total += mf.instance.line_total
# re-save line item:
mf.instance.save()
# Update grand total on the invoice:
form.instance.total = total
# re-save the invoice record:
form.instance.save()
# Chain to inherited code:
super(InvoiceAdmin, self).save_related(request, form, formsets, change)
对于糟糕的变量名,我们深表歉意。 2 层深的内联表单让我措手不及。我猜第一层是一组表单集(只有一个),第一个表单集里面是我的表单。一切都有 .instance 对象,我认为这表明它们已经被保存了。一个惊喜,因为我以为我读到这个函数的默认操作是保存表单集(主表单已经保存)。
无论如何,它有效,所以我假设我误解了文档。
如果一个有两个模型:
class Invoice(models.Model):
class Meta:
ordering = ('-date_added', )
number = models.CharField(max_length=10,)
comments = models.TextField(blank=True, help_text="Notes about this invoice." )
total = models.DecimalField(max_digits=9, decimal_places=2, default="0" )
date_added = models.DateTimeField(_('date added'), auto_now_add=True)
date_modified = models.DateTimeField(_('date modified'), auto_now=True)
def __unicode__(self):
return "%s: total %s" % (self.number, self.total)
class Part(models.Model):
for_invoice = models.ForeignKey(Invoice)
description = models.CharField(max_length=200, blank=True, help_text=_("Briefly describe the part.") )
supplier = models.CharField(max_length=100, blank=True, help_text=_("Supplier Name.") )
supplier_number = models.CharField(max_length=100, blank=False, help_text=_("Supplier's order number.") )
qty = models.DecimalField(max_digits=3, decimal_places=0, blank=False, null=False, help_text=_("How many are needed?") )
cost = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, help_text=_("Price paid per unit") )
line_total = models.DecimalField(max_digits=9, decimal_places=2, default="0")
date_added = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
def __unicode__(self):
return "%s: total %s" % (self.for_invoice, self.line_total)
我看到的第一个选择是可以将 "line_total" 或 "total" 实现为计算模型字段。但如果你这样做,那么你永远无法按 "line_total" 或 "total" 对更改列表进行排序,而用户希望能够这样做。所以我把它们作为模型上的保存字段。
阅读 Django 1.10 文档我看到 4 个地方可以定义代码来计算和更新 "total" 和 "line_total" 字段:
- ModelAdmin.save_model(请求、对象、表单、更改)和ModelAdmin.save_formset(请求、表单、表单集、更改)
- ModelAdmin.save_related(请求、表单、表单集、更改)
- model.save(self, *args, **kwargs))
- 保存信号
在我看来,保存信号级别太低 - 绑定到单个模型保存事件,它永远无法访问请求或会话。那么 "annoying" 尝试汇总所有 "Part" 记录?
同样,model.save 方法似乎也过于精细。如果我可以使用 Invoice.save 方法,那将是 "handy",因为它可以通过 Invoice.part_set.all() 轻松访问所有相关部分,遍历该查询集将是更新 line_total,然后是主总。但同样,此时是否会保存所有 new/changed 部分记录?而且它无法访问 HttpRequest。
我认为这同样适用于管理员 save_model。但是,我有一个模糊的记忆,可以确保先保存相关部分。我在管理员的发票 add/edit 页面上使用内嵌方式列出了零件。
我刚刚在 ModelAdmin.save_related 中添加了!我忘了那个。由于此时将保存主发票,也许这是更新所有部分记录的最佳位置,然后更新父合计?
提前致谢!
感谢 Alasdair 的建议,但是,该客户不想要完整的自定义解决方案。所以我坚持管理调整。
我正在使用选项 2 进行测试:管理员的 save_related 功能。代码如下:
from decimal import Decimal
def save_related(self, request, form, formsets, change):
total = Decimal('0.00')
for myform in formsets:
for mf in myform.forms:
# Skip empty rows (if any):
if len(mf.cleaned_data) > 0:
# Update item line total from item quantity and price:
mf.instance.line_total = mf.instance.qty * mf.instance.cost
# Add line item total to grand total:
total += mf.instance.line_total
# re-save line item:
mf.instance.save()
# Update grand total on the invoice:
form.instance.total = total
# re-save the invoice record:
form.instance.save()
# Chain to inherited code:
super(InvoiceAdmin, self).save_related(request, form, formsets, change)
对于糟糕的变量名,我们深表歉意。 2 层深的内联表单让我措手不及。我猜第一层是一组表单集(只有一个),第一个表单集里面是我的表单。一切都有 .instance 对象,我认为这表明它们已经被保存了。一个惊喜,因为我以为我读到这个函数的默认操作是保存表单集(主表单已经保存)。
无论如何,它有效,所以我假设我误解了文档。