diff --git a/rating_api/models/db.py b/rating_api/models/db.py index ab645c6..1c0e1b8 100644 --- a/rating_api/models/db.py +++ b/rating_api/models/db.py @@ -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 @@ -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) @@ -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) diff --git a/rating_api/routes/lecturer.py b/rating_api/routes/lecturer.py index 99c0cab..1bdd563 100644 --- a/rating_api/routes/lecturer.py +++ b/rating_api/routes/lecturer.py @@ -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 @@ -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'`, то возвращаются одобренные комментарии к преподавателю. @@ -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: @@ -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 diff --git a/rating_api/schemas/models.py b/rating_api/schemas/models.py index de510a8..157b26d 100644 --- a/rating_api/schemas/models.py +++ b/rating_api/schemas/models.py @@ -17,6 +17,7 @@ class CommentGet(Base): mark_kindness: int mark_freebie: int mark_clarity: int + mark_general: float lecturer_id: int