Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions calendar_backend/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@


class ObjectNotFound(Exception):
def __init__(self, type: Type, ids: int | list[int]):
super().__init__(f"Objects of type {type.__name__} {ids=} not found")
def __init__(self, type: Type, ids: int | list[int] = [], name: str | None = None):
msg = f"Objects of type {type.__name__} {ids=} not found"
if name:
msg = f"Objects of type {type.__name__} with {name=} not found"
super().__init__(msg)


class NotEnoughCriteria(Exception):
Expand Down
3 changes: 1 addition & 2 deletions calendar_backend/models/db.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""Database common classes and methods
"""
"""Database common classes and methods"""

from __future__ import annotations

Expand Down
64 changes: 60 additions & 4 deletions calendar_backend/routes/event/event.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import logging
from datetime import date, timedelta
from datetime import date, datetime, timedelta
from typing import Literal

from auth_lib.fastapi import UnionAuth
from fastapi import APIRouter, Depends, Query
from fastapi.responses import FileResponse
from fastapi import APIRouter, Depends, Query, status
from fastapi.responses import FileResponse, JSONResponse
from fastapi_sqlalchemy import db
from pydantic import TypeAdapter

from calendar_backend.exceptions import NotEnoughCriteria
from calendar_backend.methods import list_calendar
from calendar_backend.models import Event, Group, Lecturer, Room
from calendar_backend.routes.models import EventGet
from calendar_backend.routes.models.event import EventPatch, EventPost, GetListEvent
from calendar_backend.routes.models.event import (
EventPatch,
EventPatchName,
EventPatchResult,
EventPost,
EventRepeatedPost,
GetListEvent,
)
from calendar_backend.settings import get_settings


Expand Down Expand Up @@ -98,6 +105,44 @@ async def create_event(event: EventPost, _=Depends(UnionAuth(scopes=["timetable.
return EventGet.model_validate(event_get)


@router.post("/repeating", response_model=list[EventGet])
async def create_repeating_event(
event: EventRepeatedPost, # _=Depends(UnionAuth(scopes=["timetable.event.create"]))
) -> list[EventGet]:
if event.repeat_timedelta_days <= 0:
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST, content={"detail": f"Timedelta must be a positive integer"}
)
if event.repeat_until_ts > event.start_ts + timedelta(days=1095):
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"detail": "Due to disk utilization limits, events with duration > 3 years is restricted"},
)
events = []
event_dict = event.model_dump()
rooms = [Room.get(room_id, session=db.session) for room_id in event_dict.pop("room_id", [])]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

можно просто поидее .filter(Room.id in list

lecturers = [Lecturer.get(lecturer_id, session=db.session) for lecturer_id in event_dict.pop("lecturer_id", [])]
groups = [Group.get(group_id, session=db.session) for group_id in event_dict.pop("group_id", [])]
repeat_timedelta_days = timedelta(days=event.repeat_timedelta_days)
cur_start_ts = event_dict["start_ts"]
cur_end_ts = event_dict["end_ts"]
while cur_start_ts <= event.repeat_until_ts:
event_get = Event.create(
name=event_dict["name"],
start_ts=cur_start_ts,
end_ts=cur_end_ts,
room=rooms,
lecturer=lecturers,
group=groups,
session=db.session,
)
events.append(event_get)
cur_start_ts += repeat_timedelta_days
cur_end_ts += repeat_timedelta_days
adapter = TypeAdapter(list[EventGet])
return adapter.validate_python(events)


@router.post("/bulk", response_model=list[EventGet])
async def create_events(
events: list[EventPost], _=Depends(UnionAuth(scopes=["timetable.event.create"]))
Expand Down Expand Up @@ -139,6 +184,17 @@ async def create_events(
return adapter.validate_python(result)


@router.patch("/patch_name", response_model=EventPatchResult, summary="Batch update events by name")
async def patch_event_by_name(
event_inp: EventPatchName, _=Depends(UnionAuth(scopes=["timetable.event.update"]))
) -> EventPatchResult:
updated = (
db.session.query(Event).filter(Event.name == event_inp.old_name).update(values={"name": event_inp.new_name})
)
db.session.commit()
return EventPatchResult(old_name=event_inp.old_name, new_name=event_inp.new_name, updated=updated)


@router.patch("/{id}", response_model=EventGet)
async def patch_event(
id: int, event_inp: EventPatch, _=Depends(UnionAuth(scopes=["timetable.event.update"]))
Expand Down
30 changes: 30 additions & 0 deletions calendar_backend/routes/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ def __repr__(self):
)


class EventPatchName(Base):
old_name: str
new_name: str


class EventPatchResult(Base):
old_name: str
new_name: str
updated: int


class EventPost(Base):
name: str
room_id: list[int]
Expand All @@ -35,6 +46,25 @@ def __repr__(self):
)


class EventRepeatedPost(Base):
name: str
room_id: list[int]
group_id: list[int]
lecturer_id: list[int]
start_ts: datetime.datetime
end_ts: datetime.datetime
repeat_timedelta_days: int = 7 # set one week by default
repeat_until_ts: datetime.datetime

def __repr__(self):
return (
f"Lesson(name={self.name},\n"
f" room={self.room_id}, group={self.group_id},\n"
f" lecturer={self.lecturer_id}, start_ts={self.start_ts}, end_ts={self.end_ts})\n"
f" repeats every {self.repeat_timedelta_days} days until {repeat_until_ts}\n"
)


class Event(Base):
id: int
name: str
Expand Down
69 changes: 69 additions & 0 deletions tests/event/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,72 @@ def test_delete_from_to(client_auth: TestClient, dbsession: Session, room_factor
for row in (obj1, obj2):
dbsession.delete(row)
dbsession.commit()


def test_update_by_name(client_auth: TestClient, dbsession: Session, room_factory, group_factory, lecturer_factory):
room_path1 = room_factory(client_auth)
group_path1 = group_factory(client_auth)
lecturer_path1 = lecturer_factory(client_auth)
room_path2 = room_factory(client_auth)
group_path2 = group_factory(client_auth)
lecturer_path2 = lecturer_factory(client_auth)
room_id1 = int(room_path1.split("/")[-1])
group_id1 = int(group_path1.split("/")[-1])
lecturer_id1 = int(lecturer_path1.split("/")[-1])
room_id2 = int(room_path2.split("/")[-1])
group_id2 = int(group_path2.split("/")[-1])
lecturer_id2 = int(lecturer_path2.split("/")[-1])
request_obj = [
{
"name": "string",
"room_id": [room_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",
},
{
"name": "string",
"room_id": [room_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",
},
]
response = client_auth.post(f"{RESOURCE}bulk", json=request_obj)
created = response.json()
assert response.status_code == status.HTTP_200_OK, response.json()
name_to_patch = "not_existing_name"
response = client_auth.patch(
f"{RESOURCE}patch_name", json={"old_name": "not_existing_name", "new_name": "some_name"}
)
assert response.status_code == status.HTTP_200_OK, response.json()
assert response.json()["updated"] == 0 # no events w name "not_existing_name"
response = client_auth.patch(f"{RESOURCE}patch_name", json={"old_name": "string", "new_name": "some_name"})
assert response.status_code == status.HTTP_200_OK, response.json()
assert response.json()["updated"] > 0 # at least 2 events w name "string" (due to our post request)


def test_create_repeated_events(
client_auth: TestClient, dbsession: Session, room_factory, group_factory, lecturer_factory
):
room_path1 = room_factory(client_auth)
group_path1 = group_factory(client_auth)
lecturer_path1 = lecturer_factory(client_auth)
room_id1 = int(room_path1.split("/")[-1])
group_id1 = int(group_path1.split("/")[-1])
lecturer_id1 = int(lecturer_path1.split("/")[-1])
request_obj = {
"name": "string",
"room_id": [room_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",
"repeat_timedelta_days": 7,
"repeat_until_ts": "2023-08-26T22:32:38.575Z",
}
response = client_auth.post(f"{RESOURCE}repeating", json=request_obj)
created = response.json()
assert response.status_code == status.HTTP_200_OK, response.json()