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を使用しています。)
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 のカスタムフィルターは、正しい方向を示す必要があります。
質問は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/ でも利用可能な例
Django 1.8であるため: admin.RelatedOnlyFieldListFilter
使用例は次のとおりです。
class BookAdmin(admin.ModelAdmin):
list_filter = (
('author', admin.RelatedOnlyFieldListFilter),
)
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]
これは、データベースにとってはるかに優れています;)
これは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),
# ...
)
偉大な@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"),
)