From 3b79771c78f7b5898ea485c18c12feab8ba67047 Mon Sep 17 00:00:00 2001 From: semen603089 Date: Mon, 3 Jul 2023 08:08:22 +0300 Subject: [PATCH 1/7] many2many, depr del --- calendar_backend/exceptions.py | 4 +- calendar_backend/methods/image.py | 1 - calendar_backend/models/__init__.py | 2 + calendar_backend/models/db.py | 17 +++++--- calendar_backend/routes/base.py | 25 ----------- calendar_backend/routes/event/__init__.py | 6 --- calendar_backend/routes/event/comment.py | 7 --- .../routes/event/comment_review.py | 6 --- calendar_backend/routes/event/event.py | 22 +++------- calendar_backend/routes/gcal.py | 2 +- calendar_backend/routes/group/__init__.py | 4 -- calendar_backend/routes/group/group.py | 7 --- calendar_backend/routes/lecturer/__init__.py | 12 ------ calendar_backend/routes/lecturer/comment.py | 9 ---- .../routes/lecturer/comment_review.py | 10 ----- calendar_backend/routes/lecturer/lecturer.py | 7 --- calendar_backend/routes/lecturer/photo.py | 6 --- calendar_backend/routes/models/base.py | 2 +- calendar_backend/routes/models/event.py | 6 +-- calendar_backend/routes/room/__init__.py | 4 -- calendar_backend/routes/room/room.py | 7 --- tests/conftest.py | 2 +- tests/event/event.py | 43 ++++++++++--------- 23 files changed, 50 insertions(+), 161 deletions(-) diff --git a/calendar_backend/exceptions.py b/calendar_backend/exceptions.py index feb5cdcb..1673e13b 100644 --- a/calendar_backend/exceptions.py +++ b/calendar_backend/exceptions.py @@ -2,8 +2,8 @@ class ObjectNotFound(Exception): - def __init__(self, type: Type, id: int): - super().__init__(f"Object {type.__name__} {id=} not found") + def __init__(self, type: Type, ids: int | list[int]): + super().__init__(f"Objects of type {type.__name__} {ids=} not found") class NotEnoughCriteria(Exception): diff --git a/calendar_backend/methods/image.py b/calendar_backend/methods/image.py index 13254fe2..a0504bff 100644 --- a/calendar_backend/methods/image.py +++ b/calendar_backend/methods/image.py @@ -5,7 +5,6 @@ from concurrent.futures import ThreadPoolExecutor from functools import partial from io import BytesIO -from typing import Final import aiofiles from fastapi import File, HTTPException, UploadFile diff --git a/calendar_backend/models/__init__.py b/calendar_backend/models/__init__.py index ff7eaf65..19f25799 100644 --- a/calendar_backend/models/__init__.py +++ b/calendar_backend/models/__init__.py @@ -5,6 +5,7 @@ Credentials, Direction, Event, + EventsGroups, EventsLecturers, EventsRooms, Group, @@ -25,4 +26,5 @@ "EventsLecturers", "EventsRooms", "ApproveStatuses", + "EventsGroups", ] diff --git a/calendar_backend/models/db.py b/calendar_backend/models/db.py index e3a9237a..105a52db 100644 --- a/calendar_backend/models/db.py +++ b/calendar_backend/models/db.py @@ -106,15 +106,15 @@ class Group(BaseDbModel): events: Mapped[list[Event]] = relationship( "Event", - foreign_keys="Event.group_id", order_by="(Event.start_ts)", - primaryjoin="and_(Group.id==Event.group_id, not_(Event.is_deleted))", + secondary="events_groups", + back_populates="group", + secondaryjoin="and_(Event.id==EventsGroups.event_id, not_(Event.is_deleted))", ) class Event(BaseDbModel): name: Mapped[str] = mapped_column(String, nullable=False) - group_id: Mapped[int] = mapped_column(Integer, ForeignKey("group.id"), nullable=True) start_ts: Mapped[datetime] = mapped_column(DateTime, nullable=False) end_ts: Mapped[datetime] = mapped_column(DateTime, nullable=False) is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) @@ -125,11 +125,11 @@ class Event(BaseDbModel): secondary="events_rooms", secondaryjoin="and_(Room.id==EventsRooms.room_id, not_(Room.is_deleted))", ) - group: Mapped[Group] = relationship( + group: Mapped[list[Group]] = relationship( "Group", back_populates="events", - foreign_keys="Event.group_id", - primaryjoin="and_(Group.id==Event.group_id, not_(Group.is_deleted))", + secondary="events_groups", + secondaryjoin="and_(Group.id==EventsGroups.group_id, not_(Group.is_deleted))", ) lecturer: Mapped[list[Lecturer]] = relationship( "Lecturer", @@ -155,6 +155,11 @@ class EventsRooms(BaseDbModel): room_id: Mapped[int] = mapped_column(Integer, ForeignKey("room.id"), nullable=False) +class EventsGroups(BaseDbModel): + event_id: Mapped[int] = mapped_column(Integer, ForeignKey("event.id"), nullable=False) + group_id: Mapped[int] = mapped_column(Integer, ForeignKey("group.id"), nullable=False) + + class Photo(BaseDbModel): lecturer_id: Mapped[int] = mapped_column(Integer, ForeignKey("lecturer.id"), nullable=False) link: Mapped[str] = mapped_column(String, unique=True, nullable=False) diff --git a/calendar_backend/routes/base.py b/calendar_backend/routes/base.py index 578b8e1e..780de7ab 100644 --- a/calendar_backend/routes/base.py +++ b/calendar_backend/routes/base.py @@ -17,27 +17,15 @@ from calendar_backend.exceptions import ForbiddenAction, NotEnoughCriteria, ObjectNotFound from calendar_backend.settings import get_settings -from .event import event_comment_review_router as old_event_comment_review_router # DEPRICATED TODO: Drop 2023-04-01 -from .event import event_comment_router as old_event_comment_router # DEPRICATED TODO: Drop 2023-04-01 -from .event import event_router as old_event_router # DEPRICATED TODO: Drop 2023-04-01 from .event.comment import router as event_comment_router from .event.comment_review import router as event_comment_review_router from .event.event import router as event_router -from .gcal import gcal -from .group import group_router as old_group_router # DEPRICATED TODO: Drop 2023-04-01 from .group.group import router as group_router -from .lecturer import ( - lecturer_comment_review_router as old_lecturer_comment_review_router, # DEPRICATED TODO: Drop 2023-04-01 -) -from .lecturer import lecturer_comment_router as old_lecturer_comment_router # DEPRICATED TODO: Drop 2023-04-01 -from .lecturer import lecturer_photo_router as old_lecturer_photo_router # DEPRICATED TODO: Drop 2023-04-01 -from .lecturer import lecturer_router as old_lecturer_router # DEPRICATED TODO: Drop 2023-04-01 from .lecturer.comment import router as lecturer_comment_router from .lecturer.comment_review import router as lecturer_comment_review_router from .lecturer.lecturer import router as lecturer_router from .lecturer.photo import router as lecturer_photo_router from .lecturer.photo_review import router as lecturer_photo_review_router -from .room import room_router as old_room_router # DEPRICATED TODO: Drop 2023-04-01 from .room.room import router as room_router @@ -129,19 +117,6 @@ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) - app.mount('/static', StaticFiles(directory=settings.STATIC_PATH), 'static') -# region DEPRICATED -# TODO: Drop 2023-04-01 -app.include_router(gcal) -app.include_router(old_lecturer_router) -app.include_router(old_lecturer_comment_router) -app.include_router(old_lecturer_comment_review_router) -app.include_router(old_lecturer_photo_router) -app.include_router(old_group_router) -app.include_router(old_room_router) -app.include_router(old_event_router) -app.include_router(old_event_comment_router) -app.include_router(old_event_comment_review_router) -# endregion app.include_router(lecturer_router) app.include_router(lecturer_comment_router) diff --git a/calendar_backend/routes/event/__init__.py b/calendar_backend/routes/event/__init__.py index 12db3f89..e69de29b 100644 --- a/calendar_backend/routes/event/__init__.py +++ b/calendar_backend/routes/event/__init__.py @@ -1,6 +0,0 @@ -from .comment import event_comment_router -from .comment_review import event_comment_review_router -from .event import event_router - - -__all__ = ["event_router", "event_comment_review_router", "event_comment_router"] diff --git a/calendar_backend/routes/event/comment.py b/calendar_backend/routes/event/comment.py index d5df64b5..eace2e34 100644 --- a/calendar_backend/routes/event/comment.py +++ b/calendar_backend/routes/event/comment.py @@ -10,12 +10,9 @@ settings = get_settings() -# DEPRICATED TODO: Drop 2023-04-01 -event_comment_router = APIRouter(prefix="/timetable/event/{event_id}/comment", tags=["Event: Comment"], deprecated=True) router = APIRouter(prefix="/event/{event_id}/comment", tags=["Event: Comment"]) -@event_comment_router.post("/", response_model=CommentEventGet) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/", response_model=CommentEventGet) async def comment_event(event_id: int, comment: EventCommentPost) -> CommentEventGet: approve_status = ApproveStatuses.APPROVED if not settings.REQUIRE_REVIEW_EVENT_COMMENT else ApproveStatuses.PENDING @@ -26,7 +23,6 @@ async def comment_event(event_id: int, comment: EventCommentPost) -> CommentEven return CommentEventGet.from_orm(comment_event) -@event_comment_router.patch("/{id}", response_model=CommentEventGet) # DEPRICATED TODO: Drop 2023-04-01 @router.patch("/{id}", response_model=CommentEventGet) async def update_comment(id: int, event_id: int, comment_inp: EventCommentPatch) -> CommentEventGet: comment = DbCommentEvent.get(id, only_approved=False, session=db.session) @@ -39,7 +35,6 @@ async def update_comment(id: int, event_id: int, comment_inp: EventCommentPatch) return CommentEventGet.from_orm(comment_event) -@event_comment_router.get("/{id}", response_model=CommentEventGet) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/{id}", response_model=CommentEventGet) async def get_comment(id: int, event_id: int) -> CommentEventGet: comment = DbCommentEvent.get(id, session=db.session) @@ -48,7 +43,6 @@ async def get_comment(id: int, event_id: int) -> CommentEventGet: return CommentEventGet.from_orm(comment) -@event_comment_router.delete("/{id}", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 @router.delete("/{id}", response_model=None) async def delete_comment( id: int, event_id: int, _=Depends(UnionAuth(scopes=["timetable.event.comment.delete"])) @@ -61,7 +55,6 @@ async def delete_comment( return None -@event_comment_router.get("/", response_model=EventComments) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/", response_model=EventComments) async def get_event_comments(event_id: int, limit: int = 10, offset: int = 0) -> EventComments: res = DbCommentEvent.get_all(session=db.session).filter(DbCommentEvent.event_id == event_id) diff --git a/calendar_backend/routes/event/comment_review.py b/calendar_backend/routes/event/comment_review.py index e5ad4743..d3813231 100644 --- a/calendar_backend/routes/event/comment_review.py +++ b/calendar_backend/routes/event/comment_review.py @@ -13,14 +13,9 @@ settings = get_settings() -# DEPRICATED TODO: Drop 2023-04-01 -event_comment_review_router = APIRouter( - prefix="/timetable/event/{event_id}/comment", tags=["Event: Comment Review"], deprecated=True -) router = APIRouter(prefix="/event/{event_id}/comment", tags=["Event: Comment Review"]) -@event_comment_review_router.get("/review/", response_model=list[CommentEventGet]) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/review/", response_model=list[CommentEventGet]) async def get_unreviewed_comments( event_id: int, _=Depends(UnionAuth(scopes=["timetable.event.comment.review"])) @@ -33,7 +28,6 @@ async def get_unreviewed_comments( return parse_obj_as(list[CommentEventGet], comments) -@event_comment_review_router.post("/{id}/review/", response_model=CommentEventGet) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/{id}/review/", response_model=CommentEventGet) async def review_comment( id: int, diff --git a/calendar_backend/routes/event/event.py b/calendar_backend/routes/event/event.py index 84a2cc8b..cfb080dd 100644 --- a/calendar_backend/routes/event/event.py +++ b/calendar_backend/routes/event/event.py @@ -10,7 +10,7 @@ from calendar_backend.exceptions import NotEnoughCriteria from calendar_backend.methods import list_calendar -from calendar_backend.models import Event, EventsLecturers, EventsRooms, Group, Lecturer, Room +from calendar_backend.models import Event, EventsGroups, EventsLecturers, EventsRooms, Group, Lecturer, Room from calendar_backend.routes.models import EventGet from calendar_backend.routes.models.event import EventPatch, EventPost, GetListEvent from calendar_backend.settings import get_settings @@ -18,12 +18,9 @@ settings = get_settings() logger = logging.getLogger(__name__) -# DEPRICATED TODO: Drop 2023-04-01 -event_router = APIRouter(prefix="/timetable/event", tags=["Event"], deprecated=True) router = APIRouter(prefix="/event", tags=["Event"]) -@event_router.get("/{id}", response_model=EventGet) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/{id}", response_model=EventGet) async def get_event_by_id(id: int) -> EventGet: return EventGet.from_orm(Event.get(id, session=db.session)) @@ -37,7 +34,8 @@ async def _get_timetable(start: date, end: date, group_id, lecturer_id, room_id, Event.end_ts < end, ) if group_id: - events = events.filter(Event.group_id == group_id) + ids_ = EventsGroups.get_all(session=db.session).filter(EventsGroups.group_id == group_id).all() + events = events.filter(Event.id.in_(row.event_id for row in ids_)) elif lecturer_id: ids_ = EventsLecturers.get_all(session=db.session).filter(EventsLecturers.lecturer_id == lecturer_id).all() events = events.filter(Event.id.in_(row.event_id for row in ids_)) @@ -65,7 +63,6 @@ async def _get_timetable(start: date, end: date, group_id, lecturer_id, room_id, return GetListEvent(items=events, limit=limit, offset=offset, total=cnt).dict(exclude=fmt) -@event_router.get("/", response_model=GetListEvent | None) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/", response_model=GetListEvent | None) async def get_events( start: date | None = Query(default=None, description="Default: Today"), @@ -87,25 +84,23 @@ async def get_events( return await fmt_cases[format]() -@event_router.post("/", response_model=EventGet) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/", response_model=EventGet) async def create_event(event: EventPost, _=Depends(UnionAuth(scopes=["timetable.event.create"]))) -> EventGet: event_dict = event.dict() rooms = [Room.get(room_id, session=db.session) for room_id in event_dict.pop("room_id", [])] lecturers = [Lecturer.get(lecturer_id, session=db.session) for lecturer_id in event_dict.pop("lecturer_id", [])] - group = Group.get(event.group_id, session=db.session) + groups = [Group.get(group_id, session=db.session) for group_id in event_dict.pop("group_id", [])] event_get = Event.create( **event_dict, room=rooms, lecturer=lecturers, - group=group, + group=groups, session=db.session, ) db.session.commit() return EventGet.from_orm(event_get) -@event_router.post("/bulk", response_model=list[EventGet]) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/bulk", response_model=list[EventGet]) async def create_events( events: list[EventPost], _=Depends(UnionAuth(scopes=["timetable.event.create"])) @@ -115,13 +110,13 @@ async def create_events( event_dict = event.dict() rooms = [Room.get(room_id, session=db.session) for room_id in event_dict.pop("room_id", [])] lecturers = [Lecturer.get(lecturer_id, session=db.session) for lecturer_id in event_dict.pop("lecturer_id", [])] - group = Group.get(event.group_id, session=db.session) + groups = [Group.get(group_id, session=db.session) for group_id in event_dict.pop("group_id", [])] result.append( Event.create( **event_dict, room=rooms, lecturer=lecturers, - group=group, + group=groups, session=db.session, ) ) @@ -129,7 +124,6 @@ async def create_events( return parse_obj_as(list[EventGet], result) -@event_router.patch("/{id}", response_model=EventGet) # DEPRICATED TODO: Drop 2023-04-01 @router.patch("/{id}", response_model=EventGet) async def patch_event( id: int, event_inp: EventPatch, _=Depends(UnionAuth(scopes=["timetable.event.update"])) @@ -139,14 +133,12 @@ async def patch_event( return EventGet.from_orm(patched) -@event_router.delete("/bulk", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 @router.delete("/bulk", response_model=None) async def delete_events(start: date, end: date, _=Depends(UnionAuth(scopes=["timetable.event.delete"]))) -> None: db.session.query(Event).filter(Event.start_ts >= start, Event.end_ts < end).update(values={"is_deleted": True}) db.session.commit() -@event_router.delete("/{id}", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 @router.delete("/{id}", response_model=None) async def delete_event(id: int, _=Depends(UnionAuth(scopes=["timetable.event.delete"]))) -> None: Event.delete(id, session=db.session) diff --git a/calendar_backend/routes/gcal.py b/calendar_backend/routes/gcal.py index bf570e9b..a31b6fd3 100644 --- a/calendar_backend/routes/gcal.py +++ b/calendar_backend/routes/gcal.py @@ -22,7 +22,7 @@ settings = get_settings() logger = logging.getLogger(__name__) templates = Jinja2Templates(directory="calendar_backend/templates") -# DEPRICATED TODO: Drop 2023-04-01 +# DEPRICATED TODO: Drop ? gcal = APIRouter(tags=["Utils: Google"], deprecated=True) diff --git a/calendar_backend/routes/group/__init__.py b/calendar_backend/routes/group/__init__.py index f23f94fa..e69de29b 100644 --- a/calendar_backend/routes/group/__init__.py +++ b/calendar_backend/routes/group/__init__.py @@ -1,4 +0,0 @@ -from .group import group_router - - -__all__ = ["group_router"] diff --git a/calendar_backend/routes/group/group.py b/calendar_backend/routes/group/group.py index 275f2cdc..2797b606 100644 --- a/calendar_backend/routes/group/group.py +++ b/calendar_backend/routes/group/group.py @@ -11,18 +11,14 @@ settings = get_settings() logger = logging.getLogger(__name__) -# DEPRICATED TODO: Drop 2023-04-01 -group_router = APIRouter(prefix="/timetable/group", tags=["Group"], deprecated=True) router = APIRouter(prefix="/group", tags=["Group"]) -@group_router.get("/{id}", response_model=GroupGet) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/{id}", response_model=GroupGet) async def get_group_by_id(id: int) -> GroupGet: return GroupGet.from_orm(Group.get(id, session=db.session)) -@group_router.get("/", response_model=GetListGroup) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/", response_model=GetListGroup) async def get_groups(query: str = "", limit: int = 10, offset: int = 0) -> GetListGroup: res = Group.get_all(session=db.session).filter(Group.number.contains(query)) @@ -40,7 +36,6 @@ async def get_groups(query: str = "", limit: int = 10, offset: int = 0) -> GetLi ) -@group_router.post("/", response_model=GroupGet) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/", response_model=GroupGet) async def create_group(group: GroupPost, _=Depends(UnionAuth(scopes=["timetable.group.create"]))) -> GroupGet: if db.session.query(Group).filter(Group.number == group.number).one_or_none(): @@ -50,7 +45,6 @@ async def create_group(group: GroupPost, _=Depends(UnionAuth(scopes=["timetable. return GroupGet.from_orm(group) -@group_router.patch("/{id}", response_model=GroupGet) # DEPRICATED TODO: Drop 2023-04-01 @router.patch("/{id}", response_model=GroupGet) async def patch_group( id: int, @@ -67,7 +61,6 @@ async def patch_group( return GroupGet.from_orm(patched) -@group_router.delete("/{id}", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 @router.delete("/{id}", response_model=None) async def delete_group(id: int, _=Depends(UnionAuth(scopes=["timetable.group.delete"]))) -> None: Group.delete(id, session=db.session) diff --git a/calendar_backend/routes/lecturer/__init__.py b/calendar_backend/routes/lecturer/__init__.py index 9da8769a..e69de29b 100644 --- a/calendar_backend/routes/lecturer/__init__.py +++ b/calendar_backend/routes/lecturer/__init__.py @@ -1,12 +0,0 @@ -from .comment import lecturer_comment_router -from .comment_review import lecturer_comment_review_router -from .lecturer import lecturer_router -from .photo import lecturer_photo_router - - -__all__ = [ - "lecturer_comment_review_router", - "lecturer_comment_router", - "lecturer_router", - "lecturer_photo_router", -] diff --git a/calendar_backend/routes/lecturer/comment.py b/calendar_backend/routes/lecturer/comment.py index c409043b..1c3689f7 100644 --- a/calendar_backend/routes/lecturer/comment.py +++ b/calendar_backend/routes/lecturer/comment.py @@ -10,14 +10,9 @@ settings = get_settings() -# DEPRICATED TODO: Drop 2023-04-01 -lecturer_comment_router = APIRouter( - prefix="/timetable/lecturer/{lecturer_id}", tags=["Lecturer: Comment"], deprecated=True -) router = APIRouter(prefix="/lecturer/{lecturer_id}", tags=["Lecturer: Comment"]) -@lecturer_comment_router.post("/comment/", response_model=CommentLecturer) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/comment/", response_model=CommentLecturer) async def comment_lecturer(lecturer_id: int, comment: LecturerCommentPost) -> CommentLecturer: approve_status = ( @@ -33,7 +28,6 @@ async def comment_lecturer(lecturer_id: int, comment: LecturerCommentPost) -> Co return CommentLecturer.from_orm(db_comment_lecturer) -@lecturer_comment_router.patch("/comment/{id}", response_model=CommentLecturer) # DEPRICATED TODO: Drop 2023-04-01 @router.patch("/comment/{id}", response_model=CommentLecturer) async def update_comment_lecturer(id: int, lecturer_id: int, comment_inp: LecturerCommentPatch) -> CommentLecturer: comment = DbCommentLecturer.get(id=id, only_approved=False, session=db.session) @@ -46,7 +40,6 @@ async def update_comment_lecturer(id: int, lecturer_id: int, comment_inp: Lectur return CommentLecturer.from_orm(patched) -@lecturer_comment_router.delete("/comment/{id}", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 @router.delete("/comment/{id}", response_model=None) async def delete_comment( id: int, lecturer_id: int, _=Depends(UnionAuth(scopes=["timetable.lecturer.comment.delete"])) @@ -58,7 +51,6 @@ async def delete_comment( db.session.commit() -@lecturer_comment_router.get("/comment/{id}", response_model=CommentLecturer) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/comment/{id}", response_model=CommentLecturer) async def get_comment(id: int, lecturer_id: int) -> CommentLecturer: comment = DbCommentLecturer.get(id, session=db.session) @@ -69,7 +61,6 @@ async def get_comment(id: int, lecturer_id: int) -> CommentLecturer: return CommentLecturer.from_orm(comment) -@lecturer_comment_router.get("/comment/", response_model=LecturerComments) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/comment/", response_model=LecturerComments) async def get_all_lecturer_comments(lecturer_id: int, limit: int = 10, offset: int = 0) -> LecturerComments: res = DbCommentLecturer.get_all(session=db.session).filter(DbCommentLecturer.lecturer_id == lecturer_id) diff --git a/calendar_backend/routes/lecturer/comment_review.py b/calendar_backend/routes/lecturer/comment_review.py index f705afc5..94433728 100644 --- a/calendar_backend/routes/lecturer/comment_review.py +++ b/calendar_backend/routes/lecturer/comment_review.py @@ -11,16 +11,9 @@ from calendar_backend.routes.models import CommentLecturer -# DEPRICATED TODO: Drop 2023-04-01 -lecturer_comment_review_router = APIRouter( - prefix="/timetable/lecturer/{lecturer_id}/comment", tags=["Lecturer: Comment Review"], deprecated=True -) router = APIRouter(prefix="/lecturer/{lecturer_id}/comment", tags=["Lecturer: Comment Review"]) -@lecturer_comment_review_router.get( - "/review/", response_model=list[CommentLecturer] -) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/review/", response_model=list[CommentLecturer]) async def get_unreviewed_comments( lecturer_id: int, _=Depends(UnionAuth(scopes=["timetable.lecturer.comment.review"])) @@ -35,9 +28,6 @@ async def get_unreviewed_comments( return parse_obj_as(list[CommentLecturer], comments) -@lecturer_comment_review_router.post( - "/{id}/review/", response_model=CommentLecturer -) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/{id}/review/", response_model=CommentLecturer) async def review_comment( id: int, diff --git a/calendar_backend/routes/lecturer/lecturer.py b/calendar_backend/routes/lecturer/lecturer.py index 02e783d6..07d5ad35 100644 --- a/calendar_backend/routes/lecturer/lecturer.py +++ b/calendar_backend/routes/lecturer/lecturer.py @@ -16,12 +16,9 @@ settings = get_settings() logger = logging.getLogger(__name__) -# DEPRICATED TODO: Drop 2023-04-01 -lecturer_router = APIRouter(prefix="/timetable/lecturer", tags=["Lecturer"], deprecated=True) router = APIRouter(prefix="/lecturer", tags=["Lecturer"]) -@lecturer_router.get("/{id}", response_model=LecturerGet) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/{id}", response_model=LecturerGet) async def get_lecturer_by_id(id: int) -> LecturerGet: lecturer = Lecturer.get(id, session=db.session) @@ -31,7 +28,6 @@ async def get_lecturer_by_id(id: int) -> LecturerGet: return result -@lecturer_router.get("/", response_model=GetListLecturer) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/", response_model=GetListLecturer) async def get_lecturers( query: str = "", @@ -65,7 +61,6 @@ async def get_lecturers( } -@lecturer_router.post("/", response_model=LecturerGet) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/", response_model=LecturerGet) async def create_lecturer( lecturer: LecturerPost, _=Depends(UnionAuth(scopes=["timetable.lecturer.create"])) @@ -75,7 +70,6 @@ async def create_lecturer( return LecturerGet.from_orm(dblecturer) -@lecturer_router.patch("/{id}", response_model=LecturerGet) # DEPRICATED TODO: Drop 2023-04-01 @router.patch("/{id}", response_model=LecturerGet) async def patch_lecturer( id: int, lecturer_inp: LecturerPatch, _=Depends(UnionAuth(scopes=["timetable.lecturer.update"])) @@ -93,7 +87,6 @@ async def patch_lecturer( return LecturerGet.from_orm(lecturer_upd) -@lecturer_router.delete("/{id}", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 @router.delete("/{id}", response_model=None) async def delete_lecturer(id: int, _=Depends(UnionAuth(scopes=["timetable.lecturer.delete"]))) -> None: Lecturer.delete(id, session=db.session) diff --git a/calendar_backend/routes/lecturer/photo.py b/calendar_backend/routes/lecturer/photo.py index 224e5078..db2b2147 100644 --- a/calendar_backend/routes/lecturer/photo.py +++ b/calendar_backend/routes/lecturer/photo.py @@ -10,12 +10,9 @@ settings = get_settings() -# DEPRICATED TODO: Drop 2023-04-01 -lecturer_photo_router = APIRouter(prefix="/timetable/lecturer/{lecturer_id}", tags=["Lecturer: Photo"], deprecated=True) router = APIRouter(prefix="/lecturer/{lecturer_id}", tags=["Lecturer: Photo"]) -@lecturer_photo_router.post("/photo", response_model=Photo) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/photo", response_model=Photo) async def upload_photo(lecturer_id: int, photo: UploadFile = File(...)) -> Photo: """Загрузить фотографию преподавателя из локального файла @@ -35,7 +32,6 @@ async def upload_photo(lecturer_id: int, photo: UploadFile = File(...)) -> Photo return Photo.from_orm(photo) -@lecturer_photo_router.get("/photo", response_model=LecturerPhotos) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/photo", response_model=LecturerPhotos) async def get_lecturer_photos(lecturer_id: int, limit: int = 10, offset: int = 0) -> LecturerPhotos: if not Lecturer.get(id=lecturer_id, session=db.session): @@ -53,7 +49,6 @@ async def get_lecturer_photos(lecturer_id: int, limit: int = 10, offset: int = 0 ) -@lecturer_photo_router.delete("/photo/{id}", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 @router.delete("/photo/{id}", response_model=None) async def delete_photo(id: int, lecturer_id: int) -> None: photo = DbPhoto.get(id, only_approved=False, session=db.session) @@ -66,7 +61,6 @@ async def delete_photo(id: int, lecturer_id: int) -> None: return None -@lecturer_photo_router.get("/photo/{id}", response_model=Photo) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/photo/{id}", response_model=Photo) async def get_photo(id: int, lecturer_id: int) -> Photo: if not Lecturer.get(id=lecturer_id, session=db.session): diff --git a/calendar_backend/routes/models/base.py b/calendar_backend/routes/models/base.py index b0602ba8..97809112 100644 --- a/calendar_backend/routes/models/base.py +++ b/calendar_backend/routes/models/base.py @@ -72,7 +72,7 @@ class EventGet(Base): id: int name: str room: list[RoomGet] - group: GroupGet + group: list[GroupGet] lecturer: list[LecturerGet] start_ts: datetime.datetime end_ts: datetime.datetime diff --git a/calendar_backend/routes/models/event.py b/calendar_backend/routes/models/event.py index bf0fa382..89c87266 100644 --- a/calendar_backend/routes/models/event.py +++ b/calendar_backend/routes/models/event.py @@ -6,7 +6,7 @@ class EventPatch(Base): name: str | None room_id: list[int] | None - group_id: int | None + group_id: list[int] | None lecturer_id: list[int] | None start_ts: datetime.datetime | None end_ts: datetime.datetime | None @@ -22,7 +22,7 @@ def __repr__(self): class EventPost(Base): name: str room_id: list[int] - group_id: int + group_id: list[int] lecturer_id: list[int] start_ts: datetime.datetime end_ts: datetime.datetime @@ -39,7 +39,7 @@ class Event(Base): id: int name: str room: list[RoomGet] - group: GroupGet + group: list[GroupGet] lecturer: list[LecturerGet] start_ts: datetime.datetime end_ts: datetime.datetime diff --git a/calendar_backend/routes/room/__init__.py b/calendar_backend/routes/room/__init__.py index f86fd55b..e69de29b 100644 --- a/calendar_backend/routes/room/__init__.py +++ b/calendar_backend/routes/room/__init__.py @@ -1,4 +0,0 @@ -from .room import room_router - - -__all__ = ["room_router"] diff --git a/calendar_backend/routes/room/room.py b/calendar_backend/routes/room/room.py index d21d8b5b..57eeea05 100644 --- a/calendar_backend/routes/room/room.py +++ b/calendar_backend/routes/room/room.py @@ -11,18 +11,14 @@ settings = get_settings() logger = logging.getLogger(__name__) -# DEPRICATED TODO: Drop 2023-04-01 -room_router = APIRouter(prefix="/timetable/room", tags=["Room"], deprecated=True) router = APIRouter(prefix="/room", tags=["Room"]) -@room_router.get("/{id}", response_model=RoomGet) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/{id}", response_model=RoomGet) async def get_room_by_id(id: int) -> RoomGet: return RoomGet.from_orm(Room.get(id, session=db.session)) -@room_router.get("/", response_model=GetListRoom) # DEPRICATED TODO: Drop 2023-04-01 @router.get("/", response_model=GetListRoom) async def get_rooms(query: str = "", limit: int = 10, offset: int = 0) -> GetListRoom: res = Room.get_all(session=db.session).filter(Room.name.contains(query)) @@ -40,7 +36,6 @@ async def get_rooms(query: str = "", limit: int = 10, offset: int = 0) -> GetLis ) -@room_router.post("/", response_model=RoomGet) # DEPRICATED TODO: Drop 2023-04-01 @router.post("/", response_model=RoomGet) async def create_room(room: RoomPost, _=Depends(UnionAuth(scopes=["timetable.room.create"]))) -> RoomGet: if bool( @@ -52,7 +47,6 @@ async def create_room(room: RoomPost, _=Depends(UnionAuth(scopes=["timetable.roo return RoomGet.from_orm(db_room) -@room_router.patch("/{id}", response_model=RoomGet) # DEPRICATED TODO: Drop 2023-04-01 @router.patch("/{id}", response_model=RoomGet) async def patch_room(id: int, room_inp: RoomPatch, _=Depends(UnionAuth(scopes=["timetable.room.upadte"]))) -> RoomGet: room = ( @@ -67,7 +61,6 @@ async def patch_room(id: int, room_inp: RoomPatch, _=Depends(UnionAuth(scopes=[" return RoomGet.from_orm(patched) -@room_router.delete("/{id}", response_model=None) # DEPRICATED TODO: Drop 2023-04-01 @router.delete("/{id}", response_model=None) async def delete_room(id: int, _=Depends(UnionAuth(scopes=["timetable.room.delete"]))) -> None: Room.delete(id, session=db.session) diff --git a/tests/conftest.py b/tests/conftest.py index ef238a4c..3eac84b6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -102,7 +102,7 @@ def event_path(client_auth: TestClient, dbsession: Session, lecturer_path, room_ request_obj = { "name": "string", "room_id": [room_id], - "group_id": group_id, + "group_id": [group_id], "lecturer_id": [lecturer_id], "start_ts": "2022-08-26T22:32:38.575Z", "end_ts": "2022-08-26T22:32:38.575Z", diff --git a/tests/event/event.py b/tests/event/event.py index 66e0e7b9..a048c649 100644 --- a/tests/event/event.py +++ b/tests/event/event.py @@ -18,7 +18,7 @@ def test_create(client_auth: TestClient, dbsession: Session, room_path, group_pa request_obj = { "name": "string", "room_id": [room_id], - "group_id": group_id, + "group_id": [group_id], "lecturer_id": [lecturer_id], "start_ts": "2022-08-26T22:32:38.575Z", "end_ts": "2022-08-26T22:32:38.575Z", @@ -28,7 +28,7 @@ def test_create(client_auth: TestClient, dbsession: Session, room_path, group_pa response_obj = response.json() assert response_obj["name"] == request_obj["name"] assert response_obj["room"][0]["id"] == room_id - assert response_obj["group"]["id"] == request_obj["group_id"] + assert response_obj["group"][0]["id"] == group_id assert response_obj["lecturer"][0]["id"] == lecturer_id assert response_obj["start_ts"][:20] == request_obj["start_ts"][:20] assert response_obj["end_ts"][:20] == request_obj["end_ts"][:20] @@ -36,7 +36,7 @@ def test_create(client_auth: TestClient, dbsession: Session, room_path, group_pa assert response_model.name == request_obj["name"] assert [row.id for row in response_model.room] == request_obj["room_id"] assert [row.id for row in response_model.lecturer] == request_obj["lecturer_id"] - assert response_model.group_id == request_obj["group_id"] + assert [row.id for row in response_model.group] == request_obj["group_id"] def test_create_many(client_auth: TestClient, dbsession: Session, room_factory, group_factory, lecturer_factory): @@ -56,7 +56,7 @@ def test_create_many(client_auth: TestClient, dbsession: Session, room_factory, { "name": "string", "room_id": [room_id1], - "group_id": group_id1, + "group_id": [group_id1], "lecturer_id": [lecturer_id1], "start_ts": "2022-08-26T22:32:38.575Z", "end_ts": "2022-08-26T22:32:38.575Z", @@ -64,7 +64,7 @@ def test_create_many(client_auth: TestClient, dbsession: Session, room_factory, { "name": "string", "room_id": [room_id2], - "group_id": group_id2, + "group_id": [group_id2], "lecturer_id": [lecturer_id2], "start_ts": "2022-08-26T22:32:38.575Z", "end_ts": "2022-08-26T22:32:38.575Z", @@ -75,7 +75,7 @@ def test_create_many(client_auth: TestClient, dbsession: Session, room_factory, response_obj = response.json() assert response_obj[0]["name"] == request_obj[0]["name"] assert response_obj[0]["room"][0]["id"] == room_id1 - assert response_obj[0]["group"]["id"] == request_obj[0]["group_id"] + assert response_obj[0]["group"][0]["id"] == group_id1 assert response_obj[0]["lecturer"][0]["id"] == lecturer_id1 assert response_obj[0]["start_ts"][:20] == request_obj[0]["start_ts"][:20] assert response_obj[0]["end_ts"][:20] == request_obj[0]["end_ts"][:20] @@ -83,11 +83,11 @@ def test_create_many(client_auth: TestClient, dbsession: Session, room_factory, assert response_model.name == request_obj[0]["name"] assert [row.id for row in response_model.room] == request_obj[0]["room_id"] assert [row.id for row in response_model.lecturer] == request_obj[0]["lecturer_id"] - assert response_model.group_id == request_obj[0]["group_id"] + assert [row.id for row in response_model.group] == request_obj[0]["group_id"] assert response_obj[1]["name"] == request_obj[1]["name"] assert response_obj[1]["room"][0]["id"] == room_id2 - assert response_obj[1]["group"]["id"] == request_obj[1]["group_id"] + assert response_obj[1]["group"][0]["id"] == group_id2 assert response_obj[1]["lecturer"][0]["id"] == lecturer_id2 assert response_obj[1]["start_ts"][:20] == request_obj[1]["start_ts"][:20] assert response_obj[1]["end_ts"][:20] == request_obj[1]["end_ts"][:20] @@ -95,7 +95,7 @@ def test_create_many(client_auth: TestClient, dbsession: Session, room_factory, assert response_model.name == request_obj[1]["name"] assert [row.id for row in response_model.room] == request_obj[1]["room_id"] assert [row.id for row in response_model.lecturer] == request_obj[1]["lecturer_id"] - assert response_model.group_id == request_obj[1]["group_id"] + assert [row.id for row in response_model.group] == request_obj[1]["group_id"] def test_delete(client_auth: TestClient, dbsession: Session, room_path, lecturer_path, group_path): @@ -105,7 +105,7 @@ def test_delete(client_auth: TestClient, dbsession: Session, room_path, lecturer request_obj = { "name": "string", "room_id": [room_id], - "group_id": group_id, + "group_id": [group_id], "lecturer_id": [lecturer_id], "start_ts": "2022-08-26T22:32:38.575Z", "end_ts": "2022-08-26T22:32:38.575Z", @@ -115,7 +115,7 @@ def test_delete(client_auth: TestClient, dbsession: Session, room_path, lecturer response_obj = response.json() assert response_obj["name"] == request_obj["name"] assert response_obj["room"][0]["id"] == request_obj["room_id"][0] - assert response_obj["group"]["id"] == request_obj["group_id"] + assert response_obj["group"][0]["id"] == request_obj["group_id"][0] assert response_obj["lecturer"][0]["id"] == request_obj["lecturer_id"][0] assert response_obj["start_ts"][:20] == request_obj["start_ts"][:20] assert response_obj["end_ts"][:20] == request_obj["end_ts"][:20] @@ -163,7 +163,7 @@ def test_update_all(client_auth: TestClient, dbsession: Session): request_obj = { "name": "string", "room_id": [room.id], - "group_id": group.id, + "group_id": [group.id], "lecturer_id": [lecturer.id], "start_ts": "2022-08-26T22:32:38.575Z", "end_ts": "2022-08-26T22:32:38.575Z", @@ -173,7 +173,7 @@ def test_update_all(client_auth: TestClient, dbsession: Session): response_obj = response.json() assert response_obj["name"] == request_obj["name"] assert response_obj["room"][0]["id"] == request_obj["room_id"][0] - assert response_obj["group"]["id"] == request_obj["group_id"] + assert response_obj["group"][0]["id"] == request_obj["group_id"][0] assert response_obj["lecturer"][0]["id"] == request_obj["lecturer_id"][0] assert response_obj["start_ts"][:20] == request_obj["start_ts"][:20] assert response_obj["end_ts"][:20] == request_obj["end_ts"][:20] @@ -185,7 +185,7 @@ def test_update_all(client_auth: TestClient, dbsession: Session): response_obj = response.json() assert response_obj["name"] == request_obj["name"] assert response_obj["room"][0]["id"] == request_obj["room_id"][0] - assert response_obj["group"]["id"] == request_obj["group_id"] + assert response_obj["group"][0]["id"] == request_obj["group_id"][0] assert response_obj["lecturer"][0]["id"] == request_obj["lecturer_id"][0] assert response_obj["start_ts"][:20] == request_obj["start_ts"][:20] assert response_obj["end_ts"][:20] == request_obj["end_ts"][:20] @@ -194,18 +194,19 @@ def test_update_all(client_auth: TestClient, dbsession: Session): request_obj_2 = { "name": "frfrf", "room_id": [room.id], - "group_id": group.id, + "group_id": [group.id], "lecturer_id": [lecturer.id], "start_ts": "2022-08-26T22:32:38.575Z", "end_ts": "2022-08-26T22:32:38.575Z", } - client_auth.patch(urljoin(RESOURCE, str(id_)), json=request_obj_2) + response = client_auth.patch(urljoin(RESOURCE, str(id_)), json=request_obj_2) + assert response.status_code == 200 response = client_auth.get(urljoin(RESOURCE, str(id_))) assert response.status_code == status.HTTP_200_OK, response.json() response_obj = response.json() assert response_obj["name"] == request_obj_2["name"] assert response_obj["room"][0]["id"] == request_obj_2["room_id"][0] - assert response_obj["group"]["id"] == request_obj_2["group_id"] + assert response_obj["group"][0]["id"] == request_obj_2["group_id"][0] assert response_obj["lecturer"][0]["id"] == request_obj_2["lecturer_id"][0] assert response_obj["start_ts"][:20] == request_obj_2["start_ts"][:20] assert response_obj["end_ts"][:20] == request_obj_2["end_ts"][:20] @@ -217,7 +218,7 @@ def test_update_all(client_auth: TestClient, dbsession: Session): if item["id"] == id_: assert item[0]["name"] == request_obj["name"] assert item[0]["room"][0]["id"] == request_obj["room_id"][0] - assert item[0]["group"]["id"] == request_obj["group_id"] + assert item[0]["group"][0]["id"] == request_obj["group_id"][0] assert item[0]["lecturer"][0]["id"] == request_obj["lecturer_id"][0] assert item["start_ts"][:20] == request_obj_2["start_ts"][:20] assert item["end_ts"][:20] == request_obj_2["end_ts"][:20] @@ -227,7 +228,7 @@ def test_update_all(client_auth: TestClient, dbsession: Session): assert response_model.name == request_obj_2["name"] assert [row.id for row in response_model.room] == request_obj_2["room_id"] assert [row.id for row in response_model.lecturer] == request_obj_2["lecturer_id"] - assert response_model.group_id == request_obj_2["group_id"] + assert [row.id for row in response_model.group] == request_obj_2["group_id"] assert str(response_model.start_ts.isoformat())[:20] == request_obj_2["start_ts"][:20] assert str(response_model.end_ts.isoformat())[:20] == request_obj_2["end_ts"][:20] @@ -256,7 +257,7 @@ def test_delete_from_to(client_auth: TestClient, dbsession: Session, room_factor { "name": "string", "room_id": [room_id1], - "group_id": group_id1, + "group_id": [group_id1], "lecturer_id": [lecturer_id1], "start_ts": "2022-08-26T22:32:38.575Z", "end_ts": "2022-08-26T22:32:38.575Z", @@ -264,7 +265,7 @@ def test_delete_from_to(client_auth: TestClient, dbsession: Session, room_factor { "name": "string", "room_id": [room_id2], - "group_id": group_id2, + "group_id": [group_id2], "lecturer_id": [lecturer_id2], "start_ts": "2022-08-26T22:32:38.575Z", "end_ts": "2022-08-26T22:32:38.575Z", From a062ba9bfaee9b4f7e9e438c30434a5bb68d6e5f Mon Sep 17 00:00:00 2001 From: semen603089 Date: Mon, 3 Jul 2023 08:57:49 +0300 Subject: [PATCH 2/7] migr --- ...049fde8f4_many_to_many_events_to_groups.py | 38 +++++++++++ ...any_to_many_events_to_groups_sql_update.py | 64 +++++++++++++++++++ .../versions/7efeb3b9f4d9_del_group_id.py | 30 +++++++++ 3 files changed, 132 insertions(+) create mode 100644 migrations/versions/55a049fde8f4_many_to_many_events_to_groups.py create mode 100644 migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py create mode 100644 migrations/versions/7efeb3b9f4d9_del_group_id.py diff --git a/migrations/versions/55a049fde8f4_many_to_many_events_to_groups.py b/migrations/versions/55a049fde8f4_many_to_many_events_to_groups.py new file mode 100644 index 00000000..d601f332 --- /dev/null +++ b/migrations/versions/55a049fde8f4_many_to_many_events_to_groups.py @@ -0,0 +1,38 @@ +"""Many to Many events to groups + +Revision ID: 55a049fde8f4 +Revises: fe04c8baa5ab +Create Date: 2023-07-02 22:58:17.045433 + +""" +import sqlalchemy as sa +from alembic import op + + +# revision identifiers, used by Alembic. +revision = '55a049fde8f4' +down_revision = 'fe04c8baa5ab' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + 'events_groups', + sa.Column('event_id', sa.Integer(), nullable=False), + sa.Column('group_id', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ['event_id'], + ['event.id'], + ), + sa.ForeignKeyConstraint( + ['group_id'], + ['group.id'], + ), + sa.PrimaryKeyConstraint('id'), + ) + + +def downgrade(): + op.drop_table('events_groups') diff --git a/migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py b/migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py new file mode 100644 index 00000000..f9d1f1a1 --- /dev/null +++ b/migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py @@ -0,0 +1,64 @@ +"""Many to Many events to groups SQL UPDATE + +Revision ID: 7c171827b74f +Revises: 55a049fde8f4 +Create Date: 2023-07-02 23:07:02.440547 + +""" +import sqlalchemy as sa +from alembic import op + + +# revision identifiers, used by Alembic. +revision = '7c171827b74f' +down_revision = '55a049fde8f4' +branch_labels = None +depends_on = None + + +e = op.get_bind() + + +def merge_groups(res): + to_delete = [] + pairs = [] + for old_ids, group_ids in res: + save = old_ids.pop() + to_delete.extend(old_ids) + pairs.extend([(save, x) for x in group_ids]) + return to_delete, pairs + + +def upgrade(): + res = e.execute( + sa.text( + """ + with inner_q as ( + select + name, + start_ts, + end_ts, + array_agg(id) as old_ids, + array_agg(group_id) as group_ids, + count(*) as groups_incl + from api_timetable."event" e + where not is_deleted + group by name, start_ts, end_ts + ) + select old_ids, group_ids + from inner_q + """ + ) + ).all() + + to_delete, pairs = merge_groups(res) + delete_old_events_query = ( + f"""UPDATE event SET is_deleted = True WHERE id IN ({', '.join([str(x) for x in to_delete])})""" + ) + create_new_links_query = f"""INSERT INTO events_groups(event_id, group_id) VALUES ({'), ('.join([str(x) + ', ' + str(y) for x, y in pairs])})""" + e.execute(sa.text(delete_old_events_query)) + e.execute(sa.text(create_new_links_query)) + + +def downgrade(): + pass diff --git a/migrations/versions/7efeb3b9f4d9_del_group_id.py b/migrations/versions/7efeb3b9f4d9_del_group_id.py new file mode 100644 index 00000000..6d2ff6ae --- /dev/null +++ b/migrations/versions/7efeb3b9f4d9_del_group_id.py @@ -0,0 +1,30 @@ +"""del group_id + +Revision ID: 7efeb3b9f4d9 +Revises: 7c171827b74f +Create Date: 2023-07-03 01:34:47.491324 + +""" +import sqlalchemy as sa +from alembic import op + + +# revision identifiers, used by Alembic. +revision = '7efeb3b9f4d9' +down_revision = '7c171827b74f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint('lesson_group_id_fkey', 'event', type_='foreignkey') + op.drop_column('event', 'group_id') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('event', sa.Column('group_id', sa.INTEGER(), autoincrement=False, nullable=True)) + op.create_foreign_key('lesson_group_id_fkey', 'event', 'group', ['group_id'], ['id']) + # ### end Alembic commands ### From 2e718512fd5d45bf99ce2686a9e8d758c0acefd6 Mon Sep 17 00:00:00 2001 From: semen603089 Date: Mon, 3 Jul 2023 09:01:21 +0300 Subject: [PATCH 3/7] migr --- .../7c171827b74f_many_to_many_events_to_groups_sql_update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py b/migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py index f9d1f1a1..9972365d 100644 --- a/migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py +++ b/migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py @@ -41,7 +41,7 @@ def upgrade(): array_agg(id) as old_ids, array_agg(group_id) as group_ids, count(*) as groups_incl - from api_timetable."event" e + from "event" e where not is_deleted group by name, start_ts, end_ts ) From a202858cc1eec0c666254f8ff644e02bfb2fb3b1 Mon Sep 17 00:00:00 2001 From: semen603089 Date: Mon, 3 Jul 2023 09:09:55 +0300 Subject: [PATCH 4/7] migr --- .../7c171827b74f_many_to_many_events_to_groups_sql_update.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py b/migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py index 9972365d..34ed81b2 100644 --- a/migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py +++ b/migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py @@ -55,7 +55,10 @@ def upgrade(): delete_old_events_query = ( f"""UPDATE event SET is_deleted = True WHERE id IN ({', '.join([str(x) for x in to_delete])})""" ) - create_new_links_query = f"""INSERT INTO events_groups(event_id, group_id) VALUES ({'), ('.join([str(x) + ', ' + str(y) for x, y in pairs])})""" + list_ = '), ('.join([str(x) + ', ' + str(y) for x, y in pairs]) + if not list_: + return + create_new_links_query = f"""INSERT INTO events_groups(event_id, group_id) VALUES ({list_})""" e.execute(sa.text(delete_old_events_query)) e.execute(sa.text(create_new_links_query)) From 6360b842402ee4d2c68d87b39701c42dd099b2ba Mon Sep 17 00:00:00 2001 From: semen603089 Date: Mon, 3 Jul 2023 23:07:14 +0300 Subject: [PATCH 5/7] google del --- calendar_backend/google_engine/__init__.py | 8 -- calendar_backend/google_engine/api_utils.py | 52 --------- calendar_backend/google_engine/event.py | 41 ------- .../google_engine/event_from_db.py | 39 ------- calendar_backend/google_engine/service.py | 31 ----- calendar_backend/routes/gcal.py | 110 ------------------ 6 files changed, 281 deletions(-) delete mode 100644 calendar_backend/google_engine/__init__.py delete mode 100644 calendar_backend/google_engine/api_utils.py delete mode 100644 calendar_backend/google_engine/event.py delete mode 100644 calendar_backend/google_engine/event_from_db.py delete mode 100644 calendar_backend/google_engine/service.py delete mode 100644 calendar_backend/routes/gcal.py diff --git a/calendar_backend/google_engine/__init__.py b/calendar_backend/google_engine/__init__.py deleted file mode 100644 index 00f48d51..00000000 --- a/calendar_backend/google_engine/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from .api_utils import create_calendar_with_timetable -from .service import get_calendar_service_from_token - - -__all__ = [ - "get_calendar_service_from_token", - "create_calendar_with_timetable", -] diff --git a/calendar_backend/google_engine/api_utils.py b/calendar_backend/google_engine/api_utils.py deleted file mode 100644 index 81f099d1..00000000 --- a/calendar_backend/google_engine/api_utils.py +++ /dev/null @@ -1,52 +0,0 @@ -import logging - -import googleapiclient.discovery -from sqlalchemy.orm import Session - -from calendar_backend.settings import get_settings - -from .event import Event -from .event_from_db import create_google_events_from_db - - -settings = get_settings() -logger = logging.getLogger(__name__) - - -def create_calendar(service: googleapiclient.discovery.Resource, group: str) -> str: - """ - Creates a new calendar for timetable on user's account (if not exist) - returns calendarId - """ - timetable_calendar = { - "summary": f"Расписание на физфаке для {group} группы", - "timeZone": "Europe/Moscow", - } - calendars = service.calendarList().list().execute().get("items", []) - for calendar in calendars: - if calendar["summary"] == timetable_calendar["summary"]: - service.calendars().delete(calendarId=calendar["id"]).execute() - created_calendar = service.calendars().insert(body=timetable_calendar).execute() - logger.debug(f"Calendar for {group} created") - return created_calendar["id"] - - -def insert_event(service: googleapiclient.discovery.Resource, calendar_id: str, event: dict): - """ - Inserts an event to calendar. - API allows inserting events only partially. - Returns status string with event summary. - """ - status = service.events().insert(calendarId=calendar_id, body=event).execute() - logger.debug(f"Event {status['summary']} inserted") - return status - - -async def create_calendar_with_timetable( - service: googleapiclient.discovery.Resource, group: str, session: Session -) -> None: - calendar_id: str = create_calendar(service, group) - events: list[Event] = await create_google_events_from_db(group, session=session) - logger.debug(f"Getting events for {calendar_id} ...") - for event in events: - insert_event(service, calendar_id, event.dict()) diff --git a/calendar_backend/google_engine/event.py b/calendar_backend/google_engine/event.py deleted file mode 100644 index a7a07f5c..00000000 --- a/calendar_backend/google_engine/event.py +++ /dev/null @@ -1,41 +0,0 @@ -from pydantic import BaseModel - -from calendar_backend.methods import utils - - -class Event(BaseModel): - summary: str - location: str | None - description: str | None - start: dict[str, str] - end: dict[str, str] - recurrence: list[str] - attendees: list[str] - reminders: dict[str, bool] - - -def create_google_calendar_event( - summary: str, start_time: str, end_time: str, location: str, description: str -) -> Event: - """ - Creates a dict with a Google calendar params - """ - end_sem_date = str(utils.get_end_of_semester_date()).replace('-', '') - end_sem_date_format = f"{end_sem_date}T235900Z" - event = Event( - summary=summary, - location=location, - description=description, - start={ - "dateTime": start_time, - "timeZone": "Europe/Moscow", - }, - end={ - "dateTime": end_time, - "timeZone": "Europe/Moscow", - }, - recurrence=[f"RRULE:FREQ=WEEKLY;UNTIL={end_sem_date_format};INTERVAL=2"], - attendees=[], - reminders={"useDefault": False}, - ) - return event diff --git a/calendar_backend/google_engine/event_from_db.py b/calendar_backend/google_engine/event_from_db.py deleted file mode 100644 index fd6ffb54..00000000 --- a/calendar_backend/google_engine/event_from_db.py +++ /dev/null @@ -1,39 +0,0 @@ -import datetime -import logging - -from sqlalchemy.orm import Session - -from calendar_backend.methods.utils import get_lessons_by_group_from_date -from calendar_backend.models import Event, Group -from calendar_backend.settings import get_settings - -from .event import create_google_calendar_event - - -settings = get_settings() -logger = logging.getLogger(__name__) - - -async def create_google_events_from_db(group_id: int, session: Session) -> list[Event]: - """ - Creates a timetable for certain group from db. - Returns list[Event] of events/subjects - """ - group, _ = Group.get(group_id, session=session) - group_lessons: list[Event] = await get_lessons_by_group_from_date(group, datetime.date.today()) - list_of_lessons = [] - time_zone = "+03:00" - logger.debug(f"Getting list of subjects for {group}...") - for lesson in group_lessons: - list_of_lessons.append( - create_google_calendar_event( - summary=lesson.name, - start_time=f"{lesson.start_ts.date()}T{lesson.start_ts.time().hour}:{lesson.start_ts.time().minute}:00{time_zone}", - end_time=f"{lesson.end_ts.date()}T{lesson.end_ts.time().hour}:{lesson.end_ts.time().minute}:00{time_zone}", - location=str([row.name for row in lesson.room]), - description=str([f"{row.first_name} {row.middle_name} {row.last_name}" for row in lesson.lecturer]), - ) - ) - if lesson.start_ts.date() == datetime.date.today() + datetime.timedelta(14): - break - return list_of_lessons diff --git a/calendar_backend/google_engine/service.py b/calendar_backend/google_engine/service.py deleted file mode 100644 index bc122f55..00000000 --- a/calendar_backend/google_engine/service.py +++ /dev/null @@ -1,31 +0,0 @@ -import json - -import google.oauth2.credentials -import googleapiclient.discovery -from fastapi_sqlalchemy import db -from googleapiclient.discovery import build -from pydantic import Json -from sqlalchemy.exc import NoResultFound - -from calendar_backend.models import Credentials -from calendar_backend.settings import get_settings - - -settings = get_settings() - - -def get_calendar_service(id: int) -> googleapiclient.discovery.Resource: - try: - user_data = db.query(Credentials).filter_by(id=id).one() - credentials = google.oauth2.credentials.Credentials.from_authorized_user_info( - json.loads(user_data.token), settings.SCOPES - ) - service = build("calendar", "v3", credentials=credentials) - return service - except NoResultFound: - print(f"service for id {id} not found in db") - - -def get_calendar_service_from_token(token: Json) -> googleapiclient.discovery.Resource: - credentials = google.oauth2.credentials.Credentials.from_authorized_user_info(json.loads(token), settings.SCOPES) - return build("calendar", "v3", credentials=credentials) diff --git a/calendar_backend/routes/gcal.py b/calendar_backend/routes/gcal.py deleted file mode 100644 index a31b6fd3..00000000 --- a/calendar_backend/routes/gcal.py +++ /dev/null @@ -1,110 +0,0 @@ -"""DEPRICATED TODO: Drop 2023-04-01 -""" -import logging -import os -from functools import lru_cache -from urllib.parse import unquote - -from fastapi import APIRouter, BackgroundTasks, HTTPException -from fastapi.responses import RedirectResponse -from fastapi.templating import Jinja2Templates -from fastapi_sqlalchemy import db -from fastapi_sqlalchemy.exceptions import MissingSessionError, SessionNotInitialisedError -from google_auth_oauthlib.flow import Flow -from googleapiclient.discovery import UnknownApiNameOrVersion, build -from pydantic.types import Json - -from calendar_backend.google_engine import create_calendar_with_timetable, get_calendar_service_from_token -from calendar_backend.models import Credentials, Group -from calendar_backend.settings import get_settings - - -settings = get_settings() -logger = logging.getLogger(__name__) -templates = Jinja2Templates(directory="calendar_backend/templates") -# DEPRICATED TODO: Drop ? -gcal = APIRouter(tags=["Utils: Google"], deprecated=True) - - -os.environ["OAUTHLIB_RELAX_TOKEN_SCOPE"] = "1" - - -@lru_cache(2) -def get_flow(state=""): - return Flow.from_client_config( - settings.GOOGLE_CLIENT_SECRET, - scopes=settings.SCOPES, - state=state, - redirect_uri=f"{settings.REDIRECT_URL}/credentials", - ) - - -@gcal.get("/flow") -async def get_user_flow(state: str): - if settings.GOOGLE_CLIENT_SECRET: - user_flow = get_flow(state) - return RedirectResponse(user_flow.authorization_url()[0]) - else: - logger.info("Missing google service credentials") - return HTTPException(502, "Connection to google failed") - - -@gcal.get("/credentials") -async def get_credentials( - background_tasks: BackgroundTasks, - code: str, - scope: str, - state: str, -): - groups = [ - f"{row.number}, {row.name}" if row.name else f"{row.number}" for row in db.session.query(Group).filter().all() - ] - scope = scope.split(unquote("%20")) - group = state - flow = get_flow() - try: - flow.fetch_token(code=code) - creds = flow.credentials - token: Json = creds.to_json() - except ValueError as e: - logger.info(f"Invalid oauth2 flow session: {e}") - raise HTTPException(400, "Bad request") - try: - # build service to get an email address - service = build("oauth2", "v2", credentials=creds, cache_discovery=False) - email = service.userinfo().get().execute()["email"] - if group not in groups: - logger.info(f"No group found 404 for user {email}") - raise HTTPException(404, "No group found") - except UnknownApiNameOrVersion as e: - logger.info(f"Invalid Google service: {e}") - background_tasks.add_task( - create_calendar_with_timetable, - get_calendar_service_from_token(token), - group, - db.session, - ) - try: - db_records = db.session.query(Credentials).filter(Credentials.email == email) - if len(db_records.all()) == 0: - db.session.add( - Credentials( - group=group, - email=email, - scope=scope, - token=token, - ) - ) - else: - db_records.update( - dict( - group=group, - scope=scope, - ) - ) - logger.debug("DB session OK") - except SessionNotInitialisedError: - logger.critical("DB session not initialized") - except MissingSessionError: - logger.critical("Missing db session") - return RedirectResponse(settings.REDIRECT_URL) From defa0725836e6106a551d69fe1ac8afd67187e15 Mon Sep 17 00:00:00 2001 From: semen603089 Date: Mon, 3 Jul 2023 23:10:44 +0300 Subject: [PATCH 6/7] migr --- ...049fde8f4_many_to_many_events_to_groups.py | 48 +++++++++++++ ...any_to_many_events_to_groups_sql_update.py | 67 ------------------- .../versions/7efeb3b9f4d9_del_group_id.py | 30 --------- 3 files changed, 48 insertions(+), 97 deletions(-) delete mode 100644 migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py delete mode 100644 migrations/versions/7efeb3b9f4d9_del_group_id.py diff --git a/migrations/versions/55a049fde8f4_many_to_many_events_to_groups.py b/migrations/versions/55a049fde8f4_many_to_many_events_to_groups.py index d601f332..2843b831 100644 --- a/migrations/versions/55a049fde8f4_many_to_many_events_to_groups.py +++ b/migrations/versions/55a049fde8f4_many_to_many_events_to_groups.py @@ -15,6 +15,18 @@ branch_labels = None depends_on = None +e = op.get_bind() + + +def merge_groups(res): + to_delete = [] + pairs = [] + for old_ids, group_ids in res: + save = old_ids.pop() + to_delete.extend(old_ids) + pairs.extend([(save, x) for x in group_ids]) + return to_delete, pairs + def upgrade(): op.create_table( @@ -32,7 +44,43 @@ def upgrade(): ), sa.PrimaryKeyConstraint('id'), ) + res = e.execute( + sa.text( + """ + with inner_q as ( + select + name, + start_ts, + end_ts, + array_agg(id) as old_ids, + array_agg(group_id) as group_ids, + count(*) as groups_incl + from "event" e + where not is_deleted + group by name, start_ts, end_ts + ) + select old_ids, group_ids + from inner_q + """ + ) + ).all() + + to_delete, pairs = merge_groups(res) + delete_old_events_query = ( + f"""UPDATE event SET is_deleted = True WHERE id IN ({', '.join([str(x) for x in to_delete])})""" + ) + list_ = '), ('.join([str(x) + ', ' + str(y) for x, y in pairs]) + if not list_: + return + create_new_links_query = f"""INSERT INTO events_groups(event_id, group_id) VALUES ({list_})""" + e.execute(sa.text(delete_old_events_query)) + e.execute(sa.text(create_new_links_query)) + op.drop_constraint('lesson_group_id_fkey', 'event', type_='foreignkey') + op.drop_column('event', 'group_id') def downgrade(): + op.add_column('event', sa.Column('group_id', sa.INTEGER(), autoincrement=False, nullable=True)) + op.create_foreign_key('lesson_group_id_fkey', 'event', 'group', ['group_id'], ['id']) op.drop_table('events_groups') + diff --git a/migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py b/migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py deleted file mode 100644 index 34ed81b2..00000000 --- a/migrations/versions/7c171827b74f_many_to_many_events_to_groups_sql_update.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Many to Many events to groups SQL UPDATE - -Revision ID: 7c171827b74f -Revises: 55a049fde8f4 -Create Date: 2023-07-02 23:07:02.440547 - -""" -import sqlalchemy as sa -from alembic import op - - -# revision identifiers, used by Alembic. -revision = '7c171827b74f' -down_revision = '55a049fde8f4' -branch_labels = None -depends_on = None - - -e = op.get_bind() - - -def merge_groups(res): - to_delete = [] - pairs = [] - for old_ids, group_ids in res: - save = old_ids.pop() - to_delete.extend(old_ids) - pairs.extend([(save, x) for x in group_ids]) - return to_delete, pairs - - -def upgrade(): - res = e.execute( - sa.text( - """ - with inner_q as ( - select - name, - start_ts, - end_ts, - array_agg(id) as old_ids, - array_agg(group_id) as group_ids, - count(*) as groups_incl - from "event" e - where not is_deleted - group by name, start_ts, end_ts - ) - select old_ids, group_ids - from inner_q - """ - ) - ).all() - - to_delete, pairs = merge_groups(res) - delete_old_events_query = ( - f"""UPDATE event SET is_deleted = True WHERE id IN ({', '.join([str(x) for x in to_delete])})""" - ) - list_ = '), ('.join([str(x) + ', ' + str(y) for x, y in pairs]) - if not list_: - return - create_new_links_query = f"""INSERT INTO events_groups(event_id, group_id) VALUES ({list_})""" - e.execute(sa.text(delete_old_events_query)) - e.execute(sa.text(create_new_links_query)) - - -def downgrade(): - pass diff --git a/migrations/versions/7efeb3b9f4d9_del_group_id.py b/migrations/versions/7efeb3b9f4d9_del_group_id.py deleted file mode 100644 index 6d2ff6ae..00000000 --- a/migrations/versions/7efeb3b9f4d9_del_group_id.py +++ /dev/null @@ -1,30 +0,0 @@ -"""del group_id - -Revision ID: 7efeb3b9f4d9 -Revises: 7c171827b74f -Create Date: 2023-07-03 01:34:47.491324 - -""" -import sqlalchemy as sa -from alembic import op - - -# revision identifiers, used by Alembic. -revision = '7efeb3b9f4d9' -down_revision = '7c171827b74f' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint('lesson_group_id_fkey', 'event', type_='foreignkey') - op.drop_column('event', 'group_id') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('event', sa.Column('group_id', sa.INTEGER(), autoincrement=False, nullable=True)) - op.create_foreign_key('lesson_group_id_fkey', 'event', 'group', ['group_id'], ['id']) - # ### end Alembic commands ### From 1f8b0b9ef265dfb9b02b4a3572d5bfaf786aa8dd Mon Sep 17 00:00:00 2001 From: semen603089 Date: Mon, 3 Jul 2023 23:12:45 +0300 Subject: [PATCH 7/7] black --- .../versions/55a049fde8f4_many_to_many_events_to_groups.py | 1 - 1 file changed, 1 deletion(-) diff --git a/migrations/versions/55a049fde8f4_many_to_many_events_to_groups.py b/migrations/versions/55a049fde8f4_many_to_many_events_to_groups.py index 2843b831..11877d1d 100644 --- a/migrations/versions/55a049fde8f4_many_to_many_events_to_groups.py +++ b/migrations/versions/55a049fde8f4_many_to_many_events_to_groups.py @@ -83,4 +83,3 @@ def downgrade(): op.add_column('event', sa.Column('group_id', sa.INTEGER(), autoincrement=False, nullable=True)) op.create_foreign_key('lesson_group_id_fkey', 'event', 'group', ['group_id'], ['id']) op.drop_table('events_groups') -