ตัวกรองเริ่มต้นในผู้ดูแลระบบ Django


95

ฉันจะเปลี่ยนตัวเลือกตัวกรองเริ่มต้นจาก 'ALL' ได้อย่างไร? ฉันมีฟิลด์ชื่อเป็นstatusซึ่งมีสามค่า: activate, และpending rejectedเมื่อฉันใช้list_filterใน Django admin ตัวกรองจะถูกตั้งค่าเป็น 'ทั้งหมด' โดยค่าเริ่มต้น แต่ฉันต้องการตั้งค่าเป็นรอดำเนินการตามค่าเริ่มต้น

คำตอบ:


105

เพื่อให้บรรลุเป้าหมายนี้และมีลิงก์ "ทั้งหมด" ที่ใช้งานได้ในแถบด้านข้างของคุณ (เช่นลิงก์ที่แสดงทั้งหมดแทนที่จะแสดงอยู่ระหว่างดำเนินการ) คุณจะต้องสร้างตัวกรองรายการที่กำหนดเองโดยรับช่วงจาก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
โซลูชันนี้มีข้อเสียเปรียบเล็กน้อย เมื่อตัวกรองว่างเปล่า (ใช้ตัวกรอง 'รอดำเนินการ' จริง) Django 1.8 จะกำหนดจำนวนผลลัพธ์ทั้งหมดอย่างไม่ถูกต้องและจะไม่แสดงจำนวนผลลัพธ์หากshow_full_result_countเป็น True (โดยค่าเริ่มต้น) -
Alexander Fedotov

โปรดทราบว่าหากคุณล้มเหลวในการแทนที่choicesวิธีการในโซลูชันจะเพิ่มตัวเลือกทั้งหมดของตัวเองที่ด้านบนของรายการตัวเลือกอย่างน่ารำคาญ
richard

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

ฉันมีคำถามเหมือนกัน แต่ฉันสามารถเข้าใจการเล่นซ้ำได้ ... ขออภัยฉันเพิ่งใหม่กับ Django ... แต่อาจจะใช้งานได้ blog.dougalmatthews.com/2008/10/…
Asinox

นี่เป็นสิ่งที่ดี แต่ฉันต้องดูพารามิเตอร์ get ใน url เพื่อให้ตัวกรองของฉันสามารถเลือกและแสดงสิ่งที่เลือกได้ โพสต์วิธีแก้ปัญหาของฉันในไม่ช้า
radtek

ไม่มีคำอธิบาย การโพสต์โค้ดอาจไม่สามารถช่วยทุกคนได้ ยิ่งไปกว่านั้นมันใช้งานไม่ได้และไม่มีบริบทเล็กน้อยก็ยากที่จะหาว่าทำไม
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 ผู้เขียน

@ เบ็นฉันใช้สองบรรทัดของคุณ referer = request.META.get ('HTTP_REFERER', '') test = referer.split (request.META ['PATH_INFO']) ฉันไม่ค่อยเกี่ยวกับ HTTP_REFERER ปัญหาได้รับการแก้ไขอย่างสมบูรณ์จากบรรทัดเหล่านี้หรือไม่หากไม่มี HTTP_REFERER
the_game

@the_game ใช่แนวคิดคือถ้าคุณใช้วงเล็บเหลี่ยมเพื่อพยายามเข้าถึงคีย์ที่ไม่มีอยู่มันจะพ่นKeyErrorหากคุณใช้get()วิธีการของ dict คุณสามารถระบุค่าเริ่มต้นได้ ฉันระบุเริ่มต้นของว่างสตริงเพื่อให้แยก () AttributeErrorไม่เคยโยน นั่นคือทั้งหมด
เบ็นผู้เขียน

@ เบ็นขอบคุณที่ทำงานให้ฉัน นอกจากนี้คุณสามารถตอบคำถามนี้ผมเชื่อว่านี่เป็นส่วนขยายสำหรับคำถามนี้เท่านั้น stackoverflow.com/questions/10410982/... โปรดให้วิธีแก้ปัญหานี้แก่ฉันได้ไหม
the_game

1
นี้ใช้งานได้ดี แม้ว่าจะhas_key()เลิกใช้แล้วkey in dก็ตาม แต่ฉันรู้ว่าคุณเพิ่งเอาคำตอบของ ha22109 คำถามหนึ่ง: ทำไมต้องใช้request.META['PATH_INFO']เมื่อคุณสามารถใช้request.path_info(สั้นกว่า)
นิค

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 จะส่งผ่านพารามิเตอร์เริ่มต้นเท่านั้นหากไม่มีอยู่ แนวคิดคือการกำจัดความสามารถในการกรองข้อมูลทั่วไปเพื่อดูทั้งหมดโดยใช้พารามิเตอร์ไม่รับ หากต้องการดูทั้งหมดที่ฉันกำหนดสถานะ = 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

ขอบคุณฉันพบว่าการเปลี่ยนเส้นทางเป็นวิธีแก้ปัญหาที่สะอาดและง่ายที่สุด ฉันยังไม่เข้าใจ "ข้อแม้" ฉันมักจะได้ผลลัพธ์ที่ต้องการไม่ว่าจะโดยคลิกหรือใช้ลิงก์โดยตรง (ฉันไม่ได้ใช้ตัวกรองแบบกำหนดเอง)
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

คุณสามารถใช้return queryset.filter()หรือif self.value() is Noneและวิธีการแทนที่ของ SimpleListFilter

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()เมธอดแทน


นี่เป็นวิธีแก้ปัญหาที่ค่อนข้างสะอาดและรวดเร็วแม้ว่าจะยังคงทำให้เกิดปัญหา เมื่อเปิดใช้งานตัวเลือกการกรองในผู้ดูแลระบบผู้ใช้อาจได้รับผลลัพธ์ที่ดูเหมือนไม่ถูกต้อง หาก queryset overriden มีอนุประโยค. exclude () บันทึกที่ถูกจับจะไม่ปรากฏในรายการ แต่ตัวเลือกการกรองของผู้ดูแลระบบเพื่อแสดงอย่างชัดเจนจะยังคงมีให้โดย UI ของผู้ดูแลระบบ
Tomas Andrle

มีคำตอบอื่น ๆ ที่ถูกต้องกว่าพร้อมคะแนนโหวตที่ต่ำกว่าที่ใช้กับสถานการณ์นี้เนื่องจาก OP ได้ร้องขออย่างชัดเจนว่าเขาจะใส่ตัวกรองซึ่งแบบสอบถามจะเป็นคำตอบที่ไม่ถูกต้องตามที่ @TomasAndrle ชี้ไว้ด้านบน
eskhool

ขอบคุณที่ชี้ให้เห็น @eskhool ฉันพยายามลดคะแนนคำตอบของฉันให้เป็นศูนย์ แต่ดูเหมือนว่าจะไม่ได้รับอนุญาตให้ลงคะแนนตัวเอง
akaihola

3

การปรับปรุงคำตอบของ Greg เล็กน้อยโดยใช้ DjangoChoices, Python> = 2.5 และแน่นอน Django> = 1.4

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

ฉันรู้ว่านั่นไม่ใช่ทางออกที่ดีที่สุด แต่ฉันเปลี่ยน index.html ในเทมเพลตผู้ดูแลระบบบรรทัดที่ 25 และ 37 ดังนี้:

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

ทำอะไร (ไม่แน่ใจว่า Deminic Rodger หรือ ha22109) ตอบใน ModelAdmin สำหรับ changelist_view

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

ฉันพบว่าฉันจำเป็นต้องใช้ฟังก์ชัน 'force_text' (aka force_unicode) ในการเรียกผลผลิตในฟังก์ชันตัวเลือกมิฉะนั้นตัวเลือกตัวกรองที่เลือกจะไม่แสดงเป็น 'เลือก' นั่นคือ "'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)

ข้อดี:

ใช้ซ้ำได้ - เสียบได้ในModelAdminคลาสมาตรฐานใด ๆ

ขยายได้ - ง่ายต่อการเพิ่มตรรกะเพิ่มเติม / กำหนดเองสำหรับการQuerySetกรอง

ใช้งานง่าย - ในรูปแบบพื้นฐานที่สุดจำเป็นต้องใช้แอตทริบิวต์ที่กำหนดเองเพียงหนึ่งรายการและวิธีการที่กำหนดเอง (นอกเหนือจากที่จำเป็นสำหรับคลาสย่อย 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.