Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/364.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/django/22.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python Django-验证模型和模型表单中计算字段的唯一性_Python_Django - Fatal编程技术网

Python Django-验证模型和模型表单中计算字段的唯一性

Python Django-验证模型和模型表单中计算字段的唯一性,python,django,Python,Django,TL;DR我的模型和表单都将字段的值计算为字符。我是否可以避免双重工作,但在使用没有表单的模型时仍然检查唯一性 我使用Python 3和Django 1.11 我的模型如下所示: class Account(models.Model): parent_account = models.ForeignKey( to='self', on_delete=models.PROTECT, null=True, blank=True

TL;DR我的模型和表单都将字段的值计算为字符。我是否可以避免双重工作,但在使用没有表单的模型时仍然检查唯一性

我使用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)
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\u设置为\u char
,因为它是基于所选的
父\u帐户
计算的:它是通过链接所有父帐户和当前实例的字段
number\u后缀
的值获得的

下面是一个有三个帐户的示例:

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\u by()


我的表格如下:

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)
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\u as\u char
,我使用forms
clean()
方法计算
number\u as\u char
(必须在验证唯一性之前进行计算)


这一切都有效(模型和表单),但在验证表单后,将通过模型
save()
方法再次计算
number\u as\u char
的值。这不是一个大问题,但有没有办法避免这种双重计算

  • 如果我从forms
    clean()
    方法中删除计算,那么新值不会验证唯一性(它只会检查旧值)
  • 我不想完全从模型中删除计算,因为我在没有表单的其他部分中使用模型

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

    我看不出有任何方法可以在两个地方做这件事(
    save()
    clean()

    但是,我可以为您的
    get\u number\u as\u char
    方法提供两个效率改进:

  • 将其设为a,以便在第二次调用它时,只需返回一个缓存值并消除双重计算。显然,您需要注意的是,在更新实例之前不要调用它,否则旧的
    number\u as\u char
    将被缓存。只要只在保存/清除期间调用
    get\u number\u as\u char()
    ,这就可以了

  • 根据上面提供的信息,您不必迭代所有祖先,只需将父级的
    number\u作为\u 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
    

    另一种方法——可能没有那么好——是使用。您可以发出一个信号,为即将保存的实例上的
    number\u as\u char
    字段设置正确的值

    这样,您就不必在模型的
    save()
    方法或
    ModelForm的
    clean()
    方法中完成

    使用信号应确保使用ORM操作数据的任何操作(扩展应意味着所有
    ModelForms
    都将触发信号)


    这种方法的缺点是,直接从代码中不清楚该属性是如何生成的。人们必须偶然发现信号的定义,才能发现它确实存在。不过,如果你能接受的话,我会选择信号。

    我对它做了一些修改,我想我找到了一个更好的方法

    通过使用模型表单的
    number\u as\u char
    字段上的属性,可以完全忽略用户输入(并在单个步骤中禁用该字段)

    您的模型已经在
    save
    方法中将
    number\u计算为\u char
    属性。但是,如果唯一约束失败,那么您的管理UI将抛出500错误。但是,您可以将字段计算移动到
    clean()
    方法,保留
    save()
    方法不变

    因此,完整示例将类似于此:

    表格:

    class AccountForm(forms.ModelForm):
    
        class Meta:
            model = Account
            fields = [
                'parent_account', 'number_suffix', 'number_as_char',
            ]
            widgets = {
                'number_as_char': forms.TextInput(attrs={'disabled': True}),
            }
    
    模型:

    class Account(models.Model):
        # ...
    
        def clean(self):
            self.number_as_char = self.get_number_as_char(
                self.parent_account, self.number_suffix
            )
            super().clean()
    
    这样,任何基于您的模型生成表单的操作都会抛出一个很好的验证错误(前提是它使用内置的模型验证,这是模型表单的情况)


    唯一的缺点是,如果保存触发验证错误的模型,您将看到一个空字段,而不是验证失败的值-但我想也有很好的方法来解决这个问题-如果我也找到了解决方案,我将编辑我的答案。

    在阅读了所有答案并对文档进行了进一步挖掘之后,我使用了以下方法:

  • @samu
    建议使用模型
    clean()
    方法和
    @Laurent S
    建议对
    (父帐户,数字后缀)
    一起使用
    unique\u。由于只同时使用
    unique\u
    对我来说不起作用,因为
    父账户
    可以
    null
    ,所以我选择将这两个想法结合起来:检查模型中现有的
    (父账户,数字后缀)
    组合
    clean()
    方法
  • 随后,我从表单中删除了
    number\u As\u char
    ,现在它只在
    save()
    方法中计算。顺便说一句:感谢
    @solarismoke
    建议仅基于第一个父项计算,而不是一直迭代到链的顶部
  • 另一个连续性是,我现在需要显式调用models
    full\u clean()
    方法来验证在使用不带表单的模型时的唯一性(否则我将获得数据库
    Integr