How do you limit what choices are shown for ForeignKey fields in Django's admin when they're displayed using the raw_id_fields option?
如何限制在使用raw_id_fields选项显示Django管理中的ForeignKey字段时显示的选项?
When rendered as a select box, it's simple to define a custom ModelForm to set that field's queryset value with the choices to want. However, this queryset appears to be completely ignored when rendered using raw_id_fields. It generates a link to that ForeignKey's model, allowing you to select any record from that model via a popup window. You can still filter these values by customizing the URL, but I can't find a way to do this via a ModelAdmin.
当作为一个选择框呈现时,定义一个定制的ModelForm来设置该字段的queryset值和需要的选项是很简单的。但是,在使用raw_id_fields呈现时,这个queryset看起来完全被忽略。它生成一个到那个ForeignKey的模型的链接,允许您通过弹出窗口从模型中选择任何记录。您仍然可以通过自定义URL来过滤这些值,但是我无法通过ModelAdmin找到这样做的方法。
5 个解决方案
#1
7
I use similar to FSp approach in my Django 1.8 / Python 3.4 project:
我在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',)
to select only blog.type == 'PROJ' as foreign key Project.blog in django.admin. Because end-users may and will to select anything, unfortunately.
只选择博客。type == 'PROJ'作为外键项目。在django.admin博客。不幸的是,因为最终用户可能也将选择任何东西。
#2
4
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.
下面的方法对我有效,但是它是一个查询集,它影响每个需要使用客户模型的管理员。但是,如果您有另一个Admin,例如需要不同的queryset的Invoice,那么您可能想尝试一下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)
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', )
#3
4
I find the given solution (customizing the ModelAdmin
queryset) a bit too restrictive, for realistic projects.
我发现给定的解决方案(自定义ModelAdmin queryset)对于实际的项目来说有些限制。
What I do, is usually the following:
我的工作通常是:
- create a custom filter in my
ModelAdmin
(e.g. subclassingadmin.SimpleListFilter
, see the doc) - 在我的ModelAdmin中创建一个自定义过滤器(例如,子类管理)。SimpleListFilter,见医生)
-
create my subclass of widget
ForeignKeyRawIdWidget
as follows:创建我的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
note that what the only thing the custom widget does is to "preselect" the filter that, in turn, is responsible for "restricting" the queryset
请注意,自定义小部件所做的惟一事情是“预选择”过滤器,而过滤器又负责“限制”queryset
-
use the custom widget:
使用自定义小部件:
class MyForm(forms.ModelForm): myfield = forms.ModelChoiceField(queryset=MyModel.objects.all(), ... widget=CustomRawIdWidget( MyRelationModel._meta.get_field('myfield').rel, admin.site))
One weak point of this approach is that the filter selected by the widget does not prevent from selecting some other instance from that model. If this is desired, I override the method ModelAdmin.save_model(...)
(see the doc) to check that the related instances are only the allowed ones.
这种方法的一个弱点是,小部件选择的过滤器并不阻止从该模型中选择其他实例。如果需要,我将重写方法ModelAdmin.save_model(……)(请参阅doc),以检查相关实例是否仅为允许的实例。
I find this approach a bit more complex, but much more flexible than limiting the queryset for the entire ModelAdmin
.
我发现这种方法有点复杂,但是比为整个ModelAdmin限制queryset要灵活得多。
#4
3
If you need to filter your raw_id list_view popup based on model instance you can use example below:
如果需要根据模型实例过滤raw_id list_view弹出框,可以使用下面的示例:
1. Write custom widget
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. Pass instance on form init
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}
)
#5
2
I created a genetic solution to solve the problem of custom parameters to pass to popup window. You just need copy this code on your project:
我创建了一个遗传解决方案来解决将自定义参数传递到弹出窗口的问题。你只需要在你的项目中复制这个代码:
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
Then, you can use like this:
然后,你可以这样使用:
field.widget = GenericRawIdWidget(YOURMODEL._meta.get_field('YOUR_RELATION').rel,
admin.site, url_params={"<YOURMODEL>__id__exact": object_id})
I used it in this way:
我是这样使用的:
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
When I use GenericRawIdWidget
, I pass a dict to url_params, that will be used on url.
当我使用GenericRawIdWidget时,我将一个通知传递给url_params,该通知将在url上使用。
#1
7
I use similar to FSp approach in my Django 1.8 / Python 3.4 project:
我在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',)
to select only blog.type == 'PROJ' as foreign key Project.blog in django.admin. Because end-users may and will to select anything, unfortunately.
只选择博客。type == 'PROJ'作为外键项目。在django.admin博客。不幸的是,因为最终用户可能也将选择任何东西。
#2
4
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.
下面的方法对我有效,但是它是一个查询集,它影响每个需要使用客户模型的管理员。但是,如果您有另一个Admin,例如需要不同的queryset的Invoice,那么您可能想尝试一下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)
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', )
#3
4
I find the given solution (customizing the ModelAdmin
queryset) a bit too restrictive, for realistic projects.
我发现给定的解决方案(自定义ModelAdmin queryset)对于实际的项目来说有些限制。
What I do, is usually the following:
我的工作通常是:
- create a custom filter in my
ModelAdmin
(e.g. subclassingadmin.SimpleListFilter
, see the doc) - 在我的ModelAdmin中创建一个自定义过滤器(例如,子类管理)。SimpleListFilter,见医生)
-
create my subclass of widget
ForeignKeyRawIdWidget
as follows:创建我的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
note that what the only thing the custom widget does is to "preselect" the filter that, in turn, is responsible for "restricting" the queryset
请注意,自定义小部件所做的惟一事情是“预选择”过滤器,而过滤器又负责“限制”queryset
-
use the custom widget:
使用自定义小部件:
class MyForm(forms.ModelForm): myfield = forms.ModelChoiceField(queryset=MyModel.objects.all(), ... widget=CustomRawIdWidget( MyRelationModel._meta.get_field('myfield').rel, admin.site))
One weak point of this approach is that the filter selected by the widget does not prevent from selecting some other instance from that model. If this is desired, I override the method ModelAdmin.save_model(...)
(see the doc) to check that the related instances are only the allowed ones.
这种方法的一个弱点是,小部件选择的过滤器并不阻止从该模型中选择其他实例。如果需要,我将重写方法ModelAdmin.save_model(……)(请参阅doc),以检查相关实例是否仅为允许的实例。
I find this approach a bit more complex, but much more flexible than limiting the queryset for the entire ModelAdmin
.
我发现这种方法有点复杂,但是比为整个ModelAdmin限制queryset要灵活得多。
#4
3
If you need to filter your raw_id list_view popup based on model instance you can use example below:
如果需要根据模型实例过滤raw_id list_view弹出框,可以使用下面的示例:
1. Write custom widget
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. Pass instance on form init
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}
)
#5
2
I created a genetic solution to solve the problem of custom parameters to pass to popup window. You just need copy this code on your project:
我创建了一个遗传解决方案来解决将自定义参数传递到弹出窗口的问题。你只需要在你的项目中复制这个代码:
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
Then, you can use like this:
然后,你可以这样使用:
field.widget = GenericRawIdWidget(YOURMODEL._meta.get_field('YOUR_RELATION').rel,
admin.site, url_params={"<YOURMODEL>__id__exact": object_id})
I used it in this way:
我是这样使用的:
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
When I use GenericRawIdWidget
, I pass a dict to url_params, that will be used on url.
当我使用GenericRawIdWidget时,我将一个通知传递给url_params,该通知将在url上使用。