如何在Django中限制外键选择仅为关联对象

77 投票
11 回答
73570 浏览
提问于 2025-04-11 20:49

我有一个双向的外部关系,类似下面这个

class Parent(models.Model):
  name = models.CharField(max_length=255)
  favoritechild = models.ForeignKey("Child", blank=True, null=True)

class Child(models.Model):
  name = models.CharField(max_length=255)
  myparent = models.ForeignKey(Parent)

我该如何限制 Parent.favoritechild 的选择,只能是那些父母是自己的孩子呢?我试过

class Parent(models.Model):
  name = models.CharField(max_length=255)
  favoritechild = models.ForeignKey("Child", blank=True, null=True, limit_choices_to = {"myparent": "self"})

但是这样会导致管理界面不显示任何孩子。

11 个回答

18

从Django 1.1开始,做这件事的新方法是重写AdminModel中的formfield_for_foreignkey(self, db_field, request, **kwargs)这个函数。

你可以查看这个链接了解更多信息:http://docs.djangoproject.com/en/1.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey

对于那些不想点链接的人,下面有一个示例函数,跟上面提到的问题模型很接近。

class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "favoritechild":
            kwargs["queryset"] = Child.objects.filter(myparent=request.object_id)
        return super(MyModelAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)

我只是对如何获取当前正在编辑的对象不太确定。我觉得这个对象应该在self里面,但我不太确定。

41

正确的方法是使用一个自定义的表单。通过这个表单,你可以访问到self.instance,这个就是当前的对象。举个例子 --

from django import forms
from django.contrib import admin 
from models import *

class SupplierAdminForm(forms.ModelForm):
    class Meta:
        model = Supplier
        fields = "__all__" # for Django 1.8+


    def __init__(self, *args, **kwargs):
        super(SupplierAdminForm, self).__init__(*args, **kwargs)
        if self.instance:
            self.fields['cat'].queryset = Cat.objects.filter(supplier=self.instance)

class SupplierAdmin(admin.ModelAdmin):
    form = SupplierAdminForm
50

我刚在Django文档中发现了ForeignKey.limit_choices_to这个内容。现在还不太确定它是怎么工作的,但感觉可能在这里会有用。

更新: ForeignKey.limit_choices_to 让我们可以指定一个常量、一个可调用对象(比如函数或类的方法)或者一个Q对象,来限制外键的可选项。显然,常量在这里没什么用,因为它对涉及的对象一无所知。

使用可调用对象(函数、类方法或任何可调用的对象)似乎更有希望。不过,如何从HttpRequest对象中获取必要的信息仍然是个问题。使用线程本地存储可能是个解决方案。

2. 更新:以下是我成功的做法:

我创建了一个中间件,正如上面链接所描述的。它从请求的GET部分提取一个或多个参数,比如“product=1”,并将这些信息存储在线程本地变量中。

接下来,在模型中有一个类方法,它读取线程本地变量并返回一个id列表,以限制外键字段的选择。

@classmethod
def _product_list(cls):
    """
    return a list containing the one product_id contained in the request URL,
    or a query containing all valid product_ids if not id present in URL

    used to limit the choice of foreign key object to those related to the current product
    """
    id = threadlocals.get_current_product()
    if id is not None:
        return [id]
    else:
        return Product.objects.all().values('pk').query

如果没有选择任何项,重要的是要返回一个包含所有可能id的查询,这样正常的管理页面才能正常工作。

然后外键字段被声明为:

product = models.ForeignKey(
    Product,
    limit_choices_to={
        id__in=BaseModel._product_list,
    },
)

关键是你必须通过请求提供限制选择的信息。我在这里看不到访问“self”的方法。

撰写回答