Search code examples
pythondatabasesqlalchemy

Python sqlalchemy get all ratings from one user


I have a question. For example, I have two tables - users and suggestions.

Each user can give some rating to this suggestion, for example, there is a suggestion "Improve the interface".

User #1 gives this suggestion a rating of 10, and user #2 gives it a rating of 3.

The next suggestion, "Fix bugs", was rated only by user #1, who gave it a rating of 5.

What I want to do is to somehow get every rating that a user has given to all the suggestions. Is it possible to do this? How could I do it?

Here is the basic code, and I don't know what my next steps should be.

class Suggestion(Base):
    __tablename__ = "suggestions"
    s_id = Column("s_id", Integer, primary_key=True, autoincrement=True)
    content = Column("content", String, nullable=False)
    rating = Column("rating", Integer, nullable=False, default=0)

    def __repr__(self):
        return f"Suggestion #{self.id}: {self.content}"


class User(Base):
    __tablename__ = "users"
    user_id = Column("user_id", Integer, primary_key=True)

    def __init__(self, user_id):
        self.user_id = user_id

    def __repr__(self):
        return f"User #{self.user_id}"

Solution

  • This is a many-to-many relationship (a user can rate many suggestions, and a suggestion can be rated by many users), so you can model this with an association table between user and suggestion.

    Please check the document on: Association Object for more information.

    This is a sample implementation. I'm using the dataclass integration to automatically generate the __init__ and __repr__ method:

    from __future__ import annotations
    
    from sqlalchemy import ForeignKey, String, create_engine, select
    from sqlalchemy.orm import Mapped, MappedAsDataclass, sessionmaker
    from sqlalchemy.orm import mapped_column
    from sqlalchemy.orm import DeclarativeBase
    from sqlalchemy.orm import relationship
    
    
    class Base(MappedAsDataclass, DeclarativeBase):
        pass
    
    
    class User(Base):
        __tablename__ = "user"
        id: Mapped[int] = mapped_column(primary_key=True)
        ratings: Mapped[list[Rating]] = relationship(init=False, repr=False)
    
    
    class Rating(Base):
        __tablename__ = "rating"
        user_id: Mapped[int] = mapped_column(
            ForeignKey("user.id"),
            primary_key=True,
        )
        suggestion_id: Mapped[int] = mapped_column(
            ForeignKey("suggestion.id"),
            primary_key=True,
        )
        score: Mapped[int] = mapped_column(default=0)
        suggestion: Mapped[Suggestion] = relationship(init=False, repr=False)
    
    
    class Suggestion(Base):
        __tablename__ = "suggestion"
        id: Mapped[int] = mapped_column(primary_key=True, init=False)
        content: Mapped[str] = mapped_column(String(255), nullable=False)
    
    
    DATABASE_URL = "postgresql+psycopg2://test_username:test_password@localhost:5432/test"
    engine = create_engine(
        DATABASE_URL,
        # echo=True,
    )
    Session = sessionmaker(
        bind=engine,
    )
    
    if __name__ == "__main__":
        Base.metadata.create_all(engine)
    
        # create initial users and suggestions
        with Session.begin() as session:
            session.add_all([
                User(id=1),
                User(id=2),
                Suggestion(content="Improve the interface."),
                Suggestion(content="Fix bugs."),
            ])
    
        # users rating suggestions:
        with Session() as session:
            suggestion1_id = session.scalars(
                select(Suggestion.id).filter_by(content="Improve the interface.")
            ).one()
            suggestion2_id = session.scalars(
                select(Suggestion.id).filter_by(content="Fix bugs.")
            ).one()
    
            session.add_all([
                Rating(user_id=1, suggestion_id=suggestion1_id, score=10),
                Rating(user_id=2, suggestion_id=suggestion1_id, score=3),
                Rating(user_id=1, suggestion_id=suggestion2_id, score=5),
            ])
            session.commit()
    
        # getting ratings for a user:
        with Session() as session:
            user1 = session.get(User, 1)
            print(user1)
            for rating in user1.ratings:
                print(
                    f"  rated `{rating.suggestion.content}`"
                    f" with a score of {rating.score}"
                )
    
    

    This is the output:

    User(id=1)
      rated `Improve the interface.` with a score of 10
      rated `Fix bugs.` with a score of 5
    

    This can be done in numerous ways, so read the docs and pick one you like.