Skip to content

Commit c7401eb

Browse files
Bulk insert and delete events (#72)
1 parent 1443caa commit c7401eb

8 files changed

Lines changed: 243 additions & 15 deletions

File tree

calendar_backend/models/db.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,6 @@ class Photo(BaseDbModel):
168168
)
169169

170170

171-
172-
173171
class CommentLecturer(BaseDbModel):
174172
lecturer_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("lecturer.id"))
175173
author_name = sqlalchemy.Column(sqlalchemy.String, nullable=False)

calendar_backend/routes/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -
100100
DBSessionMiddleware,
101101
db_url=settings.DB_DSN,
102102
session_args={"autocommit": True},
103-
engine_args={"pool_pre_ping": True}
103+
engine_args={"pool_pre_ping": True},
104104
)
105105
app.add_middleware(
106106
CORSMiddleware,

calendar_backend/routes/event/event.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
from fastapi import APIRouter, Depends, Query
66
from fastapi.responses import FileResponse
77
from fastapi_sqlalchemy import db
8+
from pydantic import parse_obj_as
89

910
from calendar_backend.exceptions import NotEnoughCriteria
1011
from calendar_backend.methods import auth, list_calendar
1112
from calendar_backend.models import Room, Lecturer, Event, EventsLecturers, EventsRooms, Group
13+
from calendar_backend.routes.models.base import Base
1214
from calendar_backend.routes.models.event import (
1315
EventGet,
1416
EventPatch,
@@ -101,11 +103,38 @@ async def create_event(event: EventPost, _: auth.User = Depends(auth.get_current
101103
)
102104

103105

106+
@event_router.post("/bulk", response_model=list[EventGet])
107+
async def create_events(events: list[EventPost], _: auth.User = Depends(auth.get_current_user)) -> list[EventGet]:
108+
result = []
109+
for event in events:
110+
event_dict = event.dict()
111+
rooms = [Room.get(room_id, session=db.session) for room_id in event_dict.pop("room_id", [])]
112+
lecturers = [Lecturer.get(lecturer_id, session=db.session) for lecturer_id in event_dict.pop("lecturer_id", [])]
113+
group = Group.get(event.group_id, session=db.session)
114+
result.append(
115+
Event.create(
116+
**event_dict,
117+
room=rooms,
118+
lecturer=lecturers,
119+
group=group,
120+
session=db.session,
121+
)
122+
)
123+
return parse_obj_as(list[EventGet], result)
124+
125+
104126
@event_router.patch("/{id}", response_model=EventGet)
105127
async def patch_event(id: int, event_inp: EventPatch, _: auth.User = Depends(auth.get_current_user)) -> EventGet:
106128
return EventGet.from_orm(Event.update(id, session=db.session, **event_inp.dict(exclude_unset=True)))
107129

108130

131+
@event_router.delete("/bulk", response_model=None)
132+
async def delete_events(start: date, end: date, _: auth.User = Depends(auth.get_current_user)) -> None:
133+
db.session.query(Event).filter(Event.start_ts >= start, Event.end_ts < end).update(
134+
values={"is_deleted": True}
135+
)
136+
137+
109138
@event_router.delete("/{id}", response_model=None)
110139
async def delete_event(id: int, _: auth.User = Depends(auth.get_current_user)) -> None:
111140
Event.delete(id, session=db.session)

calendar_backend/routes/room/room.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,23 @@ async def get_rooms(query: str = "", limit: int = 10, offset: int = 0) -> GetLis
3737

3838
@room_router.post("/", response_model=RoomGet)
3939
async def create_room(room: RoomPost, _: auth.User = Depends(auth.get_current_user)) -> RoomGet:
40-
if bool(Room.get_all(session=db.session).filter(
41-
Room.name == room.name, Room.building == room.building
42-
).one_or_none()):
40+
if bool(
41+
Room.get_all(session=db.session).filter(Room.name == room.name, Room.building == room.building).one_or_none()
42+
):
4343
raise HTTPException(status_code=423, detail="Already exists")
4444
return RoomGet.from_orm(Room.create(**room.dict(exclude_unset=True), session=db.session))
4545

4646

4747
@room_router.patch("/{id}", response_model=RoomGet)
48-
async def patch_room(id: int, room: RoomPatch, _: auth.User = Depends(auth.get_current_user)) -> RoomGet:
49-
if bool(query := Room.get_all(session=db.session).filter(
50-
Room.name == room.name, Room.building == room.building
51-
).one_or_none()) and query.id != id:
48+
async def patch_room(id: int, room_inp: RoomPatch, _: auth.User = Depends(auth.get_current_user)) -> RoomGet:
49+
room = (
50+
Room.get_all(session=db.session)
51+
.filter(Room.name == room_inp.name, Room.building == room_inp.building)
52+
.one_or_none()
53+
)
54+
if room and room.id != id:
5255
raise HTTPException(status_code=423, detail="Already exists")
53-
return RoomGet.from_orm(Room.update(id, **room.dict(exclude_unset=True), session=db.session))
56+
return RoomGet.from_orm(Room.update(id, **room_inp.dict(exclude_unset=True), session=db.session))
5457

5558

5659
@room_router.delete("/{id}", response_model=None)

migrations/versions/b0d96bbca3cd_add_review.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,30 @@
99

1010

1111
def upgrade():
12-
op.add_column('comment_event', sa.Column('approve_status', sa.Enum('APPROVED', 'DECLINED', 'PENDING', name='approvestatuses', native_enum=False), nullable=False))
13-
op.add_column('comment_lecturer', sa.Column('approve_status', sa.Enum('APPROVED', 'DECLINED', 'PENDING', name='approvestatuses', native_enum=False), nullable=False))
14-
op.add_column('photo', sa.Column('approve_status', sa.Enum('APPROVED', 'DECLINED', 'PENDING', name='approvestatuses', native_enum=False), nullable=False))
12+
op.add_column(
13+
'comment_event',
14+
sa.Column(
15+
'approve_status',
16+
sa.Enum('APPROVED', 'DECLINED', 'PENDING', name='approvestatuses', native_enum=False),
17+
nullable=False,
18+
),
19+
)
20+
op.add_column(
21+
'comment_lecturer',
22+
sa.Column(
23+
'approve_status',
24+
sa.Enum('APPROVED', 'DECLINED', 'PENDING', name='approvestatuses', native_enum=False),
25+
nullable=False,
26+
),
27+
)
28+
op.add_column(
29+
'photo',
30+
sa.Column(
31+
'approve_status',
32+
sa.Enum('APPROVED', 'DECLINED', 'PENDING', name='approvestatuses', native_enum=False),
33+
nullable=False,
34+
),
35+
)
1536

1637

1738
def downgrade():

migrations/versions/d6f98271bc6b_no_datatype_directions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
directions_native = sa.Enum('North', 'South', name="Directions")
1919
directions = sa.Enum('North', 'South', native_enum=False)
2020

21+
2122
def upgrade():
2223
# FIXME: Не надо бы с прода удалять столбцы не скопировав данные
2324
op.drop_column("room", "direction")

tests/conftest.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,74 @@ def event_path(client_auth: TestClient, dbsession: Session, lecturer_path, room_
121121
response_model = dbsession.query(Event).get(id_)
122122
dbsession.delete(response_model)
123123
dbsession.commit()
124+
125+
126+
@pytest.fixture()
127+
def room_factory(dbsession: Session):
128+
ids_ = []
129+
130+
def _room_factory(client_auth: TestClient):
131+
RESOURCE = "/timetable/room/"
132+
request_obj = {
133+
"name": datetime.now().isoformat(),
134+
"direction": "North",
135+
}
136+
response = client_auth.post(RESOURCE, json=request_obj)
137+
assert response.status_code == status.HTTP_200_OK, response.json()
138+
nonlocal ids_
139+
ids_.append(yielding := response.json()["id"])
140+
return RESOURCE + str(yielding)
141+
142+
yield _room_factory
143+
for id in ids_:
144+
response_model: Room = dbsession.query(Room).get(id)
145+
dbsession.delete(response_model)
146+
dbsession.commit()
147+
148+
149+
@pytest.fixture()
150+
def lecturer_factory(dbsession: Session):
151+
ids_ = []
152+
153+
def _lecturer_factory(client_auth: TestClient):
154+
RESOURCE = "/timetable/lecturer/"
155+
request_obj = {
156+
"first_name": "Петр",
157+
"middle_name": "Васильевич",
158+
"last_name": "Тритий",
159+
"description": "Очень умный",
160+
}
161+
response = client_auth.post(RESOURCE, json=request_obj)
162+
assert response.status_code == status.HTTP_200_OK, response.json()
163+
nonlocal ids_
164+
ids_.append(yielding := response.json()["id"])
165+
return RESOURCE + str(yielding)
166+
167+
yield _lecturer_factory
168+
for id in ids_:
169+
response_model: Lecturer = dbsession.query(Lecturer).get(id)
170+
dbsession.delete(response_model)
171+
dbsession.commit()
172+
173+
174+
@pytest.fixture()
175+
def group_factory(dbsession: Session):
176+
ids_ = []
177+
178+
def _group_factory(client_auth: TestClient):
179+
RESOURCE = "/timetable/group/"
180+
request_obj = {
181+
"name": "",
182+
"number": datetime.now().isoformat(),
183+
}
184+
response = client_auth.post(RESOURCE, json=request_obj)
185+
assert response.status_code == status.HTTP_200_OK, response.json()
186+
nonlocal ids_
187+
ids_.append(yielding := response.json()["id"])
188+
return RESOURCE + str(yielding)
189+
190+
yield _group_factory
191+
for id in ids_:
192+
response_model: Group = dbsession.query(Group).get(id)
193+
dbsession.delete(response_model)
194+
dbsession.commit()

tests/event/event.py

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,65 @@ def test_create(client_auth: TestClient, dbsession: Session, room_path, group_pa
3636
assert response_model.group_id == request_obj["group_id"]
3737

3838

39+
def test_create_many(client_auth: TestClient, dbsession: Session, room_factory, group_factory, lecturer_factory):
40+
room_path1 = room_factory(client_auth)
41+
group_path1 = group_factory(client_auth)
42+
lecturer_path1 = lecturer_factory(client_auth)
43+
room_path2 = room_factory(client_auth)
44+
group_path2 = group_factory(client_auth)
45+
lecturer_path2 = lecturer_factory(client_auth)
46+
room_id1 = int(room_path1.split("/")[-1])
47+
group_id1 = int(group_path1.split("/")[-1])
48+
lecturer_id1 = int(lecturer_path1.split("/")[-1])
49+
room_id2 = int(room_path2.split("/")[-1])
50+
group_id2 = int(group_path2.split("/")[-1])
51+
lecturer_id2 = int(lecturer_path2.split("/")[-1])
52+
request_obj = [
53+
{
54+
"name": "string",
55+
"room_id": [room_id1],
56+
"group_id": group_id1,
57+
"lecturer_id": [lecturer_id1],
58+
"start_ts": "2022-08-26T22:32:38.575Z",
59+
"end_ts": "2022-08-26T22:32:38.575Z",
60+
},
61+
{
62+
"name": "string",
63+
"room_id": [room_id2],
64+
"group_id": group_id2,
65+
"lecturer_id": [lecturer_id2],
66+
"start_ts": "2022-08-26T22:32:38.575Z",
67+
"end_ts": "2022-08-26T22:32:38.575Z",
68+
},
69+
]
70+
response = client_auth.post(f"{RESOURCE}bulk", json=request_obj)
71+
assert response.status_code == status.HTTP_200_OK, response.json()
72+
response_obj = response.json()
73+
assert response_obj[0]["name"] == request_obj[0]["name"]
74+
assert response_obj[0]["room"][0]["id"] == room_id1
75+
assert response_obj[0]["group"]["id"] == request_obj[0]["group_id"]
76+
assert response_obj[0]["lecturer"][0]["id"] == lecturer_id1
77+
assert response_obj[0]["start_ts"][:20] == request_obj[0]["start_ts"][:20]
78+
assert response_obj[0]["end_ts"][:20] == request_obj[0]["end_ts"][:20]
79+
response_model: Event = dbsession.query(Event).get(response_obj[0]["id"])
80+
assert response_model.name == request_obj[0]["name"]
81+
assert [row.id for row in response_model.room] == request_obj[0]["room_id"]
82+
assert [row.id for row in response_model.lecturer] == request_obj[0]["lecturer_id"]
83+
assert response_model.group_id == request_obj[0]["group_id"]
84+
85+
assert response_obj[1]["name"] == request_obj[1]["name"]
86+
assert response_obj[1]["room"][0]["id"] == room_id2
87+
assert response_obj[1]["group"]["id"] == request_obj[1]["group_id"]
88+
assert response_obj[1]["lecturer"][0]["id"] == lecturer_id2
89+
assert response_obj[1]["start_ts"][:20] == request_obj[1]["start_ts"][:20]
90+
assert response_obj[1]["end_ts"][:20] == request_obj[1]["end_ts"][:20]
91+
response_model: Event = dbsession.query(Event).get(response_obj[1]["id"])
92+
assert response_model.name == request_obj[1]["name"]
93+
assert [row.id for row in response_model.room] == request_obj[1]["room_id"]
94+
assert [row.id for row in response_model.lecturer] == request_obj[1]["lecturer_id"]
95+
assert response_model.group_id == request_obj[1]["group_id"]
96+
97+
3998
def test_delete(client_auth: TestClient, dbsession: Session, room_path, lecturer_path, group_path):
4099
room_id = int(room_path.split("/")[-1])
41100
group_id = int(group_path.split("/")[-1])
@@ -137,7 +196,7 @@ def test_update_all(client_auth: TestClient, dbsession: Session):
137196
"start_ts": "2022-08-26T22:32:38.575Z",
138197
"end_ts": "2022-08-26T22:32:38.575Z",
139198
}
140-
client_auth.patch(RESOURCE + f"{id_}/", json=request_obj_2)
199+
client_auth.patch(RESOURCE + f"{id_}", json=request_obj_2)
141200
response = client_auth.get(RESOURCE + f"{id_}/")
142201
assert response.status_code == status.HTTP_200_OK, response.json()
143202
response_obj = response.json()
@@ -175,3 +234,49 @@ def test_update_all(client_auth: TestClient, dbsession: Session):
175234
dbsession.delete(lecturer)
176235
dbsession.delete(group)
177236
dbsession.commit()
237+
238+
239+
def test_delete_from_to(client_auth: TestClient, dbsession: Session, room_factory, group_factory, lecturer_factory):
240+
room_path1 = room_factory(client_auth)
241+
group_path1 = group_factory(client_auth)
242+
lecturer_path1 = lecturer_factory(client_auth)
243+
room_path2 = room_factory(client_auth)
244+
group_path2 = group_factory(client_auth)
245+
lecturer_path2 = lecturer_factory(client_auth)
246+
room_id1 = int(room_path1.split("/")[-1])
247+
group_id1 = int(group_path1.split("/")[-1])
248+
lecturer_id1 = int(lecturer_path1.split("/")[-1])
249+
room_id2 = int(room_path2.split("/")[-1])
250+
group_id2 = int(group_path2.split("/")[-1])
251+
lecturer_id2 = int(lecturer_path2.split("/")[-1])
252+
request_obj = [
253+
{
254+
"name": "string",
255+
"room_id": [room_id1],
256+
"group_id": group_id1,
257+
"lecturer_id": [lecturer_id1],
258+
"start_ts": "2022-08-26T22:32:38.575Z",
259+
"end_ts": "2022-08-26T22:32:38.575Z",
260+
},
261+
{
262+
"name": "string",
263+
"room_id": [room_id2],
264+
"group_id": group_id2,
265+
"lecturer_id": [lecturer_id2],
266+
"start_ts": "2022-08-26T22:32:38.575Z",
267+
"end_ts": "2022-08-26T22:32:38.575Z",
268+
},
269+
]
270+
response = client_auth.post(f"{RESOURCE}bulk", json=request_obj)
271+
created = response.json()
272+
assert response.status_code == status.HTTP_200_OK, response.json()
273+
response = client_auth.delete(f"{RESOURCE}bulk", params={"start": "2022-08-26", "end": "2022-08-27"})
274+
assert response.status_code == 200
275+
response = client_auth.get(f"{RESOURCE}{created[0]['id']}")
276+
assert response.status_code == 404
277+
response = client_auth.get(f"{RESOURCE}{created[1]['id']}")
278+
assert response.status_code == 404
279+
obj1 = dbsession.query(Event).filter(Event.id == created[0]["id"]).one()
280+
obj2 = dbsession.query(Event).filter(Event.id == created[1]["id"]).one()
281+
for row in (obj1, obj2):
282+
dbsession.delete(row)

0 commit comments

Comments
 (0)