如何防止Django Admin中FK/MTM字段的自(递归)选择

如何防止Django Admin中FK/MTM字段的自(递归)选择,django,django-models,django-admin,foreign-keys,many-to-many,Django,Django Models,Django Admin,Foreign Keys,Many To Many,给定一个带有ForeignKeyField(FKF)或ManyToManyField(MTMF)字段且foreignkey为“self”的模型,如何在Django Admin(Admin)中防止自我(递归)选择 简言之,应该可以防止在管理中自我(递归)选择模型实例。这适用于编辑模型的现有实例,而不是创建新实例 例如,以新闻应用程序中的文章为例,采用以下模型: class Article(models.Model): title = models.CharField(

给定一个带有ForeignKeyField(FKF)或ManyToManyField(MTMF)字段且foreignkey为“self”的模型,如何在Django Admin(Admin)中防止自我(递归)选择

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

例如,以新闻应用程序中的文章为例,采用以下模型:

class Article(models.Model):           
    title = models.CharField(max_length=100)
    slug = models.SlugField()
    related_articles = models.ManyToManyField('self')
如果有3个
文章
实例(标题:a1-3),当通过管理员编辑现有的
文章
实例时,
相关文章
字段默认由一个html(多个)选择框表示,该框提供所有文章的列表(
Article.objects.ALL()
)。用户应仅查看并能够选择自身以外的
文章
实例,例如,在编辑
文章
a1时,
相关文章
可供选择=a2、a3

我目前可以看到3种可能的方法来做到这一点,顺序是减少偏好

  • 提供一种设置查询集的方法,该查询集在管理表单字段中为
    相关文章提供可用选项(通过排除查询筛选器,例如
    Article.objects.filter(~Q(id\u iexact=self.id))
    从用户可以查看和选择的相关文章列表中排除正在编辑的当前实例。要使用的查询集的创建/设置可以在构造函数中进行(
    \uuuuuu init\uu
    )自定义的
    文章模型形式
    ,或者通过某种动态的
    限制选择_至模型
    选项。这需要一种方法来获取正在编辑的实例以用于筛选
  • 在保存实例之前,覆盖
    Article model
    ModelAdmin
    类的
    save\u model
    功能,以检查并从
    相关文章中删除自身。这仍然意味着管理员用户可以查看和选择所有文章,包括正在编辑的实例(对于现有文章)
  • 当需要在管理员之外使用时,过滤掉自我引用,例如模板
  • 理想溶液(1)当前可以通过管理员之外的自定义模型表单执行此操作,因为可以将正在编辑的实例的筛选queryset变量传递给模型表单构造函数。问题是,您是否可以访问
    文章
    实例,即在创建表单以执行相同操作之前,管理员正在编辑“self”

    这可能是我用了错误的方法,但是如果允许您为同一模型定义FKF/MTMF,那么应该有一种方法让管理员做正确的事情,并通过将其排除在可用选项列表中来防止用户选择自己


    注意:现在可以使用解决方案2和3,提供这些解决方案是为了尽量避免将它们作为答案,理想情况下,我希望获得解决方案1的答案。

    您可以在管理中使用自定义模型表单(通过设置)。因此,您在管理员中执行此操作的方式与在其他任何位置执行此操作的方式相同。

    Carl是正确的,这是一个剪切粘贴代码示例,将放入
    admin.py

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


    您还可以覆盖ModelAdmin的
    get\u form
    方法,如下所示:

    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
    

    请注意,这是一个通用的解决方案,可以自动查找自引用模型字段并从所有字段中删除自引用:-)

    我喜欢在
    保存()时检查的解决方案。
    时间:

    def保存(self,*args,**kwargs):
    #调用full_clean(),然后调用clean()
    self.full_clean()
    return super().save(*args,**kwargs)
    def清洁(自清洁):
    obj=自我
    parents=set()
    虽然obj不是无:
    如果父母中有obj:
    引发ValidationError('循环错误',代码='无限循环')
    父母。添加(obj)
    obj=obj.parent
    
    从技术上讲,我知道为什么不能这样做。实际上,我不明白为什么它从来没有被实现过。@kojiro-简单的事情应该是简单的,高级的事情应该是可能的。并非所有可能的特性都必须被限制在选择范围之内,正是因为您可以随时编写自己的模型代码来执行任何您想要的操作。如果您有一个直观的语法建议,可以添加它来限制您的选择,而不会使它变得更复杂,那么欢迎您打开一个补丁并提出建议。
    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