mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-12 21:09:24 +00:00
Compare commits
2 Commits
ia-explana
...
edt
Author | SHA1 | Date | |
---|---|---|---|
b52dbdd4fc | |||
db6c356f73 |
@ -636,9 +636,6 @@ class User(AbstractUser):
|
|||||||
|
|
||||||
|
|
||||||
class AnonymousUser(AuthAnonymousUser):
|
class AnonymousUser(AuthAnonymousUser):
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def was_subscribed(self):
|
def was_subscribed(self):
|
||||||
return False
|
return False
|
||||||
@ -647,10 +644,6 @@ class AnonymousUser(AuthAnonymousUser):
|
|||||||
def is_subscribed(self):
|
def is_subscribed(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
|
||||||
def subscribed(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_root(self):
|
def is_root(self):
|
||||||
return False
|
return False
|
||||||
|
@ -1,108 +0,0 @@
|
|||||||
Cette page expose la politique du Pôle informatique de l'AE
|
|
||||||
en ce qui concerne l'usage et l'implémentation de systèmes d'IA
|
|
||||||
dans le cadre de l'AE et du développement de ses outils.
|
|
||||||
|
|
||||||
## Cadre
|
|
||||||
|
|
||||||
En accord avec le règlement européen sur
|
|
||||||
l'intelligence artificielle du 13 juin 2024,
|
|
||||||
nous définissons comme IA :
|
|
||||||
|
|
||||||
> Un système basé sur une machine qui est
|
|
||||||
> conçu pour fonctionner avec différents niveaux d'autonomie
|
|
||||||
> et qui peut faire preuve d'adaptabilité après son déploiement,
|
|
||||||
> et qui, pour des objectifs explicites ou implicites, déduit,
|
|
||||||
> à partir des données qu'il reçoit,
|
|
||||||
> comment générer des résultats tels que des prédictions,
|
|
||||||
> du contenu, des recommandations ou des décisions
|
|
||||||
> qui peuvent influencer des environnements physiques ou virtuels.
|
|
||||||
|
|
||||||
Cette définition recouvre toutes les IAs génératives, ce qui inclut
|
|
||||||
ChatGPT, DeepSeek, Claude, Copilot, Llama et autres outils similaires.
|
|
||||||
|
|
||||||
## Utilisation dans le développement
|
|
||||||
|
|
||||||
!!!danger
|
|
||||||
La soumission de code généré par IA est strictement interdite.
|
|
||||||
|
|
||||||
Aucune contribution contenant du code généré par IA n'est acceptée.
|
|
||||||
Toute PR contenant en proportion significative du code duquel
|
|
||||||
on peut raisonnablement penser qu'il a été généré par IA
|
|
||||||
pourra être refusée sans aucun autre motif.
|
|
||||||
|
|
||||||
Bien que nous ne puissions pas l'interdire,
|
|
||||||
nous déconseillons également fortement l'usage de tout
|
|
||||||
recours à un système d'IA dans le processus de développement,
|
|
||||||
quel que soit son usage (debug, recherche d'information ou autres).
|
|
||||||
Référez-vous en priorité à la documentation du site,
|
|
||||||
à celle de Django et à l'aide des autres développeurs,
|
|
||||||
mais par pitié, ne faites jamais appel à l'IA.
|
|
||||||
|
|
||||||
## Intégration dans le site
|
|
||||||
|
|
||||||
L'intégration sur le site AE de systèmes d'IA
|
|
||||||
et de toute fonctionnalité basée sur des systèmes d'IA
|
|
||||||
est strictement prohibée, quel qu'en soit l'objectif.
|
|
||||||
|
|
||||||
Toute tâche de modération, de génération
|
|
||||||
ou de détection de contenu ne doit être accomplie
|
|
||||||
par des êtres humains ou par des algorithmes
|
|
||||||
déterministes, testés et compris.
|
|
||||||
|
|
||||||
L'usage des données du site a des fins d'entrainement d'IA,
|
|
||||||
ainsi que la transmission de ces données à un système d'IA
|
|
||||||
est strictement interdit.
|
|
||||||
Tout acte de cette nature sera considéré comme une violation
|
|
||||||
grave de la politique de gestion des données de l'AE.
|
|
||||||
|
|
||||||
## Motifs de cette politique
|
|
||||||
|
|
||||||
Le site AE est un programme écrit par des humains, pour des humains.
|
|
||||||
C'est un logiciel dont la complexité nécessite des connaissances
|
|
||||||
plus approfondies que ce qui est attendu de la part d'un
|
|
||||||
étudiant en TC ou en base branche.
|
|
||||||
À ce titre, l'interdiction de l'IA dans le cadre de son
|
|
||||||
développement est pensée avant tout dans une optique
|
|
||||||
de formation des développeurs, de stabilité de la base de code
|
|
||||||
et de transmission des connaissances.
|
|
||||||
|
|
||||||
Nous ferons ici abstraction de l'impact écologique néfaste de l'IA,
|
|
||||||
qui n'en reste pas moins préoccupant et qui renforce
|
|
||||||
les autres motifs ayant poussé à interdire l'IA dans le cadre de l'AE.
|
|
||||||
|
|
||||||
### Formation des développeurs
|
|
||||||
|
|
||||||
Travailler sur le site AE est possiblement le meilleur moyen de
|
|
||||||
monter en compétences en informatique pour un étudiant de l'UTBM.
|
|
||||||
Automatisation des tests, gestion des données et de la sécurité,
|
|
||||||
infrastructure, maintenance du code existant...
|
|
||||||
|
|
||||||
Le site AE est un logiciel complet, dont le développement
|
|
||||||
possède une dimension pédagogique réelle.
|
|
||||||
En utilisant l'IA, le développement n'est plus un moyen efficace
|
|
||||||
de se former.
|
|
||||||
|
|
||||||
### Stabilité de la base de code
|
|
||||||
|
|
||||||
Les développeurs du site AE sont pour la plupart en cours de formation,
|
|
||||||
sans compréhension globale de la base de code du site,
|
|
||||||
des outils logiciels sur lesquels il se base et des bonnes
|
|
||||||
pratiques permettant d'écrire du code viable.
|
|
||||||
|
|
||||||
En se reposant sur un système d'IA sans être capacité
|
|
||||||
de comprendre intégralement le code proposé ni de le mettre
|
|
||||||
en perspective avec le reste de la base de code,
|
|
||||||
c'est toute la maintenance de la base de code qui se retrouve compromise.
|
|
||||||
|
|
||||||
### Transmission des connaissances
|
|
||||||
|
|
||||||
L'équipe du pôle informatique se renouvelle très souvent.
|
|
||||||
À ce titre, les nouveaux développeurs se doivent d'hériter
|
|
||||||
d'une base de code viable.
|
|
||||||
Quant aux anciens développeurs, ils se doivent d'en avoir
|
|
||||||
compris le fonctionnement, afin d'être en mesure
|
|
||||||
de guider et d'aider leurs successeurs.
|
|
||||||
|
|
||||||
Comme développé dans les deux points précédents,
|
|
||||||
cet objectif est incompatible avec l'usage de systèmes d'IA.
|
|
||||||
|
|
@ -57,7 +57,6 @@ nav:
|
|||||||
- Accueil: explanation/index.md
|
- Accueil: explanation/index.md
|
||||||
- Technologies utilisées: explanation/technos.md
|
- Technologies utilisées: explanation/technos.md
|
||||||
- Conventions: explanation/conventions.md
|
- Conventions: explanation/conventions.md
|
||||||
- Politique IA: explanation/ia.md
|
|
||||||
- Archives: explanation/archives.md
|
- Archives: explanation/archives.md
|
||||||
- Tutoriels:
|
- Tutoriels:
|
||||||
- Installer le projet: tutorial/install.md
|
- Installer le projet: tutorial/install.md
|
||||||
|
57
package-lock.json
generated
57
package-lock.json
generated
@ -29,6 +29,8 @@
|
|||||||
"d3-force-3d": "^3.0.5",
|
"d3-force-3d": "^3.0.5",
|
||||||
"easymde": "^2.19.0",
|
"easymde": "^2.19.0",
|
||||||
"glob": "^11.0.0",
|
"glob": "^11.0.0",
|
||||||
|
"html-to-image": "^1.11.13",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
"htmx.org": "^2.0.3",
|
"htmx.org": "^2.0.3",
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
@ -3083,6 +3085,15 @@
|
|||||||
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
|
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/base64-arraybuffer": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
@ -3471,6 +3482,15 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-line-break": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"utrie": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cytoscape": {
|
"node_modules/cytoscape": {
|
||||||
"version": "3.32.0",
|
"version": "3.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.32.0.tgz",
|
||||||
@ -4149,6 +4169,25 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/html-to-image": {
|
||||||
|
"version": "1.11.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz",
|
||||||
|
"integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/html2canvas": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"css-line-break": "^2.1.0",
|
||||||
|
"text-segmentation": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/htmx.org": {
|
"node_modules/htmx.org": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.4.tgz",
|
||||||
@ -5451,6 +5490,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/text-segmentation": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"utrie": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/three": {
|
"node_modules/three": {
|
||||||
"version": "0.177.0",
|
"version": "0.177.0",
|
||||||
"resolved": "https://registry.npmjs.org/three/-/three-0.177.0.tgz",
|
"resolved": "https://registry.npmjs.org/three/-/three-0.177.0.tgz",
|
||||||
@ -5708,6 +5756,15 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/utrie": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-arraybuffer": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.3.5",
|
"version": "6.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
"d3-force-3d": "^3.0.5",
|
"d3-force-3d": "^3.0.5",
|
||||||
"easymde": "^2.19.0",
|
"easymde": "^2.19.0",
|
||||||
"glob": "^11.0.0",
|
"glob": "^11.0.0",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
"htmx.org": "^2.0.3",
|
"htmx.org": "^2.0.3",
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
|
@ -124,6 +124,7 @@ INSTALLED_APPS = (
|
|||||||
"pedagogy",
|
"pedagogy",
|
||||||
"galaxy",
|
"galaxy",
|
||||||
"antispam",
|
"antispam",
|
||||||
|
"timetable",
|
||||||
"api",
|
"api",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ urlpatterns = [
|
|||||||
path("i18n/", include("django.conf.urls.i18n")),
|
path("i18n/", include("django.conf.urls.i18n")),
|
||||||
path("jsi18n/", JavaScriptCatalog.as_view(), name="javascript-catalog"),
|
path("jsi18n/", JavaScriptCatalog.as_view(), name="javascript-catalog"),
|
||||||
path("captcha/", include("captcha.urls")),
|
path("captcha/", include("captcha.urls")),
|
||||||
|
path("timetable/", include(("timetable.urls", "timetable"), namespace="timetable")),
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
0
timetable/__init__.py
Normal file
0
timetable/__init__.py
Normal file
1
timetable/admin.py
Normal file
1
timetable/admin.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Register your models here.
|
6
timetable/apps.py
Normal file
6
timetable/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class TimetableConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "timetable"
|
0
timetable/migrations/__init__.py
Normal file
0
timetable/migrations/__init__.py
Normal file
1
timetable/models.py
Normal file
1
timetable/models.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Create your models here.
|
134
timetable/static/bundled/timetable/generator-index.ts
Normal file
134
timetable/static/bundled/timetable/generator-index.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import html2canvas from "html2canvas";
|
||||||
|
|
||||||
|
// see https://regex101.com/r/QHSaPM/2
|
||||||
|
const TIMETABLE_ROW_RE: RegExp =
|
||||||
|
/^(?<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+)?)$/;
|
||||||
|
|
||||||
|
const DEFAULT_TIMETABLE: string = `DS52\t\tCM1\t\tlundi\t08:00\t10:00\t1\tPrésentiel\tA113
|
||||||
|
DS53\t\tCM1\t\tlundi\t10:15\t12:15\t1\tPrésentiel\tA101
|
||||||
|
DS53\t\tTP1\t\tlundi\t13:00\t16:00\t1\tPrésentiel\tH010
|
||||||
|
SO03\t\tCM1\t\tlundi\t16:15\t17:45\t1\tPrésentiel\tA103
|
||||||
|
SO03\t\tTD1\t\tlundi\t17:45\t19:45\t1\tPrésentiel\tA103
|
||||||
|
DS50\t\tTP1\t\tmardi\t08:00\t10:00\t1\tPrésentiel\tA216
|
||||||
|
DS51\t\tCM1\t\tmardi\t10:15\t12:15\t1\tPrésentiel\tA216
|
||||||
|
DS51\t\tTP1\t\tmardi\t14:00\t18:00\t1\tPrésentiel\tH010
|
||||||
|
DS52\t\tTP2\tA\tjeudi\t08:00\t10:00\tA\tPrésentiel\tA110a, A110b
|
||||||
|
DS52\t\tTD1\t\tjeudi\t10:15\t12:15\t1\tPrésentiel\tA110a, A110b
|
||||||
|
LC02\t\tTP1\t\tjeudi\t15:00\t16:00\t1\tPrésentiel\tA209
|
||||||
|
LC02\t\tTD1\t\tjeudi\t16:15\t18:15\t1\tPrésentiel\tA206`;
|
||||||
|
|
||||||
|
type WeekDay =
|
||||||
|
| "lundi"
|
||||||
|
| "mardi"
|
||||||
|
| "mercredi"
|
||||||
|
| "jeudi"
|
||||||
|
| "vendredi"
|
||||||
|
| "samedi"
|
||||||
|
| "dimanche";
|
||||||
|
|
||||||
|
const WEEKDAYS = [
|
||||||
|
"lundi",
|
||||||
|
"mardi",
|
||||||
|
"mercredi",
|
||||||
|
"jeudi",
|
||||||
|
"vendredi",
|
||||||
|
"samedi",
|
||||||
|
"dimanche",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const SLOT_HEIGHT = 20 as const; // Each 15min has a height of 20px in the timetable
|
||||||
|
const SLOT_WIDTH = 400 as const; // Each weekday ha a width of 400px in the timetable
|
||||||
|
const MINUTES_PER_SLOT = 15 as const;
|
||||||
|
|
||||||
|
interface TimetableSlot {
|
||||||
|
courseType: string;
|
||||||
|
room: string;
|
||||||
|
startHour: string;
|
||||||
|
endHour: string;
|
||||||
|
startSlot: number;
|
||||||
|
endSlot: number;
|
||||||
|
ueCode: string;
|
||||||
|
weekGroup?: string;
|
||||||
|
weekday: WeekDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSlots(s: string): TimetableSlot[] {
|
||||||
|
return s
|
||||||
|
.split("\n")
|
||||||
|
.filter((s: string) => s.length > 0)
|
||||||
|
.map((row: string) => {
|
||||||
|
const parsed = TIMETABLE_ROW_RE.exec(row);
|
||||||
|
if (!parsed) {
|
||||||
|
throw new Error(`Couldn't parse row ${row}`);
|
||||||
|
}
|
||||||
|
const [startHour, startMin] = parsed.groups.startHour
|
||||||
|
.split(":")
|
||||||
|
.map((i) => Number.parseInt(i));
|
||||||
|
const [endHour, endMin] = parsed.groups.endHour
|
||||||
|
.split(":")
|
||||||
|
.map((i) => Number.parseInt(i));
|
||||||
|
return {
|
||||||
|
...parsed.groups,
|
||||||
|
startSlot: Math.floor((startHour * 60 + startMin) / MINUTES_PER_SLOT),
|
||||||
|
endSlot: Math.floor((endHour * 60 + endMin) / MINUTES_PER_SLOT),
|
||||||
|
} as unknown as TimetableSlot;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("alpine:init", () => {
|
||||||
|
Alpine.data("timetableGenerator", () => ({
|
||||||
|
content: DEFAULT_TIMETABLE,
|
||||||
|
error: "",
|
||||||
|
displayedWeekdays: [] as WeekDay[],
|
||||||
|
courses: [] as TimetableSlot[],
|
||||||
|
startSlot: 0,
|
||||||
|
table: {
|
||||||
|
height: 0,
|
||||||
|
width: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
generate() {
|
||||||
|
try {
|
||||||
|
this.courses = parseSlots(this.content);
|
||||||
|
} catch {
|
||||||
|
this.error = gettext(
|
||||||
|
"Wrong timetable format. Make sure you copied if from your student folder.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.displayedWeekdays = WEEKDAYS.filter((day) =>
|
||||||
|
this.courses.some((slot: TimetableSlot) => slot.weekday === day),
|
||||||
|
);
|
||||||
|
this.startSlot = this.courses.reduce(
|
||||||
|
(acc: number, curr: TimetableSlot) => Math.min(acc, curr.startSlot),
|
||||||
|
24 * 4,
|
||||||
|
);
|
||||||
|
this.endSlot = this.courses.reduce(
|
||||||
|
(acc: number, curr: TimetableSlot) => Math.max(acc, curr.endSlot),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
this.table.height = SLOT_HEIGHT * (this.endSlot - this.startSlot);
|
||||||
|
this.table.width = SLOT_WIDTH * this.displayedWeekdays.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
getStyle(slot: TimetableSlot) {
|
||||||
|
return {
|
||||||
|
height: `${(slot.endSlot - slot.startSlot) * SLOT_HEIGHT}px`,
|
||||||
|
width: `${SLOT_WIDTH}px`,
|
||||||
|
top: `${(slot.startSlot - this.startSlot) * SLOT_HEIGHT}px`,
|
||||||
|
left: `${this.displayedWeekdays.indexOf(slot.weekday) * SLOT_WIDTH}px`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async savePng() {
|
||||||
|
const elem = document.getElementById("timetable");
|
||||||
|
const img = (await html2canvas(elem)).toDataURL();
|
||||||
|
const downloadLink = document.createElement("a");
|
||||||
|
downloadLink.href = img;
|
||||||
|
downloadLink.download = "edt.png";
|
||||||
|
document.body.appendChild(downloadLink);
|
||||||
|
downloadLink.click();
|
||||||
|
downloadLink.remove();
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
28
timetable/static/timetable/css/generator.scss
Normal file
28
timetable/static/timetable/css/generator.scss
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#timetable {
|
||||||
|
display: block;
|
||||||
|
margin: 2em auto ;
|
||||||
|
.header {
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: none;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 0;
|
||||||
|
span {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.slot {
|
||||||
|
background-color: cadetblue;
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
timetable/templates/timetable/generator.jinja
Normal file
56
timetable/templates/timetable/generator.jinja
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
{% extends 'core/base.jinja' %}
|
||||||
|
|
||||||
|
{%- block additional_css -%}
|
||||||
|
<link rel="stylesheet" href="{{ static('timetable/css/generator.scss') }}">
|
||||||
|
{%- endblock -%}
|
||||||
|
|
||||||
|
{%- block additional_js -%}
|
||||||
|
<script type="module" src="{{ static('bundled/timetable/generator-index.ts') }}"></script>
|
||||||
|
{%- endblock -%}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans %}Timeplan generator{% endtrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div x-data="timetableGenerator">
|
||||||
|
<form @submit.prevent="generate()">
|
||||||
|
<h1>Générateur d'emploi du temps</h1>
|
||||||
|
<div class="alert alert-red" x-show="!!error" x-cloak>
|
||||||
|
<p class="alert-main" x-text="error"></p>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="timetable-input">Colle ton emploi du temps (sans l'entête)</label>
|
||||||
|
<textarea id="timetable-input" cols="30" rows="15" x-model="content"></textarea>
|
||||||
|
</div>
|
||||||
|
<input type="submit" class="btn btn-blue" value="{% trans %}Generate{% endtrans %}">
|
||||||
|
</form>
|
||||||
|
<div
|
||||||
|
id="timetable"
|
||||||
|
x-show="table.height > 0 && table.width > 0"
|
||||||
|
:style="{width: `${table.width}px`, height: `${table.height+40}px`}"
|
||||||
|
>
|
||||||
|
<div class="header">
|
||||||
|
<template x-for="weekday in displayedWeekdays">
|
||||||
|
<span x-text="weekday"></span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<template x-for="course in courses">
|
||||||
|
<div class="slot" :style="getStyle(course)">
|
||||||
|
<span x-text="`${course.ueCode} (${course.courseType})`"></span>
|
||||||
|
<span x-text="`${course.startHour} - ${course.endHour}`"></span>
|
||||||
|
<span x-text="course.room"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="margin-bottom btn btn-blue"
|
||||||
|
@click="savePng"
|
||||||
|
x-show="table.height > 0 && table.width > 0"
|
||||||
|
>
|
||||||
|
{% trans %}Save to PNG{% endtrans %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
1
timetable/tests.py
Normal file
1
timetable/tests.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Create your tests here.
|
5
timetable/urls.py
Normal file
5
timetable/urls.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from timetable.views import GeneratorView
|
||||||
|
|
||||||
|
urlpatterns = [path("generator/", GeneratorView.as_view(), name="generator")]
|
10
timetable/views.py
Normal file
10
timetable/views.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Create your views here.
|
||||||
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
|
||||||
|
class GeneratorView(UserPassesTestMixin, TemplateView):
|
||||||
|
template_name = "timetable/generator.jinja"
|
||||||
|
|
||||||
|
def test_func(self):
|
||||||
|
return self.request.user.is_subscribed
|
Reference in New Issue
Block a user