我如何限制只有在Django的相关对象的外键select

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

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的select只有父母本身的孩子? 我试过了

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

但是这导致pipe理界面不列出任何孩子。

我在Django文档中遇到了ForeignKey.limit_choices_to 。 不知道这是如何工作,但它可能是正确的想法在这里。

更新: ForeignKey.limit_choices_to允许指定一个常量,一个可调用对象或一个Q对象来限制该对象的允许选项。 一个常数显然在这里是没有用的,因为它对涉及的对象一无所知。

使用可调用(函数或类方法或任何可调用对象)似乎更有希望。 问题仍然是如何访问HttpRequest对象的必要信息。 使用线程本地存储可能是一个解决scheme。

2.更新:这是什么为我工作:

我按照上面的链接创build了一个中间件。 它从请求的GET部分提取一个或多个参数,例如“product = 1”,并将这些信息存储在线程本地。

接下来,在模型中有一个类方法,它读取线程局部variables,并返回一个ID列表来限制外键字段的select。

 @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的查询是非常重要的,如果没有select,所以正常的pipe理页面工作正常。

外键字段然后声明为:

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

问题是,你必须提供信息来限制通过请求的select。 我没有看到在这里访问“自我”的方法。

正确的做法是使用自定义表单。 从那里,你可以访问self.instance,它是当前的对象。 示例 –

 from django import forms from django.contrib import admin from models import * class SupplierAdminForm(forms.ModelForm): class Meta: model = Supplier 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的作品。 你只会以一种方式创build关系。

 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更多。;

至less从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) 

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

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

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

当然,埃里克的回答是正确的:你只需要一个外键,从小孩到家长。

@Ber:我已经添加了类似于这个模型的validation

 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) 

这正是我想要的,但是如果这个validation可以限制pipe理界面中的下拉选项,而不是在select之后validation,那将是非常好的。

如果你只需要Djangopipe理界面的限制,这可能工作。 我基于这个答案从另一个论坛 – 虽然它是ManyToMany关系,你应该能够取代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) 

另一种方法是在Parent模型上不要使用“favouritechild”fk作为字段。

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

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

这样你就避免了确保孩子只能成为他们所属的父母的最爱的问题。

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

在pipe理员,你甚至可以有一个内联子模型,暴露了is_favouritecheckbox(如果你只有一些父母子女),否则pipe理将不得不从儿童的一面。

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

这是一个耻辱的limit_choices_to = {“myparent”:“自我”}你想做的不工作…这将是干净和简单。 不幸的是,“自我”并没有被评估,而是以一个简单的string进行。

我想也许我可以这样做:

 class MyModel(models.Model): def _get_self_pk(self): return self.pk favourite = models.ForeignKey(limit_choices_to={'myparent__pk':_get_self_pk}) 

但是,唉,给出了一个错误,因为该函数不会传递一个自我精确:(

这似乎是唯一的方法是将逻辑放入所有使用此模型的表单(即将查询集传递给您的表单域的选项)。 这很容易做到,但是在模型层面上做这件事情会更干。 重写模型的保存方法似乎是防止无效select通过的好方法。

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

 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 ]