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
24 changes: 24 additions & 0 deletions migrations/versions/1c001709fc55_advanced_sort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""advanced_sort

Revision ID: 1c001709fc55
Revises: dd44854aa12a
Create Date: 2025-04-26 17:01:57.140143

"""

import sqlalchemy as sa
from alembic import op


revision = '1c001709fc55'
down_revision = 'dd44854aa12a'
branch_labels = None
depends_on = None


def upgrade():
op.alter_column('comment', 'approved_by', existing_type=sa.INTEGER(), nullable=True)


def downgrade():
op.alter_column('comment', 'approved_by', existing_type=sa.INTEGER(), nullable=False)
24 changes: 23 additions & 1 deletion rating_api/models/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from fastapi_sqlalchemy import db
from sqlalchemy import UUID, Boolean, DateTime
from sqlalchemy import Enum as DbEnum
from sqlalchemy import ForeignKey, Integer, String, UnaryExpression, and_, func, nulls_last, or_, true
from sqlalchemy import ForeignKey, Integer, String, UnaryExpression, and_, desc, func, nulls_last, or_, true
from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.attributes import InstrumentedAttribute
Expand Down Expand Up @@ -123,6 +123,28 @@ class Comment(BaseDbModel):
def mark_general(self):
return (self.mark_kindness + self.mark_freebie + self.mark_clarity) / 3

@hybrid_method
def order_by_create_ts(
self, query: str, asc_order: bool
) -> UnaryExpression[datetime.datetime] | InstrumentedAttribute:
return getattr(Comment, query) if asc_order else desc(getattr(Comment, query))

@hybrid_method
def order_by_mark(self, query: str, asc_order: bool) -> UnaryExpression[float] | InstrumentedAttribute:
return getattr(Comment, query) if asc_order else desc(getattr(Comment, query))

@hybrid_method
def search_by_lectorer_id(self, query: int) -> bool:
if not query:
return true()
Comment thread
Zimovchik marked this conversation as resolved.
return and_(Comment.review_status == ReviewStatus.APPROVED, Comment.lecturer_id == query)

@hybrid_method
def search_by_user_id(self, query: int) -> bool:
if not query:
return true()
Comment thread
Zimovchik marked this conversation as resolved.
return Comment.user_id == query


class LecturerUserComment(BaseDbModel):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
Expand Down
59 changes: 35 additions & 24 deletions rating_api/routes/comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,45 +173,60 @@ async def get_comments(
offset: int = 0,
lecturer_id: int | None = None,
user_id: int | None = None,
order_by: list[Literal["create_ts"]] = Query(default=[]),
order_by: str = Query(
enum=["create_ts", "mark_kindness", "mark_freebie", "mark_clarity", "mark_general"],
default="create_ts",
),
unreviewed: bool = False,
user=Depends(UnionAuth(scopes=["rating.comment.review"], auto_error=False, allow_none=True)),
asc_order: bool = False,
user=Depends(UnionAuth(scopes=["rating.comment.review"], auto_error=False, allow_none=False)),
) -> CommentGetAll:
"""
Scopes: `["rating.comment.review"]`
Scopes: `["rating.comment.review"]`

`limit` - максимальное количество возвращаемых комментариев

`limit` - максимальное количество возвращаемых комментариев
`offset` - смещение, определяющее, с какого по порядку комментария начинать выборку.
Если без смещения возвращается комментарий с условным номером N,
то при значении offset = X будет возвращаться комментарий с номером N + X

`offset` - смещение, определяющее, с какого по порядку комментария начинать выборку.
Если без смещения возвращается комментарий с условным номером N,
то при значении offset = X будет возвращаться комментарий с номером N + X
`order_by` - возможные значения `"create_ts", "mark_kindness", "mark_freebie", "mark_clarity", "mark_general"`.
Если передано `'create_ts'` - возвращается список комментариев отсортированных по времени
Если передано `'mark_...'` - возвращается список комментариев отсортированных по конкретной оценке

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

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

`user_id` - вернет все комментарии пользователя с конкретным id
`unreviewed` - вернет все непроверенные комментарии, если True. По дефолту False.

`unreviewed` - вернет все непроверенные комментарии, если True. По дефолту False.
`asc_order` -Если передано true, сортировать в порядке возрастания. Иначе - в порядке убывания
"""
comments = Comment.query(session=db.session).all()
comments_query = (
Comment.query(session=db.session)
.filter(Comment.search_by_lectorer_id(lecturer_id))
.filter(Comment.search_by_user_id(user_id))
Comment thread
Zimovchik marked this conversation as resolved.
.order_by(
Comment.order_by_mark(order_by, asc_order)
if "mark" in order_by
Comment thread
Zimovchik marked this conversation as resolved.
else Comment.order_by_create_ts(order_by, asc_order)
)
)

comments = comments_query.limit(limit).offset(offset).all()
if not comments:
raise ObjectNotFound(Comment, 'all')
if "rating.comment.review" in [scope['name'] for scope in user.get('session_scopes')]:
if user and "rating.comment.review" in [scope['name'] for scope in user.get('session_scopes')]:
result = CommentGetAllWithAllInfo(limit=limit, offset=offset, total=len(comments))
comment_validator = CommentGetWithAllInfo
elif user.get('id') == user_id:
elif user and user.get('id') == user_id:
result = CommentGetAllWithStatus(limit=limit, offset=offset, total=len(comments))
comment_validator = CommentGetWithStatus
else:
result = CommentGetAll(limit=limit, offset=offset, total=len(comments))
comment_validator = CommentGet
result.comments = comments
if user_id is not None:
result.comments = [comment for comment in result.comments if comment.user_id == user_id]

if lecturer_id is not None:
result.comments = [comment for comment in result.comments if comment.lecturer_id == lecturer_id]
result.comments = comments

if unreviewed:
if not user:
Expand All @@ -227,13 +242,9 @@ async def get_comments(
else:
result.comments = [comment for comment in result.comments if comment.review_status is ReviewStatus.APPROVED]

result.comments = result.comments[offset : limit + offset]

if "create_ts" in order_by:
result.comments.sort(key=lambda comment: comment.create_ts, reverse=True)
result.total = len(result.comments)
result.comments = [comment_validator.model_validate(comment) for comment in result.comments]
result.comments.sort(key=lambda comment: comment.create_ts, reverse=True)

return result


Expand Down
15 changes: 11 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def lecturer(dbsession):
@pytest.fixture
def comment(dbsession, lecturer):
_comment = Comment(
user_id=0,
create_ts="2025-04-25T19:38:56.408Z",
subject="test_subject",
text="test_comment",
mark_kindness=1,
Expand Down Expand Up @@ -166,8 +168,9 @@ def lecturers_with_comments(dbsession, lecturers):
(lecturers[2].id, 9992, 'test_subject12', ReviewStatus.APPROVED, 0, 0, 0),
]

comments = [
Comment(
comments = []
for lecturer_id, user_id, subject, review_status, mark_kindness, mark_clarity, mark_freebie in comments_data:
comment = Comment(
subject=subject,
text="test_comment",
mark_kindness=mark_kindness,
Expand All @@ -177,8 +180,12 @@ def lecturers_with_comments(dbsession, lecturers):
user_id=user_id,
review_status=review_status,
)
for lecturer_id, user_id, subject, review_status, mark_kindness, mark_clarity, mark_freebie in comments_data
]

# Set approved_by to -1 for approved or dismissed comments
if review_status in [ReviewStatus.APPROVED, ReviewStatus.DISMISSED]:
comment.approved_by = -1

comments.append(comment)

dbsession.add_all(comments)
dbsession.commit()
Expand Down
14 changes: 9 additions & 5 deletions tests/test_routes/test_comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,16 @@ def test_create_comment(client, dbsession, lecturers, body, lecturer_n, response

def test_get_comment(client, comment):
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


@pytest.mark.parametrize(
'lecturer_n,response_status', [(0, status.HTTP_200_OK), (1, status.HTTP_200_OK), (3, status.HTTP_200_OK)]
'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)],
)
def test_comments_by_lecturer_id(client, lecturers_with_comments, lecturer_n, response_status):
lecturers, comments = lecturers_with_comments
Expand All @@ -216,8 +218,10 @@ def test_comments_by_lecturer_id(client, lecturers_with_comments, lecturer_n, re
assert len(json_response["comments"]) == len(
[
comment
for comment in lecturers[lecturer_n].comments
if comment.review_status == ReviewStatus.APPROVED and not comment.is_deleted
for comment in comments
if comment.lecturer_id == lecturers[lecturer_n].id
and comment.review_status == ReviewStatus.APPROVED
and not comment.is_deleted
]
)

Expand All @@ -227,15 +231,15 @@ def test_comments_by_lecturer_id(client, lecturers_with_comments, lecturer_n, re
)
def test_comments_by_user_id(client, lecturers_with_comments, user_id, response_status):
_, comments = lecturers_with_comments
response = response = client.get(f'{url}', params={"user_id": user_id})
response = client.get(f'{url}', params={"user_id": 9990 + user_id})
assert response.status_code == response_status
if response.status_code == status.HTTP_200_OK:
json_response = response.json()
assert len(json_response["comments"]) == len(
[
comment
for comment in comments
if comment.user_id == user_id
if comment.user_id == 9990 + user_id
and comment.review_status == ReviewStatus.APPROVED
and not comment.is_deleted
]
Expand Down