Как ограничить выбор вариантов ForeignKey для Django raw_id_field
Как вы ограничиваете, какие варианты отображаются для полей ForeignKey в admin в Django, когда они отображаются с помощью опции raw_id_fields?
При отображении в качестве окна выбора просто определить пользовательский ModelForm, чтобы установить значение запроса на выбор в поле с нужными вариантами. Тем не менее, этот запрос представляется полностью игнорированным при визуализации с использованием raw_id_fields. Он создает ссылку на эту модель ForeignKey, позволяя вам выбрать любую запись из этой модели через всплывающее окно. Вы можете фильтровать эти значения, настроив URL-адрес, но я не могу найти способ сделать это с помощью ModelAdmin.
Ответы
Ответ 1
Я использую аналогичный подход FSp в своем проекте Django 1.8/Python 3.4:
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' как внешний ключ Project.blog в django.admin. Поскольку конечные пользователи могут и хотят выбрать что угодно, к сожалению.
Ответ 2
Я нахожу, что данное решение (настройка набора запросов 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
обратите внимание, что единственное, что делает пользовательский виджет, это "предварительно выбрать" фильтр, который, в свою очередь, отвечает за "ограничение" запроса
-
используйте пользовательский виджет:
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
.
Ответ 3
Метод ниже работает для меня, но это набор запросов, который влияет на каждого администратора, который должен использовать модель Customer. Но если у вас есть другой администратор, например. Счет, требующий другого запроса, может потребоваться немного поэкспериментировать с прокси-сервером модели.
Model
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', )
Ответ 4
Если вам нужно отфильтровать всплывающее окно 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}
)
Ответ 5
Я создал генетическое решение для решения проблемы пользовательских параметров, которые нужно передать во всплывающее окно.
Вам просто нужно скопировать этот код в свой проект:
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.