71

我有类似于以下的双向对外关系

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

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

4

11 回答 11

45

我刚刚在 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,则返回包含所有可能 ID 的查询非常重要,这样正常的管理页面可以正常工作。

然后将外键字段声明为:

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

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

于 2008-10-30T23:07:01.880 回答
39

这样做的“正确”方法是使用自定义表单。从那里,您可以访问 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
于 2013-10-24T03:25:58.113 回答
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)

我只是不确定如何获取正在编辑的当前对象。我希望它实际上是在某个地方,但我不确定。

于 2011-01-11T01:44:54.443 回答
13

这不是 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_object.child_set.all()。如果您在 myparent 字段中设置了一个 related_name,那么这就是您将其称为的名称。例如:related_name='children',那么你会做parent_object.children.all()

阅读文档 http://docs.djangoproject.com/en/dev/topics/db/models/#many-to-one-relationships了解更多信息。

于 2008-10-24T06:28:47.740 回答
12

如果您只需要 Django 管理界面中的限制,这可能会起作用。我基于另一个论坛的这个答案- 虽然它是针对多对多关系的,但您应该能够替换formfield_for_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)
于 2015-04-05T08:24:42.950 回答
4

@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)

这完全符合我的要求,但如果此验证可以限制管理界面下拉列表中的选择,而不是在选择后进行验证,那就太好了。

于 2008-10-24T16:44:40.307 回答
4

我正在尝试做类似的事情。似乎每个人都说“你应该只用一种方式使用外键”可能误解了你正在尝试做的事情。

很遗憾,您想做的 limit_choices_to={"myparent": "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 导致错误 :(

似乎唯一的方法是将逻辑放入使用此模型的所有表单中(即将查询集传递给表单字段的选项)。这很容易完成,但在模型级别拥有它会更加干燥。您覆盖模型的保存方法似乎是防止无效选择通过的好方法。

更新
以另一种方式查看我稍后的答案https://stackoverflow.com/a/3753916/202168

于 2009-11-17T14:40:04.950 回答
3

您想在创建/编辑模型实例时限制管理界面中可用的选项吗?

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

当然,Eric 的回答是正确的:你真的只需要一个外键,从孩子到父母。

于 2008-10-24T10:01:15.360 回答
2

另一种方法是不将“favouritechild”fk 作为父模型上的字段。

相反,您可以在 Child 上有一个 is_favourite 布尔字段。

这可能会有所帮助: https ://github.com/anentropic/django-exclusivebooleanfield

这样你就可以回避确保孩子只能成为他们所属父母的最爱的整个问题。

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

在管理员中,您甚至可以有一个内联子模型,该模型暴露了 is_favourite 复选框(如果您每个父级只有几个子级),否则管理员必须从子级一侧完成。

于 2010-09-20T17:36:57.720 回答
0

@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)
于 2020-10-28T21:42:50.427 回答
-1
from django.contrib import admin
from sopin.menus.models import Restaurant, DishType

class ObjInline(admin.TabularInline):
    def __init__(self, parent_model, admin_site, obj=None):
        self.obj = obj
        super(ObjInline, self).__init__(parent_model, admin_site)

class ObjAdmin(admin.ModelAdmin):

    def get_inline_instances(self, request, obj=None):
        inline_instances = []
        for inline_class in self.inlines:
            inline = inline_class(self.model, self.admin_site, obj)
            if request:
                if not (inline.has_add_permission(request) or
                        inline.has_change_permission(request, obj) or
                        inline.has_delete_permission(request, obj)):
                    continue
                if not inline.has_add_permission(request):
                    inline.max_num = 0
            inline_instances.append(inline)

        return inline_instances



class DishTypeInline(ObjInline):
    model = DishType

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        field = super(DishTypeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
        if db_field.name == 'dishtype':
            if self.obj is not None:
                field.queryset = field.queryset.filter(restaurant__exact = self.obj)  
            else:
                field.queryset = field.queryset.none()

        return field

class RestaurantAdmin(ObjAdmin):
    inlines = [
        DishTypeInline
    ]
于 2016-02-14T18:04:35.537 回答