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
8 changes: 8 additions & 0 deletions rating_api/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,11 @@ def __init__(self):
f"The comment contains forbidden symbols. Letters of English and Russian languages, numbers and punctuation marks are allowed",
f"Комментарий содержит запрещенный символ. Разрешены буквы английского и русского языков, цифры и знаки препинания",
)


class UpdateError(RatingAPIError):
def __init__(self, msg: str):
super().__init__(
f"{msg} Conflict with update a resource that already exists or has conflicting information.",
f"{msg} Конфликт с обновлением ресурса, который уже существует или имеет противоречивую информацию.",
)
41 changes: 39 additions & 2 deletions rating_api/routes/comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
ObjectNotFound,
TooManyCommentRequests,
TooManyCommentsToLecturer,
UpdateError,
)
from rating_api.models import Comment, Lecturer, LecturerUserComment, ReviewStatus
from rating_api.schemas.base import StatusResponseModel
from rating_api.schemas.models import CommentGet, CommentGetAll, CommentImportAll, CommentPost
from rating_api.schemas.models import CommentGet, CommentGetAll, CommentImportAll, CommentPost, CommentUpdate
from rating_api.settings import Settings, get_settings


Expand Down Expand Up @@ -224,7 +225,7 @@ async def get_comments(
return result


@comment.patch("/{uuid}", response_model=CommentGet)
@comment.patch("/{uuid}/review", response_model=CommentGet)
Comment thread
Temmmmmo marked this conversation as resolved.
async def review_comment(
uuid: UUID,
review_status: Literal[ReviewStatus.APPROVED, ReviewStatus.DISMISSED] = ReviewStatus.DISMISSED,
Expand All @@ -246,6 +247,42 @@ async def review_comment(
return CommentGet.model_validate(Comment.update(session=db.session, id=uuid, review_status=review_status))


@comment.patch("/{uuid}", response_model=CommentGet)
async def update_comment(uuid: UUID, comment_update: CommentUpdate, user=Depends(UnionAuth())) -> CommentGet:
"""Позволяет изменить свой неанонимный комментарий"""
comment: Comment = Comment.get(session=db.session, id=uuid) # Ошибка, если не найден

if comment.user_id != user.get("id") or comment.user_id is None:
raise ForbiddenAction(Comment)

# Получаем только переданные для обновления поля
update_data = comment_update.model_dump(exclude_unset=True)

# Если не передано ни одного параметра
if not update_data:
raise UpdateError(msg="Provide any parametr.")
# raise HTTPException(status_code=409, detail="Provide any parametr") # 409

# Проверяем, есть ли неизмененные поля
current_data = {key: getattr(comment, key) for key in update_data} # Берем текущие значения из БД
unchanged_fields = {k for k, v in update_data.items() if current_data.get(k) == v}

if unchanged_fields:
raise UpdateError(msg=f"No changes detected in fields: {', '.join(unchanged_fields)}.")
# raise HTTPException(status_code=409, detail=f"No changes detected in fields: {', '.join(unchanged_fields)}")

# Обновление комментария
updated_comment = Comment.update(
session=db.session,
id=uuid,
**update_data,
update_ts=datetime.datetime.utcnow(),
review_status=ReviewStatus.PENDING,
)

return CommentGet.model_validate(updated_comment)


@comment.delete("/{uuid}", response_model=StatusResponseModel)
async def delete_comment(
uuid: UUID, _=Depends(UnionAuth(scopes=["rating.comment.delete"], allow_none=False, auto_error=True))
Expand Down
8 changes: 8 additions & 0 deletions rating_api/routes/exc_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
ObjectNotFound,
TooManyCommentRequests,
TooManyCommentsToLecturer,
UpdateError,
WrongMark,
)
from rating_api.schemas.base import StatusResponseModel
Expand Down Expand Up @@ -70,3 +71,10 @@ async def forbidden_symbol_handler(req: starlette.requests.Request, exc: Forbidd
return JSONResponse(
content=StatusResponseModel(status="Error", message=exc.eng, ru=exc.ru).model_dump(), status_code=400
)


@app.exception_handler(UpdateError)
async def update_error_handler(req: starlette.requests.Request, exc: UpdateError):
return JSONResponse(
content=StatusResponseModel(status="Error", message=exc.eng, ru=exc.ru).model_dump(), status_code=409
)
15 changes: 15 additions & 0 deletions rating_api/schemas/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@ def validate_mark(cls, value):
return value


class CommentUpdate(Base):
subject: str = None
text: str = None
mark_kindness: int = None
mark_freebie: int = None
mark_clarity: int = None
Comment thread
parfenovma marked this conversation as resolved.

@field_validator('mark_kindness', 'mark_freebie', 'mark_clarity')
@classmethod
def validate_mark(cls, value):
if value not in [-2, -1, 0, 1, 2]:
raise WrongMark()
return value


class CommentImport(CommentPost):
lecturer_id: int
subject: str | None = None
Expand Down
20 changes: 20 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,26 @@ def unreviewed_comment(dbsession, lecturer):
dbsession.commit()


@pytest.fixture
def nonanonymous_comment(dbsession, lecturer):
_comment = Comment(
subject="subject",
text="comment",
mark_kindness=1,
mark_clarity=1,
mark_freebie=1,
lecturer_id=lecturer.id,
review_status=ReviewStatus.APPROVED,
user_id=0,
)
dbsession.add(_comment)
dbsession.commit()
yield _comment
dbsession.refresh(_comment)
dbsession.delete(_comment)
dbsession.commit()


@pytest.fixture(scope='function')
def lecturers(dbsession):
"""
Expand Down
91 changes: 82 additions & 9 deletions tests/test_routes/test_comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
"mark_kindness": 1,
"mark_freebie": 0,
"mark_clarity": 0,
"is_anonymous": False,
},
0,
status.HTTP_200_OK,
Expand All @@ -48,7 +47,6 @@
"mark_kindness": -2,
"mark_freebie": -2,
"mark_clarity": -2,
"is_anonymous": False,
},
1,
status.HTTP_200_OK,
Expand All @@ -60,7 +58,6 @@
"mark_kindness": 5,
"mark_freebie": -2,
"mark_clarity": 0,
"is_anonymous": False,
},
2,
status.HTTP_400_BAD_REQUEST,
Expand All @@ -72,7 +69,6 @@
"mark_kindness": 1,
"mark_freebie": -2,
"mark_clarity": 0,
"is_anonymous": False,
},
3,
status.HTTP_404_NOT_FOUND,
Expand Down Expand Up @@ -101,19 +97,18 @@
0,
status.HTTP_200_OK,
),
( # Bad anonymity
( # Not provided anonymity
{
"subject": "test_subject",
"text": "test text",
"mark_kindness": 1,
"mark_freebie": -2,
"mark_clarity": 0,
"is_anonymous": 'asd',
},
0,
status.HTTP_422_UNPROCESSABLE_ENTITY,
status.HTTP_200_OK,
),
( # Not provided anonymity
( # Bad anonymity
{
"subject": "test_subject",
"text": "test text",
Expand Down Expand Up @@ -261,13 +256,91 @@ def test_comments_by_user_id(client, lecturers_with_comments, user_id, response_
def test_review_comment(client, dbsession, unreviewed_comment, comment, review_status, response_status, is_reviewed):
commment_to_reivew = comment if is_reviewed else unreviewed_comment
query = {"review_status": review_status}
response = client.patch(f"{url}/{commment_to_reivew.uuid}", params=query)
response = client.patch(f"{url}/{commment_to_reivew.uuid}/review", params=query)
assert response.status_code == response_status
if response.status_code == status.HTTP_200_OK:
dbsession.refresh(commment_to_reivew)
assert commment_to_reivew.review_status == ReviewStatus(review_status)


@pytest.mark.parametrize(
'body, response_status',
[
(
{
"subject": "test_subject",
"text": "test_text",
"mark_kindness": 0,
"mark_freebie": -2,
"mark_clarity": 0,
},
status.HTTP_200_OK,
),
(
{
"subject": 0,
"text": "test_text",
"mark_kindness": 0,
"mark_freebie": -2,
"mark_clarity": 0,
},
status.HTTP_422_UNPROCESSABLE_ENTITY,
),
( # Отсутсвует одно поле
{
"subject": "test_subject",
"mark_kindness": 0,
"mark_freebie": -2,
"mark_clarity": 0,
},
status.HTTP_200_OK,
),
(
{
"subject": "test_subject",
"text": "test_text",
"mark_kindness": 5,
"mark_freebie": -2,
"mark_clarity": 0,
},
status.HTTP_400_BAD_REQUEST,
),
( # Отсутсвует все поля
{},
Comment thread
DR0P-database marked this conversation as resolved.
status.HTTP_409_CONFLICT,
),
( # Переданы НЕизмененные поля
{
"subject": "subject",
"text": "comment",
"mark_kindness": 1,
"mark_clarity": 1,
"mark_freebie": 1,
},
status.HTTP_409_CONFLICT,
),
( # НЕизмененным перелано одно поле
{
"subject": "asf",
"text": "asf",
"mark_kindness": 2,
"mark_clarity": 2,
"mark_freebie": 1,
},
status.HTTP_409_CONFLICT,
),
],
)
def test_update_comment(client, dbsession, nonanonymous_comment, body, response_status):
response = client.patch(f"{url}/{nonanonymous_comment.uuid}", json=body)
assert response.status_code == response_status
if response.status_code == status.HTTP_200_OK:
Comment thread
DR0P-database marked this conversation as resolved.
dbsession.refresh(nonanonymous_comment)
assert nonanonymous_comment.review_status == ReviewStatus.PENDING
for k, v in body.items():
assert getattr(nonanonymous_comment, k, None) == v # Есть ли изменения в БД


def test_delete_comment(client, dbsession, comment):
response = client.delete(f'{url}/{comment.uuid}')
assert response.status_code == status.HTTP_200_OK
Expand Down