Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 58 additions & 6 deletions rating_api/models/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
String,
UnaryExpression,
and_,
case,
desc,
func,
nulls_last,
or_,
select,
true,
)
from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
Expand Down Expand Up @@ -186,14 +188,64 @@ def search_by_subject(self, query: str) -> bool:
return and_(Comment.review_status == ReviewStatus.APPROVED, func.lower(Comment.subject).contains(query))

@hybrid_property
def like_count(self) -> int:
"""Python access to like count"""
return sum(1 for like in self.reactions if like.reaction == 'like')
def like_count(self):
"""Python доступ к числу лайков"""
return sum(1 for reaction in self.reactions if reaction.reaction == Reaction.LIKE)

@like_count.expression
def like_count(cls):
"""SQL выражение для подсчета лайков"""
return (
select(func.count(CommentReaction.uuid))
.where(and_(CommentReaction.comment_uuid == cls.uuid, CommentReaction.reaction == Reaction.LIKE))
.label('like_count')
)

@hybrid_property
def dislike_count(self) -> int:
"""Python access to dislike count"""
return sum(1 for like in self.reactions if like.reaction == 'dislike')
def dislike_count(self):
"""Python доступ к числу дизлайков"""
return sum(1 for reaction in self.reactions if reaction.reaction == Reaction.DISLIKE)

@dislike_count.expression
def dislike_count(cls):
"""SQL выражение для подсчета дизлайков"""
return (
select(func.count(CommentReaction.uuid))
.where(and_(CommentReaction.comment_uuid == cls.uuid, CommentReaction.reaction == Reaction.DISLIKE))
.label('dislike_count')
)

@hybrid_property
def like_dislike_diff(self):
"""Python доступ к разнице лайков и дизлайков"""
if hasattr(self, '_like_dislike_diff'):
return self._like_dislike_diff
return self.like_count - self.dislike_count

@like_dislike_diff.expression
def like_dislike_diff(cls):
"""SQL выражение для вычисления разницы лайков/дизлайков"""
return (
select(
func.sum(
case(
(CommentReaction.reaction == Reaction.LIKE, 1),
(CommentReaction.reaction == Reaction.DISLIKE, -1),
else_=0,
)
)
)
.where(CommentReaction.comment_uuid == cls.uuid)
.label('like_dislike_diff')
)

@hybrid_method
def order_by_like_diff(cls, asc_order: bool = False):
"""Метод для сортировки по разнице лайков/дизлайков"""
if asc_order:
return cls.like_dislike_diff.asc()
else:
return cls.like_dislike_diff.desc()


class LecturerUserComment(BaseDbModel):
Expand Down
15 changes: 10 additions & 5 deletions rating_api/routes/comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ async def get_comments(
user_id: int | None = None,
subject: str | None = None,
order_by: str = Query(
enum=["create_ts", "mark_kindness", "mark_freebie", "mark_clarity", "mark_general"],
enum=["create_ts", "mark_kindness", "mark_freebie", "mark_clarity", "mark_general", "like_diff"],
default="create_ts",
),
unreviewed: bool = False,
Expand All @@ -190,9 +190,10 @@ async def get_comments(
Если без смещения возвращается комментарий с условным номером N,
то при значении offset = X будет возвращаться комментарий с номером N + X

`order_by` - возможные значения `"create_ts", "mark_kindness", "mark_freebie", "mark_clarity", "mark_general"`.
Если передано `'create_ts'` - возвращается список комментариев отсортированных по времени
Если передано `'mark_...'` - возвращается список комментариев отсортированных по конкретной оценке
`order_by` - возможные значения `"create_ts", "mark_kindness", "mark_freebie", "mark_clarity", "mark_general", "like_diff"`.
Если передано `'create_ts'` - возвращается список комментариев, отсортированных по времени
Если передано `'mark_...'` - возвращается список комментариев, отсортированных по конкретной оценке
Если передано `'like_diff'` - возвращается список комментариев, отсортированных по разнице лайков и дизлайков

`lecturer_id` - вернет все комментарии для преподавателя с конкретным id, по дефолту возвращает вообще все аппрувнутые комментарии.

Expand All @@ -210,7 +211,11 @@ async def get_comments(
.order_by(
Comment.order_by_mark(order_by, asc_order)
if "mark" in order_by
else Comment.order_by_create_ts(order_by, asc_order)
else (
Comment.order_by_like_diff(asc_order)
if order_by == "like_diff"
else Comment.order_by_create_ts(order_by, asc_order)
)
)
)
comments = comments_query.limit(limit).offset(offset).all()
Expand Down
112 changes: 111 additions & 1 deletion tests/test_routes/test_comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pytest
from starlette import status

from rating_api.models import Comment, LecturerUserComment, ReviewStatus
from rating_api.models import Comment, CommentReaction, LecturerUserComment, Reaction, ReviewStatus
from rating_api.settings import get_settings


Expand Down Expand Up @@ -205,6 +205,116 @@ def test_get_comment(client, comment):
assert response.status_code == status.HTTP_404_NOT_FOUND


@pytest.fixture
def comments_with_likes(client, dbsession, lecturers):
"""
Создает несколько комментариев с разным количеством лайков/дизлайков
"""
comments = []

user_id = 9999

comment_data = [
{
"user_id": user_id,
"lecturer_id": lecturers[0].id,
"subject": "test_subject",
"text": "Comment with many likes",
"mark_kindness": 1,
"mark_freebie": 0,
"mark_clarity": 0,
"review_status": ReviewStatus.APPROVED,
},
{
"user_id": user_id,
"lecturer_id": lecturers[0].id,
"subject": "test_subject",
"text": "Comment with many dislikes",
"mark_kindness": 1,
"mark_freebie": 0,
"mark_clarity": 0,
"review_status": ReviewStatus.APPROVED,
},
{
"user_id": user_id,
"lecturer_id": lecturers[0].id,
"subject": "test_subject",
"text": "Comment with balanced reactions",
"mark_kindness": 1,
"mark_freebie": 0,
"mark_clarity": 0,
"review_status": ReviewStatus.APPROVED,
},
]

for data in comment_data:
comment = Comment(**data)
dbsession.add(comment)
comments.append(comment)

dbsession.commit()

for _ in range(10):
reaction = CommentReaction(comment_uuid=comments[0].uuid, user_id=user_id, reaction=Reaction.LIKE)
dbsession.add(reaction)
for _ in range(2):
reaction = CommentReaction(comment_uuid=comments[0].uuid, user_id=user_id, reaction=Reaction.DISLIKE)
dbsession.add(reaction)

for _ in range(3):
reaction = CommentReaction(comment_uuid=comments[1].uuid, user_id=user_id, reaction=Reaction.LIKE)
dbsession.add(reaction)
for _ in range(8):
reaction = CommentReaction(comment_uuid=comments[1].uuid, user_id=user_id, reaction=Reaction.DISLIKE)
dbsession.add(reaction)

for _ in range(5):
reaction = CommentReaction(comment_uuid=comments[2].uuid, user_id=user_id, reaction=Reaction.LIKE)
dbsession.add(reaction)
for _ in range(5):
reaction = CommentReaction(comment_uuid=comments[2].uuid, user_id=user_id, reaction=Reaction.DISLIKE)
dbsession.add(reaction)

dbsession.commit()

for comment in comments:
dbsession.refresh(comment)

return comments


@pytest.mark.parametrize(
'order_by, asc_order',
[
('like_diff', False),
('like_diff', True),
],
)
def test_comments_sort_by_like_diff(client, comments_with_likes, order_by, asc_order):
"""
Тестирует сортировку комментариев по разнице лайков (like_diff)
"""
params = {"order_by": order_by, "asc_order": asc_order, "limit": 10}

response = client.get('/comment', params=params)
assert response.status_code == status.HTTP_200_OK

json_response = response.json()
returned_comments = json_response["comments"]

if order_by == 'like_diff':
if asc_order:
for i in range(len(returned_comments) - 1):
current_like_diff = returned_comments[i]["like_count"] - returned_comments[i]["dislike_count"]
next_like_diff = returned_comments[i + 1]["like_count"] - returned_comments[i + 1]["dislike_count"]
assert current_like_diff <= next_like_diff
else:
for i in range(len(returned_comments) - 1):
current_like_diff = returned_comments[i]["like_count"] - returned_comments[i]["dislike_count"]
next_like_diff = returned_comments[i + 1]["like_count"] - returned_comments[i + 1]["dislike_count"]
assert current_like_diff >= next_like_diff


@pytest.mark.parametrize(
'lecturer_n,response_status',
[(0, status.HTTP_200_OK), (1, status.HTTP_200_OK), (2, status.HTTP_200_OK), (3, status.HTTP_404_NOT_FOUND)],
Expand Down
Loading