rudyryk
rudyryk

Reputation: 3805

Peewee: filtering by many-to-many relationship

Here's a basic example - posts are owned and liked by users. How to select liked posts for specific user?

import datetime
import peewee

class User(peewee.Model):
    name = peewee.CharField(max_length=200)

class Post(peewee.Model):
    user = peewee.ForeignKeyField(User)
    text = peewee.TextField()

class Like(peewee.Model):
    user = peewee.ForeignKeyField(User)
    post = peewee.ForeignKeyField(Post)
    time = peewee.DateTimeField(default=datetime.datetime.now)

I make a query from perspective of Like model, starting query with Like.select() and join Post model. Is it ok? How could I query from perspective of Post model, i.e. Post.select()...?

Here's a complete sample for convinience:

def check_likes():
    user, _ = User.get_or_create(name="John")
    post, _ = Post.get_or_create(text="Hello world!", user=user)
    like, _ = Like.get_or_create(post=post, user=user)

    print("Created: ", user, post, like)

    # Get liked posts for user
    query_likes = (Like.select()
        .where(Like.user == user)
        .join(User)
        .join(Post))
    liked_posts = [like.post for like in query_likes]

    print("Liked posts: ", liked_posts)

if __name__ == '__main__':
    User.create_table(True)
    Post.create_table(True)
    Like.create_table(True)

    check_likes()

UPD. I ended up with such query at the moment:

def liked_by(user_id):
    """Active posts liked by user."""
    return (Post.select(Post, User, Like)
        .join(User, on=(Post.user == User.id))
        .switch(Post)
        .join(Like, peewee.JOIN.LEFT_OUTER,
              on=(Like.post == Post.id).alias('like'))
        .where(Like.user == user_id)
        .order_by(Like.time.desc()))

Upvotes: 2

Views: 4487

Answers (1)

coleifer
coleifer

Reputation: 26225

The query above should work, but you might modify it to read:

Like.select(Like, Post)...

You also need to take care about how you are joining on tables. Use the 'switch' method:

query_likes = (Like.select(Like, Post)  # <<< Add like and post
    .where(Like.user == user)
    .join(User)
    .switch(Like)  # <<< You need this.
    .join(Post))
liked_posts = [like.post for like in query_likes]

You can also do:

Post.select().join(Like).where(Like.user == some_user)

Upvotes: 6

Related Questions