Python 如何仅在django中将外键选择限制为相关对象

Python 如何仅在django中将外键选择限制为相关对象,python,django,django-models,Python,Django,Django Models,我有一个类似于以下的双向外交关系 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.Forei

我有一个类似于以下的双向外交关系

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"})

但这会导致管理界面不列出任何子项。

django不是这样工作的。您只能创建单向的关系

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

class Child(models.Model):
  name = models.CharField(max_length=255)
  myparent = models.ForeignKey(Parent)
如果您试图从家长处访问孩子,您会这样做
parent\u object.child\u set.all()
。如果您在myparent字段中设置了一个相关的_名称,那么您可以将其称为。例如:
related\u name='children'
,然后执行
parent\u object.children.all()


有关详细信息,请阅读。

在创建/编辑模型实例时,是否要限制管理界面中的可用选项

一种方法是验证模型。如果外部字段不是正确的选择,则可以在管理界面中引发错误


当然,Eric的答案是正确的:您实际上只需要一个外键,从孩子到家长。

@Ber:我已经为模型添加了类似于此的验证

class Parent(models.Model):
  name = models.CharField(max_length=255)
  favoritechild = models.ForeignKey("Child", blank=True, null=True)
  def save(self, force_insert=False, force_update=False):
    if self.favoritechild is not None and self.favoritechild.myparent.id != self.id:
      raise Exception("You must select one of your own children as your favorite")
    super(Parent, self).save(force_insert, force_update)
这正是我想要的工作方式,但如果此验证可以限制管理界面下拉列表中的选择,而不是在选择后进行验证,那就太好了。

我刚刚在Django文档中遇到过。 目前还不确定这是如何工作的,但这可能正是正确的做法

更新:ForeignKey.limit\u choices\u 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,则返回包含所有可能ID的查询非常重要,以便正常的管理页面正常工作

然后将外键字段声明为:

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

问题是,您必须提供信息,通过请求限制选择。我在这里看不到访问“自我”的方法。

我正在尝试做类似的事情。似乎每个人都在说“你应该只有一个外键”,这可能误解了你的意图

遗憾的是,你想做的限制({“我的父母”:“自我”}是行不通的。。。那将是干净而简单的。不幸的是,“self”没有得到计算,而是作为一个普通字符串进行处理

我想也许我能做到:

class MyModel(models.Model):
    def _get_self_pk(self):
        return self.pk
    favourite = models.ForeignKey(limit_choices_to={'myparent__pk':_get_self_pk})
但遗憾的是,这会产生一个错误,因为函数没有传递一个self arg:(

似乎唯一的方法是将逻辑放入使用此模型的所有表单中(即将查询集传递到formfield的选项中)。这很容易做到,但在模型级别进行此操作会更加枯燥。覆盖模型的save方法似乎是防止无效选项通过的一个好方法

更新

另一种方法参见我后面的答案

另一种方法是在父模型上不使用“FavoriteChild”fk作为字段

相反,你可以在孩子身上有一个你最喜欢的布尔字段

这可能有助于:

这样你就避免了确保孩子只能成为他们所属父母的宠儿的整个问题

视图代码可能略有不同,但过滤逻辑很简单

在管理中,您甚至可以有一个用于子模型的内联,该内联暴露了is_Favorite复选框(如果您每个家长只有几个孩子),否则必须从孩子的角度进行管理。

新的“正确”方法是这样做的,至少因为Django 1.1是通过覆盖AdminModel.formfield_for_foreignkey(self,db_字段,请求,**kwargs)

对于那些不想跟随下面链接的人来说,下面是一个示例函数,与上述问题模型非常接近

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,即当前对象。示例--


如果您只需要Django admin界面中的限制,这可能会起作用。我是基于另一个论坛的,虽然它适用于许多关系,但您应该能够替换
formfield\u for\u foreignkey
以使其起作用。在
admin.py
中:

class ParentAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        self.instance = obj
        return super(ParentAdmin, self).get_form(request, obj=obj, **kwargs)

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        if db_field.name == 'favoritechild' and self.instance:       
            kwargs['queryset'] = Child.objects.filter(myparent=self.instance.pk)
        return super(ChildAdmin, self).formfield_for_foreignkey(db_field, request=request, **kwargs)

@s29答案的一个更简单的变体:

而不是定制表格, 您只需从视图中限制表单字段中的可用选项:

对我起作用的是: 在forms.py中:

class AddIncomingPaymentForm(forms.ModelForm):
    class Meta: 
        model = IncomingPayment
        fields = ('description', 'amount', 'income_source', 'income_category', 'bank_account')
在views.py中:

def addIncomingPayment(request):
    form = AddIncomingPaymentForm()
    form.fields['bank_account'].queryset = BankAccount.objects.filter(profile=request.user.profile)

我认为,你不应该使用“null=True”。请在django文档中查找它。null=True指的是字符域。在这里,使用null=True完全可以(否则,没有子项就无法保存父项)@Cerin:Django中仍在使用线程。threadlocals只是在
self
无法引用请求数据的情况下以安全方式将请求中的信息传递给线程的一种方法。在上面的示例中没有使用实际线程,它只是使用threadlocals来模拟更大的范围。您可以使用存储在模型类或任意对象上的值
class AddIncomingPaymentForm(forms.ModelForm):
    class Meta: 
        model = IncomingPayment
        fields = ('description', 'amount', 'income_source', 'income_category', 'bank_account')
def addIncomingPayment(request):
    form = AddIncomingPaymentForm()
    form.fields['bank_account'].queryset = BankAccount.objects.filter(profile=request.user.profile)