From 7a0df3d8a7d5eac373ce611e8386bae11a956385 Mon Sep 17 00:00:00 2001 From: imperosol Date: Thu, 24 Sep 2026 23:09:46 +0200 Subject: [PATCH] api to save timetable --- timetable/api.py | 27 ++++++++++++++ timetable/schemas.py | 35 +++++++++++++++++++ .../bundled/timetable/generator-index.ts | 5 +-- 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 timetable/api.py create mode 100644 timetable/schemas.py diff --git a/timetable/api.py b/timetable/api.py new file mode 100644 index 00000000..90ea85ef --- /dev/null +++ b/timetable/api.py @@ -0,0 +1,27 @@ +from ninja.security import SessionAuth +from ninja_extra import ControllerBase, api_controller, route + +from core.utils import get_semester_code +from timetable.models import Timetable, TimetableSlot +from timetable.schemas import CreateTimetableSchema, TimetableSchema + + +@api_controller("/edt", urls_namespace="timetable") +class TimetableController(ControllerBase): + @route.put("", auth=SessionAuth(), response={200: TimetableSchema}, url_name="save") + def save_timetable(self, data: CreateTimetableSchema): + user = self.context.request.user + timetable, created = Timetable.objects.get_or_create( + semester=get_semester_code(), user=user + ) + if not created: + timetable.slots.all().delete() + if user.preferences.show_my_timetable != data.is_viewable: + user.preferences.show_my_timetable = data.is_viewable + user.preferences.save() + slots = TimetableSlot.objects.bulk_create( + [TimetableSlot(timetable=timetable, **s.model_dump()) for s in data.slots] + ) + return TimetableSchema( + id=timetable.id, semester=timetable.semester, slots=slots + ) diff --git a/timetable/schemas.py b/timetable/schemas.py new file mode 100644 index 00000000..9043d3bf --- /dev/null +++ b/timetable/schemas.py @@ -0,0 +1,35 @@ +from typing import Annotated + +from annotated_types import Ge, Le +from ninja import ModelSchema, Schema +from pydantic import Field + +from timetable.models import Timetable, TimetableSlot + + +class TimetableSlotSchema(Schema): + weekday: TimetableSlot.WeekDay = Field( + description=( + f"{TimetableSlot.WeekDay.MONDAY} is monday, " + f"{TimetableSlot.WeekDay.TUESDAY} is tuesday, etc." + ) + ) + start_at: Annotated[int, Ge(0), Le(96)] + end_at: Annotated[int, Ge(0), Le(96)] + + class Meta: + model = TimetableSlot + fields = ["start_at", "end_at"] + + +class CreateTimetableSchema(Schema): + is_viewable: bool + slots: list[TimetableSlotSchema] + + +class TimetableSchema(ModelSchema): + slots: list[TimetableSlotSchema] + + class Meta: + model = Timetable + fields = ["id", "semester"] diff --git a/timetable/static/bundled/timetable/generator-index.ts b/timetable/static/bundled/timetable/generator-index.ts index 51438094..761f6248 100644 --- a/timetable/static/bundled/timetable/generator-index.ts +++ b/timetable/static/bundled/timetable/generator-index.ts @@ -78,7 +78,7 @@ function parseSlots(s: string): TimetableSlot[] { document.addEventListener("alpine:init", () => { Alpine.data("timetableGenerator", () => ({ content: DEFAULT_TIMETABLE, - error: null as { incorrectRow?: string }, + error: null as { incorrectRow?: string } | null, displayedWeekdays: [] as WeekDay[], courses: [] as TimetableSlot[], startSlot: 0, @@ -108,7 +108,8 @@ document.addEventListener("alpine:init", () => { this.courses = parseSlots(this.content); this.error = null; } catch (err) { - this.error = { incorrectRow: err?.cause?.row }; + type ParseRowError = { cause: { row: string } }; + this.error = { incorrectRow: (err as ParseRowError)?.cause?.row }; return; }