2009-12-05 11 views
21

que tienen un modelo de Django super sencillo aquí:Django Eliminar todo pero el pasado cinco de queryset

class Notification(models.Model): 
    message = models.TextField() 
    user = models.ForeignKey(User) 
    timestamp = models.DateTimeField(default=datetime.datetime.now) 

uso de AJAX, puedo comprobar si hay nuevos mensajes cada minuto. Solo muestro las cinco notificaciones más recientes al usuario en cualquier momento. Lo que estoy tratando de evitar es la siguiente situación.

usuario se conecta y no tiene notificaciones. Mientras la ventana del usuario está activa, recibe 10 mensajes nuevos. Como solo le muestro cinco, no es gran cosa. El problema ocurre cuando el usuario comienza a eliminar sus notificaciones. Si elimina los cinco que se muestran, los cinco más antiguos se mostrarán en la próxima llamada ajax o actualizar.

me gustaría tener de mi modelo, salvo método de eliminar todo menos los 5 objetos más recientes cada vez que un nuevo uno se salva. Desafortunadamente, no puedes usar [5:] para hacer esto. ¿Ayuda?

EDITAR

He intentado esto, que no funcionaba como se esperaba (en el método save del modelo):

notes = Notification.objects.filter(user=self.user)[:4] 
    Notification.objects.exclude(pk__in=notes).delete() 

no pude encontrar un patrón en el comportamiento extraño, pero después de una durante las pruebas, solo eliminará la más reciente cuando se cree una nueva. No tengo idea de por qué sería esto. el orden se resuelve en la clase Meta del modelo (por fecha y hora descendiendo). gracias por la ayuda, pero mi camino parece ser el único que funciona de manera consistente.

+0

útil subrayar que el hecho de que no se puede utilizar borrar en una rebanada se explica en el documento: https: //docs.djangoproject.com/en/dev/ref/models/querysets/# delete - genera un 'Can not use' limit 'o' offset 'con el error delete'. ¡Tenía la esperanza de que desde el '09 algo hubiera cambiado, pero la solución "para" todavía parece ser la única! – Stefano

Respuesta

20

Esto es un poco viejo, pero creo que se puede hacer lo siguiente:

notes = Notification.objects.filter(user=self.user)[:4] 
Notification.objects.exclude(pk__in=list(notes)).delete() # list() forces a database hit. 

Cuesta dos hits, pero evita usar el bucle for con las transacciones de middleware.

La razón es que Django crea una sola consulta y, en MySQL 5.1, esto plantea el error

(1235, "This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'") 

Mediante el uso de list(notes), forzamos una consulta de notes, evitando esto. Esto se puede optimizar aún más a:

notes = Notification.objects.filter(user=self.user)[:4].values_list("id", flat=True) # only retrieve ids. 
Notification.objects.exclude(pk__in=list(notes)).delete() 
+0

Ya no tengo un entorno django, pero creo que esta es una solución mejor que mi ciclo for. –

+1

En un conjunto de datos más grande probablemente sea la mejor solución, pero el bucle for es más fácil de leer, en mi opinión. – radtek

11

Utilice una consulta interna para obtener el conjunto de elementos que desea conservar y luego filtrarlos.

objects_to_keep = Notification.objects.filter(user=user).order_by('-created_at')[:5] 
Notification.objects.exclude(pk__in=objects_to_keep).delete() 

cheque doble esto antes de que lo utilice. Descubrí que las consultas internas más simples no siempre se comportan como se esperaba. El comportamiento extraño que he experimentado se ha limitado a los conjuntos de consultas que son solo un order_by y un slice. Como deberás filtrar al usuario, deberías estar bien.

+0

probé un método similar a este y fue peculiar. cuando llegó a cierto punto, borró todos los objetos con el usuario. Creo que tiene algo que ver con cómo funciona el corte con conjuntos de consulta perezosos. –

+0

Agregue el código que probó. Agradecería más puntos de datos por el comportamiento peculiar. Para mí, el order_by parecía ser eliminado de la consulta interna si no había filter(). ¿Experimentó un comportamiento extraño con una consulta interna Model.objects.filter (...). Orber_by (...) [: 5]? Ah, y puede encontrar .query.as_sql() útil. – istruble

+0

he editado mi pregunta con el código que probé. No tenía el código que eliminaba todos los objetos, pero probé mi código anterior (incluso más parecido al tuyo), y eso no funcionó como yo esperaba. –

8

así es como terminé haciendo esto.

notes = Notification.objects.filter(user=self.user) 
    for note in notes[4:]: 
     note.delete() 

porque estoy haciendo esto en el método save, la única manera el bucle tendría nunca para ejecutar más de una vez sería si el usuario tiene varias notificaciones a la vez. No me preocupa que eso ocurra (aunque puede que no sea suficiente para causar un problema).

+1

Esto es perfectamente válido. Y ya conoce el hecho de que obtendrá una operación sql para cada una de esas eliminaciones dentro del ciclo for. Gracias por la actualización. – istruble

+9

Al dividir las líneas anteriores en un método y agregar un decorador commit_on_success a su alrededor, se asegura de que se mantenga rápido aunque haya muchas consultas que realizar. –

Cuestiones relacionadas