Kako dodati tekstualni filtar u Django Admin

Kako zamijeniti Django pretraživanje tekstualnim filtrima za određena polja

Za bolje iskustvo čitanja pogledajte ovaj članak na mojoj web stranici.

Prilikom stvaranja nove stranice Django Admin zajednički razgovor između programera i osobne podrške mogao bi zvučati ovako:

Programer: Hej, dodajem novu administrativnu stranicu za transakcije. Možete li mi reći kako želite tražiti transakcije?
Podrška: Naravno, obično pretražujem samo korisničko ime.
Programer: Cool.
search_fields = (
    user__username,
)
Još nešto?
Podrška: Ponekad želim pretražiti i korisničku adresu e-pošte.
Programer: u redu.
search_fields = (
   user__username,
   user__email,
)
Podrška: I ime i prezime, naravno.
Programer: Da, u redu.
search_fields = (
    user__username,
    user__email,
    user__first_name,
    user__last_name,
)
Je li to to?
Podrška: Pa, ponekad moram tražiti po broju vaučera za plaćanje.
Programer: u redu.
search_fields = (
    user__username,
    user__email,
    user__first_name,
    user__last_name,
    payment__voucher_number,
)
Još nešto?
Podrška: Neki kupci šalju svoje račune i postavljaju pitanja, tako da pretražujem i broj računa.
Programer: FINE!
search_fields = (
    user__username,
    user__email,
    user__first_name,
    user__last_name,
    payment__voucher_number,
    invoice__invoice_number,
)
OK, jesi li siguran da je to to?
Podrška: Pa, programeri nam ponekad prosljeđuju karte i koriste ove duge slučajne nizove. Nikad nisam siguran što oni jesu, pa samo tražim i nadam se najboljem.
Programer: Ti se nazivaju UUID-ovi.
search_fields = (
    user__username,
    user__email,
    user__first_name,
    user__last_name,
    payment__voucher_number,
    invoice__invoice_number,
    uid,
    user__uid,
    payment__uid,
    invoice__uid,
)
Pa je li to?
Podrška: Da, za sada ...

Problem s poljima za pretraživanje

Django Admin polja za pretragu su sjajna - bacite gomilu polja u search_fields, a Django će obraditi ostatak.

Problem s poljem pretraživanja počinje kada ih je previše.

Kad administrator korisnika želi pretraživati ​​putem UID-a ili e-pošte, Django nema pojma, to je korisnik namijenio, pa mora pretraživati ​​po svim poljima navedenim u search_fields. Ovi upiti "podudaraju se s bilo kojim" imaju ogromne klauzule WHERE i puno pridruživanja i mogu brzo postati vrlo spori.

Korištenje običnog ListFiltera nije opcija - ListFilter će prikazati popis izbora iz različitih vrijednosti polja. Neka polja koja smo naveli gore su jedinstvena, a druga imaju različite vrijednosti - Prikazivanje izbora nije opcija.

Premostiti jaz između Django i korisnika

Počeli smo razmišljati o načinima kako možemo stvoriti više polja za pretraživanje - po jedno za svako polje ili grupu polja. Smatrali smo da ako korisnik želi pretraživati ​​putem e-pošte ili UID-a, nema razloga za pretraživanje u bilo kojem drugom polju.

Nakon nekog razmišljanja došli smo do rješenja - prilagođenog SimpleListFilter:

  • ListFilter omogućava prilagođenu logiku filtriranja.
  • ListFilter može imati prilagođeni predložak.
  • Django već ima podršku za više ListFiltera.

Željeli smo da izgleda ovako:

Filter teksta tekst

Implementacija InputFiltera

Ono što želimo učiniti je imati ListFilter s unosom teksta umjesto izbora.

Prije nego što uđemo u implementaciju, krenimo od kraja. Ovako želimo koristiti naš InputFilter u ModelAdmin:

klasa UIDFilter (InputFilter):
    parameter_name = 'uid'
    title = _ ('UID')
 
    definiranje skupa upita (self, zahtjev, set upita):
        ako self.value () nije None:
            uid = self.value ()
            uzvrati queryset.filter (
                Q (uid = uid) |
                Q (plaćanje__uid = uid) |
                Q (= user__uid tekućem)
            )

I koristite ga poput bilo kojeg drugog filtra popisa u modelAdmin:

klasa TransactionAdmin (admin.ModelAdmin):
    ...
    list_filter = (
        UUIDFilter,
    )
    ...
  • Stvaramo prilagođeni filter za uuid polje - UIDFilter.
  • Postavljamo naziv parametra u URL-u kao nevaljan. URL filtriran od strane uid će izgledati ovako / admin / app / transakcija? Uid =
  • Ako je korisnik unijeo uid, tražimo po uidu transakcije, uidu plaćanja ili uid-u.

Za sada je to baš kao i uobičajeni prilagođeni ListFilter.

Sada kada imamo bolju predodžbu o tome što želimo da provedemo svoj InputFilter:

klasa InputFilter (admin.SimpleListFilter):
    template = 'admin / input_filter.html'
    def pretraživanja (samo, zahtjev, model_admin):
        # Lutka, potrebna za prikazivanje filtra.
        povratak ((),)

Nasljeđujemo od SimpleListFiltera i nadjačavamo predložak. Nemamo pretraživanja i želimo da predložak umjesto teksta izabere unos teksta:

// predlošci / admin / input_filter.html
{% load i18n%}

{% blocktrans with filter_title = title%} Do {{filter_title}} {% endblocktrans%}

      
  •                      

Mi koristimo slično označavanje Djangova postojećeg filtra popisa kako bismo ga učinili izvornim. Predložak čini jednostavan oblik s GET radnjom i tekstnim poljem za parametar. Kada se preda ovaj obrazac, URL će se ažurirati s nazivom parametra i poslanom vrijednošću.

Igrajte lijepo s drugim filtrima

Za sada naš filtar radi, ali samo ako nema drugih filtera. Ako se želimo igrati s drugim filtrima, moramo ih uzeti u obzir u obliku. Da bismo to postigli, moramo dobiti njihove vrijednosti.

Filtar popisa ima još jednu funkciju koja se zove „izbora“. Funkcija prihvaća objekt s popisom promjena koji sadrži sve informacije o trenutnom prikazu i vraća popis izbora.

Nemamo izbora, pa ćemo ovom funkcijom izvući sve filtre koji su primijenjeni na skup upita i izložiti ih predlošku:

klasa InputFilter (admin.SimpleListFilter):
    template = 'admin / input_filter.html'
    def pretraživanja (samo, zahtjev, model_admin):
        # Lutka, potrebna za prikazivanje filtra.
        povratak ((),)
    Def izbori (samo, lista promjena):
        # Uhvatite samo opciju "sve".
        all_choice = next (super (). izbora (popis promjena))
        all_choice ['query_parts'] = (
            (k, v)
            za k, v u changelist.get_filters_params (). items ()
            ako je k! = self.parameter_name
        )
        prinos all_choice

Da bismo uključili filtre, za svaki parametar dodamo skriveno polje unosa:

// predlošci / admin / input_filter.html
{% load i18n%}

{% blocktrans with filter_title = title%} Do {{filter_title}} {% endblocktrans%}

      
  •     {% s izborom.0 kao all_choice%}     
        {% za k, v u all_choice.query_parts%}
        
        {% endfor%}
        
    
    {% endwith%}
  

Sada imamo filter s unosom teksta koji se lijepo igra s ostalim filtrima. Jedino što vam preostaje je dodati „jasnu“ opciju.

Da bismo očistili filtar, potreban nam je URL koji uključuje sve filtre osim našeg:

// predlošci / admin / input_filter.html
...

    
{% ako ne all_choice.selected%}
    ⨉ {% trans 'Ukloni'%}  
{% završi ako %}
...

Evo ga!

To je ono što dobivamo:

InputFilter s drugim filtrima i gumbom za uklanjanje

Kompletan kod:

Bonus

Pretražite više riječi slične pretraživanju Django

Možda ste primijetili da prilikom pretraživanja više riječi Django pronalazi rezultate koji uključuju barem jednu riječ, a ne sve.

Na primjer, ako tražite korisnika "John Duo", Django će naći i "John Foo" i "Bar Due". To je vrlo zgodno kada tražite stvari poput punog imena, imena proizvoda i tako dalje.

Sličan uvjet možemo implementirati pomoću našeg InputFiltera:

iz django.db.models uvoz Q
klasa UserFilter (InputFilter):
    parameter_name = 'korisnik'
    title = _ ("Korisnik")
    definiranje skupa upita (self, zahtjev, set upita):
        izraz = samo-vrijednost ()
        ako je izraz Nema:
            povratak
        any_name = Q ()
        za bit u terminu.split ():
            any_name & = (
                Q (korisnik__first_name__icontains = bit) |
                Q (= user__last_name__icontains bita)
            )
        uzvrati queryset.filter (bilo koje ime)

To je to!

Pogledajte i ostale moje postove na Django Admin: