Django obtiene un QuerySet de una matriz de identificadores en un orden específico

83

aquí hay uno rápido para ti:

Tengo una lista de identificaciones que quiero usar para devolver un QuerySet (o una matriz si es necesario), pero quiero mantener ese orden.

Gracias

neolaser
fuente

Respuestas:

73

No creo que pueda hacer cumplir ese orden en particular en el nivel de la base de datos, por lo que debe hacerlo en Python.

id_list = [1, 5, 7]
objects = Foo.objects.filter(id__in=id_list)

objects = dict([(obj.id, obj) for obj in objects])
sorted_objects = [objects[id] for id in id_list]

Esto crea un diccionario de los objetos con su ID como clave, por lo que se pueden recuperar fácilmente al crear la lista ordenada.

Reiner Gerecke
fuente
Esperaba que este no fuera el caso :( ¡gracias por el código limpio!
neolaser
3
Solo tenga cuidado con las consultas grandes, ya que almacenará el resultado en la memoria al hacer esto.
Jj.
13
¿Quizás usar el método querysets in_bulk (), en lugar de construir el diccionario usted mismo?
Matt Austin
El problema con esto es que si un objeto en id_list no existe, arrojará un error.
madprops
¿Por qué no utilizar la comprensión de dict?
wieczorek1990
180

Desde Django 1.8, puede hacer:

from django.db.models import Case, When

pk_list = [10, 2, 1]
preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(pk_list)])
queryset = MyModel.objects.filter(pk__in=pk_list).order_by(preserved)
Soitje
fuente
15
La mejor respuesta porque en realidad devuelve un conjunto de consultas
Teebes
1
¡Sigo pensando que los django Case Whenestán subestimados!
Babu
Voy a usar distinct()con order_by case when cláusula pero obtuve el error. cualquier apoyo, por favor.
elquimista
SELECT DISTINCT ON expressions must match initial ORDER BY expressions- aquí está el mensaje de error
elquimista
¡Recomiendo encarecidamente que esta es la mejor respuesta!
Tony
28

Si desea hacer esto usando in_bulk, en realidad debe combinar las dos respuestas anteriores:

id_list = [1, 5, 7]
objects = Foo.objects.in_bulk(id_list)
sorted_objects = [objects[id] for id in id_list]

De lo contrario, el resultado será un diccionario en lugar de una lista ordenada específicamente.

Rick Westera
fuente
1
Esta no es una mejor respuesta.
Tony
25

He aquí una forma de hacerlo a nivel de base de datos. Copiar y pegar de: blog.mathieu-leplatre.info :

MySQL :

SELECT *
FROM theme
ORDER BY FIELD(`id`, 10, 2, 1);

Lo mismo con Django:

pk_list = [10, 2, 1]
ordering = 'FIELD(`id`, %s)' % ','.join(str(id) for id in pk_list)
queryset = Theme.objects.filter(pk__in=[pk_list]).extra(
           select={'ordering': ordering}, order_by=('ordering',))

PostgreSQL :

SELECT *
FROM theme
ORDER BY
  CASE
    WHEN id=10 THEN 0
    WHEN id=2 THEN 1
    WHEN id=1 THEN 2
  END;

Lo mismo con Django:

pk_list = [10, 2, 1]
clauses = ' '.join(['WHEN id=%s THEN %s' % (pk, i) for i, pk in enumerate(pk_list)])
ordering = 'CASE %s END' % clauses
queryset = Theme.objects.filter(pk__in=pk_list).extra(
           select={'ordering': ordering}, order_by=('ordering',))
usuario
fuente
Guau. Eso es intenso. ¡Gracias!
Dan Gayle
Gracias por esta respuesta.
Subangkar KrS
9
id_list = [1, 5, 7]
objects = Foo.objects.filter(id__in=id_list)
sorted(objects, key=lambda i: id_list.index(i.pk))
Andrew G
fuente
1
Agregue un texto que explique cómo funciona esto y por qué resolvería el problema del OP. Ayude a otros a comprender.
APC
La mejor respuesta. ¡Gracias!
webjunkie
Funciona bien, aunque podría usar información adicional
xtrinch
esto funcionará solo para la lista de identificación ordenada. ¿Puedes confirmar si funcionará en alguna secuencia ... no me parece cierto?
Doogle