如何限制Django raw_id_field的ForeignKey选择

时间:2014-05-12 20:04:51

标签: python django django-admin

如何使用raw_id_fields选项限制Django管理员显示的ForeignKey字段选项?

当呈现为选择框时,define a custom ModelForm很容易设置该字段的查询集值以及所需的选项。但是,使用raw_id_fields呈现时,此查询集似乎完全被忽略。它生成一个指向该ForeignKey模型的链接,允许您通过弹出窗口从该模型中选择任何记录。您仍然可以通过自定义网址来过滤这些值,但我无法通过ModelAdmin找到解决方法。

6 个答案:

答案 0 :(得分:7)

我在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').rel, 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。不幸的是,最终用户可能会也将会选择任何东西。

答案 1 :(得分:5)

对于实际项目,我发现给定的解决方案(自定义ModelAdmin查询集)有点过于严格。

我的工作通常如下:

  • 在我的ModelAdmin中创建自定义过滤器(例如,继承admin.SimpleListFilter,请参阅doc
  • 创建我的小部件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
    

    请注意,自定义小部件的唯一功能是&#34;预选&#34;过滤器,反过来,负责&#34;限制&#34;查询集

  • 使用自定义小部件:

    class MyForm(forms.ModelForm):
    
        myfield = forms.ModelChoiceField(queryset=MyModel.objects.all(),
            ...
            widget=CustomRawIdWidget(
                 MyRelationModel._meta.get_field('myfield').rel,
                 admin.site))
    

此方法的一个弱点是窗口小部件选择的过滤器不会阻止从该模型中选择其他实例。如果需要,我会覆盖方法ModelAdmin.save_model(...)(请参阅doc)以检查相关实例是否只是允许的实例。

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

答案 2 :(得分:4)

以下方法适用于我,但它是一个影响每个需要使用Customer模型的管理员的查询集。但如果你有另一个管理员,例如需要不同查询集的发票,您可能希望尝试使用模型代理。

模型

class Customer(models.Model):
    name = models.CharField(max_length=100)
    is_active = models.BooleanField()

class Order(models.Model):
    cust = models.ForeignKey(Customer)

管理

class CustomerAdmin(admin.ModelAdmin):         
    def queryset(self, request):
        qs = super(CustomerAdmin, self).queryset(request)           
        return qs.filter(is_active=1)

class OrderAdmin():     
    raw_id_fields = ('cust', )    

答案 3 :(得分:3)

如果您需要根据模型实例过滤raw_id list_view弹出窗口,可以使用以下示例:

<强> 1。编写自定义小部件

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

<强> 2。在表单init上传递实例

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}
            )

答案 4 :(得分:2)

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

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

当我使用GenericRawIdWidget时,我将dict传递给url_params,它将在url上使用。

答案 5 :(得分:0)

@Dmitriy Sintsov 的回答很好,如果您只需要过滤静态类型,但在我的情况下,我在两个模型之间都有外部关系,我希望它根据我正在使用的特定 ID 进行过滤。

要构建 on his answer,假设 ProjectBlog 有外键关系,并且在选择 Blog 进行过滤时,您希望它只显示那些与 Project 相关。他的回答的这两个变化表明:

  1. 在小部件中添加一个新变量 -- 我将其命名为 project_id
class BlogRawIdWidget(widgets.ForeignKeyRawIdWidget):

    def __init__(self, *args, **kwargs):
        self.project_id = kwargs.pop('project_id')
        super().__init__(*args, **kwargs)
  1. 修改调用该小部件的行:
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',)
相关问题