From c9cbff52fe45285ec4d062dd1a3e59943e391bd6 Mon Sep 17 00:00:00 2001 From: Vladislav <2007.5v.voskoboinik.vladislav@gmail.com> Date: Sun, 24 Aug 2025 14:22:21 +0300 Subject: [PATCH 1/7] post-rank-handler --- .../versions/bbdb2cd2c64d_lecturer_rating.py | 33 +++++++++++++++++++ rating_api/models/db.py | 19 +++++++++++ rating_api/routes/lecturer.py | 16 ++++++++- rating_api/schemas/models.py | 9 +++++ 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 migrations/versions/bbdb2cd2c64d_lecturer_rating.py diff --git a/migrations/versions/bbdb2cd2c64d_lecturer_rating.py b/migrations/versions/bbdb2cd2c64d_lecturer_rating.py new file mode 100644 index 0000000..4887376 --- /dev/null +++ b/migrations/versions/bbdb2cd2c64d_lecturer_rating.py @@ -0,0 +1,33 @@ +"""lecturer-rating + +Revision ID: bbdb2cd2c64d +Revises: fc7cb93684e0 +Create Date: 2025-08-24 13:55:03.326827 + +""" +from alembic import op +import sqlalchemy as sa + + + +revision = 'bbdb2cd2c64d' +down_revision = 'fc7cb93684e0' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table('lecturer_rating', + sa.Column('id', sa.Integer(), nullable=False, comment='Идентификатор препода'), + sa.Column('mark_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка преподавателя, посчитана в dwh'), + sa.Column('mark_kindness_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка доброты, посчитана в dwh'), + sa.Column('mark_clarity_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка понятности, посчитана в dwh'), + sa.Column('mark_freebie_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка халявности, посчитана в dwh'), + sa.Column('rank', sa.Integer(), nullable=True, comment='Место в рейтинге, посчитана в dwh'), + sa.Column('update_ts', sa.DateTime(), nullable=True, comment='Время обновления записи'), + sa.PrimaryKeyConstraint('id') + ) + + +def downgrade(): + op.drop_table('lecturer_rating') diff --git a/rating_api/models/db.py b/rating_api/models/db.py index 6ad4a52..c1452a3 100644 --- a/rating_api/models/db.py +++ b/rating_api/models/db.py @@ -15,6 +15,7 @@ from sqlalchemy import ( ForeignKey, Integer, + Float, String, UnaryExpression, and_, @@ -211,3 +212,21 @@ class CommentReaction(BaseDbModel): edited_at: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.now(datetime.timezone.utc)) user_id: Mapped[int] = mapped_column(Integer, nullable=False) comment = relationship("Comment", back_populates="reactions") + + +class LecturerRating(BaseDbModel): + id: Mapped[int] = mapped_column(Integer, primary_key=True, comment="Идентификатор препода") + mark_weighted: Mapped[float] = mapped_column( + Float, nullable=True, comment="Взвешенная оценка преподавателя, посчитана в dwh" + ) + mark_kindness_weighted: Mapped[float] = mapped_column( + Float, nullable=True, comment="Взвешенная оценка доброты, посчитана в dwh" + ) + mark_clarity_weighted: Mapped[float] = mapped_column( + Float, nullable=True, comment="Взвешенная оценка понятности, посчитана в dwh" + ) + mark_freebie_weighted: Mapped[float] = mapped_column( + Float, nullable=True, comment="Взвешенная оценка халявности, посчитана в dwh" + ) + rank: Mapped[int] = mapped_column(Integer, nullable=True, comment="Место в рейтинге, посчитана в dwh") + update_ts: Mapped[datetime.datetime] = mapped_column(DateTime, nullable=True, comment="Время обновления записи") \ No newline at end of file diff --git a/rating_api/routes/lecturer.py b/rating_api/routes/lecturer.py index ff882bd..38f3a21 100644 --- a/rating_api/routes/lecturer.py +++ b/rating_api/routes/lecturer.py @@ -7,11 +7,12 @@ from sqlalchemy import and_ from rating_api.exceptions import AlreadyExists, ObjectNotFound -from rating_api.models import Comment, Lecturer, LecturerUserComment, ReviewStatus +from rating_api.models import Comment, Lecturer, LecturerRating, LecturerUserComment, ReviewStatus from rating_api.schemas.base import StatusResponseModel from rating_api.schemas.models import ( CommentGet, LecturerGet, + LecturerRank, LecturerGetAll, LecturerPatch, LecturerPost, @@ -223,3 +224,16 @@ async def delete_lecturer( return StatusResponseModel( status="Success", message="Lecturer has been deleted", ru="Преподаватель удален из RatingAPI" ) + + +@lecturer.post("/{rating}", response_model=LecturerRank) +async def create_lecturer_rating( + lecturer_rank_info: LecturerRank, + allow_none = False, +)-> LecturerRank: + """ + Создает рейтинг преподавателя в базе данных RatingAPI + """ + new_lecturer_rating: LecturerRating = LecturerRating.create(session=db.session, **lecturer_rank_info.model_dump()) + db.session.commit() + return LecturerRank.model_validate(new_lecturer_rating) \ No newline at end of file diff --git a/rating_api/schemas/models.py b/rating_api/schemas/models.py index d74e938..160ef2a 100644 --- a/rating_api/schemas/models.py +++ b/rating_api/schemas/models.py @@ -102,6 +102,15 @@ class LecturerGet(Base): mark_weighted: float | None = None comments: list[CommentGet] | None = None +class LecturerRank(Base): + id: int + mark_weighted: float + mark_kindness_weighted: float | None = None + mark_clarity_weighted: float | None = None + mark_freebie_weighted: float | None = None + rank: int + update_ts: datetime.datetime + class LecturerGetAll(Base): lecturers: list[LecturerGet] = [] From 2f47a48b93fc3c8cc6d6a5ad4b83650179028098 Mon Sep 17 00:00:00 2001 From: Vladislav <2007.5v.voskoboinik.vladislav@gmail.com> Date: Sun, 24 Aug 2025 14:25:32 +0300 Subject: [PATCH 2/7] lint-fix --- .../versions/bbdb2cd2c64d_lecturer_rating.py | 31 ++++++++++++------- rating_api/models/db.py | 4 +-- rating_api/routes/lecturer.py | 8 ++--- rating_api/schemas/models.py | 1 + 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/migrations/versions/bbdb2cd2c64d_lecturer_rating.py b/migrations/versions/bbdb2cd2c64d_lecturer_rating.py index 4887376..e5e4a9e 100644 --- a/migrations/versions/bbdb2cd2c64d_lecturer_rating.py +++ b/migrations/versions/bbdb2cd2c64d_lecturer_rating.py @@ -5,9 +5,9 @@ Create Date: 2025-08-24 13:55:03.326827 """ -from alembic import op -import sqlalchemy as sa +import sqlalchemy as sa +from alembic import op revision = 'bbdb2cd2c64d' @@ -17,15 +17,24 @@ def upgrade(): - op.create_table('lecturer_rating', - sa.Column('id', sa.Integer(), nullable=False, comment='Идентификатор препода'), - sa.Column('mark_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка преподавателя, посчитана в dwh'), - sa.Column('mark_kindness_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка доброты, посчитана в dwh'), - sa.Column('mark_clarity_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка понятности, посчитана в dwh'), - sa.Column('mark_freebie_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка халявности, посчитана в dwh'), - sa.Column('rank', sa.Integer(), nullable=True, comment='Место в рейтинге, посчитана в dwh'), - sa.Column('update_ts', sa.DateTime(), nullable=True, comment='Время обновления записи'), - sa.PrimaryKeyConstraint('id') + op.create_table( + 'lecturer_rating', + sa.Column('id', sa.Integer(), nullable=False, comment='Идентификатор препода'), + sa.Column( + 'mark_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка преподавателя, посчитана в dwh' + ), + sa.Column( + 'mark_kindness_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка доброты, посчитана в dwh' + ), + sa.Column( + 'mark_clarity_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка понятности, посчитана в dwh' + ), + sa.Column( + 'mark_freebie_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка халявности, посчитана в dwh' + ), + sa.Column('rank', sa.Integer(), nullable=True, comment='Место в рейтинге, посчитана в dwh'), + sa.Column('update_ts', sa.DateTime(), nullable=True, comment='Время обновления записи'), + sa.PrimaryKeyConstraint('id'), ) diff --git a/rating_api/models/db.py b/rating_api/models/db.py index c1452a3..f244434 100644 --- a/rating_api/models/db.py +++ b/rating_api/models/db.py @@ -13,9 +13,9 @@ ) from sqlalchemy import Enum as DbEnum from sqlalchemy import ( + Float, ForeignKey, Integer, - Float, String, UnaryExpression, and_, @@ -229,4 +229,4 @@ class LecturerRating(BaseDbModel): Float, nullable=True, comment="Взвешенная оценка халявности, посчитана в dwh" ) rank: Mapped[int] = mapped_column(Integer, nullable=True, comment="Место в рейтинге, посчитана в dwh") - update_ts: Mapped[datetime.datetime] = mapped_column(DateTime, nullable=True, comment="Время обновления записи") \ No newline at end of file + update_ts: Mapped[datetime.datetime] = mapped_column(DateTime, nullable=True, comment="Время обновления записи") diff --git a/rating_api/routes/lecturer.py b/rating_api/routes/lecturer.py index 38f3a21..43403dd 100644 --- a/rating_api/routes/lecturer.py +++ b/rating_api/routes/lecturer.py @@ -12,10 +12,10 @@ from rating_api.schemas.models import ( CommentGet, LecturerGet, - LecturerRank, LecturerGetAll, LecturerPatch, LecturerPost, + LecturerRank, LecturersFilter, ) from rating_api.utils.mark import calc_weighted_mark @@ -229,11 +229,11 @@ async def delete_lecturer( @lecturer.post("/{rating}", response_model=LecturerRank) async def create_lecturer_rating( lecturer_rank_info: LecturerRank, - allow_none = False, -)-> LecturerRank: + allow_none=False, +) -> LecturerRank: """ Создает рейтинг преподавателя в базе данных RatingAPI """ new_lecturer_rating: LecturerRating = LecturerRating.create(session=db.session, **lecturer_rank_info.model_dump()) db.session.commit() - return LecturerRank.model_validate(new_lecturer_rating) \ No newline at end of file + return LecturerRank.model_validate(new_lecturer_rating) diff --git a/rating_api/schemas/models.py b/rating_api/schemas/models.py index 160ef2a..0c8acdd 100644 --- a/rating_api/schemas/models.py +++ b/rating_api/schemas/models.py @@ -102,6 +102,7 @@ class LecturerGet(Base): mark_weighted: float | None = None comments: list[CommentGet] | None = None + class LecturerRank(Base): id: int mark_weighted: float From faec6acfd19da3c5fac770a39aedb6b9b5d61506 Mon Sep 17 00:00:00 2001 From: VladislavV Date: Thu, 28 Aug 2025 19:39:58 +0300 Subject: [PATCH 3/7] route-test --- .../versions/bbdb2cd2c64d_lecturer_rating.py | 42 ----- .../versions/beb11fd89531_lecturer_rating.py | 165 ++++++++++++++++++ rating_api/models/db.py | 59 ++++--- rating_api/routes/lecturer.py | 44 +++-- rating_api/schemas/models.py | 9 +- tests/test_routes/test_lecturer.py | 68 ++++++++ 6 files changed, 306 insertions(+), 81 deletions(-) delete mode 100644 migrations/versions/bbdb2cd2c64d_lecturer_rating.py create mode 100644 migrations/versions/beb11fd89531_lecturer_rating.py diff --git a/migrations/versions/bbdb2cd2c64d_lecturer_rating.py b/migrations/versions/bbdb2cd2c64d_lecturer_rating.py deleted file mode 100644 index e5e4a9e..0000000 --- a/migrations/versions/bbdb2cd2c64d_lecturer_rating.py +++ /dev/null @@ -1,42 +0,0 @@ -"""lecturer-rating - -Revision ID: bbdb2cd2c64d -Revises: fc7cb93684e0 -Create Date: 2025-08-24 13:55:03.326827 - -""" - -import sqlalchemy as sa -from alembic import op - - -revision = 'bbdb2cd2c64d' -down_revision = 'fc7cb93684e0' -branch_labels = None -depends_on = None - - -def upgrade(): - op.create_table( - 'lecturer_rating', - sa.Column('id', sa.Integer(), nullable=False, comment='Идентификатор препода'), - sa.Column( - 'mark_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка преподавателя, посчитана в dwh' - ), - sa.Column( - 'mark_kindness_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка доброты, посчитана в dwh' - ), - sa.Column( - 'mark_clarity_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка понятности, посчитана в dwh' - ), - sa.Column( - 'mark_freebie_weighted', sa.Float(), nullable=True, comment='Взвешенная оценка халявности, посчитана в dwh' - ), - sa.Column('rank', sa.Integer(), nullable=True, comment='Место в рейтинге, посчитана в dwh'), - sa.Column('update_ts', sa.DateTime(), nullable=True, comment='Время обновления записи'), - sa.PrimaryKeyConstraint('id'), - ) - - -def downgrade(): - op.drop_table('lecturer_rating') diff --git a/migrations/versions/beb11fd89531_lecturer_rating.py b/migrations/versions/beb11fd89531_lecturer_rating.py new file mode 100644 index 0000000..075f8e6 --- /dev/null +++ b/migrations/versions/beb11fd89531_lecturer_rating.py @@ -0,0 +1,165 @@ +"""lecturer-rating + +Revision ID: beb11fd89531 +Revises: fc7cb93684e0 +Create Date: 2025-08-25 14:59:47.363354 + +""" + +import sqlalchemy as sa +from alembic import op + + +# revision identifiers, used by Alembic. +revision = 'beb11fd89531' +down_revision = 'fc7cb93684e0' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + 'lecturer', + sa.Column( + 'mark_weighted', + sa.Float(), + server_default='0.0', + nullable=False, + comment='Взвешенная оценка преподавателя, посчитана в dwh', + ), + ) + op.add_column( + 'lecturer', + sa.Column( + 'mark_kindness_weighted', + sa.Float(), + server_default='0.0', + nullable=False, + comment='Взвешенная оценка доброты, посчитана в dwh', + ), + ) + op.add_column( + 'lecturer', + sa.Column( + 'mark_clarity_weighted', + sa.Float(), + server_default='0.0', + nullable=False, + comment='Взвешенная оценка понятности, посчитана в dwh', + ), + ) + op.add_column( + 'lecturer', + sa.Column( + 'mark_freebie_weighted', + sa.Float(), + server_default='0.0', + nullable=False, + comment='Взвешенная оценка халявности, посчитана в dwh', + ), + ) + op.add_column( + 'lecturer', + sa.Column( + 'rank', sa.Integer(), server_default='0', nullable=False, comment='Место в рейтинге, посчитана в dwh' + ), + ) + op.add_column( + 'lecturer', + sa.Column( + 'rank_update_ts', + sa.DateTime(), + server_default=sa.func.now(), + nullable=False, + comment='Время обновления записи', + ), + ) + op.alter_column( + 'lecturer', + 'id', + existing_type=sa.INTEGER(), + comment='Идентификатор преподавателя', + existing_nullable=False, + autoincrement=True, + existing_server_default=sa.text("nextval('lecturer_id_seq'::regclass)"), + ) + op.alter_column( + 'lecturer', 'first_name', existing_type=sa.VARCHAR(), comment='Имя препода', existing_nullable=False + ) + op.alter_column( + 'lecturer', 'last_name', existing_type=sa.VARCHAR(), comment='Фамилия препода', existing_nullable=False + ) + op.alter_column( + 'lecturer', 'middle_name', existing_type=sa.VARCHAR(), comment='Отчество препода', existing_nullable=False + ) + op.alter_column( + 'lecturer', 'avatar_link', existing_type=sa.VARCHAR(), comment='Ссылка на аву препода', existing_nullable=True + ) + op.alter_column( + 'lecturer', + 'is_deleted', + existing_type=sa.BOOLEAN(), + comment='Идентификатор софт делита', + existing_nullable=False, + existing_server_default=sa.text('false'), + ) + + +def downgrade(): + op.alter_column( + 'lecturer', + 'is_deleted', + existing_type=sa.BOOLEAN(), + comment=None, + existing_comment='Идентификатор софт делита', + existing_nullable=False, + existing_server_default=sa.text('false'), + ) + op.alter_column( + 'lecturer', + 'avatar_link', + existing_type=sa.VARCHAR(), + comment=None, + existing_comment='Ссылка на аву препода', + existing_nullable=True, + ) + op.alter_column( + 'lecturer', + 'middle_name', + existing_type=sa.VARCHAR(), + comment=None, + existing_comment='Отчество препода', + existing_nullable=False, + ) + op.alter_column( + 'lecturer', + 'last_name', + existing_type=sa.VARCHAR(), + comment=None, + existing_comment='Фамилия препода', + existing_nullable=False, + ) + op.alter_column( + 'lecturer', + 'first_name', + existing_type=sa.VARCHAR(), + comment=None, + existing_comment='Имя препода', + existing_nullable=False, + ) + op.alter_column( + 'lecturer', + 'id', + existing_type=sa.INTEGER(), + comment=None, + existing_comment='Идентификатор преподавателя', + existing_nullable=False, + autoincrement=True, + existing_server_default=sa.text("nextval('lecturer_id_seq'::regclass)"), + ) + op.drop_column('lecturer', 'rank_update_ts') + op.drop_column('lecturer', 'rank') + op.drop_column('lecturer', 'mark_freebie_weighted') + op.drop_column('lecturer', 'mark_clarity_weighted') + op.drop_column('lecturer', 'mark_kindness_weighted') + op.drop_column('lecturer', 'mark_weighted') diff --git a/rating_api/models/db.py b/rating_api/models/db.py index f244434..02d130e 100644 --- a/rating_api/models/db.py +++ b/rating_api/models/db.py @@ -44,14 +44,43 @@ class ReviewStatus(str, Enum): class Lecturer(BaseDbModel): - id: Mapped[int] = mapped_column(Integer, primary_key=True) - first_name: Mapped[str] = mapped_column(String, nullable=False) - last_name: Mapped[str] = mapped_column(String, nullable=False) - middle_name: Mapped[str] = mapped_column(String, nullable=False) - avatar_link: Mapped[str] = mapped_column(String, nullable=True) + id: Mapped[int] = mapped_column(Integer, primary_key=True, comment="Идентификатор преподавателя") + first_name: Mapped[str] = mapped_column(String, nullable=False, comment="Имя препода") + last_name: Mapped[str] = mapped_column(String, nullable=False, comment="Фамилия препода") + middle_name: Mapped[str] = mapped_column(String, nullable=False, comment="Отчество препода") + avatar_link: Mapped[str] = mapped_column(String, nullable=True, comment="Ссылка на аву препода") timetable_id: Mapped[int] comments: Mapped[list[Comment]] = relationship("Comment", back_populates="lecturer") - is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) + mark_weighted: Mapped[float] = mapped_column( + Float, + nullable=False, + server_default='0.0', + default=0, + comment="Взвешенная оценка преподавателя, посчитана в dwh", + ) + mark_kindness_weighted: Mapped[float] = mapped_column( + Float, nullable=False, server_default='0.0', default=0, comment="Взвешенная оценка доброты, посчитана в dwh" + ) + mark_clarity_weighted: Mapped[float] = mapped_column( + Float, nullable=False, server_default='0.0', default=0, comment="Взвешенная оценка понятности, посчитана в dwh" + ) + mark_freebie_weighted: Mapped[float] = mapped_column( + Float, nullable=False, server_default='0.0', default=0, comment="Взвешенная оценка халявности, посчитана в dwh" + ) + rank: Mapped[int] = mapped_column( + Integer, nullable=False, server_default='0', default=0, comment="Место в рейтинге, посчитана в dwh" + ) + rank_update_ts: Mapped[datetime.datetime] = mapped_column( + DateTime, + nullable=False, + server_default=func.now(), + default=datetime.datetime.now(), + comment="Время обновления записи", + ) + + is_deleted: Mapped[bool] = mapped_column( + Boolean, nullable=False, default=False, comment="Идентификатор софт делита" + ) @hybrid_method def search_by_name(self, query: str) -> bool: @@ -212,21 +241,3 @@ class CommentReaction(BaseDbModel): edited_at: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.now(datetime.timezone.utc)) user_id: Mapped[int] = mapped_column(Integer, nullable=False) comment = relationship("Comment", back_populates="reactions") - - -class LecturerRating(BaseDbModel): - id: Mapped[int] = mapped_column(Integer, primary_key=True, comment="Идентификатор препода") - mark_weighted: Mapped[float] = mapped_column( - Float, nullable=True, comment="Взвешенная оценка преподавателя, посчитана в dwh" - ) - mark_kindness_weighted: Mapped[float] = mapped_column( - Float, nullable=True, comment="Взвешенная оценка доброты, посчитана в dwh" - ) - mark_clarity_weighted: Mapped[float] = mapped_column( - Float, nullable=True, comment="Взвешенная оценка понятности, посчитана в dwh" - ) - mark_freebie_weighted: Mapped[float] = mapped_column( - Float, nullable=True, comment="Взвешенная оценка халявности, посчитана в dwh" - ) - rank: Mapped[int] = mapped_column(Integer, nullable=True, comment="Место в рейтинге, посчитана в dwh") - update_ts: Mapped[datetime.datetime] = mapped_column(DateTime, nullable=True, comment="Время обновления записи") diff --git a/rating_api/routes/lecturer.py b/rating_api/routes/lecturer.py index 43403dd..62a07c7 100644 --- a/rating_api/routes/lecturer.py +++ b/rating_api/routes/lecturer.py @@ -1,13 +1,15 @@ +import datetime from typing import Literal from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends, Query +from fastapi.exceptions import ValidationException from fastapi_filter import FilterDepends from fastapi_sqlalchemy import db from sqlalchemy import and_ from rating_api.exceptions import AlreadyExists, ObjectNotFound -from rating_api.models import Comment, Lecturer, LecturerRating, LecturerUserComment, ReviewStatus +from rating_api.models import Comment, Lecturer, LecturerUserComment, ReviewStatus from rating_api.schemas.base import StatusResponseModel from rating_api.schemas.models import ( CommentGet, @@ -15,8 +17,8 @@ LecturerGetAll, LecturerPatch, LecturerPost, - LecturerRank, LecturersFilter, + LecturerWithRank, ) from rating_api.utils.mark import calc_weighted_mark @@ -204,9 +206,7 @@ async def update_lecturer( @lecturer.delete("/{id}", response_model=StatusResponseModel) -async def delete_lecturer( - id: int, _=Depends(UnionAuth(scopes=["rating.lecturer.delete"], allow_none=False, auto_error=True)) -): +async def delete_lecturer(id: int, allow_none=False, auto_error=True): """ Scopes: `["rating.lecturer.delete"]` """ @@ -226,14 +226,30 @@ async def delete_lecturer( ) -@lecturer.post("/{rating}", response_model=LecturerRank) -async def create_lecturer_rating( - lecturer_rank_info: LecturerRank, - allow_none=False, -) -> LecturerRank: +@lecturer.patch("/import_rating", response_model=list[LecturerWithRank]) +async def update_lecturer_rating( + lecturer_rank_info: list[LecturerWithRank], + _=Depends(UnionAuth(scopes=["rating.lecturer.update_rating"], allow_none=False, auto_error=True)), +) -> list[LecturerWithRank]: """ - Создает рейтинг преподавателя в базе данных RatingAPI + Обновляет рейтинг преподавателя в базе данных RatingAPI + """ - new_lecturer_rating: LecturerRating = LecturerRating.create(session=db.session, **lecturer_rank_info.model_dump()) - db.session.commit() - return LecturerRank.model_validate(new_lecturer_rating) + updated_lecturers = [] + for lecturer_rank in lecturer_rank_info: + try: + LecturerWithRank.model_validate(lecturer_rank) + except ValidationException: + raise ValidationException + + lecturer_rank_dumped = lecturer_rank.model_dump() + lecturer_rank_dumped["update_ts"] = datetime.datetime.now(datetime.timezone.utc()) + if Lecturer.get(id=lecturer_rank_dumped["id"], session=db.session): + updated_lecturers.append( + Lecturer.update(id=lecturer_rank_dumped["id"], session=db.session, **lecturer_rank_dumped) + ) + else: + raise ObjectNotFound(Lecturer, id) + + db.commit() + return updated_lecturers diff --git a/rating_api/schemas/models.py b/rating_api/schemas/models.py index 0c8acdd..81b65be 100644 --- a/rating_api/schemas/models.py +++ b/rating_api/schemas/models.py @@ -103,8 +103,15 @@ class LecturerGet(Base): comments: list[CommentGet] | None = None -class LecturerRank(Base): +class LecturerWithRank(Base): id: int + first_name: str + last_name: str + middle_name: str + avatar_link: str | None = None + subjects: list[str] | None = None + timetable_id: int + comments: list[CommentGet] | None = None mark_weighted: float mark_kindness_weighted: float | None = None mark_clarity_weighted: float | None = None diff --git a/tests/test_routes/test_lecturer.py b/tests/test_routes/test_lecturer.py index 8c42600..e28b6c0 100644 --- a/tests/test_routes/test_lecturer.py +++ b/tests/test_routes/test_lecturer.py @@ -1,4 +1,6 @@ +import datetime import logging +from unittest.mock import AsyncMock, patch import pytest from fastapi_sqlalchemy import db @@ -357,3 +359,69 @@ def test_delete_lecturer(client, dbsession, lecturers_with_comments): # trying to get deleted response = client.get(f'{url}/{lecturers[0].id}') assert response.status_code == status.HTTP_404_NOT_FOUND + + +""" +@pytest.fixture +def mock_union_auth(): + with patch("rating_api.routes.lecturer.UnionAuth.__call__", new_callable=AsyncMock) as mock_auth: + mock_auth.return_value = True + yield mock_auth + +""" + + +@pytest.mark.parametrize( + 'body, response_status', + [ + ( + { + "id": 101, + "first_name": "Иван", + "last_name": "Петров", + "middle_name": "Сергеевич", + "avatar_link": "https://example.com/avatars/ipetrov.jpg", + "subjects": ["Математика", "Физика"], + "timetable_id": 5001, + "comments": None, + "mark_weighted": 4.7, + "mark_kindness_weighted": 4.9, + "mark_clarity_weighted": 4.5, + "mark_freebie_weighted": 3.8, + "rank": 12, + "update_ts": datetime.datetime.now(datetime.timezone.utc).isoformat(), + }, + status.HTTP_200_OK, + ), + ( + { + "id": 1, + "first_name": "Иван", + "last_name": "Иванов", + "middle_name": "Иванович", + "avatar_link": None, + "subjects": ["Математика", "Физика"], + "timetable_id": 100, + "comments": None, + "mark_weighted": 4.5, + "mark_kindness_weighted": 4.0, + "mark_clarity_weighted": 4.8, + "mark_freebie_weighted": 3.9, + "rank": 5, + "update_ts": datetime.datetime.now(datetime.timezone.utc).isoformat(), + }, + status.HTTP_200_OK, + ), + ], +) +def test_lecturer_rating_update(client, dbsession, body, response_status): + response = client.patch('/lecturer/import_rating', json=[body]) + if response.status_code != response_status: + print(f"Response: {response.json()}") + if response_status == status.HTTP_200_OK: + data = response.json() + assert isinstance(data, list) + updated_lecturer = data[0] + assert updated_lecturer["id"] == body["id"] + assert updated_lecturer["rank"] == body["rank"] + assert updated_lecturer["first_name"] == body["first_name"] From eaca41aab3f378041bc97eb4fa9932b0cc44dbcf Mon Sep 17 00:00:00 2001 From: VladislavV Date: Thu, 28 Aug 2025 23:36:29 +0300 Subject: [PATCH 4/7] route-lecturer-update-fix --- rating_api/routes/lecturer.py | 60 ++++++++++++------------- rating_api/schemas/models.py | 10 ++--- tests/conftest.py | 14 +++--- tests/test_routes/test_lecturer.py | 71 ++++++++++++++---------------- 4 files changed, 73 insertions(+), 82 deletions(-) diff --git a/rating_api/routes/lecturer.py b/rating_api/routes/lecturer.py index 62a07c7..2ba2041 100644 --- a/rating_api/routes/lecturer.py +++ b/rating_api/routes/lecturer.py @@ -46,6 +46,33 @@ async def create_lecturer( raise AlreadyExists(Lecturer, lecturer_info.timetable_id) +@lecturer.patch("/import_rating", response_model=list[LecturerWithRank]) +async def update_lecturer_rating( + lecturer_rank_info: list[LecturerWithRank], + _=Depends(UnionAuth(scopes=["rating.lecturer.update_rating"], allow_none=False, auto_error=True)), +) -> list[LecturerWithRank]: + """ + Обновляет рейтинг преподавателя в базе данных RatingAPI + """ + updated_lecturers = [] + for lecturer_rank in lecturer_rank_info: + try: + LecturerWithRank.model_validate(lecturer_rank) + except ValidationException: + raise ValidationException + + lecturer_rank_dumped = lecturer_rank.model_dump() + lecturer_rank_dumped["update_ts"] = datetime.datetime.now(tz=datetime.timezone.utc) + + # Извлекаем ID и удаляем его из словаря, чтобы не передавать дважды + lecturer_id = lecturer_rank_dumped.pop("id") + + if Lecturer.get(id=lecturer_id, session=db.session): + updated_lecturers.append(Lecturer.update(id=lecturer_id, session=db.session, **lecturer_rank_dumped)) + + return updated_lecturers + + @lecturer.get("/{id}", response_model=LecturerGet) async def get_lecturer(id: int, info: list[Literal["comments", "mark"]] = Query(default=[])) -> LecturerGet: """ @@ -206,7 +233,9 @@ async def update_lecturer( @lecturer.delete("/{id}", response_model=StatusResponseModel) -async def delete_lecturer(id: int, allow_none=False, auto_error=True): +async def delete_lecturer( + id: int, _=Depends(UnionAuth(scopes=["rating.lecturer.delete"], allow_none=False, auto_error=True)) +): """ Scopes: `["rating.lecturer.delete"]` """ @@ -224,32 +253,3 @@ async def delete_lecturer(id: int, allow_none=False, auto_error=True): return StatusResponseModel( status="Success", message="Lecturer has been deleted", ru="Преподаватель удален из RatingAPI" ) - - -@lecturer.patch("/import_rating", response_model=list[LecturerWithRank]) -async def update_lecturer_rating( - lecturer_rank_info: list[LecturerWithRank], - _=Depends(UnionAuth(scopes=["rating.lecturer.update_rating"], allow_none=False, auto_error=True)), -) -> list[LecturerWithRank]: - """ - Обновляет рейтинг преподавателя в базе данных RatingAPI - - """ - updated_lecturers = [] - for lecturer_rank in lecturer_rank_info: - try: - LecturerWithRank.model_validate(lecturer_rank) - except ValidationException: - raise ValidationException - - lecturer_rank_dumped = lecturer_rank.model_dump() - lecturer_rank_dumped["update_ts"] = datetime.datetime.now(datetime.timezone.utc()) - if Lecturer.get(id=lecturer_rank_dumped["id"], session=db.session): - updated_lecturers.append( - Lecturer.update(id=lecturer_rank_dumped["id"], session=db.session, **lecturer_rank_dumped) - ) - else: - raise ObjectNotFound(Lecturer, id) - - db.commit() - return updated_lecturers diff --git a/rating_api/schemas/models.py b/rating_api/schemas/models.py index 81b65be..f5fb227 100644 --- a/rating_api/schemas/models.py +++ b/rating_api/schemas/models.py @@ -109,15 +109,13 @@ class LecturerWithRank(Base): last_name: str middle_name: str avatar_link: str | None = None - subjects: list[str] | None = None timetable_id: int - comments: list[CommentGet] | None = None mark_weighted: float - mark_kindness_weighted: float | None = None - mark_clarity_weighted: float | None = None - mark_freebie_weighted: float | None = None + mark_kindness_weighted: float + mark_clarity_weighted: float + mark_freebie_weighted: float rank: int - update_ts: datetime.datetime + update_ts: datetime.datetime | None = None class LecturerGetAll(Base): diff --git a/tests/conftest.py b/tests/conftest.py index 32bc43d..3742f8f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -184,25 +184,25 @@ def lecturers(dbsession): Creates 4 lecturers(one with flag is_deleted=True) """ lecturers_data = [ - ("test_fname1", "test_lname1", "test_mname1", 9900), - ("test_fname2", "test_lname2", "test_mname2", 9901), - ("Bibka", "Bobka", "Bobkovich", 9902), + (1, "test_fname1", "test_lname1", "test_mname1", 9900), + (2, "test_fname2", "test_lname2", "test_mname2", 9901), + (3, "Bibka", "Bobka", "Bobkovich", 9902), ] lecturers = [ - Lecturer(first_name=fname, last_name=lname, middle_name=mname, timetable_id=timetable_id) - for fname, lname, mname, timetable_id in lecturers_data + Lecturer(id=lecturer_id, first_name=fname, last_name=lname, middle_name=mname, timetable_id=timetable_id) + for lecturer_id, fname, lname, mname, timetable_id in lecturers_data ] lecturers.append( - Lecturer(first_name='test_fname3', last_name='test_lname3', middle_name='test_mname3', timetable_id=9903) + Lecturer(id=4, first_name='test_fname3', last_name='test_lname3', middle_name='test_mname3', timetable_id=9903) ) lecturers[-1].is_deleted = True for lecturer in lecturers: dbsession.add(lecturer) dbsession.commit() yield lecturers + for lecturer in lecturers: - dbsession.refresh(lecturer) for row in lecturer.comments: dbsession.delete(row) lecturer_user_comments = dbsession.query(LecturerUserComment).filter( diff --git a/tests/test_routes/test_lecturer.py b/tests/test_routes/test_lecturer.py index e28b6c0..3771e22 100644 --- a/tests/test_routes/test_lecturer.py +++ b/tests/test_routes/test_lecturer.py @@ -1,6 +1,6 @@ import datetime import logging -from unittest.mock import AsyncMock, patch +from unittest.mock import patch import pytest from fastapi_sqlalchemy import db @@ -361,54 +361,38 @@ def test_delete_lecturer(client, dbsession, lecturers_with_comments): assert response.status_code == status.HTTP_404_NOT_FOUND -""" -@pytest.fixture -def mock_union_auth(): - with patch("rating_api.routes.lecturer.UnionAuth.__call__", new_callable=AsyncMock) as mock_auth: - mock_auth.return_value = True - yield mock_auth - -""" - - +@pytest.mark.usefixtures('lecturers') @pytest.mark.parametrize( 'body, response_status', [ ( { - "id": 101, - "first_name": "Иван", - "last_name": "Петров", - "middle_name": "Сергеевич", - "avatar_link": "https://example.com/avatars/ipetrov.jpg", - "subjects": ["Математика", "Физика"], - "timetable_id": 5001, - "comments": None, - "mark_weighted": 4.7, - "mark_kindness_weighted": 4.9, - "mark_clarity_weighted": 4.5, - "mark_freebie_weighted": 3.8, + "id": 1, + "first_name": "test_fname1", + "last_name": "test_lname1", + "middle_name": "test_mname1", + "timetable_id": 9900, + "mark_weighted": 4.5, + "mark_kindness_weighted": 4.0, + "mark_clarity_weighted": 4.8, + "mark_freebie_weighted": 3.9, "rank": 12, - "update_ts": datetime.datetime.now(datetime.timezone.utc).isoformat(), }, status.HTTP_200_OK, ), ( { - "id": 1, - "first_name": "Иван", - "last_name": "Иванов", - "middle_name": "Иванович", + "id": 2, + "first_name": "test_fname2", + "last_name": "test_lname2", + "middle_name": "test_mname2", "avatar_link": None, - "subjects": ["Математика", "Физика"], - "timetable_id": 100, - "comments": None, + "timetable_id": 9901, "mark_weighted": 4.5, "mark_kindness_weighted": 4.0, "mark_clarity_weighted": 4.8, "mark_freebie_weighted": 3.9, "rank": 5, - "update_ts": datetime.datetime.now(datetime.timezone.utc).isoformat(), }, status.HTTP_200_OK, ), @@ -416,12 +400,21 @@ def mock_union_auth(): ) def test_lecturer_rating_update(client, dbsession, body, response_status): response = client.patch('/lecturer/import_rating', json=[body]) - if response.status_code != response_status: - print(f"Response: {response.json()}") + if response_status == status.HTTP_200_OK: - data = response.json() - assert isinstance(data, list) - updated_lecturer = data[0] - assert updated_lecturer["id"] == body["id"] + + lecturers = response.json() + assert isinstance(lecturers, list) + + updated_lecturer = None + for lecturer in lecturers: + if lecturer["id"] == body["id"]: + updated_lecturer = lecturer + break + + assert updated_lecturer is not None, f"Lecturer with id {body['id']} not found in response" + assert updated_lecturer["rank"] == body["rank"] - assert updated_lecturer["first_name"] == body["first_name"] + + if "first_name" in body: + assert updated_lecturer["first_name"] == body["first_name"] From d20a223434fc49b386dc6642b242037a3a6e7680 Mon Sep 17 00:00:00 2001 From: VladislavV Date: Sat, 30 Aug 2025 22:10:09 +0300 Subject: [PATCH 5/7] change-update-response --- rating_api/routes/lecturer.py | 28 ++++++++++++++++++++++------ tests/test_routes/test_lecturer.py | 18 ++++-------------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/rating_api/routes/lecturer.py b/rating_api/routes/lecturer.py index 2ba2041..aa8fc3a 100644 --- a/rating_api/routes/lecturer.py +++ b/rating_api/routes/lecturer.py @@ -46,31 +46,47 @@ async def create_lecturer( raise AlreadyExists(Lecturer, lecturer_info.timetable_id) -@lecturer.patch("/import_rating", response_model=list[LecturerWithRank]) +@lecturer.patch("/import_rating", response_model=dict) async def update_lecturer_rating( lecturer_rank_info: list[LecturerWithRank], _=Depends(UnionAuth(scopes=["rating.lecturer.update_rating"], allow_none=False, auto_error=True)), -) -> list[LecturerWithRank]: +) -> dict: """ Обновляет рейтинг преподавателя в базе данных RatingAPI """ updated_lecturers = [] + response = { + "updated": 0, + "failed": 0, + "updated_id": [], + "failed_id": [], + } for lecturer_rank in lecturer_rank_info: + success_fl = True try: LecturerWithRank.model_validate(lecturer_rank) except ValidationException: - raise ValidationException + success_fl = False lecturer_rank_dumped = lecturer_rank.model_dump() lecturer_rank_dumped["update_ts"] = datetime.datetime.now(tz=datetime.timezone.utc) - # Извлекаем ID и удаляем его из словаря, чтобы не передавать дважды lecturer_id = lecturer_rank_dumped.pop("id") if Lecturer.get(id=lecturer_id, session=db.session): updated_lecturers.append(Lecturer.update(id=lecturer_id, session=db.session, **lecturer_rank_dumped)) - - return updated_lecturers + else: + success_fl = False + + if success_fl: + response["updated"] += 1 + response["updated_id"].append(lecturer_id) + else: + response["failed"] += 1 + response["failed_id"].append(lecturer_id) + + + return response @lecturer.get("/{id}", response_model=LecturerGet) diff --git a/tests/test_routes/test_lecturer.py b/tests/test_routes/test_lecturer.py index 3771e22..0a33115 100644 --- a/tests/test_routes/test_lecturer.py +++ b/tests/test_routes/test_lecturer.py @@ -403,18 +403,8 @@ def test_lecturer_rating_update(client, dbsession, body, response_status): if response_status == status.HTTP_200_OK: - lecturers = response.json() - assert isinstance(lecturers, list) + response_dict = response.json() + assert isinstance(response_dict, dict) - updated_lecturer = None - for lecturer in lecturers: - if lecturer["id"] == body["id"]: - updated_lecturer = lecturer - break - - assert updated_lecturer is not None, f"Lecturer with id {body['id']} not found in response" - - assert updated_lecturer["rank"] == body["rank"] - - if "first_name" in body: - assert updated_lecturer["first_name"] == body["first_name"] + assert response_dict["failed"] == 0 + From 280f88f4d5e3ab5c1e4cc714e3d2c107d4896fb3 Mon Sep 17 00:00:00 2001 From: VladislavV Date: Sat, 30 Aug 2025 22:11:58 +0300 Subject: [PATCH 6/7] lint-fix --- rating_api/routes/lecturer.py | 3 +-- tests/test_routes/test_lecturer.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/rating_api/routes/lecturer.py b/rating_api/routes/lecturer.py index aa8fc3a..2ee3a33 100644 --- a/rating_api/routes/lecturer.py +++ b/rating_api/routes/lecturer.py @@ -77,7 +77,7 @@ async def update_lecturer_rating( updated_lecturers.append(Lecturer.update(id=lecturer_id, session=db.session, **lecturer_rank_dumped)) else: success_fl = False - + if success_fl: response["updated"] += 1 response["updated_id"].append(lecturer_id) @@ -85,7 +85,6 @@ async def update_lecturer_rating( response["failed"] += 1 response["failed_id"].append(lecturer_id) - return response diff --git a/tests/test_routes/test_lecturer.py b/tests/test_routes/test_lecturer.py index 0a33115..f7a207e 100644 --- a/tests/test_routes/test_lecturer.py +++ b/tests/test_routes/test_lecturer.py @@ -407,4 +407,3 @@ def test_lecturer_rating_update(client, dbsession, body, response_status): assert isinstance(response_dict, dict) assert response_dict["failed"] == 0 - From 9908360199b5ac1d705b5c942f9d3aeb856d2230 Mon Sep 17 00:00:00 2001 From: VladislavV Date: Sun, 31 Aug 2025 20:45:40 +0300 Subject: [PATCH 7/7] rating-refactor --- rating_api/models/db.py | 29 ++++-------- rating_api/routes/lecturer.py | 37 ++------------- rating_api/schemas/models.py | 9 ++-- tests/test_routes/test_lecturer.py | 76 +++++++----------------------- 4 files changed, 34 insertions(+), 117 deletions(-) diff --git a/rating_api/models/db.py b/rating_api/models/db.py index 02d130e..18dae0b 100644 --- a/rating_api/models/db.py +++ b/rating_api/models/db.py @@ -111,11 +111,15 @@ def order_by_mark( self, query: str, asc_order: bool ) -> tuple[UnaryExpression[float], InstrumentedAttribute, InstrumentedAttribute]: if "mark_weighted" in query: - comments_num = func.count(self.comments).filter(Comment.review_status == ReviewStatus.APPROVED) - lecturer_mark_general = func.avg(Comment.mark_general).filter( - Comment.review_status == ReviewStatus.APPROVED - ) - expression = calc_weighted_mark(lecturer_mark_general, comments_num, Lecturer.mean_mark_general()) + expression = self.mark_weighted + elif "mark_clarity_weighted" in query: + expression = self.mark_clarity_weighted + elif "mark_freebie_weighted" in query: + expression = self.mark_freebie_weighted + elif "mark_kindness_weighted" in query: + expression = self.mark_kindness_weighted + elif "rank" in query: + expression = self.rank else: expression = func.avg(getattr(Comment, query)).filter(Comment.review_status == ReviewStatus.APPROVED) if not asc_order: @@ -128,21 +132,6 @@ def order_by_name( ) -> tuple[UnaryExpression[str] | InstrumentedAttribute, InstrumentedAttribute]: return (getattr(Lecturer, query) if asc_order else getattr(Lecturer, query).desc()), Lecturer.id - @staticmethod - def mean_mark_general() -> float: - mark_general_rows = ( - db.session.query(func.avg(Comment.mark_general)) - .filter(Comment.review_status == ReviewStatus.APPROVED) - .group_by(Comment.lecturer_id) - .all() - ) - mean_mark_general = float( - sum(mark_general_row[0] for mark_general_row in mark_general_rows) / len(mark_general_rows) - if len(mark_general_rows) != 0 - else 0 - ) - return mean_mark_general - class Comment(BaseDbModel): uuid: Mapped[uuid.UUID] = mapped_column(UUID, primary_key=True, default=uuid.uuid4) diff --git a/rating_api/routes/lecturer.py b/rating_api/routes/lecturer.py index 2ee3a33..c650020 100644 --- a/rating_api/routes/lecturer.py +++ b/rating_api/routes/lecturer.py @@ -89,16 +89,14 @@ async def update_lecturer_rating( @lecturer.get("/{id}", response_model=LecturerGet) -async def get_lecturer(id: int, info: list[Literal["comments", "mark"]] = Query(default=[])) -> LecturerGet: +async def get_lecturer(id: int, info: list[Literal["comments"]] = Query(default=[])) -> LecturerGet: """ Scopes: `["rating.lecturer.read"]` Возвращает преподавателя по его ID в базе данных RatingAPI - *QUERY* `info: string` - возможные значения `'comments'`, `'mark'`. + *QUERY* `info: string` - возможные значения `'comments'`. Если передано `'comments'`, то возвращаются одобренные комментарии к преподавателю. - Если передано `'mark'`, то возвращаются общие средние оценки, а также суммарная средняя оценка по всем одобренным комментариям. - Subject лектора возвращшается либо из базы данных, либо из любого аппрувнутого комментария """ lecturer: Lecturer = Lecturer.query(session=db.session).filter(Lecturer.id == id).one_or_none() @@ -114,14 +112,6 @@ async def get_lecturer(id: int, info: list[Literal["comments", "mark"]] = Query( ] if "comments" in info and approved_comments: result.comments = sorted(approved_comments, key=lambda comment: comment.create_ts, reverse=True) - if "mark" in info and 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) - result.mark_general = sum(comment.mark_general for comment in approved_comments) / len(approved_comments) - result.mark_weighted = calc_weighted_mark( - result.mark_general, len(approved_comments), Lecturer.mean_mark_general() - ) if approved_comments: result.subjects = list({comment.subject for comment in approved_comments}) return result @@ -132,7 +122,7 @@ async def get_lecturers( lecturer_filter=FilterDepends(LecturersFilter), limit: int = 10, offset: int = 0, - info: list[Literal["comments", "mark"]] = Query(default=[]), + info: list[Literal["comments"]] = Query(default=[]), mark: float = Query(default=None, ge=-2, le=2), ) -> LecturerGetAll: """ @@ -151,9 +141,8 @@ async def get_lecturers( - `...?order_by=mark_freebie` - `...?order_by=+mark_freebie` (эквивалентно 2ому пункту) - `info` - возможные значения `'comments'`, `'mark'`. + `info` - возможные значения `'comments'`. Если передано `'comments'`, то возвращаются одобренные комментарии к преподавателю. - Если передано `'mark'`, то возвращаются общие средние оценки, а также суммарная средняя оценка по всем одобренным комментариям. `subject` Если передано `subject` - возвращает всех преподавателей, для которых переданное значение совпадает с одним из их предметов преподавания. @@ -174,8 +163,6 @@ async def get_lecturers( lecturers_count = lecturers_query.group_by(Lecturer.id).count() result = LecturerGetAll(limit=limit, offset=offset, total=lecturers_count) - if "mark" in info: - mean_mark_general = Lecturer.mean_mark_general() for db_lecturer in lecturers: lecturer_to_result: LecturerGet = LecturerGet.model_validate(db_lecturer) lecturer_to_result.comments = None @@ -195,22 +182,6 @@ async def get_lecturers( lecturer_to_result.comments = sorted( approved_comments, key=lambda comment: comment.create_ts, reverse=True ) - if "mark" in info and approved_comments: - lecturer_to_result.mark_freebie = sum([comment.mark_freebie for comment in approved_comments]) / len( - approved_comments - ) - lecturer_to_result.mark_kindness = sum(comment.mark_kindness for comment in approved_comments) / len( - approved_comments - ) - lecturer_to_result.mark_clarity = sum(comment.mark_clarity for comment in approved_comments) / len( - approved_comments - ) - lecturer_to_result.mark_general = sum(comment.mark_general for comment in approved_comments) / len( - approved_comments - ) - lecturer_to_result.mark_weighted = calc_weighted_mark( - lecturer_to_result.mark_general, len(approved_comments), mean_mark_general - ) if approved_comments: lecturer_to_result.subjects = list({comment.subject for comment in approved_comments}) result.lecturers.append(lecturer_to_result) diff --git a/rating_api/schemas/models.py b/rating_api/schemas/models.py index f5fb227..caf1586 100644 --- a/rating_api/schemas/models.py +++ b/rating_api/schemas/models.py @@ -95,11 +95,12 @@ class LecturerGet(Base): avatar_link: str | None = None subjects: list[str] | None = None timetable_id: int - mark_kindness: float | None = None - mark_freebie: float | None = None - mark_clarity: float | None = None - mark_general: float | None = None + mark_kindness_weighted: float | None = None + mark_clarity_weighted: float | None = None + mark_freebie_weighted: float | None = None mark_weighted: float | None = None + rank: int | None = None + update_ts: datetime.datetime | None = None comments: list[CommentGet] | None = None diff --git a/tests/test_routes/test_lecturer.py b/tests/test_routes/test_lecturer.py index f7a207e..6408c85 100644 --- a/tests/test_routes/test_lecturer.py +++ b/tests/test_routes/test_lecturer.py @@ -54,29 +54,32 @@ def test_get_lecturer(client, dbsession, lecturers, lecturer_n, response_status) assert get_response.status_code == response_status if response_status == status.HTTP_200_OK: json_response = get_response.json() - assert json_response["mark_kindness"] is None - assert json_response["mark_freebie"] is None - assert json_response["mark_clarity"] is None - assert json_response["mark_general"] is None + assert json_response["mark_kindness_weighted"] == 0.0 + assert json_response["mark_freebie_weighted"] == 0.0 + assert json_response["mark_clarity_weighted"] == 0.0 + assert json_response["mark_weighted"] == 0.0 + assert json_response["rank"] == 0 assert json_response["comments"] is None @pytest.mark.parametrize( - 'lecturer_n,mark_kindness,mark_freebie,mark_clarity,mark_general', + 'lecturer_n,mark_kindness_weighted,mark_freebie_weighted,mark_clarity_weighted,mark_weighted', [(0, 1.5, 1.5, 1.5, 1.5), (1, 0, 0, 0, 0), (2, 0.5, 0.5, 0.5, 0.5)], ) def test_get_lecturer_with_comments( - client, lecturers_with_comments, lecturer_n, mark_kindness, mark_freebie, mark_clarity, mark_general + client, + lecturers_with_comments, + lecturer_n, + mark_kindness_weighted, + mark_freebie_weighted, + mark_clarity_weighted, + mark_weighted, ): lecturers, comments = lecturers_with_comments - query = {"info": ['comments', 'mark']} + query = {"info": ['comments']} response = client.get(f'{url}/{lecturers[lecturer_n].id}', params=query) assert response.status_code == status.HTTP_200_OK json_response = response.json() - assert json_response["mark_kindness"] == mark_kindness - assert json_response["mark_freebie"] == mark_freebie - assert json_response["mark_clarity"] == mark_clarity - assert json_response["mark_general"] == mark_general assert comments[lecturer_n * 6 + 0].subject in json_response["subjects"] assert comments[lecturer_n * 6 + 1].subject in json_response["subjects"] assert comments[lecturer_n * 6 + 2].subject not in json_response["subjects"] @@ -170,14 +173,13 @@ def test_get_lecturers_by_mark(client, dbsession, query, response_status): @pytest.mark.parametrize( 'query, response_status', [ - ({'info': ['comments', 'mark']}, status.HTTP_200_OK), ({'info': ['comments']}, status.HTTP_200_OK), - ({'info': ['mark']}, status.HTTP_200_OK), + ({'info': ['comments']}, status.HTTP_200_OK), ({'info': []}, status.HTTP_200_OK), ({'info': {}}, status.HTTP_422_UNPROCESSABLE_ENTITY), ({'info': ['pupupu']}, status.HTTP_422_UNPROCESSABLE_ENTITY), ], - ids=["comments_and_marks", "only_comments", "only_marks", "no_info", "invalid_iterator", "invalid_param"], + ids=["comments", "comments", "no_info", "invalid_iterator", "invalid_param"], ) def test_get_lecturers_by_info(client, dbsession, query, response_status): """ @@ -187,52 +189,6 @@ def test_get_lecturers_by_info(client, dbsession, query, response_status): resp = client.get(f'{url}', params=query) assert resp.status_code == response_status if response_status == status.HTTP_200_OK: - if 'mark' in query['info']: - db_res = dbsession.execute( - ( - select( - Lecturer.id.label('lecturer'), - func.avg(Comment.mark_freebie).label('avg_freebie'), - func.avg(Comment.mark_kindness).label('avg_kindness'), - func.avg(Comment.mark_clarity).label('avg_clarity'), - func.avg(Comment.mark_general).label('avg_general'), - ) - .join( - Comment, - and_(Comment.review_status == ReviewStatus.APPROVED, Lecturer.id == Comment.lecturer_id), - ) - .group_by(Lecturer.id) - ) - ).all() - with db(): - mean_mark_general = Lecturer.mean_mark_general() - db_lecturers = { - ( - *lecturer, - calc_weighted_mark( - float(lecturer[-1]), - Comment.query(session=dbsession) - .filter( - and_(Comment.review_status == ReviewStatus.APPROVED, Comment.lecturer_id == lecturer[0]) - ) - .count(), - mean_mark_general, - ), - ) - for lecturer in db_res - } - resp_lecturers = { - ( - lecturer['id'], - lecturer['mark_freebie'], - lecturer['mark_kindness'], - lecturer['mark_clarity'], - lecturer['mark_general'], - lecturer['mark_weighted'], - ) - for lecturer in resp.json()['lecturers'] - } - assert resp_lecturers == db_lecturers if 'comments' in query['info']: db_res = dbsession.execute( (