web-dev-qa-db-ja.com

list_filterをDjango adminで作成して、参照された外部キーのみを表示できますか?

Djangoアプリケーションがあり、次のような2つのモデルがあります。

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()

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を使用しています。)

52
m000

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 Admin のカスタムフィルターは、正しい方向を示す必要があります。

72
Alasdair

質問はDjango 1.3でしたが、1.4にすぐにアップグレードすることについて言及しました。1.4のソリューションを探していたが、このエントリを見つけた人にも SimpleListFilter (available 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)

例では、2つのモデル-都市と国を見ることができます。 CityにはCountry to ForeignKeyがあります。通常のlist_filter =( 'country'、)を使用すると、すべての国が選択されます。ただし、このスニペットは、関連する国(都市との関係が少なくとも1つある国)のみをフィルタリングします。

here の元のアイデア。著者に感謝します。わかりやすくするためにクラス名を改善し、ハードコーディングされたモデル名の代わりにmodel_admin.modelを使用します。

Django Snippets: http://djangosnippets.org/snippets/2885/ でも利用可能な例

32
darklow

Django 1.8であるため: admin.RelatedOnlyFieldListFilter

使用例は次のとおりです。

class BookAdmin(admin.ModelAdmin):
    list_filter = (
        ('author', admin.RelatedOnlyFieldListFilter),
    )
20
andilabs

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
user3471924

これはDjango 1.4の一般的な再利用可能な実装についての私の見解です。たまたまそのバージョンで立ち往生している場合。これは ビルトインバージョンに触発されますDjango 1.8 以降)の一部になりました。また、1.5〜1.7に適応させるのは非常に小さなタスクである必要があります。主にquerysetメソッドはそれらの名前を変更しました。私が持っている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

@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),
        # ...
    )
1
darklow

偉大な@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"),
    )
1