具有特定子句的 Django Filter ManyToManyField

Django Filter ManyToManyField with particular clause

我使用的是 Django 1.8。我有几个播放列表,每个播放列表包含几个视频。其中一些视频的状态为已删除或错误。

我只想要包含状态为 "online" 的视频的播放列表。

class Video(models.Model):  

    # Title of the video
    title = models.CharField(max_length=100)


    # Status of video (error, online, deleted)
    status = models.CharField(max_length=20, default="online")


class UserPlaylist(models.Model):  

    # Name of the playlist
    name = models.CharField(max_length=500, blank=True, null=True)

    # Playlist owner
    owner = models.ForeignKey(User)

    # videos
    videos = models.ManyToManyField(Video, null=False)

我试过但没有成功:

UserPlaylist.objects.filter(videos__status="online")
UserPlaylist.objects.filter(videos__status="online").distinct()

有效但很痛苦:

UserPlaylist.objects.exclude(videos__status='error').exclude(videos__status='deleted').distinct()

我们可以用 status='online' 来计算 Video 的数量,并检查它是否与总视频数相同,例如:

from django.db.models import Case, F, Q, When

UserPlaylist.objects.annotate(
    nvideo=Count('videos'),
    nonline=Count(Case(
        When(videos__status='online', then='videos'),
        default=None
    ))
).filter(
    nvideo=F('nonline')
)

这将生成如下所示的查询:

SELECT userplaylist.id, userplaylist.name,
       COUNT(userplaylist_videos.video_id) AS nvideo,
       COUNT(CASE WHEN video.status = online
             THEN userplaylist_videos.video_id
             ELSE NULL END) AS nonline
FROM userplaylist
LEFT OUTER JOIN userplaylist_videos
             ON userplaylist.id = userplaylist_videos.userplaylist_id
LEFT OUTER JOIN video ON userplaylist_videos.video_id = video.id
GROUP BY userplaylist.id
HAVING COUNT(userplaylist_videos.video_id) = COUNT(
    CASE WHEN video.status = online
    THEN userplaylist_videos.video_id
    ELSE NULL END)

请注意,包含 没有 个视频的 UserPlaylist 也将成为结果的一部分,因为它的所有视频都在线。