Skip to content

Commit db6c356

Browse files
committed
timetable base
1 parent eb4fbcb commit db6c356

File tree

14 files changed

+222
-7
lines changed

14 files changed

+222
-7
lines changed

core/models.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -636,9 +636,6 @@ def is_com_admin(self):
636636

637637

638638
class AnonymousUser(AuthAnonymousUser):
639-
def __init__(self):
640-
super().__init__()
641-
642639
@property
643640
def was_subscribed(self):
644641
return False
@@ -647,10 +644,6 @@ def was_subscribed(self):
647644
def is_subscribed(self):
648645
return False
649646

650-
@property
651-
def subscribed(self):
652-
return False
653-
654647
@property
655648
def is_root(self):
656649
return False

sith/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ def optional_file_parser(value: str) -> Path | None:
124124
"pedagogy",
125125
"galaxy",
126126
"antispam",
127+
"timetable",
127128
"api",
128129
)
129130

sith/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
path("i18n/", include("django.conf.urls.i18n")),
5050
path("jsi18n/", JavaScriptCatalog.as_view(), name="javascript-catalog"),
5151
path("captcha/", include("captcha.urls")),
52+
path("timetable/", include(("timetable.urls", "timetable"), namespace="timetable")),
5253
]
5354

5455
if settings.DEBUG:

timetable/__init__.py

Whitespace-only changes.

timetable/admin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Register your models here.

timetable/apps.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.apps import AppConfig
2+
3+
4+
class TimetableConfig(AppConfig):
5+
default_auto_field = "django.db.models.BigAutoField"
6+
name = "timetable"

timetable/migrations/__init__.py

Whitespace-only changes.

timetable/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Create your models here.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// see https://regex101.com/r/QHSaPM/2
2+
const TIMETABLE_ROW_RE: RegExp =
3+
/^(?<ueCode>[A-Z\d]{4}(?:\+[A-Z\d]{4})?)\s+(?<courseType>[A-Z]{2}\d)\s+((?<weekGroup>[AB])\s+)?(?<weekday>(lundi)|(mardi)|(mercredi)|(jeudi)|(vendredi)|(samedi)|(dimanche))\s+(?<startHour>\d{2}:\d{2})\s+(?<endHour>\d{2}:\d{2})\s+[\dA-B]\s+(?:[\wé]*\s+)?(?<room>\w+(?:, \w+)?)$/;
4+
5+
const DEFAULT_TIMETABLE: string = `DS52\t\tCM1\t\tlundi\t08:00\t10:00\t1\tPrésentiel\tA113
6+
DS53\t\tCM1\t\tlundi\t10:15\t12:15\t1\tPrésentiel\tA101
7+
DS53\t\tTP1\t\tlundi\t13:00\t16:00\t1\tPrésentiel\tH010
8+
SO03\t\tCM1\t\tlundi\t16:15\t17:45\t1\tPrésentiel\tA103
9+
SO03\t\tTD1\t\tlundi\t17:45\t19:45\t1\tPrésentiel\tA103
10+
DS50\t\tTP1\t\tmardi\t08:00\t10:00\t1\tPrésentiel\tA216
11+
DS51\t\tCM1\t\tmardi\t10:15\t12:15\t1\tPrésentiel\tA216
12+
DS51\t\tTP1\t\tmardi\t14:00\t18:00\t1\tPrésentiel\tH010
13+
DS52\t\tTP2\tA\tjeudi\t08:00\t10:00\tA\tPrésentiel\tA110a, A110b
14+
DS52\t\tTD1\t\tjeudi\t10:15\t12:15\t1\tPrésentiel\tA110a, A110b
15+
LC02\t\tTP1\t\tjeudi\t15:00\t16:00\t1\tPrésentiel\tA209
16+
LC02\t\tTD1\t\tjeudi\t16:15\t18:15\t1\tPrésentiel\tA206`;
17+
18+
type WeekDay =
19+
| "lundi"
20+
| "mardi"
21+
| "mercredi"
22+
| "jeudi"
23+
| "vendredi"
24+
| "samedi"
25+
| "dimanche";
26+
27+
const WEEKDAYS = [
28+
"lundi",
29+
"mardi",
30+
"mercredi",
31+
"jeudi",
32+
"vendredi",
33+
"samedi",
34+
"dimanche",
35+
] as const;
36+
37+
const SLOT_HEIGHT = 20 as const; // Each 15min has a height of 20px in the timetable
38+
const SLOT_WIDTH = 400 as const; // Each weekday ha a width of 400px in the timetable
39+
const MINUTES_PER_SLOT = 15 as const;
40+
41+
interface TimetableSlot {
42+
courseType: string;
43+
room: string;
44+
startHour: string;
45+
endHour: string;
46+
startSlot: number;
47+
endSlot: number;
48+
ueCode: string;
49+
weekGroup?: string;
50+
weekday: WeekDay;
51+
}
52+
53+
function parseSlots(s: string): TimetableSlot[] {
54+
return s
55+
.split("\n")
56+
.filter((s: string) => s.length > 0)
57+
.map((row: string) => {
58+
const parsed = TIMETABLE_ROW_RE.exec(row);
59+
if (!parsed) {
60+
throw new Error(`Couldn't parse row ${row}`);
61+
}
62+
const [startHour, startMin] = parsed.groups.startHour
63+
.split(":")
64+
.map((i) => Number.parseInt(i));
65+
const [endHour, endMin] = parsed.groups.endHour
66+
.split(":")
67+
.map((i) => Number.parseInt(i));
68+
return {
69+
...parsed.groups,
70+
startSlot: Math.floor((startHour * 60 + startMin) / MINUTES_PER_SLOT),
71+
endSlot: Math.floor((endHour * 60 + endMin) / MINUTES_PER_SLOT),
72+
} as unknown as TimetableSlot;
73+
});
74+
}
75+
76+
document.addEventListener("alpine:init", () => {
77+
Alpine.data("timetableGenerator", () => ({
78+
content: DEFAULT_TIMETABLE,
79+
error: "",
80+
displayedWeekdays: [] as WeekDay[],
81+
courses: [] as TimetableSlot[],
82+
startSlot: 0,
83+
table: {
84+
height: 0,
85+
width: 0,
86+
},
87+
88+
generate() {
89+
try {
90+
this.courses = parseSlots(this.content);
91+
} catch {
92+
this.error = gettext(
93+
"Wrong timetable format. Make sure you copied if from your student folder.",
94+
);
95+
return;
96+
}
97+
this.displayedWeekdays = WEEKDAYS.filter((day) =>
98+
this.courses.some((slot: TimetableSlot) => slot.weekday === day),
99+
);
100+
this.startSlot = this.courses.reduce(
101+
(acc: number, curr: TimetableSlot) => Math.min(acc, curr.startSlot),
102+
24 * 4,
103+
);
104+
this.endSlot = this.courses.reduce(
105+
(acc: number, curr: TimetableSlot) => Math.max(acc, curr.endSlot),
106+
0,
107+
);
108+
this.table.height = SLOT_HEIGHT * (this.endSlot - this.startSlot);
109+
this.table.width = SLOT_WIDTH * this.displayedWeekdays.length;
110+
},
111+
112+
getStyle(slot: TimetableSlot) {
113+
return {
114+
height: `${(slot.endSlot - slot.startSlot) * SLOT_HEIGHT}px`,
115+
width: `${SLOT_WIDTH}px`,
116+
top: `${(slot.startSlot - this.startSlot) * SLOT_HEIGHT}px`,
117+
left: `${this.displayedWeekdays.indexOf(slot.weekday) * SLOT_WIDTH}px`,
118+
};
119+
},
120+
}));
121+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#timetable {
2+
.header {
3+
background-color: white;
4+
box-shadow: none;
5+
width: 100%;
6+
display: flex;
7+
flex-direction: row;
8+
gap: 0;
9+
span {
10+
flex: 1;
11+
text-align: center;
12+
}
13+
}
14+
.content {
15+
position: relative;
16+
text-align: center;
17+
18+
.slot {
19+
background-color: cadetblue;
20+
position: absolute;
21+
display: flex;
22+
flex-direction: column;
23+
justify-content: center;
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)