Django ORM 按字段值过滤相关对象?

Django ORM Filtering Related Objects By Field Value?

我正在从事一个涉及 channel/post 机制的项目。我目前正在通过将名为 "deleted" 的字段设置为删除时间的时间戳来实现 "delete" posts 的功能。它需要保留历史意义(最后 activity)并跟踪煽动性的人(该服务是整个项目的基于特权的部分)。

models.py

class Post(models.Model):
    deleted = models.DateTimeField(null=True, blank=True)
    created_date = models.DateTimeField(auto_now_add=True)
    updated_date = models.DateTimeField(auto_now_add=True)
    post_text = models.CharField(max_length=1000)
    post_to = models.ForeignKey('Channel', related_name='post_to_channel')
    post_from = models.ForeignKey(User, related_name='user_from_post')

class Channel(models.Model):
    created_date = models.DateTimeField(auto_now_add=True)
    channel_title = models.CharField(max_length=50, unique=True)
    channel_description = models.CharField(max_length=100)
    channel_creator = models.ForeignKey(User)
    channel_private = models.BooleanField(default=False)
    channel_users = models.ManyToManyField(User, through='Membership', related_name='channels', blank=True)
    channel_posts = models.ManyToManyField(Post, related_name='channel_post', blank=True)
    initial_invites = models.ForeignKey(User, null=True, blank=True, related_name='initial_invites')
    objects = models.Manager()

class Membership(models.Model):
    channel = models.ForeignKey(Channel)
    channel_user = models.ForeignKey(User, related_name='channel_membership')
    join_date = models.DateTimeField(auto_now_add=True)

我有一个端点可以将 "Post" 对象的 "deleted" 字段更新为空的时间戳。然后,如何避免在 "Channel" 级别查询时返回所有具有时间戳的帖子,并通过它们的关系和单独的序列化程序加载帖子?基本上,我需要能够查询 "Channel" 并获取我所属的频道列表,然后通过它们与 "Post" 的 ManyToMany 关系加载所有相关帖子,但不包括那些在"deleted" 字段。这是否可能无需为每个通道调用 ORM,随后过滤掉已删除的 posts?需要明确的是,如果删除了 post,"Channel" 仍应通过,只有 "Post" 本身应该被隐藏。

如果我没有提供足够的信息,请告诉我您需要看什么。

如果答案是如果没有重复的 ORM 调用这是不可能的,建议其他选项将不胜感激,包括权衡 "archive" post 到重复模型的选项,在这些情况下没有调用钩子并删除原始钩子。

编辑 1: 按照所选答案中的说明添加 属性,但它看不到 self.channel_posts。 models.py

class Channel(models.Model):
    created_date = models.DateTimeField(auto_now_add=True)
    channel_title = models.CharField(max_length=50, unique=True)
    channel_description = models.CharField(max_length=100)
    channel_creator = models.ForeignKey(User)
    channel_private = models.BooleanField(default=False)
    channel_users = models.ManyToManyField(User, through='Membership', related_name='channels', blank=True)
    channel_posts = models.ManyToManyField(Post, related_name='channel_post', blank=True)
    initial_invites = models.ForeignKey(User, null=True, blank=True, related_name='initial_invites')
    objects = models.Manager()
    @property
    def get_viewable_posts(self):
        print (self.channel_users) # prints []
        print (self.channel_posts) # prints []
        return CourtyardPost.objects.filter(deleted__isnull=True, courtyard_post_to=self.pk) #works, but adds IO strain

目前,如上所示,它是每个通道的 ORM 调用,但在所有情况下都打印 self.channel_postsself.channel_users returns 一个空列表。 属性 作为 Django Rest Framework 的序列化程序的一部分被调用,如下所示:

class CourtyardChannelSerializer(serializers.ModelSerializer):
    current_user = serializers.SerializerMethodField('_user')
    post_to_channel = CourtyardPostSerializer(many=True, source='get_viewable_posts', read_only=True)
    most_recent = CourtyardPostSerializer(many=True, source='latest_post', read_only=True)
    channel_users = CourtyardUserSerializer(many=True, read_only=True)
)
invite_list = serializers.ReadOnlyField(source='get_invitation_set', required=False)

    def _user(self, obj):
        user = self.context['request'].user.username
        return user

我猜你现在正在做类似的事情:

def channels_and_posts_for_user(user):
    for channel in user.channel_membership:
        posts = channel.channel_posts.all().filter(deleted__isnull=True)
        channels_and_posts.append(channel, posts)
    return channels_and_posts

您想在每次调用时都去掉该过滤器吗?

这很痛苦,我同意,我一直在尝试对我的网络应用程序的某些模型中的 'archived' 变量做一些类似的事情。

我认为没有解决办法。您可以像这样创建自定义管理器:

class ChannelManagerWithUndeleted(models.Manager):
    def get_queryset(self):
        return super(ChannelManagerWithUndeleted, self).get_queryset().filter(deleted__isnull=True)

class Channel(models.Model):
    #...
    objects = models.Manager()    # Default Manager
    undeleted = EntryManager()    # Custom Manager

然后通过 Channel.undeleted.all() 而不是 Channel.objects.all 直接访问对象,但是你仍然需要在 related calls anyway 上指定这个新的管理器,它最终几乎是冗长的(如果再干一点):

channels_and_posts = []
for channel in user.channel_membership:
    posts = channel.channel_posts.all(manager='undeleted').all()
    channels_and_posts.append(channel, posts)

这也是一个相关的post:How to use custom manager with related objects?


我认为这很复杂,因为每个人都希望在不同情况下表现出略微不同的行为。例如我希望我的存档 'Events' 仍然能够 运行 报告,但不会向最终用户显示 select,所以我到处都使用自定义管理器 consumer-facing .

如果您只想稍后计算它们以进行报告,也许一个选择是添加一个 channel_deleted_posts ManyToManyField 并从 channel_posts 移动 Post(不删除它)至 channel_deleted_posts。恐怕需要挂钩。

我真的希望有一个更好的答案并且你想要的是微不足道的。我很想被证明是错误的! :)

class Channel(models.Model):
    created_date = models.DateTimeField(auto_now_add=True)
    channel_title = models.CharField(max_length=50, unique=True)
    channel_description = models.CharField(max_length=100)
    channel_creator = models.ForeignKey(User)
    channel_private = models.BooleanField(default=False)
    channel_users = models.ManyToManyField(User, through='Membership', related_name='channels', blank=True)
    channel_posts = models.ManyToManyField(Post, related_name='channel_post', blank=True)
    initial_invites = models.ForeignKey(User, null=True, blank=True, related_name='initial_invites')
    objects = models.Manager()

    @property
    def active_posts(self):
        return self.channel_posts.filter(deleted=None)

很简单,就是多加了一个属性,现在就可以这样用了

channel = Channel.objects.first()
print(channel.active_posts.count())