Python 如何限制Django raw_id_字段的ForeignKey选项的选择

Python 如何限制Django raw_id_字段的ForeignKey选项的选择,python,django,django-admin,Python,Django,Django Admin,当使用该选项显示ForeignKey字段时,如何限制在Django的admin中显示这些字段的选项 当呈现为选择框时,使用所需的选项设置该字段的queryset值非常简单。但是,在使用原始id字段渲染时,此查询集似乎完全被忽略。它生成一个指向ForeignKey模型的链接,允许您通过弹出窗口从该模型中选择任何记录。您仍然可以通过自定义URL来过滤这些值,但我找不到通过ModelAdmin实现这一点的方法。下面的方法对我很有用,但它是一个查询集,会影响需要使用客户模型的每个管理员。但是如果您有另一

当使用该选项显示ForeignKey字段时,如何限制在Django的admin中显示这些字段的选项


当呈现为选择框时,使用所需的选项设置该字段的queryset值非常简单。但是,在使用原始id字段渲染时,此查询集似乎完全被忽略。它生成一个指向ForeignKey模型的链接,允许您通过弹出窗口从该模型中选择任何记录。您仍然可以通过自定义URL来过滤这些值,但我找不到通过ModelAdmin实现这一点的方法。

下面的方法对我很有用,但它是一个查询集,会影响需要使用客户模型的每个管理员。但是如果您有另一个管理员,例如需要不同查询集的Invoice,您可能需要尝试一下model proxy

模型

管理员


我发现给定的定制ModelAdmin queryset的解决方案对于现实项目来说有点过于严格

我所做的通常是:

在my ModelAdmin中创建自定义筛选器,例如子类化admin.SimpleListFilter,请参阅 创建我的widget ForeignKeyRawIdWidget子类,如下所示:

class CustomRawIdWidget(ForeignKeyRawIdWidget):

    def url_parameters(self):
        """
        activate one or more filters by default
        """

        res = super(CustomRawIdWidget, self).url_parameters()

        res["<filter_name>__exact"] = "<filter_value>"

        return res
这种方法的一个弱点是小部件选择的过滤器不会阻止从该模型中选择其他实例。如果需要,我将重写ModelAdmin.save\u model方法。。。请参阅以检查相关实例是否仅为允许的实例


我发现这种方法有点复杂,但比限制整个ModelAdmin的查询集灵活得多。

我在Django 1.8/Python 3.4项目中使用了类似于FSp的方法:

from django.contrib import admin
from django.contrib.admin import widgets
from django.contrib.admin.sites import site
from django import forms

class BlogRawIdWidget(widgets.ForeignKeyRawIdWidget):
    def url_parameters(self):
        res = super().url_parameters()
        res['type__exact'] = 'PROJ'
        return res

class ProjectAdminForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['blog'].queryset = Blog.objects.filter(type='PROJ')
        self.fields['blog'].widget = BlogRawIdWidget(rel=Project._meta.get_field('blog').remote_field, admin_site=site)

    class Meta:
        # Django 1.8 convenience:
        fields = '__all__'
        model = Project

class ProjectAdmin(admin.ModelAdmin):
    form = ProjectAdminForm
    raw_id_fields = ('blog',)

仅选择blog.type==“PROJ”作为django.admin中的外键Project.blog。因为最终用户可能也将选择任何东西,不幸的是。

如果您需要根据模型实例筛选原始id列表视图弹出窗口,您可以使用以下示例:

一,。编写自定义小部件

二,。在表单init上传递实例


我创建了一个遗传解决方案来解决自定义参数传递到弹出窗口的问题。 您只需在项目中复制以下代码:

from django.contrib.admin import widgets

class GenericRawIdWidget(widgets.ForeignKeyRawIdWidget):
    url_params = []

    def __init__(self, rel, admin_site, attrs=None, \
        using=None, url_params=[]):
        super(GenericRawIdWidget, self).__init__(
            rel, admin_site, attrs=attrs, using=using)
        self.url_params = url_params

    def url_parameters(self):
        """
        activate one or more filters by default
        """
        res = super(GenericRawIdWidget, self).url_parameters()
        res.update(**self.url_params)

        return res
然后,您可以这样使用:

field.widget = GenericRawIdWidget(YOURMODEL._meta.get_field('YOUR_RELATION').rel,
            admin.site, url_params={"<YOURMODEL>__id__exact":     object_id})

当我使用Genericrawidget时,我将dict传递给url_参数,该参数将在url上使用。

@Dmitry Sintsov的答案很好,如果您只需要在静态类型上进行筛选,但在我的例子中,我在两个模型之间有一个外来关系,我希望它根据我使用的特定ID进行筛选

若要构建,请假设项目与Blog具有外键关系,并且在选择要筛选的Blog时,您希望它仅显示与项目相关的Blog。对他的回答的这两个变化表明:

在小部件中添加一个新变量-我将其命名为project\u id 修改调用该小部件的行: 完整代码如下:

from django.contrib import admin
from django.contrib.admin import widgets
from django.contrib.admin.sites import site
from django import forms

class BlogRawIdWidget(widgets.ForeignKeyRawIdWidget):

    def __init__(self, *args, **kwargs):
        self.project_id = kwargs.pop('project_id')
        super().__init__(*args, **kwargs)

    def url_parameters(self):
        res = super().url_parameters()
        res['type__exact'] = 'PROJ'
        return res

class ProjectAdminForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['blog'].queryset = Blog.objects.filter(type='PROJ')
        self.fields['blog'].widget = BlogRawIdWidget(rel=Project._meta.get_field('blog').remote_field, admin_site=site,
        project_id=self.instance.project.id)

    class Meta:
        # Django 1.8 convenience:
        fields = '__all__'
        model = Project

class ProjectAdmin(admin.ModelAdmin):
    form = ProjectAdminForm
    raw_id_fields = ('blog',)

谢谢,这其实很简单也很聪明。此外,您可以检查请求中的GET参数,并在弹出窗口时调整过滤器,等等。qs.filteris_active=1也会影响CustomerAdmin。如果从订单模型定义了筛选器参数,或者该参数不适用于CustomerAdmin,则此操作将不起作用。例如,在OrderAdmin中,我们希望客户仅列出活动客户,但在CustomerAdmin中,我们希望同时列出活动客户和非活动客户。如果类型为静态,则我喜欢这个答案。但是,如果您希望它是动态的,例如,基于选定的站点,您将如何做到这一点呢。在主窗体中,您将选择一个站点。然后,您可以将内联表单中的raw_id_字段的选择限制到该站点。但我遇到的问题是,在Django 2.0中,在用户有机会选择站点之前,表单被实例化。属性Field.rel已被删除。您应该使用BlogRawIdWidgetrel=Project.\u meta.get\u field'blog'.remote\u field,admin\u site=site.@straykiwi我找到了一个使用动态值的解决方案,我将其添加为新答案:
class RawIdWidget(widgets.ForeignKeyRawIdWidget):

    def url_parameters(self):
        res = super(RawIdWidget, self).url_parameters()
        object = self.attrs.get('object', None)
        if object:
            # Filter variants by product_id
            res['product_id'] = object.variant.product_id
        return res
class ModelForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super(ModelForm, self).__init__(*args, **kwargs)
        obj = kwargs.get('instance', None)
        if obj and obj.pk is not None:
            self.fields['variant'].widget = RawIdWidget(
                rel=obj._meta.get_field('variant').rel,
                admin_site=admin.site,
                # Pass the object to attrs
                attrs={'object': obj}
            )
from django.contrib.admin import widgets

class GenericRawIdWidget(widgets.ForeignKeyRawIdWidget):
    url_params = []

    def __init__(self, rel, admin_site, attrs=None, \
        using=None, url_params=[]):
        super(GenericRawIdWidget, self).__init__(
            rel, admin_site, attrs=attrs, using=using)
        self.url_params = url_params

    def url_parameters(self):
        """
        activate one or more filters by default
        """
        res = super(GenericRawIdWidget, self).url_parameters()
        res.update(**self.url_params)

        return res
field.widget = GenericRawIdWidget(YOURMODEL._meta.get_field('YOUR_RELATION').rel,
            admin.site, url_params={"<YOURMODEL>__id__exact":     object_id})
class ANSRuleInline(admin.TabularInline):
    model = ANSRule 
    form = ANSRuleInlineForm
    extra = 1
    raw_id_fields = ('parent',)

    def __init__(self, *args, **kwargs):
        super (ANSRuleInline,self ).__init__(*args,**kwargs)

    def formfield_for_dbfield(self, db_field, **kwargs):
        formfield = super(ANSRuleInline, self).formfield_for_dbfield(db_field, **kwargs)
        request = kwargs.get("request", None)
        object_id = self.get_object(request)

        if db_field.name == 'parent':
            formfield.widget = GenericRawIdWidget(ANSRule._meta.get_field('parent').rel,
                admin.site, url_params={"pathology__id__exact": object_id})

        return formfield

    def get_object(self, request):
        object_id = request.META['PATH_INFO'].strip('/').split('/')[-1]
        try:
            object_id = int(object_id)
        except ValueError:
            return None
        return object_id
class BlogRawIdWidget(widgets.ForeignKeyRawIdWidget):

    def __init__(self, *args, **kwargs):
        self.project_id = kwargs.pop('project_id')
        super().__init__(*args, **kwargs)
rel=Project._meta.get_field('blog').remote_field, admin_site=site,
        project_id=self.instance.project.id)
from django.contrib import admin
from django.contrib.admin import widgets
from django.contrib.admin.sites import site
from django import forms

class BlogRawIdWidget(widgets.ForeignKeyRawIdWidget):

    def __init__(self, *args, **kwargs):
        self.project_id = kwargs.pop('project_id')
        super().__init__(*args, **kwargs)

    def url_parameters(self):
        res = super().url_parameters()
        res['type__exact'] = 'PROJ'
        return res

class ProjectAdminForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['blog'].queryset = Blog.objects.filter(type='PROJ')
        self.fields['blog'].widget = BlogRawIdWidget(rel=Project._meta.get_field('blog').remote_field, admin_site=site,
        project_id=self.instance.project.id)

    class Meta:
        # Django 1.8 convenience:
        fields = '__all__'
        model = Project

class ProjectAdmin(admin.ModelAdmin):
    form = ProjectAdminForm
    raw_id_fields = ('blog',)