首页 > 解决方案 > Django - 验证模型和模型表单中计算字段的唯一性

问题描述

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不应该由用户设置,因为它是根据 selected 计算的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()方法再次计算 的值。问题不大,但是有没有办法避免这种双重计算呢?

  1. 如果我从 formsclean()方法中删除计算,则不会使用新值验证唯一性(它只会检查旧值)。
  2. 我不想完全从模型中删除计算,因为我在没有表格的其他部分使用模型。

您有什么建议可以采取不同的方式来避免重复计算该字段吗?

标签: pythondjango

解决方案


我看不到在两个地方(save()clean())执行此操作的任何方法,因为您也需要它来处理非基于表单的保存)。

但是,我可以为您的方法提供两个效率改进get_number_as_char

  1. 将其设为 acached_property以便第二次调用它时,您只需返回一个缓存值并消除重复计算。显然,您需要注意在更新实例之前number_as_char不要调用它,否则旧的将被缓存。只要get_number_as_char()仅在保存/清理期间调用,这应该没问题。

  2. 根据您在上面提供的信息,您不必遍历所有祖先,而可以简单地获取number_as_char父级并附加到它。

以下包含两者:

@cached_property
def get_number_as_char(self, parent_account, number_suffix):
    number_as_char = str(number_suffix)
    if parent_account is not None:
        number_as_char = '{}-{}'.format(parent_account.number_as_char, number_as_char)

    return number_as_char

为确保缓存不会导致问题,您可以在完成保存后清除缓存值:

def save(self, *args, **kwargs):
    self.number_as_char = self.get_number_as_char(
        self.parent_account, self.number_suffix)
    super().save(*args, **kwargs)
    # Clear the cache, in case something edits this object again.
    del self.get_number_as_char

推荐阅读