如何在Django中限制外键选择仅为关联对象
我有一个双向的外部关系,类似下面这个
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 个回答
从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里面,但我不太确定。
正确的方法是使用一个自定义的表单。通过这个表单,你可以访问到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
我刚在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”的方法。