From 52043ab933afff43f8e1350df2007428190214c6 Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Wed, 6 Nov 2024 05:29:50 -0500 Subject: [PATCH 1/2] Add lecturer Search --- rating_api/models/base.py | 4 +- rating_api/models/db.py | 15 ++++++- rating_api/routes/comment.py | 4 +- rating_api/routes/lecturer.py | 10 ++++- tests/test_routes/test_lecturer.py | 63 ++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 8 deletions(-) diff --git a/rating_api/models/base.py b/rating_api/models/base.py index 678078a..8ffc3b1 100644 --- a/rating_api/models/base.py +++ b/rating_api/models/base.py @@ -2,9 +2,9 @@ import re -from sqlalchemy import Integer, not_ +from sqlalchemy import not_ from sqlalchemy.exc import NoResultFound -from sqlalchemy.orm import Mapped, Query, Session, as_declarative, declared_attr, mapped_column +from sqlalchemy.orm import Query, Session, as_declarative, declared_attr from rating_api.exceptions import ObjectNotFound diff --git a/rating_api/models/db.py b/rating_api/models/db.py index 21ad8d5..ed6cc9f 100644 --- a/rating_api/models/db.py +++ b/rating_api/models/db.py @@ -5,9 +5,10 @@ import uuid from enum import Enum -from sqlalchemy import UUID, Boolean, DateTime +from sqlalchemy import UUID, DateTime from sqlalchemy import Enum as DbEnum -from sqlalchemy import ForeignKey, Integer, String +from sqlalchemy import ForeignKey, Integer, String, and_, or_, true +from sqlalchemy.ext.hybrid import hybrid_method from sqlalchemy.orm import Mapped, mapped_column, relationship from rating_api.settings import get_settings @@ -34,6 +35,16 @@ class Lecturer(BaseDbModel): timetable_id: Mapped[int] = mapped_column(Integer, unique=True, nullable=False) comments: Mapped[list[Comment]] = relationship("Comment", back_populates="lecturer") + @hybrid_method + def search(self, query: str) -> bool: + response = true + query = query.split(' ') + for q in query: + response = and_( + response, or_(self.first_name.contains(q), self.middle_name.contains(q), self.last_name.contains(q)) + ) + return response + class Comment(BaseDbModel): uuid: Mapped[uuid.UUID] = mapped_column(UUID, primary_key=True, default=uuid.uuid4) diff --git a/rating_api/routes/comment.py b/rating_api/routes/comment.py index 678a691..c0cbcf4 100644 --- a/rating_api/routes/comment.py +++ b/rating_api/routes/comment.py @@ -98,9 +98,9 @@ async def get_comments( raise ForbiddenAction(Comment) 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) result.total = len(result.comments) diff --git a/rating_api/routes/lecturer.py b/rating_api/routes/lecturer.py index eb3b770..d7da120 100644 --- a/rating_api/routes/lecturer.py +++ b/rating_api/routes/lecturer.py @@ -81,6 +81,7 @@ async def get_lecturers( info: list[Literal["comments", "mark"]] = Query(default=[]), order_by: list[Literal["general", '']] = Query(default=[]), subject: str = Query(''), + name: str = Query(''), _=Depends(UnionAuth(scopes=["rating.lecturer.read"], allow_none=False, auto_error=True)), ) -> LecturerGetAll: """ @@ -100,8 +101,11 @@ async def get_lecturers( `subject` Если передано `subject` - возвращает всех преподавателей, для которых переданное значение совпадает с одним из их предметов преподавания. Также возвращает всех преподавателей, у которых есть комментарий с совпадающим с данным subject. + + `name` + Поле для ФИО. Если передано `name` - возвращает всех преподователей, для которых нашлись совпадения с переданной строкой """ - lecturers = Lecturer.query(session=db.session).all() + lecturers = Lecturer.query(session=db.session).filter(Lecturer.search(name)).all() if not lecturers: raise ObjectNotFound(Lecturer, 'all') result = LecturerGetAll(limit=limit, offset=offset, total=len(lecturers)) @@ -138,7 +142,9 @@ async def get_lecturers( if "general" in order_by: result.lecturers.sort(key=lambda item: (item.mark_general is None, item.mark_general)) if subject: - result.lecturers = [lecturer for lecturer in result.lecturers if lecturer.subjects and subject in lecturer.subjects] + result.lecturers = [ + lecturer for lecturer in result.lecturers if lecturer.subjects and subject in lecturer.subjects + ] result.total = len(result.lecturers) return result diff --git a/tests/test_routes/test_lecturer.py b/tests/test_routes/test_lecturer.py index 81fefd8..e98858a 100644 --- a/tests/test_routes/test_lecturer.py +++ b/tests/test_routes/test_lecturer.py @@ -101,9 +101,55 @@ def test_get_lecturer_with_comments(client, dbsession): assert json_response["mark_general"] == 0.5 assert "Физика" in json_response["subjects"] assert len(json_response["comments"]) != 0 + dbsession.delete(comment1) + dbsession.delete(comment2) + dbsession.delete(comment3) + dbsession.delete(lecturer) + dbsession.commit() + + +def test_get_lecturers_by_name(client, dbsession): + body_list = [ + {"first_name": 'Алиса', "last_name": 'Селезнёва', "middle_name": 'Ивановна', "timetable_id": 0}, + {"first_name": 'Марат', "last_name": 'Сельков', "middle_name": 'Анатольевич', "timetable_id": 1}, + {"first_name": 'М.', "last_name": 'Измайлов', "middle_name": 'Р.', "timetable_id": 2}, + {"first_name": 'Михаил', "last_name": 'Измайлов', "middle_name": 'Ильич', "timetable_id": 3}, + ] + lecturer_list: list[Lecturer] = [Lecturer(**body_list[i]) for i in range(4)] + for lecturer in lecturer_list: + dbsession.add(lecturer) + dbsession.commit() + db_lecturer: Lecturer = Lecturer.query(session=dbsession).filter(Lecturer.timetable_id == 0).one_or_none() + assert db_lecturer is not None + query = {"name": "Селезнёва"} + get_response = client.get(f'{url}', params=query) + assert get_response.status_code == status.HTTP_200_OK + json_response = get_response.json() + assert json_response["total"] == 1 + assert json_response["lecturers"][0]["first_name"] == "Алиса" + + query = {"name": "Сел"} + get_response = client.get(f'{url}', params=query) + assert get_response.status_code == status.HTTP_200_OK + json_response = get_response.json() + assert json_response["total"] == 2 + assert json_response["lecturers"][0]["first_name"] == "Алиса" + assert json_response["lecturers"][1]["first_name"] == "Марат" + + query = {"name": "Измайлова"} + get_response = client.get(f'{url}', params=query) + assert get_response.status_code == status.HTTP_404_NOT_FOUND + + for lecturer in lecturer_list: + dbsession.delete(lecturer) + dbsession.commit() def test_update_lecturer(client, dbsession): + body = {"first_name": 'Иван', "last_name": 'Иванов', "middle_name": 'Иванович', "timetable_id": 0} + lecturer: Lecturer = Lecturer(**body) + dbsession.add(lecturer) + dbsession.commit() body = { "first_name": 'Алексей', "last_name": 'Алексеев', @@ -128,9 +174,26 @@ def test_update_lecturer(client, dbsession): assert json_response["first_name"] == 'Иван' assert json_response["last_name"] == 'Иванов' assert json_response["middle_name"] == "Иванович" + dbsession.delete(lecturer) + dbsession.commit() def test_delete_lecturer(client, dbsession): + body = {"first_name": 'Иван', "last_name": 'Иванов', "middle_name": 'Иванович', "timetable_id": 0} + lecturer: Lecturer = Lecturer(**body) + dbsession.add(lecturer) + dbsession.commit() + comment: dict = { + "subject": "Физика", + "text": "Хороший преподаватель", + "mark_kindness": 2, + "mark_freebie": 0, + "mark_clarity": 2, + "lecturer_id": lecturer.id, + "review_status": ReviewStatus.APPROVED, + } + comment: Comment = Comment.create(session=dbsession, **comment) + dbsession.commit() lecturer = dbsession.query(Lecturer).filter(Lecturer.timetable_id == 0).one_or_none() assert lecturer is not None response = client.delete(f"{url}/{lecturer.id}") From 909b6a8c31bcdd8588e63413025a52e2eda8a3ce Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Thu, 7 Nov 2024 12:38:53 -0500 Subject: [PATCH 2/2] Style fix --- rating_api/routes/lecturer.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rating_api/routes/lecturer.py b/rating_api/routes/lecturer.py index 2462a24..1a7cd47 100644 --- a/rating_api/routes/lecturer.py +++ b/rating_api/routes/lecturer.py @@ -34,10 +34,7 @@ async def create_lecturer( @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", "mark"]] = Query(default=[])) -> LecturerGet: """ Scopes: `["rating.lecturer.read"]`