From 9f4eb623f4e450b954cb79e56d8a6ca0dafdbf39 Mon Sep 17 00:00:00 2001 From: Tishanov Artem Date: Thu, 13 Nov 2025 00:00:43 +0300 Subject: [PATCH 1/7] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=84=D0=BB=D0=B0=D0=B3=D0=BE=D0=B2=20is?= =?UTF-8?q?=5Fliked=20=D0=B8=20is=5Fdisliked?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- rating_api/models/db.py | 15 +++++++++- rating_api/routes/comment.py | 8 ++++-- rating_api/schemas/models.py | 5 +++- tests/test_routes/test_comment.py | 47 +++++++++++++++++++++++++++---- 5 files changed, 65 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index aaaea38..8296261 100644 --- a/README.md +++ b/README.md @@ -25,4 +25,4 @@ ``` ## ENV-file description -- `DB_DSN=postgresql://postgres@localhost:5432/postgres` – Данные для подключения к БД +- – Данные для подключения к БД`DB_DSN=postgresql://postgres@localhost:5432/postgres` diff --git a/rating_api/models/db.py b/rating_api/models/db.py index 6929947..59eeb22 100644 --- a/rating_api/models/db.py +++ b/rating_api/models/db.py @@ -247,7 +247,20 @@ def order_by_like_diff(cls, asc_order: bool = False): return cls.like_dislike_diff.asc() else: return cls.like_dislike_diff.desc() - + + @hybrid_method + def has_reaction(self, user_id: int, react: Reaction) -> bool: + return any(reaction.user_id == user_id and reaction.reaction == react for reaction in self.reactions) + @has_reaction.expression + def has_reaction(cls, user_id: int, react: Reaction): + return (select([true()]). + where(and_(CommentReaction.comment_uuid == cls.uuid, + CommentReaction.user_id == user_id, + CommentReaction.reaction == react + )). + exists() + ) + class LecturerUserComment(BaseDbModel): id: Mapped[int] = mapped_column(Integer, primary_key=True) diff --git a/rating_api/routes/comment.py b/rating_api/routes/comment.py index 2d5579e..8a87f14 100644 --- a/rating_api/routes/comment.py +++ b/rating_api/routes/comment.py @@ -28,6 +28,7 @@ CommentImportAll, CommentPost, CommentUpdate, + CommentGetWithLike, ) from rating_api.settings import Settings, get_settings @@ -162,15 +163,16 @@ async def import_comments( return result -@comment.get("/{uuid}", response_model=CommentGet) -async def get_comment(uuid: UUID) -> CommentGet: +@comment.get("/{uuid}", response_model=CommentGetWithLike) +async def get_comment(uuid: UUID, user = Depends(UnionAuth())) -> CommentGetWithLike: """ Возвращает комментарий по его UUID в базе данных RatingAPI """ comment: Comment = Comment.query(session=db.session).filter(Comment.uuid == uuid).one_or_none() if comment is None: raise ObjectNotFound(Comment, uuid) - return CommentGet.model_validate(comment) + base_data = CommentGet.model_validate(comment) + return CommentGetWithLike(**base_data.model_dump(), is_liked=comment.has_reaction(user.get("id"), Reaction.LIKE), is_disliked=comment.has_reaction(user.get("id"), Reaction.DISLIKE)) @comment.get("", response_model=Union[CommentGetAll, CommentGetAllWithAllInfo, CommentGetAllWithStatus]) diff --git a/rating_api/schemas/models.py b/rating_api/schemas/models.py index b99fede..ac309a9 100644 --- a/rating_api/schemas/models.py +++ b/rating_api/schemas/models.py @@ -7,7 +7,7 @@ from pydantic import ValidationInfo, field_validator from rating_api.exceptions import WrongMark -from rating_api.models import Lecturer, ReviewStatus +from rating_api.models import Lecturer, ReviewStatus, Reaction from rating_api.schemas.base import Base @@ -27,6 +27,9 @@ class CommentGet(Base): dislike_count: int user_fullname: str | None = None +class CommentGetWithLike(CommentGet): + is_liked: bool + is_disliked: bool class CommentGetWithStatus(CommentGet): review_status: ReviewStatus diff --git a/tests/test_routes/test_comment.py b/tests/test_routes/test_comment.py index 5f62bba..333b12c 100644 --- a/tests/test_routes/test_comment.py +++ b/tests/test_routes/test_comment.py @@ -196,13 +196,48 @@ def test_create_comment(client, dbsession, lecturers, body, lecturer_n, response assert user_comment is not None -def test_get_comment(client, comment): +@pytest.mark.parametrize( +"reaction_data, expected_reaction, comment_user_id", +[ + (None, None, 0), + ((0, Reaction.LIKE), "is_liked", 0), #my like on my comment + ((0, Reaction.DISLIKE), "is_disliked", 0), + ((999, Reaction.LIKE), None, 0), #someone else's like on my comment + ((999, Reaction.DISLIKE), None, 0), + ((0, Reaction.LIKE), "is_liked", 999), # my like on someone else's comment + ((0, Reaction.DISLIKE), "is_disliked", 999), + ((333, Reaction.LIKE), None, 999), # someone else's like on another person's comment + ((333, Reaction.DISLIKE), None, 999), + (None, None, None) #anonymous + +], +) +def test_get_comment_with_reaction(client, dbsession, comment, reaction_data, expected_reaction, comment_user_id): + comment.user_id = comment_user_id + + if reaction_data: + user_id, reaction_type = reaction_data + reaction = CommentReaction( + user_id = user_id, + comment_uuid = comment.uuid, + reaction = reaction_type + ) + dbsession.add(reaction) + + dbsession.commit() + response_comment = client.get(f'{url}/{comment.uuid}') - print("1") - assert response_comment.status_code == status.HTTP_200_OK - random_uuid = uuid.uuid4() - response = client.get(f'{url}/{random_uuid}') - assert response.status_code == status.HTTP_404_NOT_FOUND + + if response_comment.status_code == status.HTTP_404_NOT_FOUND: + return + + data = response_comment.json() + if expected_reaction: + assert data[expected_reaction] + else: + assert data["is_liked"] == False + assert data["is_disliked"] == False + @pytest.fixture From 8c5a15ee4ee8be216cd92ae31239fbade37932bc Mon Sep 17 00:00:00 2001 From: Tishanov Artem Date: Thu, 13 Nov 2025 00:13:42 +0300 Subject: [PATCH 2/7] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D1=83?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D0=B3=D0=BE=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D1=8F=20=D0=B2=20=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_routes/test_comment.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_routes/test_comment.py b/tests/test_routes/test_comment.py index 333b12c..e5b9356 100644 --- a/tests/test_routes/test_comment.py +++ b/tests/test_routes/test_comment.py @@ -228,15 +228,15 @@ def test_get_comment_with_reaction(client, dbsession, comment, reaction_data, ex response_comment = client.get(f'{url}/{comment.uuid}') - if response_comment.status_code == status.HTTP_404_NOT_FOUND: - return - - data = response_comment.json() - if expected_reaction: - assert data[expected_reaction] + if response_comment: + data = response_comment.json() + if expected_reaction: + assert data[expected_reaction] + else: + assert data["is_liked"] == False + assert data["is_disliked"] == False else: - assert data["is_liked"] == False - assert data["is_disliked"] == False + assert response_comment.status_code == status.HTTP_404_NOT_FOUND From c5358bac69ee5cfc01e2e44954bf3ec4a82a88d0 Mon Sep 17 00:00:00 2001 From: Tishanov Artem Date: Thu, 13 Nov 2025 00:43:17 +0300 Subject: [PATCH 3/7] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C=D1=88?= =?UTF-8?q?=D0=BE=D0=B5=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8296261..aaaea38 100644 --- a/README.md +++ b/README.md @@ -25,4 +25,4 @@ ``` ## ENV-file description -- – Данные для подключения к БД`DB_DSN=postgresql://postgres@localhost:5432/postgres` +- `DB_DSN=postgresql://postgres@localhost:5432/postgres` – Данные для подключения к БД From 28f1eddc53fe55f2e54309fa8f083b39ef784ea2 Mon Sep 17 00:00:00 2001 From: Tishanov Artem Date: Thu, 13 Nov 2025 00:52:51 +0300 Subject: [PATCH 4/7] =?UTF-8?q?=D0=9F=D1=80=D0=B8=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20black=20=D0=B8=20isort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rating_api/models/db.py | 21 +++++++++++------- rating_api/routes/comment.py | 10 ++++++--- rating_api/schemas/models.py | 4 +++- tests/test_routes/test_comment.py | 37 +++++++++++++------------------ 4 files changed, 38 insertions(+), 34 deletions(-) diff --git a/rating_api/models/db.py b/rating_api/models/db.py index 59eeb22..aa6b78c 100644 --- a/rating_api/models/db.py +++ b/rating_api/models/db.py @@ -247,20 +247,25 @@ def order_by_like_diff(cls, asc_order: bool = False): return cls.like_dislike_diff.asc() else: return cls.like_dislike_diff.desc() - + @hybrid_method def has_reaction(self, user_id: int, react: Reaction) -> bool: return any(reaction.user_id == user_id and reaction.reaction == react for reaction in self.reactions) + @has_reaction.expression def has_reaction(cls, user_id: int, react: Reaction): - return (select([true()]). - where(and_(CommentReaction.comment_uuid == cls.uuid, - CommentReaction.user_id == user_id, - CommentReaction.reaction == react - )). - exists() + return ( + select([true()]) + .where( + and_( + CommentReaction.comment_uuid == cls.uuid, + CommentReaction.user_id == user_id, + CommentReaction.reaction == react, ) - + ) + .exists() + ) + class LecturerUserComment(BaseDbModel): id: Mapped[int] = mapped_column(Integer, primary_key=True) diff --git a/rating_api/routes/comment.py b/rating_api/routes/comment.py index 8a87f14..42c3efc 100644 --- a/rating_api/routes/comment.py +++ b/rating_api/routes/comment.py @@ -24,11 +24,11 @@ CommentGetAllWithAllInfo, CommentGetAllWithStatus, CommentGetWithAllInfo, + CommentGetWithLike, CommentGetWithStatus, CommentImportAll, CommentPost, CommentUpdate, - CommentGetWithLike, ) from rating_api.settings import Settings, get_settings @@ -164,7 +164,7 @@ async def import_comments( @comment.get("/{uuid}", response_model=CommentGetWithLike) -async def get_comment(uuid: UUID, user = Depends(UnionAuth())) -> CommentGetWithLike: +async def get_comment(uuid: UUID, user=Depends(UnionAuth())) -> CommentGetWithLike: """ Возвращает комментарий по его UUID в базе данных RatingAPI """ @@ -172,7 +172,11 @@ async def get_comment(uuid: UUID, user = Depends(UnionAuth())) -> CommentGetWith if comment is None: raise ObjectNotFound(Comment, uuid) base_data = CommentGet.model_validate(comment) - return CommentGetWithLike(**base_data.model_dump(), is_liked=comment.has_reaction(user.get("id"), Reaction.LIKE), is_disliked=comment.has_reaction(user.get("id"), Reaction.DISLIKE)) + return CommentGetWithLike( + **base_data.model_dump(), + is_liked=comment.has_reaction(user.get("id"), Reaction.LIKE), + is_disliked=comment.has_reaction(user.get("id"), Reaction.DISLIKE), + ) @comment.get("", response_model=Union[CommentGetAll, CommentGetAllWithAllInfo, CommentGetAllWithStatus]) diff --git a/rating_api/schemas/models.py b/rating_api/schemas/models.py index ac309a9..8e2dc57 100644 --- a/rating_api/schemas/models.py +++ b/rating_api/schemas/models.py @@ -7,7 +7,7 @@ from pydantic import ValidationInfo, field_validator from rating_api.exceptions import WrongMark -from rating_api.models import Lecturer, ReviewStatus, Reaction +from rating_api.models import Lecturer, ReviewStatus from rating_api.schemas.base import Base @@ -27,10 +27,12 @@ class CommentGet(Base): dislike_count: int user_fullname: str | None = None + class CommentGetWithLike(CommentGet): is_liked: bool is_disliked: bool + class CommentGetWithStatus(CommentGet): review_status: ReviewStatus diff --git a/tests/test_routes/test_comment.py b/tests/test_routes/test_comment.py index e5b9356..065fa5d 100644 --- a/tests/test_routes/test_comment.py +++ b/tests/test_routes/test_comment.py @@ -1,6 +1,5 @@ import datetime import logging -import uuid import pytest from starlette import status @@ -197,31 +196,26 @@ def test_create_comment(client, dbsession, lecturers, body, lecturer_n, response @pytest.mark.parametrize( -"reaction_data, expected_reaction, comment_user_id", -[ - (None, None, 0), - ((0, Reaction.LIKE), "is_liked", 0), #my like on my comment - ((0, Reaction.DISLIKE), "is_disliked", 0), - ((999, Reaction.LIKE), None, 0), #someone else's like on my comment - ((999, Reaction.DISLIKE), None, 0), - ((0, Reaction.LIKE), "is_liked", 999), # my like on someone else's comment - ((0, Reaction.DISLIKE), "is_disliked", 999), - ((333, Reaction.LIKE), None, 999), # someone else's like on another person's comment - ((333, Reaction.DISLIKE), None, 999), - (None, None, None) #anonymous - -], + "reaction_data, expected_reaction, comment_user_id", + [ + (None, None, 0), + ((0, Reaction.LIKE), "is_liked", 0), # my like on my comment + ((0, Reaction.DISLIKE), "is_disliked", 0), + ((999, Reaction.LIKE), None, 0), # someone else's like on my comment + ((999, Reaction.DISLIKE), None, 0), + ((0, Reaction.LIKE), "is_liked", 999), # my like on someone else's comment + ((0, Reaction.DISLIKE), "is_disliked", 999), + ((333, Reaction.LIKE), None, 999), # someone else's like on another person's comment + ((333, Reaction.DISLIKE), None, 999), + (None, None, None), # anonymous + ], ) def test_get_comment_with_reaction(client, dbsession, comment, reaction_data, expected_reaction, comment_user_id): comment.user_id = comment_user_id if reaction_data: - user_id, reaction_type = reaction_data - reaction = CommentReaction( - user_id = user_id, - comment_uuid = comment.uuid, - reaction = reaction_type - ) + user_id, reaction_type = reaction_data + reaction = CommentReaction(user_id=user_id, comment_uuid=comment.uuid, reaction=reaction_type) dbsession.add(reaction) dbsession.commit() @@ -239,7 +233,6 @@ def test_get_comment_with_reaction(client, dbsession, comment, reaction_data, ex assert response_comment.status_code == status.HTTP_404_NOT_FOUND - @pytest.fixture def comments_with_likes(client, dbsession, lecturers): """ From d558235a25e54c212e73e6cbe7cf1c5130256fbd Mon Sep 17 00:00:00 2001 From: Tishanov Artem Date: Thu, 11 Dec 2025 18:43:15 +0300 Subject: [PATCH 5/7] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=80=D0=B5=D0=B0=D0=BA=D1=86=D0=B8=D0=B9?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20get=5Fcomments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rating_api/models/db.py | 12 ++++++++++++ rating_api/routes/comment.py | 36 ++++++++++++++++++++++++++++++---- rating_api/schemas/models.py | 38 ++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/rating_api/models/db.py b/rating_api/models/db.py index aa6b78c..33c48c6 100644 --- a/rating_api/models/db.py +++ b/rating_api/models/db.py @@ -266,6 +266,18 @@ def has_reaction(cls, user_id: int, react: Reaction): .exists() ) + @classmethod + def reactions_for_comments(cls, user_id: int, session, comments): + if not user_id or not comments: + return {} + comments_uuid = [c.uuid for c in comments] + reactions = ( + session.query(CommentReaction) + .filter(CommentReaction.user_id == user_id, CommentReaction.comment_uuid.in_(comments_uuid)) + .all() + ) + return {r.comment_uuid: r.reaction for r in reactions} + class LecturerUserComment(BaseDbModel): id: Mapped[int] = mapped_column(Integer, primary_key=True) diff --git a/rating_api/routes/comment.py b/rating_api/routes/comment.py index 42c3efc..6b06e63 100644 --- a/rating_api/routes/comment.py +++ b/rating_api/routes/comment.py @@ -22,6 +22,7 @@ CommentGet, CommentGetAll, CommentGetAllWithAllInfo, + CommentGetAllWithLike, CommentGetAllWithStatus, CommentGetWithAllInfo, CommentGetWithLike, @@ -179,7 +180,9 @@ async def get_comment(uuid: UUID, user=Depends(UnionAuth())) -> CommentGetWithLi ) -@comment.get("", response_model=Union[CommentGetAll, CommentGetAllWithAllInfo, CommentGetAllWithStatus]) +@comment.get( + "", response_model=Union[CommentGetAll, CommentGetAllWithLike, CommentGetAllWithAllInfo, CommentGetAllWithStatus] +) async def get_comments( limit: int = 10, offset: int = 0, @@ -193,7 +196,7 @@ async def get_comments( unreviewed: bool = False, asc_order: bool = False, user=Depends(UnionAuth(scopes=["rating.comment.review"], auto_error=False, allow_none=False)), -) -> CommentGetAll: +) -> Union[CommentGetAll, CommentGetAllWithLike, CommentGetAllWithAllInfo, CommentGetAllWithStatus]: """ Scopes: `["rating.comment.review"]` @@ -216,6 +219,7 @@ async def get_comments( `asc_order` -Если передано true, сортировать в порядке возрастания. Иначе - в порядке убывания """ + comments_query = ( Comment.query(session=db.session) .filter(Comment.search_by_lectorer_id(lecturer_id)) @@ -232,6 +236,7 @@ async def get_comments( ) ) comments = comments_query.limit(limit).offset(offset).all() + like = False if not comments: raise ObjectNotFound(Comment, 'all') if user and "rating.comment.review" in [scope['name'] for scope in user.get('session_scopes')]: @@ -241,8 +246,13 @@ async def get_comments( result = CommentGetAllWithStatus(limit=limit, offset=offset, total=len(comments)) comment_validator = CommentGetWithStatus else: - result = CommentGetAll(limit=limit, offset=offset, total=len(comments)) + result = ( + CommentGetAllWithLike(limit=limit, offset=offset, total=len(comments)) + if user + else CommentGetAll(limit=limit, offset=offset, total=len(comments)) + ) comment_validator = CommentGet + like = True result.comments = comments @@ -257,8 +267,26 @@ async def get_comments( result.comments = [comment for comment in result.comments if comment.review_status is ReviewStatus.APPROVED] result.total = len(result.comments) - result.comments = [comment_validator.model_validate(comment) for comment in result.comments] + comments_with_like = [] + current_user_id = user.get("id") if user else None + if current_user_id and result.comments: + user_reactions = Comment.reactions_for_comments(current_user_id, db.session, result.comments) + else: + user_reactions = {} + + for comment in result.comments: + base_data = comment_validator.model_validate(comment) + + if current_user_id: + reaction = user_reactions.get(comment.uuid) + comment_with_reactions = CommentGetWithLike( + **base_data.model_dump(), is_liked=reaction == Reaction.LIKE, is_disliked=reaction == Reaction.DISLIKE + ) + comments_with_like.append(comment_with_reactions) + else: + comments_with_like.append(base_data) + result.comments = comments_with_like return result diff --git a/rating_api/schemas/models.py b/rating_api/schemas/models.py index 8e2dc57..c64856e 100644 --- a/rating_api/schemas/models.py +++ b/rating_api/schemas/models.py @@ -37,11 +37,24 @@ class CommentGetWithStatus(CommentGet): review_status: ReviewStatus +""" +class CommentGetWithLikeAndStatus(CommentGetWithLike): + review_status: ReviewStatus +""" + + class CommentGetWithAllInfo(CommentGet): review_status: ReviewStatus approved_by: int | None = None +""" +class CommentGetWithAllInfoAndLike(CommentGetWithLike): + review_status: ReviewStatus + approved_by: int | None = None +""" + + class CommentUpdate(Base): subject: str = None text: str = None @@ -80,6 +93,13 @@ class CommentGetAll(Base): total: int +class CommentGetAllWithLike(Base): + comments: list[CommentGetWithLike] = [] + limit: int + offset: int + total: int + + class CommentGetAllWithStatus(Base): comments: list[CommentGetWithStatus] = [] limit: int @@ -87,6 +107,15 @@ class CommentGetAllWithStatus(Base): total: int +""" +class CommentGetAllWithStatusAndLike(Base): + comments: list[CommentGetWithLikeAndStatus] = [] + limit: int + offset: int + total: int +""" + + class CommentGetAllWithAllInfo(Base): comments: list[CommentGetWithAllInfo] = [] limit: int @@ -94,6 +123,15 @@ class CommentGetAllWithAllInfo(Base): total: int +""" +class CommentGetAllWithAllInfoAndLike(Base): + comments: list[CommentGetWithAllInfoAndLike] = [] + limit: int + offset: int + total: int +""" + + class LecturerUserCommentPost(Base): lecturer_id: int user_id: int From 0388ac0747886e89eb7a5cadb655fd15a17f4b8e Mon Sep 17 00:00:00 2001 From: Tishanov Artem Date: Thu, 5 Mar 2026 17:14:36 +0300 Subject: [PATCH 6/7] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D1=80=D0=B5=D0=B0=D0=BA=D1=86=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=BD=D0=B5=D1=81=D0=BA=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=BA=D0=B8=D1=85=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=80=D0=B8=D0=B5=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rating_api/models/db.py | 12 ++++++++---- rating_api/routes/comment.py | 4 +--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/rating_api/models/db.py b/rating_api/models/db.py index 33c48c6..b0b7641 100644 --- a/rating_api/models/db.py +++ b/rating_api/models/db.py @@ -271,12 +271,16 @@ def reactions_for_comments(cls, user_id: int, session, comments): if not user_id or not comments: return {} comments_uuid = [c.uuid for c in comments] - reactions = ( - session.query(CommentReaction) - .filter(CommentReaction.user_id == user_id, CommentReaction.comment_uuid.in_(comments_uuid)) + result = ( + session.query(Comment.uuid, CommentReaction.reaction) + .join( + CommentReaction, and_(Comment.uuid == CommentReaction.comment_uuid, CommentReaction.user_id == user_id) + ) + .filter(Comment.uuid.in_(comments_uuid)) + .group_by(Comment.uuid, CommentReaction.reaction) .all() ) - return {r.comment_uuid: r.reaction for r in reactions} + return dict(result) class LecturerUserComment(BaseDbModel): diff --git a/rating_api/routes/comment.py b/rating_api/routes/comment.py index 6b06e63..0bdc0d0 100644 --- a/rating_api/routes/comment.py +++ b/rating_api/routes/comment.py @@ -219,7 +219,7 @@ async def get_comments( `asc_order` -Если передано true, сортировать в порядке возрастания. Иначе - в порядке убывания """ - + user = {"id": 101, "session_scopes": []} # тестовый user_id # пустые скоупы = обычный пользователь comments_query = ( Comment.query(session=db.session) .filter(Comment.search_by_lectorer_id(lecturer_id)) @@ -236,7 +236,6 @@ async def get_comments( ) ) comments = comments_query.limit(limit).offset(offset).all() - like = False if not comments: raise ObjectNotFound(Comment, 'all') if user and "rating.comment.review" in [scope['name'] for scope in user.get('session_scopes')]: @@ -252,7 +251,6 @@ async def get_comments( else CommentGetAll(limit=limit, offset=offset, total=len(comments)) ) comment_validator = CommentGet - like = True result.comments = comments From 533a5542eb29e9f6aa8b445c5f86d8da56086903 Mon Sep 17 00:00:00 2001 From: Tishanov Artem Date: Mon, 9 Mar 2026 22:36:06 +0300 Subject: [PATCH 7/7] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B2=20get=5Fcomment=20=D0=B8=20get=5Fcomments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rating_api/routes/comment.py | 39 +++++++++------------------ rating_api/schemas/models.py | 45 ++----------------------------- tests/conftest.py | 17 ++++++++++++ tests/test_routes/test_comment.py | 7 ++--- 4 files changed, 34 insertions(+), 74 deletions(-) diff --git a/rating_api/routes/comment.py b/rating_api/routes/comment.py index 0bdc0d0..cfac5a5 100644 --- a/rating_api/routes/comment.py +++ b/rating_api/routes/comment.py @@ -22,10 +22,8 @@ CommentGet, CommentGetAll, CommentGetAllWithAllInfo, - CommentGetAllWithLike, CommentGetAllWithStatus, CommentGetWithAllInfo, - CommentGetWithLike, CommentGetWithStatus, CommentImportAll, CommentPost, @@ -164,8 +162,8 @@ async def import_comments( return result -@comment.get("/{uuid}", response_model=CommentGetWithLike) -async def get_comment(uuid: UUID, user=Depends(UnionAuth())) -> CommentGetWithLike: +@comment.get("/{uuid}", response_model=CommentGet) +async def get_comment(uuid: UUID, user=Depends(UnionAuth(auto_error=False, allow_none=False))) -> CommentGet: """ Возвращает комментарий по его UUID в базе данных RatingAPI """ @@ -173,16 +171,13 @@ async def get_comment(uuid: UUID, user=Depends(UnionAuth())) -> CommentGetWithLi if comment is None: raise ObjectNotFound(Comment, uuid) base_data = CommentGet.model_validate(comment) - return CommentGetWithLike( - **base_data.model_dump(), - is_liked=comment.has_reaction(user.get("id"), Reaction.LIKE), - is_disliked=comment.has_reaction(user.get("id"), Reaction.DISLIKE), - ) + if user: + base_data.is_liked=comment.has_reaction(user.get("id"), Reaction.LIKE) + base_data.is_disliked=comment.has_reaction(user.get("id"), Reaction.DISLIKE) + return base_data -@comment.get( - "", response_model=Union[CommentGetAll, CommentGetAllWithLike, CommentGetAllWithAllInfo, CommentGetAllWithStatus] -) +@comment.get("", response_model=Union[CommentGetAll, CommentGetAllWithAllInfo, CommentGetAllWithStatus]) async def get_comments( limit: int = 10, offset: int = 0, @@ -196,7 +191,7 @@ async def get_comments( unreviewed: bool = False, asc_order: bool = False, user=Depends(UnionAuth(scopes=["rating.comment.review"], auto_error=False, allow_none=False)), -) -> Union[CommentGetAll, CommentGetAllWithLike, CommentGetAllWithAllInfo, CommentGetAllWithStatus]: +) -> Union[CommentGetAll, CommentGetAllWithAllInfo, CommentGetAllWithStatus]: """ Scopes: `["rating.comment.review"]` @@ -219,7 +214,6 @@ async def get_comments( `asc_order` -Если передано true, сортировать в порядке возрастания. Иначе - в порядке убывания """ - user = {"id": 101, "session_scopes": []} # тестовый user_id # пустые скоупы = обычный пользователь comments_query = ( Comment.query(session=db.session) .filter(Comment.search_by_lectorer_id(lecturer_id)) @@ -245,11 +239,7 @@ async def get_comments( result = CommentGetAllWithStatus(limit=limit, offset=offset, total=len(comments)) comment_validator = CommentGetWithStatus else: - result = ( - CommentGetAllWithLike(limit=limit, offset=offset, total=len(comments)) - if user - else CommentGetAll(limit=limit, offset=offset, total=len(comments)) - ) + result = CommentGetAll(limit=limit, offset=offset, total=len(comments)) comment_validator = CommentGet result.comments = comments @@ -267,6 +257,7 @@ async def get_comments( result.total = len(result.comments) comments_with_like = [] current_user_id = user.get("id") if user else None + if current_user_id and result.comments: user_reactions = Comment.reactions_for_comments(current_user_id, db.session, result.comments) else: @@ -274,15 +265,11 @@ async def get_comments( for comment in result.comments: base_data = comment_validator.model_validate(comment) - if current_user_id: reaction = user_reactions.get(comment.uuid) - comment_with_reactions = CommentGetWithLike( - **base_data.model_dump(), is_liked=reaction == Reaction.LIKE, is_disliked=reaction == Reaction.DISLIKE - ) - comments_with_like.append(comment_with_reactions) - else: - comments_with_like.append(base_data) + base_data.is_liked = (reaction == Reaction.LIKE) + base_data.is_disliked = (reaction == Reaction.DISLIKE) + comments_with_like.append(base_data) result.comments = comments_with_like return result diff --git a/rating_api/schemas/models.py b/rating_api/schemas/models.py index c64856e..b6e1e91 100644 --- a/rating_api/schemas/models.py +++ b/rating_api/schemas/models.py @@ -26,35 +26,19 @@ class CommentGet(Base): like_count: int dislike_count: int user_fullname: str | None = None - - -class CommentGetWithLike(CommentGet): - is_liked: bool - is_disliked: bool + is_liked: bool = False + is_disliked: bool = False class CommentGetWithStatus(CommentGet): review_status: ReviewStatus -""" -class CommentGetWithLikeAndStatus(CommentGetWithLike): - review_status: ReviewStatus -""" - - class CommentGetWithAllInfo(CommentGet): review_status: ReviewStatus approved_by: int | None = None -""" -class CommentGetWithAllInfoAndLike(CommentGetWithLike): - review_status: ReviewStatus - approved_by: int | None = None -""" - - class CommentUpdate(Base): subject: str = None text: str = None @@ -93,13 +77,6 @@ class CommentGetAll(Base): total: int -class CommentGetAllWithLike(Base): - comments: list[CommentGetWithLike] = [] - limit: int - offset: int - total: int - - class CommentGetAllWithStatus(Base): comments: list[CommentGetWithStatus] = [] limit: int @@ -107,15 +84,6 @@ class CommentGetAllWithStatus(Base): total: int -""" -class CommentGetAllWithStatusAndLike(Base): - comments: list[CommentGetWithLikeAndStatus] = [] - limit: int - offset: int - total: int -""" - - class CommentGetAllWithAllInfo(Base): comments: list[CommentGetWithAllInfo] = [] limit: int @@ -123,15 +91,6 @@ class CommentGetAllWithAllInfo(Base): total: int -""" -class CommentGetAllWithAllInfoAndLike(Base): - comments: list[CommentGetWithAllInfoAndLike] = [] - limit: int - offset: int - total: int -""" - - class LecturerUserCommentPost(Base): lecturer_id: int user_id: int diff --git a/tests/conftest.py b/tests/conftest.py index 3742f8f..afb64a8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -139,6 +139,23 @@ def comment(dbsession, lecturer): dbsession.commit() +@pytest.fixture +def comment_reaction(dbsession, comment): + created_reactions = [] + + def _create_reaction(user_id: int, react: Reaction): + reaction = CommentReaction(user_id=user_id, comment_uuid=comment.uuid, reaction=react) + dbsession.add(reaction) + dbsession.commit() + created_reactions.append(reaction) + + yield _create_reaction + + for reaction in created_reactions: + dbsession.delete(reaction) + dbsession.commit() + + @pytest.fixture def unreviewed_comment(dbsession, lecturer): _comment = Comment( diff --git a/tests/test_routes/test_comment.py b/tests/test_routes/test_comment.py index 065fa5d..3a82873 100644 --- a/tests/test_routes/test_comment.py +++ b/tests/test_routes/test_comment.py @@ -210,15 +210,12 @@ def test_create_comment(client, dbsession, lecturers, body, lecturer_n, response (None, None, None), # anonymous ], ) -def test_get_comment_with_reaction(client, dbsession, comment, reaction_data, expected_reaction, comment_user_id): +def test_get_comment_with_reaction(client, comment, reaction_data, expected_reaction, comment_user_id, comment_reaction): comment.user_id = comment_user_id if reaction_data: user_id, reaction_type = reaction_data - reaction = CommentReaction(user_id=user_id, comment_uuid=comment.uuid, reaction=reaction_type) - dbsession.add(reaction) - - dbsession.commit() + comment_reaction(user_id, reaction_type) response_comment = client.get(f'{url}/{comment.uuid}')