Kuinka lisätä tekstisuodatin Django Adminiin

Kuinka korvata Django-haku tiettyjen kenttien tekstisuodattimilla

Paremman lukukokemuksen saamiseksi tutustu tähän artikkeliin verkkosivustollani.

Kun luot uuden Django-järjestelmänvalvojan sivun, yhteinen keskustelu kehittäjän ja tukihenkilöiden välillä saattaa kuulostaa seuraavalta:

Kehittäjä: Hei, lisään uuden järjestelmänvalvojasivun tapahtumia varten. Voitko kertoa minulle, kuinka haluat etsiä tapahtumia?
Tuki: Toki, etsin yleensä vain käyttäjänimen perusteella.
Kehittäjä: Cool.
hakukentät = (
    user__username,
)
Mitään muuta?
Tuki: Toisinaan haluan tehdä hakuja myös käyttäjän sähköpostiosoitteen perusteella.
Kehittäjä: OK.
hakukentät = (
   user__username,
   user__email,
)
Tuki: Ja tietysti etunimi ja sukunimi.
Kehittäjä: Kyllä, OK.
hakukentät = (
    user__username,
    user__email,
    user__first_name,
    user__last_name,
)
Onko tuo se?
Tuki: No, joskus minun on etsittävä maksusetelinumeron perusteella.
Kehittäjä: OK.
hakukentät = (
    user__username,
    user__email,
    user__first_name,
    user__last_name,
    payment__voucher_number,
)
Mitään muuta?
Tuki: Jotkut asiakkaat lähettävät laskunsa ja esittävät kysymyksiä, joten etsin myös laskun numeron perusteella.
Kehittäjä: FINE!
hakukentät = (
    user__username,
    user__email,
    user__first_name,
    user__last_name,
    payment__voucher_number,
    invoice__invoice_number,
)
OK, oletko varma, että siinä on?
Tuki: No, kehittäjät välittävät joskus lippuja meille ja käyttävät näitä pitkiä satunnaisia ​​merkkijonoja. En ole koskaan oikein varma mitä he ovat, joten etsin ja toivon vain parasta.
Kehittäjä: Näitä kutsutaan UUID: ksi.
hakukentät = (
    user__username,
    user__email,
    user__first_name,
    user__last_name,
    payment__voucher_number,
    invoice__invoice_number,
    uid,
    user__uid,
    payment__uid,
    invoice__uid,
)
Joten onko se?
Tuki: Kyllä, toistaiseksi…

Ongelma hakukentissä

Django Admin -hakukentät ovat hienoja - heitä joukko kenttiä hakukenttiin ja Django käsittelee loput.

Hakukentän ongelma alkaa, kun niitä on liian paljon.

Kun järjestelmänvalvojan käyttäjä haluaa hakea UID: n tai sähköpostin avulla, Djangolla ei ole aavistustakaan, mitä käyttäjä tarkoitti, joten hänen on tehtävä haku kaikista hakukentissä luetelluista kentistä. Nämä "vastaa mitä tahansa" -kyselyihin sisältyy valtavia WHERE-lauseita ja paljon liittymiä, ja niistä voi nopeasti tulla erittäin hitaita.

Normaalin ListFilterin käyttö ei ole vaihtoehto - ListFilter näyttää luettelon valinnoista kentän erillisistä arvoista. Jotkut yllä luetellut kentät ovat ainutlaatuisia ja toisilla on monia erillisiä arvoja - Valintojen näyttäminen ei ole vaihtoehto.

Djangon ja käyttäjän välisen kuilun kaventaminen

Aloin miettiä tapoja, joilla voimme luoda useita hakukenttiä - yhden jokaiselle kentälle tai kenttäryhmälle. Ajattelimme, että jos käyttäjä haluaa hakea sähköpostitse tai UID: llä, ei ole mitään syytä etsiä kenenkään muun kentän perusteella.

Jotkut ajatukset keksivät ratkaisun - mukautetun SimpleListFilter:

  • ListFilter mahdollistaa mukautetun suodatuslogiikan.
  • ListFilterillä voi olla mukautettu malli.
  • Django tukee jo useita ListFilters-ohjelmia.

Halusimme sen näyttävän tältä:

Tekstiluettelosuodatin

InputFilterin käyttöönotto

Mitä haluamme tehdä, on luettelosuodatin, jossa on tekstinsyöttö valintojen sijasta.

Aloitetaan lopusta, ennen kuin sukellamme toteutukseen. Näin haluamme käyttää InputFilter-mallia ModelAdmin -sovelluksessa:

luokan UIDFilter (InputFilter):
    parametrin_nimi = 'uid'
    otsikko = _ ('UID')
 
    def queryset (itse, pyyntö, queryset):
        jos itsearvo () ei ole Ei mitään:
            uid = itsearvo ()
            palauta queryset.filter (
                Q (uid = uid) |
                Q (maksu__uid = uid) |
                Q (user__uid = uid)
            )

Ja käytä sitä kuten mitä tahansa muuta ModelAdminin luettelosuodatinta:

luokan TransactionAdmin (admin.ModelAdmin):
    ...
    list_filter = (
        UUIDFilter,
    )
    ...
  • Luomme mukautetun suodattimen uuid-kenttään - UIDFilter.
  • Asetamme parametrin_nimi URL-osoitteessa uidiksi. Uidin suodattama URL näyttää tältä / admin / app / transaktio? Uid =
  • Jos käyttäjä on kirjoittanut tunnuksen, etsimme tapahtuman tunnuksen, maksun tai käyttäjän tunnuksen perusteella.

Toistaiseksi tämä on kuin tavallinen mukautettu ListFilter.

Nyt kun meillä on parempi käsitys siitä, mitä haluamme toteuttaa InputFilter:

luokan InputFilter (admin.SimpleListFilter):
    malli = 'admin / input_filter.html'
    def-haku (itse, pyyntö, malliadmin):
        # Nukke, vaaditaan suodattimen näyttämiseen.
        palata ((),)

Perimme SimpleListFilteriltä ja ohitamme mallin. Meillä ei ole hakuja, ja haluamme mallin tuottavan tekstinsyötön valintojen sijasta:

// mallit / admin / input_filter.html
{% kuorma i18n%}

{% blocktrans with filter_title = title%} Lähettäjä {{filter_title}} {% endblocktrans%}

      
  •     
                    

Käytämme samanlaisia ​​merkintöjä kuin Djangon nykyisessä luettelosuodattimessa sen tekemiseksi alkuperäiseksi. Malli tuottaa yksinkertaisen muodon, jossa on GET-toiminto ja parametrille tekstikenttä. Kun tämä lomake lähetetään, URL päivitetään parametrin nimellä ja lähetetyllä arvolla.

Pelaa mukavasti muiden suodattimien kanssa

Toistaiseksi suodatinmme toimii, mutta vain, jos muita suodattimia ei ole. Jos haluamme pelata mukavasti muiden suodattimien kanssa, meidän on harkittava niitä muodollamme. Tätä varten meidän on hankittava heidän arvonsa.

Luettelosuodattimella on toinen toiminto, nimeltään “valinnat”. Toiminto hyväksyy muutoslistaobjektin, joka sisältää kaikki tiedot nykyisestä näkymästä, ja palauttaa luettelon valinnoista.

Meillä ei ole vaihtoehtoja, joten käytämme tätä toimintoa poimiaksesi kaikki suodattimet, joita on käytetty kyselyyn, ja paljastaa ne malliin:

luokan InputFilter (admin.SimpleListFilter):
    malli = 'admin / input_filter.html'
    def-haku (itse, pyyntö, malliadmin):
        # Nukke, vaaditaan suodattimen näyttämiseen.
        palata ((),)
    def valinnat (itse, muuttajalista):
        # Ota vain "kaikki" -vaihtoehto.
        all_choice = seuraava (super (). valinnat (muutoslista))
        all_choice ['query_parts'] = (
            (k, v)
            k, v: lle changelist.get_filters_params (). items ()
            jos k! = itseparametrin_nimi
        )
        tuottaa kaikki_valinnat

Suodattimien sisällyttämiseksi lisäämme piilotetun syöttökentän jokaiselle parametrille:

// mallit / admin / input_filter.html
{% kuorma i18n%}

{% blocktrans with filter_title = title%} Lähettäjä {{filter_title}} {% endblocktrans%}

      
  •     {% with options.0 kuin all_choice%}     
        {% k: lle, v: lle kaikissa_choice.query_parts%}
        
        {% endfor%}
        
    
    {% endwith%}
  

Nyt meillä on suodatin, jossa on tekstinsyöttö, joka pelaa mukavasti muiden suodattimien kanssa. Ainoa jäljellä oleva asia, jotta siihen lisätään ”tyhjennys”.

Suodattimen tyhjentämiseksi tarvitaan URL, joka sisältää kaikki suodattimet paitsi meidän:

// mallit / admin / input_filter.html
...

    
{% jos ei kaikki_choice.selected%}
    ⨉ {% trans 'Poista'%}  
{% loppu Jos %}
...

Voilà!

Tämän saamme:

InputFilter muiden suodattimien ja poistopainikkeen kanssa

Täydellinen koodi:

Bonus

Etsi useita sanoja, jotka ovat samanlaisia ​​kuin Django-haku

Olet ehkä huomannut, että etsiessäsi useita sanoja Django löytää tuloksia, jotka sisältävät ainakin yhden sanoista eikä kaikkia.

Jos esimerkiksi etsit käyttäjää ”John Duo”, Django löytää sekä “John Foo” että “Bar Due”. Tämä on erittäin kätevää etsiessäsi esimerkiksi täydellistä nimeä, tuotenimiä ja niin edelleen.

Voimme toteuttaa samanlaisen tilan InputFilter-ohjelmalla:

django.db.malleista tuo Q
luokan UserFilter (InputFilter):
    parametrin_nimi = 'käyttäjä'
    otsikko = _ ('käyttäjä')
    def queryset (itse, pyyntö, queryset):
        termi = itsearvo ()
        jos termi ei ole:
            palata
        any_name = Q ()
        bitille term.split ():
            any_name & = (
                Q (käyttäjän __ ensimmäinen_nimi__icontains = bitti) |
                Q (user__last_name__icontains = bitti)
            )
        palauta queryset.filter (any_name)

Tämä se on!

Tutustu muihin viesteihini Django Adminissa: