如何限制Django raw_id_field的ForeignKey选择

Cer*_*rin 12 python django django-admin

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

当渲染为选择框时,可以很容易地定义自定义ModelForm,以便根据需要选择该字段的queryset值.但是,使用raw_id_fields呈现时,此查询集似乎完全被忽略.它生成一个指向ForeignKey模型的链接,允许您通过弹出窗口从该模型中选择任何记录.您仍然可以通过自定义URL来过滤这些值,但我找不到通过ModelAdmin执行此操作的方法.

Dmi*_*sov 8

我在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',)
Run Code Online (Sandbox Code Playgroud)

在django.admin中只选择blog.type =='PROJ'作为外键Project.blog.因为最终用户可能会并且将会选择任何东西,不幸的是.

  • 如果类型是静态的,我喜欢这个答案,总是“PROJ”。但是,如果您希望它是动态的,例如基于选定的站点,您将如何执行此操作。在主窗体中,您将选择一个站点。然后,您可以将内联形式的 raw_id_field 选择为仅限于该站点。但我遇到的问题是,在用户有机会选择站点之前,表单已被实例化 (2认同)
  • 在Django 2.0中,属性“ Field.rel”已被删除(https://docs.djangoproject.com/en/2.0/releases/2.0/#features-removed-in-2-0)。您应该改用`BlogRawIdWidget(rel = Project._meta.get_field('blog')。remote_field,admin_site = site)`。 (2认同)

小智 6

The method below works for me but it is a queryset that affects every admin that needs to use the Customer model. But if you have another Admin, e.g. Invoice that requires a different queryset, you might want to experiment a bit with model proxy.

Model

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

class Order(models.Model):
    cust = models.ForeignKey(Customer)
Run Code Online (Sandbox Code Playgroud)

Admin

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', )    
Run Code Online (Sandbox Code Playgroud)


FSp*_*FSp 5

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

我的工作通常如下:

  • 在我的创建自定义过滤器ModelAdmin(例如子类admin.SimpleListFilter,请参阅doc)
  • 创建我的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
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,自定义小部件唯一做的是"预选"过滤器,而过滤器又负责"限制"查询集

  • 使用自定义小部件:

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

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

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


小智 5

如果您需要根据模型实例过滤 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
Run Code Online (Sandbox Code Playgroud)

2. 在表单初始化中传递实例

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}
            )
Run Code Online (Sandbox Code Playgroud)