Django删除查询集中除了最后五个以外的所有项

38 投票
3 回答
26897 浏览
提问于 2025-04-15 16:42

我这里有一个超级简单的 Django 模型:

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

我使用 Ajax 每分钟检查一次新消息。我只会在任何时候向用户显示最近的五条通知。我想避免的情况是这样的。

用户登录时没有任何通知。在用户的窗口打开期间,他收到了 10 条新消息。因为我只显示五条,所以没什么大不了的。问题出现在用户开始删除通知的时候。如果他删除了显示的五条通知,下一次 Ajax 调用或刷新时,之前的五条旧通知就会显示出来。

我希望在保存新通知时,我的模型的保存方法能删除除了最近五条以外的所有通知。不幸的是,不能用 [5:] 这样的方式来实现。谁能帮帮我?

编辑

我尝试了这个,但效果并不如预期(在模型的保存方法中):

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

我找不到奇怪行为的规律,但经过一段时间的测试,只有在创建新通知时,它才会删除最近的一条。我完全不知道为什么会这样。排序是在模型的 Meta 类中处理的(按时间戳降序)。谢谢大家的帮助,但我觉得我的方法似乎是唯一一个能稳定工作的。

3 个回答

11

这是我最后做到这个的方式。

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

因为我是在保存方法里做这个的,所以循环只会在用户同时收到多个通知时才会运行超过一次。我并不担心这种情况会发生(虽然有可能发生,但不太可能会造成问题)。

12

使用内部查询来获取你想保留的项目集合,然后再对这些项目进行筛选。

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

在使用之前,最好再检查一下。我发现一些简单的内部查询有时并不会按预期工作。我遇到的奇怪情况主要出现在那些只有排序和切片的查询集上。因为你需要根据用户进行筛选,所以你应该没问题。

54

这个内容有点旧,但我觉得你可以这样做:

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

这样做需要两次操作,但可以避免在事务中使用for循环。

之所以使用 list(notes) 是因为如果不这样做,Django会只生成一个查询,而在Mysql 5.1中,这会导致错误

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

通过使用 list(notes),我们强制对 notes 进行查询,从而避免了这个问题。接下来可以进一步优化为:

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

撰写回答