Подтвердить что ты не робот

Могу ли я сделать 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.)

4b9b3361

Ответ 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"),
    )