From fed92ce6b73fd09cf937ad82f7d7652150d91bc4 Mon Sep 17 00:00:00 2001 From: armantovmasyan Date: Wed, 13 Mar 2024 15:55:41 +0300 Subject: [PATCH] feat: get requests and orm relationships --- src/actions/actions.py | 41 +++++++++++++++++++++++++++++++++++++++++ src/auth/db.py | 2 +- src/auth/manager.py | 2 +- src/auth/models.py | 5 +++++ src/main.py | 4 ++-- src/polls/models.py | 16 +++++++++++++++- src/polls/router.py | 10 ++++++++-- src/polls/schemas.py | 3 ++- 8 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/actions/actions.py b/src/actions/actions.py index 16f9fc8..9da4576 100644 --- a/src/actions/actions.py +++ b/src/actions/actions.py @@ -6,6 +6,7 @@ from fastapi.encoders import jsonable_encoder from pydantic import BaseModel +from sqlalchemy.orm import selectinload from src.database import Base, get_async_session from src.auth.utils import map_to_datetime @@ -47,6 +48,46 @@ async def get(self, id: int, db: AsyncSession = Depends(get_async_session)) -> O else: raise HTTPException(status_code=404, detail=f"Object with ID {id} not found.") + async def get_poll_with_questions(self, id: int, db: AsyncSession = Depends(get_async_session)) -> Optional[ModelType]: + async with db as session: + query = ( + select(self.model) + .filter(self.model.id == id) + .options( + selectinload(models.Poll.questions) + .selectinload(models.Question.choices) + ) + ) + result = await session.execute(query) + obj = result.scalars().first() + if obj: + obj_data = jsonable_encoder(obj) + + for question in obj_data["questions"]: + question.pop("poll_id", None) + return obj_data + else: + raise HTTPException(status_code=404, detail=f"Object with ID {id} not found.") + + async def get_question_with_choices(self, id: int, db: AsyncSession = Depends(get_async_session)) -> Optional[ModelType]: + async with db as session: + query = ( + select(self.model) + .filter(self.model.id == id) + .options( + selectinload(models.Question.choices) + ) + ) + result = await session.execute(query) + obj = result.scalars().first() + if obj: + obj_data = jsonable_encoder(obj) + question_data = obj_data + question_data.pop("poll_id", None) + return question_data + else: + raise HTTPException(status_code=404, detail=f"Object with ID {id} not found.") + async def create(self, *, obj_in: CreateSchemaType, db: AsyncSession = Depends(get_async_session)) -> ModelType: obj_in_data = map_to_datetime(jsonable_encoder(obj_in)) db_obj = self.model(**obj_in_data) diff --git a/src/auth/db.py b/src/auth/db.py index d1e18c0..8e2c2cc 100644 --- a/src/auth/db.py +++ b/src/auth/db.py @@ -7,7 +7,7 @@ class CustomUserDatabase(SQLAlchemyUserDatabase): """ - Класс для работы с базой данных пользователей, расширенный методом поиска по username. + Extend the SQLAlchemyUserDatabase to add custom methods. """ def __init__(self, session, user_table) -> None: diff --git a/src/auth/manager.py b/src/auth/manager.py index 0676d98..26d33dc 100644 --- a/src/auth/manager.py +++ b/src/auth/manager.py @@ -20,7 +20,7 @@ def __init__(self, user_db: BaseUserDatabase, *args, **kwargs): super().__init__(user_db, *args, **kwargs) async def on_after_register(self, user: User, request: Optional[Request] = None): - # TODO: add email verification + # TODO: add email confirmation print(f"User {user.username} has registered.") async def authenticate( diff --git a/src/auth/models.py b/src/auth/models.py index cc003fb..4a0c421 100644 --- a/src/auth/models.py +++ b/src/auth/models.py @@ -1,6 +1,8 @@ from fastapi_users_db_sqlalchemy import SQLAlchemyBaseUserTable from sqlalchemy import (Boolean, Column, Integer, String) +from sqlalchemy.orm import relationship + from src.database import Base @@ -14,3 +16,6 @@ class User(SQLAlchemyBaseUserTable[int], Base): is_active: bool = Column(Boolean, default=True, nullable=False) is_superuser: bool = Column(Boolean, default=False, nullable=False) is_verified: bool = Column(Boolean, default=False, nullable=False) + + polls = relationship("Poll", back_populates="users") + votes = relationship("Vote", back_populates="user") \ No newline at end of file diff --git a/src/main.py b/src/main.py index 5916f1d..e1d59d9 100644 --- a/src/main.py +++ b/src/main.py @@ -26,5 +26,5 @@ app.include_router(router_votes) -if __name__ == '__main__': - uvicorn.run(app) +# if __name__ == '__main__': +# uvicorn.run(app) diff --git a/src/polls/models.py b/src/polls/models.py index d9b66dc..df7a5ac 100644 --- a/src/polls/models.py +++ b/src/polls/models.py @@ -1,9 +1,11 @@ from sqlalchemy import Column, Integer, String, TIMESTAMP, ForeignKey +from sqlalchemy.orm import relationship + from src.database import Base import datetime -# TODO: Response models + class Poll(Base): __tablename__ = "poll" @@ -14,6 +16,9 @@ class Poll(Base): start_date = Column("start_date", TIMESTAMP, default=datetime.datetime.now()) end_date = Column("end_date", TIMESTAMP, nullable=False) + questions = relationship("Question", back_populates="poll") + users = relationship("User", back_populates="polls") + class Question(Base): __tablename__ = "question" @@ -22,6 +27,9 @@ class Question(Base): poll_id = Column("poll_id", Integer, ForeignKey("poll.id")) question_text = Column("question_text", String, nullable=False) + poll = relationship("Poll", back_populates="questions") + choices = relationship("Choice", back_populates="questions") + class Choice(Base): __tablename__ = "choice" @@ -30,6 +38,9 @@ class Choice(Base): question_id = Column("question_id", Integer, ForeignKey("question.id")) choice_text = Column("choice_text", String, nullable=False) + questions = relationship("Question", back_populates="choices") + votes = relationship("Vote", back_populates="choice") + class Vote(Base): __tablename__ = "vote" @@ -38,3 +49,6 @@ class Vote(Base): choice_id = Column("choice_id", Integer, ForeignKey("choice.id")) user_id = Column("user_id", Integer, ForeignKey("user.id")) vote_timestamp = Column("vote_timestamp", TIMESTAMP) + + choice = relationship("Choice", back_populates="votes") + user = relationship("User", back_populates="votes") diff --git a/src/polls/router.py b/src/polls/router.py index 6dcb2c5..f353b61 100644 --- a/src/polls/router.py +++ b/src/polls/router.py @@ -60,9 +60,9 @@ async def create_poll( return await poll_action.create(db=session, obj_in=new_poll_instance) -@router_polls.get("/{poll_id}", response_model=schema.ReadPoll) +@router_polls.get("/{poll_id}") async def get_poll(poll_id: int, session: AsyncSession = Depends(get_async_session)): - pass + return await poll_action.get_poll_with_questions(db=session, id=poll_id) @router_questions.post("/{poll_id}") @@ -79,6 +79,11 @@ async def create_question( return await question_action.create(db=session, obj_in=new_question_instance) +@router_questions.get("/{question_id}") +async def get_question(question_id: int, session: AsyncSession = Depends(get_async_session)): + return await question_action.get_question_with_choices(db=session, id=question_id) + + @router_choices.post("/{question_id}") async def create_choice( question_id: int, @@ -93,6 +98,7 @@ async def create_choice( return await choice_action.create(db=session, obj_in=new_choice_instance) +# TODO: handle multiple votes from the same user on the same choice @router_votes.post("/{choice_id}") async def create_vote( choice_id: int, diff --git a/src/polls/schemas.py b/src/polls/schemas.py index 6f1a2b3..6f2e09f 100644 --- a/src/polls/schemas.py +++ b/src/polls/schemas.py @@ -2,6 +2,8 @@ from pydantic import BaseModel, constr, FutureDatetime +# TODO: Response models + class CreatePoll(BaseModel): id: int @@ -38,4 +40,3 @@ class ReadPoll(BaseModel): title: str description: str questions: list[ReadQuestion] -