给定具有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种方法可以做到这一点,按优先顺序递减;
related_articles
的管理表单字段中提供可用选项(通过排除查询过滤器,例如Article.objects.filter(~Q(id__iexact=self.id))
,以从related_articles列表中排除正在编辑的当前实例用户可以查看和选择。要使用的查询集的创建/设置可以在自定义__init__
的构造函数(Article ModelForm
)内进行,或者通过某种动态limit_choices_to Model
选项进行这需要一种方法来获取正在编辑的实例以用于过滤。save_model
或Article Model
类的ModelAdmin
函数以检查并从related_articles
中删除自身。这仍然意味着管理员用户可以查看和选择所有文章,包括正在编辑的实例(对于现有文章)。理想的解决方案(1)目前可以通过管理员之外的自定义模型表单来完成,因为可以将正在编辑的实例的已过滤的查询集变量传递给模型表单构造函数。问题是,您是否可以访问Article
实例,即在创建表单之前对管理员进行“自我”编辑以执行相同的操作。
我可能会以错误的方式解决这个问题,但是如果您允许将FKF / MTMF定义为同一模型,则应该有一种方法让管理员 - 做正确的事情 - 并阻止用户通过将其排除在可用选项列表中来选择自己。
注意:解决方案2和3现在可以做,并提供尝试避免将这些作为答案,理想情况下我想得到解决方案1的答案。
答案 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