简体   繁体   English

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

[英]How do I restrict foreign keys choices to related objects only in django

I have a two way foreign relation similar to the following我有一个类似于以下的双向对外关系

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)

How do I restrict the choices for Parent.favoritechild to only children whose parent is itself?我如何将 Parent.favoritechild 的选择限制为只有父母是自己的孩子? I tried我试过

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

but that causes the admin interface to not list any children.但这会导致管理界面没有列出任何孩子。

I just came across ForeignKey.limit_choices_to in the Django docs.我刚刚在 Django 文档中遇到ForeignKey.limit_choices_to Not sure yet how this works, but it might just be the right thing here.尚不确定这是如何工作的,但这可能是正确的做法。

Update: ForeignKey.limit_choices_to allows to specify either a constant, a callable or a Q object to restrict the allowable choices for the key.更新: ForeignKey.limit_choices_to 允许指定常量、可调用对象或 Q 对象来限制键的允许选择。 A constant obviously is of no use here, since it knows nothing about the objects involved.一个常量在这里显然没有用,因为它对所涉及的对象一无所知。

Using a callable (function or class method or any callable object) seems more promising.使用可调用(函数或类方法或任何可调用对象)似乎更有希望。 However, the problem of how to access the necessary information from the HttpRequest object remains.但是,如何从 HttpRequest 对象访问必要信息的问题仍然存在。 Using thread local storage may be a solution.使用线程本地存储可能是一种解决方案。

2. Update: Here is what has worked for me: 2.更新:这是对我有用的:

I created a middleware as described in the link above.我创建了一个中间件,如上面链接中所述。 It extracts one or more arguments from the request's GET part, such as "product=1", and stores this information in the thread locals.它从请求的 GET 部分中提取一个或多个参数,例如“product=1”,并将此信息存储在线程局部变量中。

Next there is a class method in the model that reads the thread local variable and returns a list of ids to limit the choice of a foreign key field.接下来在模型中有一个类方法,它读取线程局部变量并返回一个 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

It is important to return a query containing all possible ids if none was selected so that the normal admin pages work ok.如果没有被选择,那么返回一个包含所有可能的 id 的查询是很重要的,这样正常的管理页面才能正常工作。

The foreign key field is then declared as:然后将外键字段声明为:

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

The catch is that you have to provide the information to restrict the choices via the request.问题是您必须提供信息以通过请求限制选择。 I don't see a way to access "self" here.我在这里看不到访问“自我”的方法。

The 'right' way to do it is to use a custom form. “正确”的方法是使用自定义表单。 From there, you can access self.instance, which is the current object.从那里,您可以访问 self.instance,它是当前对象。 Example --例子 -

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

The new "right" way of doing this, at least since Django 1.1 is by overriding the AdminModel.formfield_for_foreignkey(self, db_field, request, **kwargs).这样做的新“正确”方式,至少从 Django 1.1 开始是通过覆盖 AdminModel.formfield_for_foreignkey(self, db_field, request, **kwargs)。

See http://docs.djangoproject.com/en/1.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey请参阅http://docs.djangoproject.com/en/1.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey

For those who don't want to follow the link below is an example function that is close for the above questions models.对于那些不想遵循以下链接的人来说,这是一个与上述问题模型很接近的示例函数。

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)

I'm only not sure about how to get the current object that is being edited.我只是不确定如何获取正在编辑的当前对象。 I expect it is actually on the self somewhere but I'm not sure.我希望它实际上是在某个地方的自我,但我不确定。

This isn't how django works.这不是 Django 的工作方式。 You would only create the relation going one way.您只会以一种方式创建关系。

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

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

And if you were trying to access the children from the parent you would do parent_object.child_set.all() .如果你试图从父级访问子级,你会做parent_object.child_set.all() If you set a related_name in the myparent field, then that is what you would refer to it as.如果您在 myparent 字段中设置了 related_name,那么这就是您所说的。 Ex: related_name='children' , then you would do parent_object.children.all()例如: related_name='children' ,那么你会做parent_object.children.all()

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

If you only need the limitations in the Django admin interface, this might work.如果您只需要 Django 管理界面中的限制,这可能会奏效。 I based it on this answer from another forum - although it's for ManyToMany relationships, you should be able to replace formfield_for_foreignkey for it to work.我基于另一个论坛的这个答案- 尽管它是针对多对多关系的,但您应该能够替换formfield_for_foreignkey使其工作。 In admin.py :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)

I'm trying to do something similar.我正在尝试做类似的事情。 It seems like everyone saying 'you should only have a foreign key one way' has maybe misunderstood what you're trying do.似乎每个人都说“你应该只以一种方式使用外键”可能误解了你正在尝试做什么。

It's a shame the limit_choices_to={"myparent": "self"} you wanted to do doesn't work... that would have been clean and simple.很遗憾,你想做的 limit_choices_to={"myparent": "self"} 不起作用......那本来是干净和简单的。 Unfortunately the 'self' doesn't get evaluated and goes through as a plain string.不幸的是,'self' 没有得到评估,而是作为一个普通的字符串通过。

I thought maybe I could do:我想也许我可以这样做:

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

But alas that gives an error because the function doesn't get passed a self arg :(但是,唉,因为该函数没有通过 self arg 来传递错误:(

It seems like the only way is to put the logic into all the forms that use this model (ie pass a queryset in to the choices for your formfield).似乎唯一的方法是将逻辑放入使用此模型的所有表单中(即将查询集传递到表单域的选择中)。 Which is easily done, but it'd be more DRY to have this at the model level.这很容易完成,但在模型级别使用它会更干燥。 Your overriding the save method of the model seems a good way to prevent invalid choices getting through.您覆盖模型的 save 方法似乎是防止无效选择通过的好方法。

Update更新
See my later answer for another way https://stackoverflow.com/a/3753916/202168另一种方式见我后来的回答https://stackoverflow.com/a/3753916/202168

@Ber: I have added validation to the model similar to this @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)

which works exactly how I want, but it would be really nice if this validation could restrict choices in the dropdown in the admin interface rather than validating after the choice.这完全符合我的要求,但是如果此验证可以限制管理界面下拉列表中的选择而不是在选择后进行验证,那就太好了。

Do you want to restrict the choices available in the admin interface when creating/editing a model instance?您想在创建/编辑模型实例时限制管理界面中可用的选项吗?

One way to do this is validation of the model.一种方法是验证模型。 This lets you raise an error in the admin interface if the foreign field is not the right choice.如果外部字段不是正确的选择,这可以让您在管理界面中引发错误。

Of course, Eric's answer is correct: You only really need one foreign key, from child to parent here.当然,Eric 的回答是正确的:你真的只需要一个外键,在这里从孩子到父母。

An alternative approach would be not to have 'favouritechild' fk as a field on the Parent model.另一种方法是不将 'favouritechild' fk 作为 Parent 模型上的字段。

Instead you could have an is_favourite boolean field on the Child.相反,您可以在 Child 上有一个 is_favourite 布尔字段。

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

That way you'd sidestep the whole problem of ensuring Children could only be made the favourite of the Parent they belong to.这样,您就可以避免确保 Children 只能成为他们所属的 Parent 的最爱的整个问题。

The view code would be slightly different but the filtering logic would be straightforward.视图代码会略有不同,但过滤逻辑会很简单。

In the admin you could even have an inline for Child models that exposed the is_favourite checkbox (if you only have a few children per parent) otherwise the admin would have to be done from the Child's side.在管理员中,您甚至可以对暴露 is_favourite 复选框的 Child 模型进行内联(如果每个父级只有几个孩子),否则必须从 Child 一侧完成管理。

A much simpler variation of @s29's answer: @s29 答案的一个更简单的变体:

Instead of customising the form, You can simply restrict the choices available in form field from your view:您可以简单地从视图中限制表单字段中可用的选项,而不是自定义表单

what worked for me was: in forms.py:对我有用的是:在 forms.py 中:

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

in views.py:在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.

相关问题 Django-如何将外键选择限制为另一个模型中的ManyToMany字段 - Django - How to restrict Foreign Key choices to a ManyToMany field in another model 如果我有外键,如何在Django中进行“排序”? - How do I do this “order by” in Django, if I have foreign keys? 如何在Django ModelForm中获取仅与该用户的下拉列表相关的外键 - How to get the foreign keys related to drop down only of that user in Django ModelForm 如何通过外键的属性对 Django 对象进行排序? - How to order Django objects by the attributes of their foreign keys? Django-如何根据数据时间字段限制下拉菜单中的外键选择 - Django - How to restrict foreign key choices in dropdown menu depending on datatime field 当我有带有多个外键的Django对象时,如何命名/排列我的Memcached键? - How to name/arrange my memcached keys when I have Django objects with multiple foreign keys? Django select_related链接的外键不返回非直接相关的对象 - Django select_related chained foreign keys doesn't return non-direct related objects 如何将外键选择限制为同一模型中的另一个外键 - How to restrict Foreign Key choices to another Foreign Key in the same model 如何遍历Django模板中的相关对象列表? - How do I iterate through a list of related objects in a django template? Django:如何计算数据库中与另一个对象相关的对象数? - Django: How do I count the number of objects related to another in the database?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM