mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-13 13:29:23 +00:00
Compare commits
1 Commits
edt
...
ia-explana
Author | SHA1 | Date | |
---|---|---|---|
c5193243a5 |
@ -636,6 +636,9 @@ 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
|
||||||
@ -644,6 +647,10 @@ 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
|
||||||
|
108
docs/explanation/ia.md
Normal file
108
docs/explanation/ia.md
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
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,6 +57,7 @@ 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,8 +29,6 @@
|
|||||||
"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",
|
||||||
@ -3085,15 +3083,6 @@
|
|||||||
"@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",
|
||||||
@ -3482,15 +3471,6 @@
|
|||||||
"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",
|
||||||
@ -4169,25 +4149,6 @@
|
|||||||
"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",
|
||||||
@ -5490,15 +5451,6 @@
|
|||||||
"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",
|
||||||
@ -5756,15 +5708,6 @@
|
|||||||
"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,7 +59,6 @@
|
|||||||
"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,7 +124,6 @@ INSTALLED_APPS = (
|
|||||||
"pedagogy",
|
"pedagogy",
|
||||||
"galaxy",
|
"galaxy",
|
||||||
"antispam",
|
"antispam",
|
||||||
"timetable",
|
|
||||||
"api",
|
"api",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,7 +49,6 @@ 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:
|
||||||
|
@ -1 +0,0 @@
|
|||||||
# Register your models here.
|
|
@ -1,6 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class TimetableConfig(AppConfig):
|
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
|
||||||
name = "timetable"
|
|
@ -1 +0,0 @@
|
|||||||
# Create your models here.
|
|
@ -1,134 +0,0 @@
|
|||||||
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();
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
});
|
|
@ -1,28 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
{% 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 +0,0 @@
|
|||||||
# Create your tests here.
|
|
@ -1,5 +0,0 @@
|
|||||||
from django.urls import path
|
|
||||||
|
|
||||||
from timetable.views import GeneratorView
|
|
||||||
|
|
||||||
urlpatterns = [path("generator/", GeneratorView.as_view(), name="generator")]
|
|
@ -1,10 +0,0 @@
|
|||||||
# 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