Django管理のデフォルトフィルター


94

デフォルトのフィルター選択を「すべて」から変更するにはどうすればよいですか?という名前のフィールドがあり、とのstatus3つの値activatependingありrejectedます。list_filterDjango adminで使用する場合、フィルターはデフォルトで「すべて」に設定されていますが、デフォルトで保留に設定したいと思います。

回答:


102

これを達成するために そしてあなたのサイドバーで使用可能な「すべて」リンク(すなわち、1つのすべてではなく、保留示すよりも表示されていることを)持っている、あなたは継承、カスタムリストフィルタを作成する必要があるだろうdjango.contrib.admin.filters.SimpleListFilterと、デフォルトでは「保留中」にフィルタリング。これらの線に沿った何かがうまくいくはずです:

from datetime import date

from django.utils.translation import ugettext_lazy as _
from django.contrib.admin import SimpleListFilter

class StatusFilter(SimpleListFilter):
    title = _('Status')

    parameter_name = 'status'

    def lookups(self, request, model_admin):
        return (
            (None, _('Pending')),
            ('activate', _('Activate')),
            ('rejected', _('Rejected')),
            ('all', _('All')),
        )

    def choices(self, cl):
        for lookup, title in self.lookup_choices:
            yield {
                'selected': self.value() == lookup,
                'query_string': cl.get_query_string({
                    self.parameter_name: lookup,
                }, []),
                'display': title,
            }

    def queryset(self, request, queryset):
        if self.value() in ('activate', 'rejected'):
            return queryset.filter(status=self.value())    
        elif self.value() == None:
            return queryset.filter(status='pending')


class Admin(admin.ModelAdmin): 
    list_filter = [StatusFilter] 

編集:Django 1.4が必要です(Simonに感謝)


3
これはすべての中で最もクリーンなソリューションですが、賛成票が最も少ないのですが、Django 1.4が必要ですが、現時点ではそれが提供されているはずです。
Simon

@Gregどのようにして、管理ページからフィルターとフィルタータブの機能完全に削除しますか?


2
このソリューションには小さな欠点があります。フィルターが空の場合(実際には「保留」フィルターが使用されます)、show_full_result_countがTrue(デフォルト)の場合、Django 1.8は完全な結果カウントを誤って決定し、結果カウントを表示しません。–
アレクサンダーフェドトフ2015

choicesソリューションでメソッドのオーバーライドに失敗した場合、迷惑なことに、選択肢のリストの上部に独自のAllオプションが追加され続けることに注意してください。
リチャード、

47
class MyModelAdmin(admin.ModelAdmin):   

    def changelist_view(self, request, extra_context=None):

        if not request.GET.has_key('decommissioned__exact'):

            q = request.GET.copy()
            q['decommissioned__exact'] = 'N'
            request.GET = q
            request.META['QUERY_STRING'] = request.GET.urlencode()
        return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)

18
このソリューションには、「すべて」の選択肢がUIに表示されているにもかかわらず、それを選択してもデフォルトのフィルタリングが適用されるという欠点があります。
akaihola 2009年

私は同じ質問がありますが、リプレイを理解できます... Djangoを使って申し訳ありませんが、これはうまくいく
2008/10 /

これは良いことですが、フィルターがそれを取得して選択して表示できるように、URLのgetパラメーターを確認する必要がありました。私のソリューションを間もなく投稿します。
radtek、2014

説明がありません。コードを投稿するだけでは、すべての人に役立つとは限りません。その上、それは機能しておらず、少しのコンテキストなしでは理由を見つけるのは難しい
EvilSmurf

19

上記ha22109の答えを取り、比較することにより、「すべて」の選択を可能にするように変更HTTP_REFERERPATH_INFO

class MyModelAdmin(admin.ModelAdmin):

    def changelist_view(self, request, extra_context=None):

        test = request.META['HTTP_REFERER'].split(request.META['PATH_INFO'])

        if test[-1] and not test[-1].startswith('?'):
            if not request.GET.has_key('decommissioned__exact'):

                q = request.GET.copy()
                q['decommissioned__exact'] = 'N'
                request.GET = q
                request.META['QUERY_STRING'] = request.GET.urlencode()
        return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)

3
HTTP_REFERERが常に存在するとは限らなかったため、これは私にとって破綻しました。私は 'referer = request.META.get(' HTTP_REFERER '、' '); test = referer.split(request.META ['PATH_INFO']) `
ben作者

@ベン私はあなたの2行を使用していますreferer = request.META.get( 'HTTP_REFERER'、 '')test = referer.split(request.META ['PATH_INFO'])。HTTP_REFERERについてはあまり気にしません。HTTP_REFERERが存在しない場合、問題はこれらの行から完全に修正されていますか?
the_game

@the_gameええ、アイデアは、角括弧を使用して存在しないキーにアクセスしようとすると、スローされKeyErrorますが、dictのget()メソッドを使用する場合は、デフォルトを指定できます。split()がスローしないように、デフォルトのempty-stringを指定しましたAttributeError。それで全部です。
benの作成者

@Ben。ありがとうございます。また、あなたはこの質問に答えることができますか?私はこれがこの質問のみの拡張であると思います。stackoverflow.com/ questions / 10410982 /…。この問題の解決策を教えてください。
the_game

1
これはうまくいきます。ただし、のhas_key()代わりに非推奨key in dです。しかし、私はあなたがha22109の答えから取ったばかりだと知っています。1つの質問:なぜ(より短い)request.META['PATH_INFO']だけを使用できるのに使用するのrequest.path_info
Nick

19

私はこの質問がかなり古いことを知っていますが、それでもまだ有効です。これが最も正しい方法だと思います。基本的にはGregのメソッドと同じですが、簡単に再利用できるように拡張可能なクラスとして作成されています。

from django.contrib.admin import SimpleListFilter
from django.utils.encoding import force_text
from django.utils.translation import ugettext as _

class DefaultListFilter(SimpleListFilter):
    all_value = '_all'

    def default_value(self):
        raise NotImplementedError()

    def queryset(self, request, queryset):
        if self.parameter_name in request.GET and request.GET[self.parameter_name] == self.all_value:
            return queryset

        if self.parameter_name in request.GET:
            return queryset.filter(**{self.parameter_name:request.GET[self.parameter_name]})

        return queryset.filter(**{self.parameter_name:self.default_value()})

    def choices(self, cl):
        yield {
            'selected': self.value() == self.all_value,
            'query_string': cl.get_query_string({self.parameter_name: self.all_value}, []),
            'display': _('All'),
        }
        for lookup, title in self.lookup_choices:
            yield {
                'selected': self.value() == force_text(lookup) or (self.value() == None and force_text(self.default_value()) == force_text(lookup)),
                'query_string': cl.get_query_string({
                    self.parameter_name: lookup,
                }, []),
                'display': title,
            }

class StatusFilter(DefaultListFilter):
    title = _('Status ')
    parameter_name = 'status__exact'

    def lookups(self, request, model_admin):
        return ((0,'activate'), (1,'pending'), (2,'rejected'))

    def default_value(self):
        return 1

class MyModelAdmin(admin.ModelAdmin):
    list_filter = (StatusFilter,)

8

これがリダイレクトを使用した私の一般的なソリューションです。GETパラメーターがあるかどうかを確認するだけです。存在しない場合は、デフォルトのgetパラメーターでリダイレクトします。また、list_filterが設定されているので、それを取得してデフォルトを表示します。

from django.shortcuts import redirect

class MyModelAdmin(admin.ModelAdmin):   

    ...

    list_filter = ('status', )

    def changelist_view(self, request, extra_context=None):
        referrer = request.META.get('HTTP_REFERER', '')
        get_param = "status__exact=5"
        if len(request.GET) == 0 and '?' not in referrer:
            return redirect("{url}?{get_parms}".format(url=request.path, get_parms=get_param))
        return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)

唯一の注意点は、「?」で直接ページにアクセスする場合です。URLに存在し、HTTP_REFERERが設定されていないため、デフォルトのパラメーターを使用してリダイレクトします。これは私にとっては問題ありません。管理フィルターをクリックするとうまくいきます。

更新

警告を回避するために、changelist_view機能を簡略化するカスタムフィルター関数を作成しました。これがフィルターです:

class MyModelStatusFilter(admin.SimpleListFilter):
    title = _('Status')
    parameter_name = 'status'

    def lookups(self, request, model_admin):  # Available Values / Status Codes etc..
        return (
            (8, _('All')),
            (0, _('Incomplete')),
            (5, _('Pending')),
            (6, _('Selected')),
            (7, _('Accepted')),
        )

    def choices(self, cl):  # Overwrite this method to prevent the default "All"
        from django.utils.encoding import force_text
        for lookup, title in self.lookup_choices:
            yield {
                'selected': self.value() == force_text(lookup),
                'query_string': cl.get_query_string({
                    self.parameter_name: lookup,
                }, []),
                'display': title,
            }

    def queryset(self, request, queryset):  # Run the queryset based on your lookup values
        if self.value() is None:
            return queryset.filter(status=5)
        elif int(self.value()) == 0:
            return queryset.filter(status__lte=4)
        elif int(self.value()) == 8:
            return queryset.all()
        elif int(self.value()) >= 5:
            return queryset.filter(status=self.value())
        return queryset.filter(status=5)

そして、changelist_viewは、何も存在しない場合にのみデフォルトのパラメーターを渡すようになりました。アイデアは、getパラメーターを使用せずにすべてを表示するジェネリックフィルター機能を取り除くことでした。すべてを表示するには、その目的でステータス= 8を割り当てました。

class MyModelAdmin(admin.ModelAdmin):   

    ...

    list_filter = ('status', )

    def changelist_view(self, request, extra_context=None):
        if len(request.GET) == 0:
            get_param = "status=5"
            return redirect("{url}?{get_parms}".format(url=request.path, get_parms=get_param))
        return super(MyModelAdmin, self).changelist_view(request, extra_context=extra_context)

私は警告、カスタムフィルターの修正を持っています。代替ソリューションとして紹介します。
radtek、2014年

ありがとうございます。リダイレクトが最もクリーンでシンプルなソリューションであることがわかりました。「注意」もわかりません。クリックするか直接リンクを使用しても、常に希望する結果が得られます(カスタムフィルターは使用していません)。
Dennis Golomazov

6
def changelist_view( self, request, extra_context = None ):
    default_filter = False
    try:
        ref = request.META['HTTP_REFERER']
        pinfo = request.META['PATH_INFO']
        qstr = ref.split( pinfo )

        if len( qstr ) < 2:
            default_filter = True
    except:
        default_filter = True

    if default_filter:
        q = request.GET.copy()
        q['registered__exact'] = '1'
        request.GET = q
        request.META['QUERY_STRING'] = request.GET.urlencode()

    return super( InterestAdmin, self ).changelist_view( request, extra_context = extra_context )

4

SimpleListFilterのreturn queryset.filter()or if self.value() is NoneおよびOverrideメソッドを使用するだけです。

from django.utils.encoding import force_text

def choices(self, changelist):
    for lookup, title in self.lookup_choices:
        yield {
            'selected': force_text(self.value()) == force_text(lookup),
            'query_string': changelist.get_query_string(
                {self.parameter_name: lookup}, []
            ),
            'display': title,
        }

3

フィルター値を事前に選択する代わりに、管理者に表示する前に常にデータを事前にフィルター処理する場合は、ModelAdmin.queryset()代わりにメソッドをオーバーライドする必要があることに注意してください。


それでも問題が発生する可能性がありますが、これはかなりクリーンで迅速なソリューションです。フィルタリングオプションが管理者で有効になっている場合、ユーザーは一見誤った結果を得る可能性があります。オーバーライドされたクエリセットに.exclude()句が含まれている場合、それによってキャッチされたレコードはリストに表示されませんが、それらを明示的に表示する管理フィルタリングオプションは引き続き管理UIによって提供されます。
Tomas Andrle 2009

OPは、上記の@TomasAndrleでも指摘されているように、クエリセットが間違ったソリューションになるフィルターを配置することをOPが明確に要求しているため、この状況に適用される投票数が少ない正解が他にもあります。
eskhool 2015

これを@eskhoolに指摘してくれてありがとう、私は自分の回答を0に反対投票しようとしましたが、自分に反対投票することはできません。
akaihola

3

DjangoChoices、Python> = 2.5、およびもちろんDjango> = 1.4を使用したGregの回答のわずかな改善。

from django.utils.translation import ugettext_lazy as _
from django.contrib.admin import SimpleListFilter

class OrderStatusFilter(SimpleListFilter):
    title = _('Status')

    parameter_name = 'status__exact'
    default_status = OrderStatuses.closed

    def lookups(self, request, model_admin):
        return (('all', _('All')),) + OrderStatuses.choices

    def choices(self, cl):
        for lookup, title in self.lookup_choices:
            yield {
                'selected': self.value() == lookup if self.value() else lookup == self.default_status,
                'query_string': cl.get_query_string({self.parameter_name: lookup}, []),
                'display': title,
            }

    def queryset(self, request, queryset):
        if self.value() in OrderStatuses.values:
            return queryset.filter(status=self.value())
        elif self.value() is None:
            return queryset.filter(status=self.default_status)


class Admin(admin.ModelAdmin):
    list_filter = [OrderStatusFilter] 

素晴らしいソリューションを提供してくれたGregに感謝します。


2

私はそれが最良の解決策ではないことを知っていますが、管理テンプレートの25行目と37行目のindex.htmlを次のように変更しました。

25: <th scope="row"><a href="{{ model.admin_url }}{% ifequal model.name "yourmodelname" %}?yourflag_flag__exact=1{% endifequal %}">{{ model.name }}</a></th>

37: <td><a href="{{ model.admin_url }}{% ifequal model.name "yourmodelname" %}?yourflag__exact=1{% endifequal %}" class="changelink">{% trans 'Change' %}</a></td>


1

フィルタリングを正しく機能させるには、変更を加える必要がありました。前の解決策は、ページが読み込まれたときに私にとってうまくいきました。「アクション」が実行された場合、フィルターはデフォルトではなく「すべて」に戻りました。このソリューションは、デフォルトのフィルターを使用して管理者変更ページをロードしますが、ページで他のアクティビティーが発生したときにも、フィルター変更または現在のフィルターを維持します。すべてのケースをテストしたわけではありませんが、実際には、ページが読み込まれたときにのみ発生するデフォルトフィルターの設定が制限されている可能性があります。

def changelist_view(self, request, extra_context=None):
    default_filter = False

    try:
        ref = request.META['HTTP_REFERER']
        pinfo = request.META['PATH_INFO']
        qstr = ref.split(pinfo)
        querystr = request.META['QUERY_STRING']

        # Check the QUERY_STRING value, otherwise when
        # trying to filter the filter gets reset below
        if querystr is None:
            if len(qstr) < 2 or qstr[1] == '':
                default_filter = True
    except:
        default_filter = True

    if default_filter:
        q = request.GET.copy()
        q['registered__isnull'] = 'True'
        request.GET = q
        request.META['QUERY_STRING'] = request.GET.urlencode()

    return super(MyAdmin, self).changelist_view(request, extra_context=extra_context)

1

少し話題外ですが、同様の質問を検索したところ、ここにたどり着きました。私は日付によるデフォルトのクエリ(つまり、入力が提供されない場合timestamp、「今日」のオブジェクトのみを表示する)を探していましたが、これは質問を少し複雑にします。これが私が思いついたものです:

from django.contrib.admin.options import IncorrectLookupParameters
from django.core.exceptions import ValidationError

class TodayDefaultDateFieldListFilter(admin.DateFieldListFilter):
    """ If no date is query params are provided, query for Today """

    def queryset(self, request, queryset):
        try:
            if not self.used_parameters:
                now = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
                self.used_parameters = {
                    ('%s__lt' % self.field_path): str(now + datetime.timedelta(days=1)),
                    ('%s__gte' % self.field_path): str(now),
                }
                # Insure that the dropdown reflects 'Today'
                self.date_params = self.used_parameters
            return queryset.filter(**self.used_parameters)
        except ValidationError, e:
            raise IncorrectLookupParameters(e)

class ImagesAdmin(admin.ModelAdmin):
    list_filter = (
        ('timestamp', TodayDefaultDateFieldListFilter),
    )

これは、デフォルトの単純なオーバーライドですDateFieldListFilter。を設定self.date_paramsすると、フィルタードロップダウンがに一致するオプションに更新されますself.used_parameters。このため、self.used_parametersこれらのドロップダウン選択のいずれかによって使用されるものが正確にあることを確認する必要があります(つまり、date_params「今日」または「過去7日間」を使用する場合の内容を確認し、self.used_parameters)。

これはDjango 1.4.10で動作するように構築されています


1

これは古いスレッドの可能性がありますが、Google検索でより良い答えを見つけることができなかったため、解決策を追加すると思いました。

changelist_viewのModelAdminで何を回答したか(そのDeminic Rodger、またはha22109かどうかは不明)

class MyModelAdmin(admin.ModelAdmin):   
    list_filter = (CustomFilter,)

    def changelist_view(self, request, extra_context=None):

        if not request.GET.has_key('decommissioned__exact'):

            q = request.GET.copy()
            q['decommissioned__exact'] = 'N'
            request.GET = q
            request.META['QUERY_STRING'] = request.GET.urlencode()
        return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)

次に、カスタムSimpleListFilterを作成する必要があります

class CustomFilter(admin.SimpleListFilter):
    title = 'Decommissioned'
    parameter_name = 'decommissioned'  # i chose to change it

def lookups(self, request, model_admin):
    return (
        ('All', 'all'),
        ('1', 'Decommissioned'),
        ('0', 'Active (or whatever)'),
    )

# had to override so that we could remove the default 'All' option
# that won't work with our default filter in the ModelAdmin class
def choices(self, cl):
    yield {
        'selected': self.value() is None,
        'query_string': cl.get_query_string({}, [self.parameter_name]),
        # 'display': _('All'),
    }
    for lookup, title in self.lookup_choices:
        yield {
            'selected': self.value() == lookup,
            'query_string': cl.get_query_string({
                self.parameter_name: lookup,
            }, []),
            'display': title,
        }

def queryset(self, request, queryset):
    if self.value() == '1':
        return queryset.filter(decommissioned=1)
    elif self.value() == '0':
        return queryset.filter(decommissioned=0)
    return queryset

choices関数のyield呼び出しで 'force_text'(別名force_unicode)関数を使用する必要があることがわかりました。そうしないと、選択したフィルターオプションが 'selected'として表示されません。それは "'selected':self.value()== force_text(lookup)、"
MagicLAMP

1

これは、再定義された「すべて」とデフォルト値が選択されたフィルターを生成することができた最もクリーンなバージョンです。

デフォルトで表示されている場合は、現在発生している旅行。

class HappeningTripFilter(admin.SimpleListFilter):
    """
    Filter the Trips Happening in the Past, Future or now.
    """
    default_value = 'now'
    title = 'Happening'
    parameter_name = 'happening'

    def lookups(self, request, model_admin):
        """
        List the Choices available for this filter.
        """
        return (
            ('all', 'All'),
            ('future', 'Not yet started'),
            ('now', 'Happening now'),
            ('past', 'Already finished'),
        )

    def choices(self, changelist):
        """
        Overwrite this method to prevent the default "All".
        """
        value = self.value() or self.default_value
        for lookup, title in self.lookup_choices:
            yield {
                'selected': value == force_text(lookup),
                'query_string': changelist.get_query_string({
                    self.parameter_name: lookup,
                }, []),
                'display': title,
            }

    def queryset(self, request, queryset):
        """
        Returns the Queryset depending on the Choice.
        """
        value = self.value() or self.default_value
        now = timezone.now()
        if value == 'future':
            return queryset.filter(start_date_time__gt=now)
        if value == 'now':
            return queryset.filter(start_date_time__lte=now, end_date_time__gte=now)
        if value == 'past':
            return queryset.filter(end_date_time__lt=now)
        return queryset.all()

0

ここでの回答の一部(ほとんどはGregのもの)に触発された、再利用可能なFilterサブクラスを作成しました。

利点:

再利用可能 -標準ModelAdminクラスでプラグ可能

拡張可能 -追加/カスタムロジックの追加が容易QuerySetフィルタリング

使いやすさ -最も基本的な形式では、1つのカスタム属性と1つのカスタムメソッドのみを実装する必要があります(SimpleListFilterサブクラス化に必要なものを除く)

直感的な管理 -「すべて」のフィルターリンクは期待どおりに機能しています。他のすべてのように

リダイレクトなし - GETに依存しない要求ペイロードHTTP_REFERER(またはその他の要求に関連するもの、その基本形式)を検査する必要はありません。

ビューの操作(チェンジリスト)なし -テンプレートの操作なし(禁止)

コード:

(ほとんどのはimport型ヒントと例外用です)

from typing import List, Tuple, Any

from django.contrib.admin.filters import SimpleListFilter
from django.contrib.admin.options import IncorrectLookupParameters
from django.contrib.admin.views.main import ChangeList
from django.db.models.query import QuerySet
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError


class PreFilteredListFilter(SimpleListFilter):

    # Either set this or override .get_default_value()
    default_value = None

    no_filter_value = 'all'
    no_filter_name = _("All")

    # Human-readable title which will be displayed in the
    # right admin sidebar just above the filter options.
    title = None

    # Parameter for the filter that will be used in the URL query.
    parameter_name = None

    def get_default_value(self):
        if self.default_value is not None:
            return self.default_value
        raise NotImplementedError(
            'Either the .default_value attribute needs to be set or '
            'the .get_default_value() method must be overridden to '
            'return a URL query argument for parameter_name.'
        )

    def get_lookups(self) -> List[Tuple[Any, str]]:
        """
        Returns a list of tuples. The first element in each
        tuple is the coded value for the option that will
        appear in the URL query. The second element is the
        human-readable name for the option that will appear
        in the right sidebar.
        """
        raise NotImplementedError(
            'The .get_lookups() method must be overridden to '
            'return a list of tuples (value, verbose value).'
        )

    # Overriding parent class:
    def lookups(self, request, model_admin) -> List[Tuple[Any, str]]:
        return [(self.no_filter_value, self.no_filter_name)] + self.get_lookups()

    # Overriding parent class:
    def queryset(self, request, queryset: QuerySet) -> QuerySet:
        """
        Returns the filtered queryset based on the value
        provided in the query string and retrievable via
        `self.value()`.
        """
        if self.value() is None:
            return self.get_default_queryset(queryset)
        if self.value() == self.no_filter_value:
            return queryset.all()
        return self.get_filtered_queryset(queryset)

    def get_default_queryset(self, queryset: QuerySet) -> QuerySet:
        return queryset.filter(**{self.parameter_name: self.get_default_value()})

    def get_filtered_queryset(self, queryset: QuerySet) -> QuerySet:
        try:
            return queryset.filter(**self.used_parameters)
        except (ValueError, ValidationError) as e:
            # Fields may raise a ValueError or ValidationError when converting
            # the parameters to the correct type.
            raise IncorrectLookupParameters(e)

    # Overriding parent class:
    def choices(self, changelist: ChangeList):
        """
        Overridden to prevent the default "All".
        """
        value = self.value() or force_str(self.get_default_value())
        for lookup, title in self.lookup_choices:
            yield {
                'selected': value == force_str(lookup),
                'query_string': changelist.get_query_string({self.parameter_name: lookup}),
                'display': title,
            }

完全な使用例:

from django.contrib import admin
from .models import SomeModelWithStatus


class StatusFilter(PreFilteredListFilter):
    default_value = SomeModelWithStatus.Status.FOO
    title = _('Status')
    parameter_name = 'status'

    def get_lookups(self):
        return SomeModelWithStatus.Status.choices


@admin.register(SomeModelWithStatus)
class SomeModelAdmin(admin.ModelAdmin):
    list_filter = (StatusFilter, )

これが誰かを助けることを願っています。フィードバックは常に感謝しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.