2012-07-10 10 views
8

Ho un modello di django, chiamato "Utente" che memorizza alcune informazioni di base sulle persone, vale a dire il nome e il cognome. Al momento ho una semplice ricerca sul mio modello di django in cui, se un utente digita il nome primo, The django queryset restituisce le prime 10 corrispondenze, ordinate per ultimo nome.Restituisci corrispondenze esatte in cima a Django Queryset

Per esempio, attualmente, se si cerca "Sam" si potrebbe ottenere i seguenti risultati:

  1. Sam Abbott
  2. Samuel Baker
  3. Sammy Rogers
  4. Sam Simmons

Il codice per questo è semplice:

User.objects.filter(Q(first__istartswith=token)).order_by('last') 

Tuttavia, desidero modificare questo in modo che vengano restituite tutte le corrispondenze esatte per il nome , seguite dal resto dei risultati. Quindi, se qualcuno digita "sam", i risultati dovrebbero essere invece:

  1. Sam Abbott
  2. Sam Simmons
  3. Samuel Baker
  4. Sammy Rogers

(Exact primo nome corrisponde primo , ordinati per cognome, seguiti dal resto delle partite ordinati per cognome).

Ho pensato di trasformare questo in 2 querysets e quindi semplicemente combinando le liste, ma mi chiedevo se fosse possibile farlo in 1 query, idealmente attenendosi alla base dell'API django queryset (piuttosto che scrivere una tantum query). Qualcuno sa un modo per farlo?

Grazie in anticipo.

+0

anche Sei usando 'django.contrib.auth.models.User'? in tal caso, 'first_name' e' last_name' sono i nomi di campo predefiniti. –

+0

No questo è un modello personalizzato diverso. – Chad

+0

solo doppio controllo =) –

risposta

3

Non penso che sia davvero possibile farlo con una sola query (almeno con Django ORM).

Così i vostri 2 query dovrebbe essere simile a questo:

limit = 10 
q1 = User.objects.filter(first__iexact=token).order_by('last')[:limit] 
limit -= len(q1) 
if limit: 
    q2 = User.objects.exclude(pk__in=q1).filter(first__istartswith=token).order_by('last')[:limit] 
else: 
    q2 = [] 
users = list(q1) + list(q2) 

Un altro modo per farlo è quello di filtrare la query in pitone ma si dovrà ottenere tutti i risultati, non solo l'ultimo 10:

query = User.objects.filter(first__istartswith=token).order_by('last') 
exacts = [user for user in query if user.first == token] 
others = [user for user in query if user.first != token] 
users = exacts + others 
+0

Sì, penso che questo sia il modo migliore per farlo. Probabilmente finirò per farlo in questo modo, eccetto l'uso di itertools.chain come suggerito da Francis qui sotto, dal momento che dovrebbe essere più veloce della costruzione di 2 elenchi e della loro combinazione. Grazie! – Chad

+1

Sono contento di aver appreso il trucco della catena degli itertools. Ma nel tuo caso, se il tuo limite è davvero di 10, questo non dovrebbe cambiare nulla. Le query richiederanno più tempo e la creazione della lista e il tempo di concatenazione saranno insignificanti. – Etienne

0

È possibile ordinare per più attributi.

User.objects.filter(Q(first__istartswith=token)).order_by('first', 'last') 

Così si ordinare la prima volta da first così i vostri oggetti arrivare filtri in base alla corrispondenza esatta e successive partite. E poi si ordina ancora su last per ordinare in base al cognome.

+0

Ma non sarebbe quello ordinare l'intero elenco per nome prima? Quindi finirò con Sammy Rogers e Samuel Baker acceso, giusto? – Chad

+0

@Chad Sì, lo sarà, ma non dovrebbe essere così. IMHO Sento che è un comportamento previsto. – Rohan

+0

No, in questo caso non è quello che stiamo cercando, sfortunatamente. Devono essere 2 gruppi, corrispondenze esatte del nome e poi tutto il resto, e ogni gruppo deve essere ordinato per cognome. – Chad

1

Penso che in realtà potrebbe essere necessaria la ricerca di testo completo per ottenere ciò. Controlla djang-sphinx.

Se il tuo esempio è complicato come il tuo caso di utilizzo completo, puoi semplicemente gestire l'ordinamento e l'ordinamento nel tuo codice utente.

+0

Grazie per il suggerimento. In questo caso non penso che la ricerca full-text sia necessaria poiché ho indici sui nomi, quindi posso semplicemente fare una startwith nella chiamata django ORM. – Chad

3
# Get exact matches first 
qs1 = User.objects.filter(first__iexact=token).order_by('last') 

# get secondary results second 
qs2 = User.objects.filter(first__istartswith=token).exclude(qs1).order_by('last') 

result = itertools.chain(qs1, qs2) 

anche un'occhiata a questa domanda, che va inot più profondità che probabilmente è necessario: How to combine 2 or more querysets in a Django view?

+0

Buona soluzione, anche se ho finito per accettare l'altro perché considerava il limite di 10 elementi in. (Inoltre penso che ci sia un piccolo refuso in exclude(), che penso dovrebbe essere escluso (pk__in = qs1)). – Chad

3

Non è abbastanza e personalmente mi consiglia di utilizzare un motore di ricerca come django-haystack, ma, se si sa cosa database in uso è possibile utilizzare QuerySet.extra per aggiungere un campo per ordinare i record:

extra = {'is_exact': "%s.first LIKE '%s'" % (User._meta.db_table, token)} 
User.objects.filter(Q(first__istartswith=token)).extra(select=extra).order_by('-is_exact', 'last') 
Problemi correlati