如何仅在 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.Favitchild 的选择限制在父母本身是独生子女的情况下? 我试过了吗

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

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

67340 次浏览

这不是 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().如果您在 myfather 字段中设置了 related _ name,那么您将把它称为。例句: related_name='children',然后做 parent_object.children.all()

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

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

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

当然,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)

但是如果这个验证可以限制管理界面下拉列表中的选项,而不是在选择之后验证,那就太好了。

我刚在姜戈的文件里找到了 Limit _ options _ to。 还不确定这是怎么运作的,但也许这是正确的选择。

更新: ForeignKey.limit _ options _ to 允许指定常量、可调用对象或 Q 对象来限制键的允许选择。常量在这里显然没有用处,因为它对所涉及的对象一无所知。

使用可调用(函数或类方法或任何可调用对象)似乎更有前途。但是,如何从 HttpRequest 对象访问必要信息的问题仍然存在。使用 线程本地存储线程本地存储可能是一种解决方案。

更新: 以下是对我有效的方法:

我按照上面的链接中的描述创建了一个中间件。它从请求的 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,
},
)

问题是您必须通过请求提供信息来限制选择。我找不到进入“自我”的方法。

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

遗憾的是,您想要做的 limit _ options _ to = {“ myfather”: “ self”}不起作用... ... 那将是干净而简单的。不幸的是,“ 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: (

似乎唯一的方法是将逻辑放入所有使用此模型的表单中(即将查询集传递到您的表单字段的选项中)。这很容易做到,但是如果在模型级别使用这种方法会更加干燥。重写模型的 save 方法似乎是防止无效选择通过的一个好方法。

更新
请参阅我后面的回答以了解另一种 https://stackoverflow.com/a/3753916/202168

另一种方法是不使用“最喜欢的孩子”fk 作为父母模型中的一个字段。

相反,您可以在 Child 上有一个 is _ like 布尔值字段。

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

这样你就可以避开整个问题,确保孩子只能成为他们所属的家长的最爱。

视图代码将略有不同,但过滤逻辑将是直接的。

在管理中,你甚至可以为 Child 模型设置一个内联,它会显示 is _  鄣确(如果你每个父母只有几个孩子的话) ,否则管理将不得不从 Child 方面完成。

至少从 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. 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 管理界面中的限制,那么这可能是有效的。我的基础上从另一个论坛的 这个答案-虽然它的许多关系,你应该能够取代它的工作 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)
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
]

@ s29的回答要简单得多:

与其自定义表单, 您可以从视图中简单地限制表单字段中可用的选项:

对我起作用的是: 请参阅表格.py:

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)