Skip to content

Commit

Permalink
Merge branch 'main' into timestamp
Browse files Browse the repository at this point in the history
  • Loading branch information
zipperman1 authored Nov 24, 2024
2 parents 7f32918 + cf3f423 commit c73f67a
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 43 deletions.
21 changes: 19 additions & 2 deletions rating_api/models/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

from sqlalchemy import UUID, Boolean, DateTime
from sqlalchemy import Enum as DbEnum
from sqlalchemy import ForeignKey, Integer, String, and_, func, or_, true
from sqlalchemy.ext.hybrid import hybrid_method
from sqlalchemy import ForeignKey, Integer, String, UnaryExpression, and_, 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

from rating_api.settings import get_settings

Expand Down Expand Up @@ -60,6 +61,18 @@ def search_by_subject(self, query: str) -> bool:
response = and_(Comment.review_status == ReviewStatus.APPROVED, func.lower(Comment.subject).contains(query))
return response

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

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


class Comment(BaseDbModel):
uuid: Mapped[uuid.UUID] = mapped_column(UUID, primary_key=True, default=uuid.uuid4)
Expand All @@ -80,6 +93,10 @@ class Comment(BaseDbModel):
review_status: Mapped[ReviewStatus] = mapped_column(DbEnum(ReviewStatus, native_enum=False), nullable=False)
is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)

@hybrid_property
def mark_general(self):
return (self.mark_kindness + self.mark_freebie + self.mark_clarity) / 3


class LecturerUserComment(BaseDbModel):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
Expand Down
23 changes: 21 additions & 2 deletions rating_api/routes/comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,19 @@ async def create_comment(lecturer_id: int, comment_info: CommentPost, user=Depen
- datetime.datetime.utcnow()
)

# Сначала добавляем с user_id, который мы получили при авторизации,
# в LecturerUserComment, чтобы нельзя было слишком быстро добавлять комментарии
LecturerUserComment.create(session=db.session, lecturer_id=lecturer_id, user_id=user.get('id'))

# Обрабатываем анонимность комментария, и удаляем этот флаг чтобы добавить запись в БД
user_id = None if comment_info.is_anonymous else user.get('id')

new_comment = Comment.create(
session=db.session, **comment_info.model_dump(), lecturer_id=lecturer_id, review_status=ReviewStatus.PENDING
session=db.session,
**comment_info.model_dump(exclude={"is_anonymous"}),
lecturer_id=lecturer_id,
user_id=user_id,
review_status=ReviewStatus.PENDING,
)
return CommentGet.model_validate(new_comment)

Expand All @@ -72,6 +82,7 @@ async def get_comments(
limit: int = 10,
offset: int = 0,
lecturer_id: int | None = None,
user_id: int | None = None,
order_by: list[Literal["create_ts"]] = Query(default=[]),
unreviewed: bool = False,
user=Depends(UnionAuth(scopes=['rating.comment.review'], auto_error=False, allow_none=True)),
Expand All @@ -89,15 +100,21 @@ async def get_comments(
`lecturer_id` - вернет все комментарии для преподавателя с конкретным id, по дефолту возвращает вообще все аппрувнутые комментарии.
`user_id` - вернет все комментарии пользователя с конкретным id
`unreviewed` - вернет все непроверенные комментарии, если True. По дефолту False.
"""
comments = Comment.query(session=db.session).all()
if not comments:
raise ObjectNotFound(Comment, 'all')
result = CommentGetAll(limit=limit, offset=offset, total=len(comments))
result.comments = comments
if lecturer_id:
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]

if unreviewed:
if not user:
raise ForbiddenAction(Comment)
Expand Down Expand Up @@ -131,9 +148,11 @@ async def review_comment(
`approved` - комментарий одобрен и возвращается при запросе лектора
`dismissed` - комментарий отклонен, не отображается в запросе лектора
"""

check_comment: Comment = Comment.query(session=db.session).filter(Comment.uuid == uuid).one_or_none()
if not check_comment:
raise ObjectNotFound(Comment, uuid)

return CommentGet.model_validate(Comment.update(session=db.session, id=uuid, review_status=review_status))


Expand Down
57 changes: 34 additions & 23 deletions rating_api/routes/lecturer.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,10 @@ async def get_lecturer(id: int, info: list[Literal["comments", "mark"]] = Query(
if "comments" in info and approved_comments:
result.comments = approved_comments
if "mark" in info and approved_comments:
result.mark_freebie = sum([comment.mark_freebie for comment in approved_comments]) / len(approved_comments)
result.mark_freebie = sum(comment.mark_freebie for comment in approved_comments) / len(approved_comments)
result.mark_kindness = sum(comment.mark_kindness for comment in approved_comments) / len(approved_comments)
result.mark_clarity = sum(comment.mark_clarity for comment in approved_comments) / len(approved_comments)
general_marks = [result.mark_freebie, result.mark_kindness, result.mark_clarity]
result.mark_general = sum(general_marks) / len(general_marks)
result.mark_general = sum(comment.mark_general for comment in approved_comments) / len(approved_comments)
if approved_comments:
result.subjects = list({comment.subject for comment in approved_comments})
return result
Expand All @@ -76,19 +75,21 @@ async def get_lecturers(
limit: int = 10,
offset: int = 0,
info: list[Literal["comments", "mark"]] = Query(default=[]),
order_by: list[Literal["general", '']] = Query(default=[]),
order_by: str = Query(
enum=["mark_kindness", "mark_freebie", "mark_clarity", "mark_general", "last_name"], default="mark_general"
),
subject: str = Query(''),
name: str = Query(''),
asc_order: bool = False,
) -> LecturerGetAll:
"""
Scopes: `["rating.lecturer.read"]`
`limit` - максимальное количество возвращаемых преподавателей
`offset` - нижняя граница получения преподавателей, т.е. если по дефолту первым возвращается преподаватель с условным номером N, то при наличии ненулевого offset будет возвращаться преподаватель с номером N + offset
`order_by` - возможные значения `'general'`.
Если передано `'general'` - возвращается список преподавателей отсортированных по общей оценке
`order_by` - возможные значения `"mark_kindness", "mark_freebie", "mark_clarity", "mark_general", "last_name"`.
Если передано `'last_name'` - возвращается список преподавателей отсортированных по алфавиту по фамилиям
Если передано `'mark_...'` - возвращается список преподавателей отсортированных по конкретной оценке
`info` - возможные значения `'comments'`, `'mark'`.
Если передано `'comments'`, то возвращаются одобренные комментарии к преподавателю.
Expand All @@ -100,16 +101,31 @@ async def get_lecturers(
`name`
Поле для ФИО. Если передано `name` - возвращает всех преподователей, для которых нашлись совпадения с переданной строкой
`asc_order`
Если передано true, сортировать в порядке возрастания
Иначе - в порядке убывания
"""
lecturers_query = Lecturer.query(session=db.session)
if subject:
lecturers_query = lecturers_query.join(Lecturer.comments).filter(Lecturer.search_by_subject(subject))
lecturers_query = lecturers_query.filter(Lecturer.search_by_name(name))
lecturers = lecturers_query.all()
lecturers_query = (
Lecturer.query(session=db.session)
.outerjoin(Lecturer.comments)
.group_by(Lecturer.id)
.filter(Lecturer.search_by_subject(subject))
.filter(Lecturer.search_by_name(name))
.order_by(
Lecturer.order_by_mark(order_by, asc_order)
if "mark" in order_by
else Lecturer.order_by_name(order_by, asc_order)
)
)

lecturers = lecturers_query.offset(offset).limit(limit).all()
lecturers_count = lecturers_query.group_by(Lecturer.id).count()

if not lecturers:
raise ObjectNotFound(Lecturer, 'all')
result = LecturerGetAll(limit=limit, offset=offset, total=len(lecturers))
for db_lecturer in lecturers[offset : limit + offset]:
result = LecturerGetAll(limit=limit, offset=offset, total=lecturers_count)
for db_lecturer in lecturers:
lecturer_to_result: LecturerGet = LecturerGet.model_validate(db_lecturer)
lecturer_to_result.comments = None
if db_lecturer.comments:
Expand All @@ -130,17 +146,12 @@ async def get_lecturers(
lecturer_to_result.mark_clarity = sum(comment.mark_clarity for comment in approved_comments) / len(
approved_comments
)
general_marks = [
lecturer_to_result.mark_freebie,
lecturer_to_result.mark_kindness,
lecturer_to_result.mark_clarity,
]
lecturer_to_result.mark_general = sum(general_marks) / len(general_marks)
lecturer_to_result.mark_general = sum(comment.mark_general for comment in approved_comments) / len(
approved_comments
)
if approved_comments:
lecturer_to_result.subjects = list({comment.subject for comment in approved_comments})
result.lecturers.append(lecturer_to_result)
if "general" in order_by:
result.lecturers.sort(key=lambda item: (item.mark_general is None, item.mark_general))
return result


Expand Down
2 changes: 2 additions & 0 deletions rating_api/schemas/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class CommentGet(Base):
mark_kindness: int
mark_freebie: int
mark_clarity: int
mark_general: float
lecturer_id: int


Expand All @@ -28,6 +29,7 @@ class CommentPost(Base):
mark_kindness: int
mark_freebie: int
mark_clarity: int
is_anonymous: bool = True

@field_validator('mark_kindness', 'mark_freebie', 'mark_clarity')
@classmethod
Expand Down
27 changes: 17 additions & 10 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,22 +121,29 @@ def lecturers(dbsession):
def lecturers_with_comments(dbsession, lecturers):
"""
Creates 4 lecturers(one with flag is_deleted=True)
with 4 comments to non-deleted lecturers 2 approved and one dismissed and one pending.
with 6 comments to non-deleted lecturers 4 approved and one dismissed and one pending.
Two of them have alike names.
Two of them have a different user_id.
"""
comments_data = [
(lecturers[0].id, 0, 'test_subject', ReviewStatus.APPROVED, 1, 1, 1),
(lecturers[0].id, 9990, 'test_subject', ReviewStatus.APPROVED, 1, 1, 1),
(lecturers[0].id, None, 'test_subject1', ReviewStatus.APPROVED, 2, 2, 2),
(lecturers[0].id, 0, 'test_subject2', ReviewStatus.DISMISSED, -1, -1, -1),
(lecturers[0].id, 0, 'test_subject2', ReviewStatus.PENDING, -2, -2, -2),
(lecturers[1].id, 0, 'test_subject', ReviewStatus.APPROVED, 1, 1, 1),
(lecturers[0].id, 9990, 'test_subject2', ReviewStatus.DISMISSED, -1, -1, -1),
(lecturers[0].id, 9990, 'test_subject2', ReviewStatus.PENDING, -2, -2, -2),
(lecturers[0].id, 9991, 'test_subject11', ReviewStatus.APPROVED, 1, 1, 1),
(lecturers[0].id, 9992, 'test_subject12', ReviewStatus.APPROVED, 2, 2, 2),
(lecturers[1].id, 9990, 'test_subject', ReviewStatus.APPROVED, 1, 1, 1),
(lecturers[1].id, None, 'test_subject1', ReviewStatus.APPROVED, -1, -1, -1),
(lecturers[1].id, 0, 'test_subject2', ReviewStatus.DISMISSED, -2, -2, -2),
(lecturers[1].id, 0, 'test_subject2', ReviewStatus.PENDING, -2, -2, -2),
(lecturers[2].id, 0, 'test_subject', ReviewStatus.APPROVED, 1, 1, 1),
(lecturers[1].id, 9990, 'test_subject2', ReviewStatus.DISMISSED, -2, -2, -2),
(lecturers[1].id, 9990, 'test_subject2', ReviewStatus.PENDING, -2, -2, -2),
(lecturers[1].id, 9991, 'test_subject11', ReviewStatus.APPROVED, 1, 1, 1),
(lecturers[1].id, 9992, 'test_subject12', ReviewStatus.APPROVED, -1, -1, -1),
(lecturers[2].id, 9990, 'test_subject', ReviewStatus.APPROVED, 1, 1, 1),
(lecturers[2].id, None, 'test_subject1', ReviewStatus.APPROVED, 0, 0, 0),
(lecturers[2].id, 0, 'test_subject2', ReviewStatus.DISMISSED, 2, 2, 2),
(lecturers[2].id, 0, 'test_subject2', ReviewStatus.PENDING, -2, -2, -2),
(lecturers[2].id, 9990, 'test_subject2', ReviewStatus.DISMISSED, 2, 2, 2),
(lecturers[2].id, 9990, 'test_subject2', ReviewStatus.PENDING, -2, -2, -2),
(lecturers[2].id, 9991, 'test_subject11', ReviewStatus.APPROVED, 1, 1, 1),
(lecturers[2].id, 9992, 'test_subject12', ReviewStatus.APPROVED, 0, 0, 0),
]

comments = [
Expand Down
Loading

0 comments on commit c73f67a

Please sign in to comment.