Могу ли я сделать list_filter в django admin только для отображения ссылок ForeignKeys?
У меня есть приложение django, которое имеет две модели:
class MyModel(models.Model):
name = models.CharField()
country = models.ForeignKey('Country')
class Country(models.Model):
code2 = models.CharField(max_length=2, primary_key=True)
name = models.CharField()
Класс admin для MyModel
выглядит следующим образом:
class MyModelAdmin(admin.ModelAdmin):
list_display = ('name', 'country',)
list_filter = ('country',)
admin.site.register(models.MyModel, MyModelAdmin)
Таблица Country
содержит ~ 250 стран. Только несколько стран на самом деле ссылаются на экземпляр MyModel
.
Проблема в том, что фильтр списка в django admin перечисляет ВСЕ страны на панели фильтров. Список всех стран (а не только тех, на которые ссылается экземпляр) в значительной степени побеждает цель иметь фильтр списка в этом случае.
Можно ли показывать только страны, на которые ссылается MyModel
, как выбор в фильтре списка? (Я использую Django 1.3.)
Ответы
Ответ 1
По состоянию на Django 1.8 есть встроенный RelatedOnlyFieldListFilter
, который можно использовать для отображения связанных стран.
class MyModelAdmin(admin.ModelAdmin):
list_display = ('name', 'country',)
list_filter = (
('country', admin.RelatedOnlyFieldListFilter),
)
Для Django 1.4-1.7 list_filter
позволяет использовать подкласс SimpleListFilter
. Должно быть возможно создать простой фильтр списка, в котором перечислены значения, которые вы хотите.
Если вы не можете выполнить обновление с Django 1.3, вам нужно будет использовать внутренний и недокументированный FilterSpec
api. Вопрос о переполнении стека Пользовательский фильтр в администраторе Django должен указывать на вас в правильном направлении.
Ответ 2
Я знаю, что вопрос касался Django 1.3, но вы упомянули о том, что вскоре будут обновлены до 1.4.
Также для людей, таких как я, которые искали решение для 1.4, но нашел эту запись, я решил показать полный пример использования SimpleListFilter (доступный Django 1.4), чтобы показать только ссылочные (связанные, используемые) значения внешнего ключа
from django.contrib.admin import SimpleListFilter
# admin.py
class CountryFilter(SimpleListFilter):
title = 'country' # or use _('country') for translated title
parameter_name = 'country'
def lookups(self, request, model_admin):
countries = set([c.country for c in model_admin.model.objects.all()])
return [(c.id, c.name) for c in countries]
# You can also use hardcoded model name like "Country" instead of
# "model_admin.model" if this is not direct foreign key filter
def queryset(self, request, queryset):
if self.value():
return queryset.filter(country__id__exact=self.value())
else:
return queryset
# Example setup and usage
# models.py
from django.db import models
class Country(models.Model):
name = models.CharField(max_length=64)
class City(models.Model):
name = models.CharField(max_length=64)
country = models.ForeignKey(Country)
# admin.py
from django.contrib.admin import ModelAdmin
class CityAdmin(ModelAdmin):
list_filter = (CountryFilter,)
admin.site.register(City, CityAdmin)
В примере вы можете увидеть две модели - город и страну. Город имеет ForeignKey to Country. Если вы используете обычный list_filter = ('country',), у вас будут все страны в списке. Однако этот фрагмент фильтрует только связанные страны - те, которые имеют хотя бы одно отношение к городу.
Оригинальная идея здесь. Большое спасибо автору. Улучшенные имена классов для лучшей ясности и использования model_admin.model вместо имени жесткой кодировки.
Пример также доступен в Django Snippets:
http://djangosnippets.org/snippets/2885/
Ответ 3
Так как Django 1.8 есть: admin.RelatedOnlyFieldListFilter
Пример использования:
class BookAdmin(admin.ModelAdmin):
list_filter = (
('author', admin.RelatedOnlyFieldListFilter),
)
Ответ 4
Идентификатор меняет поиск в тексте darklow следующим образом:
def lookups(self, request, model_admin):
users = User.objects.filter(id__in = model_admin.model.objects.all().values_list('user_id', flat = True).distinct())
return [(user.id, unicode(user)) for user in users]
Это намного лучше для базы данных;)
Ответ 5
Это мой подход к общей и многоразовой реализации Django 1.4, если вы застряли в этой версии. Он вдохновлен встроенной версией которая теперь является частью Django 1.8 и выше. Кроме того, довольно небольшая задача - адаптировать его к 1.5-1.7, главным образом, методы запроса изменили имя в них. Я поместил фильтр в приложение core
, которое у меня есть, но вы можете, очевидно, поместить его в любом месте.
Реализация:
# myproject/core/admin/filters.py:
from django.contrib.admin.filters import RelatedFieldListFilter
class RelatedOnlyFieldListFilter(RelatedFieldListFilter):
def __init__(self, field, request, params, model, model_admin, field_path):
self.request = request
self.model_admin = model_admin
super(RelatedOnlyFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path)
def choices(self, cl):
limit_choices_to = set(self.model_admin.queryset(self.request).values_list(self.field.name, flat=True))
self.lookup_choices = [(pk_val, val) for pk_val, val in self.lookup_choices if pk_val in limit_choices_to]
return super(RelatedOnlyFieldListFilter, self).choices(cl)
Применение:
# myapp/admin.py:
from django.contrib import admin
from myproject.core.admin.filters import RelatedOnlyFieldListFilter
from myproject.myapp.models import MyClass
class MyClassAdmin(admin.ModelAdmin):
list_filter = (
('myfield', RelatedOnlyFieldListFilter),
)
admin.site.register(MyClass, MyClassAdmin)
Если вы позже обновите Django 1.8, вы сможете просто изменить этот импорт:
from myproject.core.admin.filters import RelatedOnlyFieldListFilter
Для этого:
from django.contrib.admin.filters import RelatedOnlyFieldListFilter
Ответ 6
@andi, спасибо за то, что вы знаете о том, что Django 1.8 будет иметь эту функцию.
Я посмотрел, как он был реализован и основывается на этой созданной версии, которая работает для Django 1.7. Это более эффективная реализация, чем мой предыдущий ответ, потому что теперь вы можете повторно использовать этот фильтр с любыми полями внешнего ключа. Протестировано только в Django 1.7, не уверен, что он работает в более ранних версиях.
Вот мое окончательное решение:
from django.contrib.admin import RelatedFieldListFilter
class RelatedOnlyFieldListFilter(RelatedFieldListFilter):
def __init__(self, field, request, params, model, model_admin, field_path):
super(RelatedOnlyFieldListFilter, self).__init__(
field, request, params, model, model_admin, field_path)
qs = field.related_field.model.objects.filter(
id__in=model_admin.get_queryset(request).values_list(
field.name, flat=True).distinct())
self.lookup_choices = [(each.id, unicode(each)) for each in qs]
Использование:
class MyAdmin(admin.ModelAdmin):
list_filter = (
('user', RelatedOnlyFieldListFilter),
('category', RelatedOnlyFieldListFilter),
# ...
)
Ответ 7
Обобщенная многоразовая версия большого ответа @darklow:
def make_RelatedOnlyFieldListFilter(attr_name, filter_title):
class RelatedOnlyFieldListFilter(admin.SimpleListFilter):
"""Filter that shows only referenced options, i.e. options having at least a single object."""
title = filter_title
parameter_name = attr_name
def lookups(self, request, model_admin):
related_objects = set([getattr(obj, attr_name) for obj in model_admin.model.objects.all()])
return [(related_obj.id, unicode(related_obj)) for related_obj in related_objects]
def queryset(self, request, queryset):
if self.value():
return queryset.filter(**{'%s__id__exact' % attr_name: self.value()})
else:
return queryset
return RelatedOnlyFieldListFilter
Использование:
class CityAdmin(ModelAdmin):
list_filter = (
make_RelatedOnlyFieldListFilter("country", "Country with cities"),
)