【发布时间】:2018-10-15 04:34:53
【问题描述】:
TL;DR 我的模型和表单都计算字段 number_as_char 的值。我可以避免双重工作,但在使用没有表单的模型时仍然检查唯一性吗?
我使用 Python 3 和 Django 1.11
我的模型如下所示:
class Account(models.Model):
parent_account = models.ForeignKey(
to='self',
on_delete=models.PROTECT,
null=True,
blank=True)
number_suffix = models.PositiveIntegerField()
number_as_char = models.CharField(
max_length=100,
blank=True,
default='',
unique=True)
@classmethod
def get_number_as_char(cls, parent_account, number_suffix):
# iterate over all parents
suffix_list = [str(number_suffix), ]
parent = parent_account
while parent is not None:
suffix_list.insert(0, str(parent.number_suffix))
parent = parent.parent_account
return '-'.join(suffix_list)
def save(self, *args, **kwargs):
self.number_as_char = self.get_number_as_char(
self.parent_account, self.number_suffix)
super().save(*args, **kwargs)
number_as_char字段不应该由用户设置,因为它是根据选择的parent_account计算的:它是通过链接所有父帐户的字段number_suffix的值和当前实例。
这是一个包含三个帐户的示例:
ac1 = Account()
ac1.parent_account = None
ac1.number_suffix = 2
ac1.save()
# ac1.number_as_char is '2'
ac2 = Account()
ac2.parent_account = ac1
ac2.number_suffix = 5
ac2.save()
# ac2.number_as_char is '2-5'
ac3 = Account()
ac3.parent_account = ac2
ac3.number_suffix = 1
ac3.save()
# ac3.number_as_char is '2-5-1'
不是删除字段并改用模型属性的选项,因为我需要确保唯一性并使用该字段对带有order_by() 的查询集进行排序。
我的表格如下:
class AccountForm(forms.ModelForm):
class Meta:
model = Account
fields = [
'parent_account', 'number_suffix', 'number_as_char',
]
widgets = {
'number_as_char': forms.TextInput(attrs={'readonly': True}),
}
def clean(self):
super().clean()
self.cleaned_data['number_as_char'] = self.instance.get_number_as_char(
self.cleaned_data['parent_account'], self.cleaned_data['number_suffix'])
我将number_as_char 包含在带有小部件属性readonly 的表单中,并使用表单clean() 方法计算number_as_char(必须在验证唯一性之前计算)。
这一切都有效(模型和表单),但在验证表单后,number_as_char 的值将由模型save() 方法再次计算。问题不大,但有没有办法避免这种双重计算?
- 如果我从表单
clean()方法中删除计算,则不会使用新值验证唯一性(它只会检查旧值)。 - 我不想从模型中完全删除计算,因为我在没有表单的其他部分使用模型。
您有什么建议可以采取不同的方式来避免重复计算字段吗?
【问题讨论】:
-
根据您的模型构建方式,您可以通过检查
(parent, suffix)对是否唯一来确保唯一性,但它可能会比您正在做的更昂贵。它不需要对number_as_char进行双重计算,但我认为你不会赢太多,如果有的话。这是 django 的一个超级常见的头痛...... -
@LaurentS 像您建议的那样使用
unique_together也不是一个完整的解决方案,因为带有parent_account=None的行可以重复(SQL NULL 不等于另一个SQL NULL)。有关该问题,请参阅 this question。 -
啊,抱歉,我错过了
parent_account可能是None的事实...您能否设置一个所有没有父链接的帐户的“root”帐户?根据您的应用程序,这可能有助于使unique_together工作? -
@LaurentS 一个伪 root 帐户听起来有点复杂。我必须在显示列表时排除该列表以避免混淆最终用户。有可能,但听起来比我目前的解决方案更复杂。
-
它对验证没有帮助,但是有什么理由反对将
number_as_char设为属性。因为它是你的模型是非规范化的。使用属性,您可以对其进行规范化并仅在需要时执行计算,而不是每次保存时。