[Fixed]-Django ORM Filtering Related Objects By Field Value?

1👍

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)

Very simple, just added an extra property and now you can use it like this

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

0👍

I’m guessing that at the moment, you’re doing something like:

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

and you’d like to get rid of that filter on every single call?

It is a pain, I agree, I’ve been trying to do something similar with an ‘archived’ variable in some of the models for my webapp.

I don’t believe there’s a way around it. You can create a custom manager like so:

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

and then access the objects directly via Channel.undeleted.all() instead of Channel.objects.all, but you still need to specify this new manager on related calls anyway, and it ends up almost as verbose (if a little more DRY):

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

This is also a relevant post: How to use custom manager with related objects?.


I think it’s complicated because everyone wants slightly different behaviour in the different situations. e.g. I want my archived ‘Events’ to still be able to run reports, but not show up for the end user to select, so I’m using a custom manager everywhere consumer-facing.

If all you want to do is count them later for reporting, perhaps an option is to add a channel_deleted_posts ManyToManyField and move the Post (without deleting it) from channel_posts to channel_deleted_posts. Hooks necessary, I’m afraid.

I really hope there’s a better answer and that what you want is trivial. I’d love to be proven wrong! 🙂

Leave a comment