[英]Django - How to restrict Foreign Key choices to a ManyToMany field in another model
[英]How do I restrict foreign keys choices to related objects only in 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"})
但这会导致管理界面没有列出任何孩子。
我刚刚在 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.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 1.1 开始是通过覆盖 AdminModel.formfield_for_foreignkey(self, db_field, request, **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)
我只是不确定如何获取正在编辑的当前对象。 我希望它实际上是在某个地方的自我,但我不确定。
这不是 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了解更多信息。
如果您只需要 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)
我正在尝试做类似的事情。 似乎每个人都说“你应该只以一种方式使用外键”可能误解了你正在尝试做什么。
很遗憾,你想做的 limit_choices_to={"myparent": "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
@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)
这完全符合我的要求,但是如果此验证可以限制管理界面下拉列表中的选择而不是在选择后进行验证,那就太好了。
您想在创建/编辑模型实例时限制管理界面中可用的选项吗?
一种方法是验证模型。 如果外部字段不是正确的选择,这可以让您在管理界面中引发错误。
当然,Eric 的回答是正确的:你真的只需要一个外键,在这里从孩子到父母。
另一种方法是不将 'favouritechild' fk 作为 Parent 模型上的字段。
相反,您可以在 Child 上有一个 is_favourite 布尔字段。
这可能有帮助: https : //github.com/anentropic/django-exclusivebooleanfield
这样,您就可以避免确保 Children 只能成为他们所属的 Parent 的最爱的整个问题。
视图代码会略有不同,但过滤逻辑会很简单。
在管理员中,您甚至可以对暴露 is_favourite 复选框的 Child 模型进行内联(如果每个父级只有几个孩子),否则必须从 Child 一侧完成管理。
@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)
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
]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.