13

给定具有 ForeignKeyField (FKF) 或 ManyToManyField (MTMF) 字段的模型,其外键为“自我”,我如何防止在 Django Admin (admin) 中进行自我(递归)选择。

简而言之,应该可以防止在管理员中自我(递归)选择模型实例。这适用于编辑模型的现有实例,而不是创建新实例。

例如,新闻应用中的文章采用以下模型;

class Article(models.Model):           
    title = models.CharField(max_length=100)
    slug = models.SlugField()
    related_articles = models.ManyToManyField('self')

如果有 3 个实例(标题:a1-3),当通过管理员Article编辑现有实例时,该字段默认由一个 html(多个)选择框表示,该框提供所有文章的列表 ( )。用户应该只看到并能够选择除自身以外的实例,例如在编辑a1 时,可用于选择 = a2、a3。Articlerelated_articlesArticle.objects.all()ArticleArticlerelated_articles

我目前可以看到 3 种可能的方法来做到这一点,按偏好递减的顺序排列;

  1. 提供一种方法来设置查询集,在管理表单字段中提供可用选项related_articles(通过排除查询过滤器,例如Article.objects.filter(~Q(id__iexact=self.id)),从用户可以查看和选择的相关文章列表中排除正在编辑的当前实例。创建/设置要使用的查询集可以在__init__自定义的构造函数 () 中发生Article ModelForm,或者通过某种动态limit_choices_to Model选项发生。这需要一种方法来获取正在编辑的实例以用于过滤。
  2. 覆盖or类的save_model函数以在保存实例之前检查并从中删除自身。这仍然意味着管理员用户可以查看和选择所有文章,包括正在编辑的实例(对于现有文章)。Article ModelModelAdminrelated_articles
  3. 当需要在管理员之外使用时过滤掉自我引用,例如模板。

理想的解决方案 (1) 目前可以通过管理员外部的自定义模型表单来完成,因为可以将正在编辑的实例的过滤查询集变量传递给模型表单构造函数。问题是,您能否获得Article实例,即在创建表单之前正在编辑“自我”以执行相同的操作。

可能是我以错误的方式解决这个问题,但是如果您允许将 FKF / MTMF 定义为同一模型,那么应该有一种方法让管理员 -做正确的事情- 并阻止用户通过以下方式选择自己在可用选项列表中排除它。

注意:现在可以执行解决方案 2 和 3,并提供解决方案以尝试避免将这些作为答案,理想情况下,我希望得到解决方案 1 的答案。

4

4 回答 4

10

卡尔是正确的,这是一个剪切和粘贴代码示例admin.py

我发现如果你没有扎实的掌握,导航 Django 关系可能会很棘手,一个活生生的例子可能比“去读这个”更有价值 1000 倍(并不是说你不需要了解正在发生的事情) .

class MyForm(forms.ModelForm):
    class Meta:
        model = MyModel

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        self.fields['myManyToManyField'].queryset = MyModel.objects.exclude(
            id__exact=self.instance.id)
于 2009-12-08T21:18:57.257 回答
2

您可以在管理员中使用自定义 ModelForm(通过设置ModelAdmin 子类的“form”属性)。因此,您在管理员中的操作方式与在其他任何地方相同。

于 2009-05-16T16:23:07.037 回答
1

您还可以get_form像这样覆盖 ModelAdmin 的方法:

def get_form(self, request, obj=None, **kwargs):
    """
    Modify the fields in the form that are self-referential by
    removing self instance from queryset
    """
    form = super().get_form(request, obj=None, **kwargs)
    # obj won't exist yet for create page
    if obj:
        # Finds fieldnames of related fields whose model is self
        rmself_fields = [f.name for f in self.model._meta.get_fields() if (
            f.concrete and f.is_relation and f.related_model is self.model)]
        for fieldname in rmself_fields:
            form.base_fields[fieldname]._queryset =
                form.base_fields[fieldname]._queryset.exclude(id=obj.id)
    return form

请注意,这是一个万能的解决方案,它会自动查找自引用模型字段并从所有这些字段中删除 self :-)

于 2016-09-28T15:45:01.093 回答
0

我喜欢定时检查的解决方案save()

    def save(self, *args, **kwargs):
        # call full_clean() that in turn will call clean()
        self.full_clean()
        return super().save(*args, **kwargs)

    def clean(self):
        obj = self
        parents = set()
        while obj is not None:
            if obj in parents:
                raise ValidationError('Loop error', code='infinite_loop')
            parents.add(obj)
            obj = obj.parent
于 2021-04-19T07:36:59.240 回答