Skip to content

Commit 6d21ecb

Browse files
committed
reservable rooms API
1 parent 8defc6b commit 6d21ecb

6 files changed

Lines changed: 176 additions & 1 deletion

File tree

reservation/api.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from datetime import timedelta
2+
from typing import Any, Literal
3+
4+
from django.core.exceptions import ValidationError
5+
from ninja import Query
6+
from ninja_extra import ControllerBase, api_controller, paginate, route
7+
from ninja_extra.pagination import PageNumberPaginationExtra
8+
from ninja_extra.schemas import PaginatedResponseSchema
9+
from pydantic import FutureDatetime
10+
11+
from api.permissions import HasPerm
12+
from reservation.models import ReservationSlot, Room
13+
from reservation.schemas import (
14+
RoomFilterSchema,
15+
RoomSchema,
16+
SlotFilterSchema,
17+
SlotSchema,
18+
)
19+
20+
21+
@api_controller("/reservation/room")
22+
class ReservableRoomController(ControllerBase):
23+
@route.get(
24+
"",
25+
response=list[RoomSchema],
26+
permissions=[HasPerm("reservation.viem_room")],
27+
url_name="fetch_reservable_rooms",
28+
)
29+
def fetch_rooms(self, filters: Query[RoomFilterSchema]):
30+
return filters.filter(Room.objects.select_related("club"))
31+
32+
33+
@api_controller("/reservation/slot")
34+
class ReservationSlotController(ControllerBase):
35+
@route.get(
36+
"",
37+
response=PaginatedResponseSchema[SlotSchema],
38+
permissions=[HasPerm("reservation.view_reservationslot")],
39+
)
40+
@paginate(PageNumberPaginationExtra)
41+
def fetch_slots(self, filters: Query[SlotFilterSchema]):
42+
return filters.filter(
43+
ReservationSlot.objects.select_related("author").order_by("start_at")
44+
)
45+
46+
@route.patch(
47+
"/reservation/slot/{int:slot_id}",
48+
permissions=[HasPerm("reservation.change_reservationslot")],
49+
response={
50+
200: None,
51+
409: dict[Literal["detail"], dict[str, list[str]]],
52+
422: dict[Literal["detail"], list[dict[str, Any]]],
53+
},
54+
)
55+
def update_slot(self, start: FutureDatetime, duration: timedelta, slot_id: int):
56+
slot = self.get_object_or_exception(ReservationSlot, id=slot_id)
57+
slot.start_at = start
58+
slot.end_at = start + duration
59+
try:
60+
slot.full_clean()
61+
slot.save()
62+
except ValidationError as e:
63+
return self.create_response({"detail": dict(e)}, status_code=409)

reservation/schemas.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from datetime import datetime
2+
3+
from ninja import FilterSchema, ModelSchema
4+
from pydantic import Field
5+
6+
from club.schemas import ClubSchema
7+
from core.schemas import SimpleUserSchema
8+
from reservation.models import ReservationSlot, Room
9+
10+
11+
class RoomFilterSchema(FilterSchema):
12+
club: set[int] | None = Field(None, q="club_id__in")
13+
14+
15+
class RoomSchema(ModelSchema):
16+
class Meta:
17+
model = Room
18+
fields = ["id", "name", "description", "location"]
19+
20+
club: ClubSchema
21+
url: str
22+
23+
@staticmethod
24+
def resolve_url(obj: Room):
25+
return obj.get_absolute_url()
26+
27+
@staticmethod
28+
def resolve_location(obj: Room):
29+
return obj.get_location_display()
30+
31+
32+
class SlotFilterSchema(FilterSchema):
33+
after: datetime = Field(default=None, q="end_at__gt")
34+
before: datetime = Field(default=None, q="start_at__lt")
35+
room: set[int] | None = None
36+
club: set[int] | None = None
37+
38+
39+
class SlotSchema(ModelSchema):
40+
class Meta:
41+
model = ReservationSlot
42+
fields = ["id", "room", "nb_people", "comment"]
43+
44+
start: datetime = Field(alias="start_at")
45+
end: datetime = Field(alias="end_at")
46+
author: SimpleUserSchema

reservation/tests.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

reservation/tests/__init__.py

Whitespace-only changes.

reservation/tests/test_room_api.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import pytest
2+
from model_bakery import baker
3+
from ninja_extra.testing import TestClient
4+
from pytest_django.asserts import assertNumQueries
5+
6+
from reservation.api import ReservableRoomController
7+
from reservation.models import Room
8+
9+
10+
@pytest.mark.django_db
11+
class TestFetchRoom:
12+
def test_fetch_simple(self):
13+
rooms = baker.make(Room, _quantity=3, _bulk_create=True)
14+
response = TestClient(ReservableRoomController).get("")
15+
assert response.json() == [
16+
{
17+
"id": room.id,
18+
"name": room.name,
19+
"description": room.description,
20+
"address": room.address,
21+
"club": {"id": room.club.id, "name": room.club.name},
22+
}
23+
for room in rooms
24+
]
25+
26+
def test_nb_queries(self):
27+
with assertNumQueries(1):
28+
TestClient(ReservableRoomController).get("")

reservation/tests/test_slot_api.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import pytest
2+
from model_bakery import baker
3+
from ninja_extra.testing import TestClient
4+
from pytest_django.asserts import assertNumQueries
5+
6+
from reservation.api import ReservableRoomController, ReservationSlotController
7+
from reservation.models import ReservationSlot
8+
9+
10+
@pytest.mark.django_db
11+
class TestFetchRoom:
12+
def test_fetch_simple(self):
13+
slots = baker.make(ReservationSlot, _quantity=5, _bulk_create=True)
14+
response = TestClient(ReservationSlotController).get("")
15+
assert response.json() == [
16+
{
17+
"id": slot.id,
18+
"room": slot.room_id,
19+
"comment": slot.comment,
20+
"nb_people": slot.nb_people,
21+
"start": slot.start_at.isoformat(timespec="milliseconds").replace(
22+
"+00:00", "Z"
23+
),
24+
"end": slot.end_at.isoformat(timespec="milliseconds").replace(
25+
"+00:00", "Z"
26+
),
27+
"author": {
28+
"id": slot.author.id,
29+
"first_name": slot.author.first_name,
30+
"last_name": slot.author.last_name,
31+
"nick_name": slot.author.nick_name,
32+
},
33+
}
34+
for slot in slots
35+
]
36+
37+
def test_nb_queries(self):
38+
with assertNumQueries(1):
39+
TestClient(ReservableRoomController).get("")

0 commit comments

Comments
 (0)