【问题标题】:Limit Recursive Foreign Key Depth in Django Model限制 Django 模型中的递归外键深度
【发布时间】:2013-08-07 04:00:12
【问题描述】:

我有一个 Django 模型,它带有一个可以为“自我”的外键。我有兴趣在递归深度上引入一个硬限制(比如 10)。哪里是检查这个的合适的地方,我应该在那里抛出什么样的异常?

我的意思的伪示例:

def limit_recursion_depth(self):
    depth = 0
    model = self
    while model.parent is not None:
        depth += 1
        model = model.parent
    if depth > 10:
        pass # Complain here and stop processing
    else:
        pass # Return the model, save, whatever is normal

我对覆盖模型的 save() 方法之类的解决方案更感兴趣,而不是仅适用于管理员的任何类型的验证。 (例如,我希望能够验证 Model.objects.create()。)

更新

尽管这个问题主要是学术性的,并且与我不再从事的项目有关,但我想更新这个问题,以防有人通过 Google 发现这个问题。以下来自django-mptt documentation

要获取所有这些页面标题,您可以执行以下操作:

titles = []
while page:
    titles.append(page.title)
    page = page.parent

每个页面都有一个数据库查询...

即使对于小型项目,Django-mptt 的效率也要高得多,即使在这种情况下,我也应该使用它。

【问题讨论】:

    标签: django django-models


    【解决方案1】:

    也许是这样的?

    def save(self, *args, **kwargs):
        if Foo.objects.filter(foo=self.foo).count() > 10:
            raise Exception("not more than 10")
        else:
            super(Foo, self).save(*args, **kwargs)
    

    更新:

    对于带有django-mpttself 引用字段,您可以执行以下操作:

    def save(self, *args, **kwargs):
        if Foo.objects.filter(foo=self.foo).get_descendant_count() > 10:
            raise Exception("not more than 10")
        else:
            super(Foo, self).save(*args, **kwargs)
    

    【讨论】:

    • 这绝对值得一票,至少在我得到代表授予的时候。我喜欢这个想法,因为它简单、清晰,并且(对我而言)没有任何明显的问题。如果像“实际上有一种方法可以在 Django 中正常执行此操作”这样的答案没有很快出现,我肯定会接受这个。谢谢!
    • 现在我在家并且有时间研究这个,我确实看到了一个小问题。您的 if 条件实际上检查说 A 的父母是否有超过 10 个孩子。我想沿着 A 的祖先走,以确保 A 不能有一个伟大的伟大伟大的伟大(递归深度)祖父。这更有意义吗?否则,这回答了我的问题——在哪里检查(model.save()),以及抛出什么(通用或自定义异常)。另外,assert Exception() 是什么。那不应该说raise吗?
    • 是的,我错过了“自我”部分。如果您要经常爬树,您真的应该看看django-mptt。你也是对的,应该说raise
    • 对不起,我已经有一段时间没有接手这个项目了,但我不得不不接受你的回答。 Django 的管理员只捕获某些异常,因此如果触发递归深度,此方法会导致所有表单崩溃并出现 500 错误!
    • 所以覆盖你想要这个功能的表单上的保存方法。是的,使用 ValidationError。
    【解决方案2】:

    无循环版本:

        def clean_parent(self):
            parent = self.cleaned_data['parent']
            if parent and parent.level >= 10:
                raise ValidationError('Recursion Depth Exceeded')
            return parent
    

    注意:levels 从 0 开始。

    【讨论】:

    • 家长怎么知道他的水平
    【解决方案3】:

    不幸的是,正如this answer 中很好解释的那样,在 Django 中确实没有一个很好的方法来做到这一点。例如,每次最终用户点击更改表单上的保存按钮时,上述方法只会让管理员崩溃,并显示 500 错误——几乎没有帮助。唯一正确的方法是define clean_{fieldname} and throw a ValidationError。例如,它不会验证来自manage.py shellmodel.save(),但也不会覆盖该验证,例如queryset.update()

    因此,我现在采用的解决方案如下所示:

    def clean_parent(self):
        depth = 0
        model = self.instance
        while model.parent is not None:
            depth += 1
            model = model.parent
        if depth > 10:
            raise ValidationError('Recursion Depth Exceeded')
    

    请注意,这属于模型的管理表单。

    【讨论】:

    • 什么情况,什么时候为当前根定义父级?))这个例子不检查后代的深度。
    • 我不确定我是否理解您的投诉。你是说检查另一个方向的深度吗?如果是这样,这可以很容易地修改以适合您的目的(无论如何这不是问题所在)。
    • 我只是想解释一下——如果需要限制节点深度,我们必须考虑所有后代的深度。只是假设。 A - 根 (depth=0) B - 另一个根 (depth=0) C - B 中的节点 (C.parent 是 B) (depth=1) 例如模型 1 中的深度限制。例如,我们打开 B 页面编辑并将父级从 None 更改为 A。当前验证检查 B 深度,它将为 1 并且很好,但是在重建树之后,C 将具有深度为 2,而不是深度限制。
    • 是的——这就是为什么我在更新中建议任何这样做的人认真考虑使用 django-mptt。使用类似于此答案中的方法检查后代将非常昂贵,而且在性能方面根本不值得。
    猜你喜欢
    • 1970-01-01
    • 2020-08-06
    • 1970-01-01
    • 2012-10-26
    • 2016-03-02
    • 2011-02-07
    • 1970-01-01
    • 1970-01-01
    • 2013-07-10
    相关资源
    最近更新 更多