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

时间:2009-05-15 17:12:01

标签: django django-models django-admin foreign-keys many-to-many

给定具有ForeignKeyField(FKF)或ManyToManyField(MTMF)字段的模型以及“self”的外键,如何在Django Admin(admin)中阻止 self (递归)选择。

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

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

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

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

我现在可以看到3种方法可以做到这一点,按优先顺序递减;

  1. 提供一种方法来设置查询集,在related_articles的管理表单字段中提供可用选项(通过排除查询过滤器,例如Article.objects.filter(~Q(id__iexact=self.id)),以从related_articles列表中排除正在编辑的当前实例用户可以查看和选择。要使用的查询集的创建/设置可以在自定义__init__的构造函数(Article ModelForm)内进行,或者通过某种动态limit_choices_to Model选项进行这需要一种方法来获取正在编辑的实例以用于过滤。
  2. 在保存实例之前,覆盖save_modelArticle Model类的ModelAdmin函数以检查并从related_articles中删除自身。这仍然意味着管理员用户可以查看和选择所有文章,包括正在编辑的实例(对于现有文章)。
  3. 如果需要在管理员之外使用,请过滤掉自我引用,例如模板。
  4. 理想的解决方案(1)目前可以通过管理员之外的自定义模型表单来完成,因为可以将正在编辑的实例的已过滤的查询集变量传递给模型表单构造函数。问题是,您是否可以访问Article实例,即在创建表单之前对管理员进行“自我”编辑以执行相同的操作。

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

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

4 个答案:

答案 0 :(得分:9)

Carl是正确的,这是一个剪切和粘贴的代码示例,可以放在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)

答案 1 :(得分:2)

您可以在管理员中使用自定义ModelForm(通过设置the "form" attribute of your ModelAdmin subclass)。因此,您可以像在其他任何地方一样在管理员中执行此操作。

答案 2 :(得分:1)

你也可以像这样覆盖ModelAdmin的get_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

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

答案 3 :(得分: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