Аннотировать подзапрос «exists» в SQLAlchemy

У меня есть простое новостное приложение, в котором пользователи могут видеть последние сообщения, и некоторые из них могут им нравиться.

Модели:

from sqlalchemy.orm import relationship, backref

from app import db


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String, nullable=False)
    ...

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String)
    body = db.Column(db.String)
    ...

class Like(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.ForeignKey(User.id))
    post_id = db.Column(db.ForeignKey(Post.id))
    post = relationship(Post, backref=backref('likes'))

Чего я пытаюсь добиться, так это получить каждое сообщение с дополнительным полем, которое указывает, нравится ли сообщение вошедшему в систему пользователю или нет, но оптимизированным способом, не делая запрос для каждого сообщения.

Я пытался сделать это на уровне схемы, используя marshmallow-sqlalchemy:

from marshmallow import fields

from app import ma
from news.models import Post

class PostSchema(ma.ModelSchema):
    is_liked = fields.Method("get_is_liked")

    def get_is_liked(self, obj):
        user = self.context['user']
        return bool(Like.filter_by(user_id=user.id, post_id=obj.id).first())

    class Meta:
        model = Post

Но это приведет к N запросам для каждого сообщения.

Я также пытался реализовать это как аннотации в Django ORM:

class PostListView(Resource):

    # I have the user object here using a decorator
    def get(self, user):
        # I know that this is a bad syntax
        query = Post.query(Post.likes.filter_by(user_id=user.id).exists().label('is_liked')))

        schema = PostSchema(strict=True, many=True, context={"user": user})
        result = schema.dump(query.all())

        return result.data

Я также видел этот ответ, который делает то же самое, что мне нужно на уровне базы данных, но я хочу сделать это с помощью SQLAlchemy.

Заранее спасибо.


person Peter Sobhi    schedule 13.02.2019    source источник


Ответы (1)


Будет ли это работать? Идея состоит в том, чтобы загружать сообщения так, чтобы Post.likes содержал только те случаи, когда текущий пользователь — это тот, кому сообщение понравилось. contains_eager('likes') должен загрузить массив likes в Post.

# user_id is the id of the current user
q = session.query(Post) \
           .outerjoin(Like, and_(Like.post_id == Post.id,
                                 Like.user_id == user_id)) \
           .options(contains_eager('likes'))

Тогда is_liked может быть чем-то вроде return len(self.likes) > 0

person J.J. Hakala    schedule 14.02.2019