Solution Complète - Python + HTML + CSS + MySQL (XAMPP)
Solution Complète - Python + HTML + CSS + MySQL (XAMPP)
Structure du Projet
```
iai-schedul-python/
├── [Link] # Application principale Flask
├── [Link] # Connexion MySQL
├── [Link] # Modèles de données
├── [Link] # Algorithme de génération
├── [Link] # Dépendances Python
├── static/
│ ├── css/
│ │ └── [Link] # Styles CSS
│ └── js/
│ └── [Link] # JavaScript
└── templates/
├── [Link] # Template de base
├── [Link] # Page de connexion
├── admin/
│ ├── [Link] # Dashboard admin
│ ├── [Link]
│ ├── [Link]
│ ├── [Link]
│ └── [Link]
├── etudiant/
│ ├── [Link]
│ └── [Link]
└── professeur/
├── [Link]
└── [Link]
```
```sql
-- Créer la base de données
CREATE DATABASE IF NOT EXISTS iai_schedul;
USE iai_schedul;
-- Table Admin
CREATE TABLE IF NOT EXISTS admin (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(100) UNIQUE NOT NULL,
matricule VARCHAR(20) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Table Classes
CREATE TABLE IF NOT EXISTS classes (
id INT AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(10) UNIQUE NOT NULL,
niveau INT NOT NULL,
effectif INT DEFAULT 0
);
-- Table Étudiants
CREATE TABLE IF NOT EXISTS etudiants (
id INT AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(50) NOT NULL,
prenom VARCHAR(50) NOT NULL,
matricule VARCHAR(20) UNIQUE NOT NULL,
telephone VARCHAR(20) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
niveau INT NOT NULL,
classe VARCHAR(10) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (classe) REFERENCES classes(nom)
);
-- Table Professeurs
CREATE TABLE IF NOT EXISTS professeurs (
id INT AUTO_INCREMENT PRIMARY KEY,
nom_complet VARCHAR(100) NOT NULL,
matricule VARCHAR(20) UNIQUE NOT NULL,
telephone VARCHAR(20) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Table Salles
CREATE TABLE IF NOT EXISTS salles (
id INT AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(20) UNIQUE NOT NULL,
capacite INT NOT NULL,
type ENUM('Amphi', 'Salle', 'Laboratoire') DEFAULT 'Salle'
);
-- Table Matières
CREATE TABLE IF NOT EXISTS matieres (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(20) NOT NULL,
nom VARCHAR(100) NOT NULL,
quota_heures INT NOT NULL,
heures_restantes INT NOT NULL,
classe VARCHAR(10) NOT NULL,
professeur_id INT,
FOREIGN KEY (classe) REFERENCES classes(nom),
FOREIGN KEY (professeur_id) REFERENCES professeurs(id),
UNIQUE(code, classe)
UNIQUE(code, classe)
);
-- Table Notifications
CREATE TABLE IF NOT EXISTS notifications (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
user_type ENUM('etudiant', 'professeur') NOT NULL,
message TEXT NOT NULL,
type ENUM('modification_emploi', 'quota_epuise', 'nouveau_cours') NOT NULL,
lu BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
2. Configuration Python
[Link] :
```
Flask==3.0.0
Flask-SQLAlchemy==3.0.5
mysql-connector-python==8.1.0
mysql-connector-python==8.1.0
python-constraint==1.4.0
python-dotenv==1.0.0
Werkzeug==3.0.1
Jinja2==3.1.2
reportlab==4.0.4
python-dateutil==2.8.2
```
[Link] :
```python
import [Link]
from [Link] import Error
from functools import wraps
import os
from dotenv import load_dotenv
load_dotenv()
class Database:
def __init__(self):
[Link] = [Link]('DB_HOST', 'localhost')
[Link] = [Link]('DB_NAME', 'iai_schedul')
[Link] = [Link]('DB_USER', 'root')
[Link] = [Link]('DB_PASSWORD', '')
def get_connection(self):
"""Établir une connexion à la base de données"""
try:
connection = [Link](
host=[Link],
database=[Link],
user=[Link],
password=[Link]
)
if connection.is_connected():
return connection
except Error as e:
print(f"Erreur de connexion: {e}")
return None
def execute_query(self, query, params=None, fetch_one=False, fetch_all=False):
"""Exécuter une requête SQL"""
connection = self.get_connection()
if not connection:
return None
cursor = connection×cursor(dictionary=True)
try:
[Link](query, params or ())
if fetch_one:
result = cursor×fetchone()
elif fetch_all:
result = cursor×fetchall()
else:
[Link]()
result = cursor×lastrowid
[Link]()
[Link]()
return result
except Error as e:
print(f"Erreur d'exécution: {e}")
[Link]()
[Link]()
[Link]()
return None
def db_transaction(func):
"""Décorateur pour les transactions"""
@wraps(func)
def wrapper(self, *args, **kwargs):
connection = self.get_connection()
if not connection:
return None
cursor = [Link](dictionary=True)
try:
result = func(self, cursor, *args, **kwargs)
[Link]()
return result
except Error as e:
print(f"Erreur transaction: {e}")
[Link]()
return None
finally:
[Link]()
[Link]()
return wrapper
# Instance globale
db = Database()
```
3. Modèles de Données
[Link] :
```python
from database import db
from [Link] import generate_password_hash, check_password_hash
from datetime import datetime, date, timedelta
import json
class User:
@staticmethod
def hash_password(password):
return generate_password_hash(password)
@staticmethod
def check_password(hashed_password, password):
return check_password_hash(hashed_password, password)
class Admin(User):
@staticmethod
def login(email, password):
query = "SELECT * FROM admin WHERE email = %s"
admin = db.execute_query(query, (email,), fetch_one=True)
@staticmethod
def get_stats():
queries = {
'professeurs': "SELECT COUNT(*) as count FROM professeurs",
'etudiants': "SELECT COUNT(*) as count FROM etudiants",
'matieres': "SELECT COUNT(*) as count FROM matieres",
'cours': "SELECT COUNT(*) as count FROM emploi_du_temps",
'salles': "SELECT COUNT(*) as count FROM salles"
}
stats = {}
for key, query in [Link]():
result = db.execute_query(query, fetch_one=True)
stats[key] = result['count'] if result else 0
return stats
class Etudiant(User):
@staticmethod
def inscrire(nom, prenom, matricule, telephone, email, niveau, classe, password):
hashed_password = User.hash_password(password)
query = """
INSERT INTO etudiants
(nom, prenom, matricule, telephone, email, niveau, classe, password_hash)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
"""
return db.execute_query(query, (nom, prenom, matricule, telephone, email,
niveau, classe, hashed_password))
@staticmethod
def login(email, password):
query = "SELECT * FROM etudiants WHERE email = %s"
etudiant = db.execute_query(query, (email,), fetch_one=True)
@staticmethod
def get_emploi_du_temps(classe, date_semaine):
# Calculer début et fin de semaine
date_obj = datetime×strptime(date_semaine, '%Y-%m-%d').date()
debut_semaine = date_obj - timedelta(days=date_obj.weekday())
fin_semaine = debut_semaine + timedelta(days=5)
query = """
SELECT e.*, [Link] as matiere_nom,
p.nom_complet as professeur_nom, [Link] as salle_nom
FROM emploi_du_temps e
JOIN matieres m ON e.matiere_id = [Link]
JOIN professeurs p ON e.professeur_id = [Link]
JOIN salles s ON e.salle_id = [Link]
WHERE e×classe = %s
AND e.date_semaine BETWEEN %s AND %s
ORDER BY e.date_semaine, e.heure_debut
"""
return db.execute_query(query, (classe, debut_semaine, fin_semaine), fetch_all=True)
class Professeur(User):
@staticmethod
def inscrire(nom_complet, matricule, telephone, email, password):
hashed_password = User.hash_password(password)
query = """
INSERT INTO professeurs (nom_complet, matricule, telephone, email, password_hash)
VALUES (%s, %s, %s, %s, %s)
"""
return db.execute_query(query, (nom_complet, matricule, telephone, email,
hashed_password))
@staticmethod
def login(email, password):
query = "SELECT * FROM professeurs WHERE email = %s"
professeur = db.execute_query(query, (email,), fetch_one=True)
@staticmethod
def get_emploi_du_temps(professeur_id, date_semaine):
date_obj = datetime×strptime(date_semaine, '%Y-%m-%d').date()
debut_semaine = date_obj - timedelta(days=date_obj.weekday())
fin_semaine = debut_semaine + timedelta(days=5)
query = """
SELECT e.*, [Link] as matiere_nom, [Link] as salle_nom, [Link]
FROM emploi_du_temps e
JOIN matieres m ON e.matiere_id = [Link]
JOIN salles s ON e.salle_id = [Link]
WHERE e.professeur_id = %s
AND e.date_semaine BETWEEN %s AND %s
ORDER BY e.date_semaine, e.heure_debut
"""
return db.execute_query(query, (professeur_id, debut_semaine, fin_semaine),
fetch_all=True)
class Matiere:
@staticmethod
def ajouter(code, nom, quota_heures, classe, professeur_id=None):
query = """
INSERT INTO matieres (code, nom, quota_heures, heures_restantes, classe,
professeur_id)
VALUES (%s, %s, %s, %s, %s, %s)
"""
return db.execute_query(query, (code, nom, quota_heures, quota_heures, classe,
professeur_id))
@staticmethod
def get_par_classe(classe):
query = """
SELECT m.*, p.nom_complet as professeur_nom
FROM matieres m
LEFT JOIN professeurs p ON m.professeur_id = p×id
WHERE [Link] = %s
"""
return db.execute_query(query, (classe,), fetch_all=True)
@staticmethod
def update_quota(matiere_id, heures_utilisees):
query = """
UPDATE matieres
SET heures_restantes = heures_restantes - %s
WHERE id = %s AND heures_restantes >= %s
"""
return db.execute_query(query, (heures_utilisees, matiere_id, heures_utilisees))
class Salle:
@staticmethod
def ajouter(nom, capacite, type_salle='Salle'):
query = "INSERT INTO salles (nom, capacite, type) VALUES (%s, %s, %s)"
return db.execute_query(query, (nom, capacite, type_salle))
@staticmethod
def get_all():
query = "SELECT * FROM salles ORDER BY capacite DESC"
return db.execute_query(query, fetch_all=True)
class EmploiDuTemps:
@staticmethod
def ajouter(jour, date_semaine, heure_debut, heure_fin, matiere_id,
professeur_id, classe, salle_id, duree_heures):
query = """
INSERT INTO emploi_du_temps
(jour, date_semaine, heure_debut, heure_fin, matiere_id,
professeur_id, classe, salle_id, duree_heures)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
return db.execute_query(query, (jour, date_semaine, heure_debut, heure_fin,
matiere_id, professeur_id, classe, salle_id, duree_heures))
@staticmethod
def supprimer_par_classe_semaine(classe, date_semaine):
date_obj = datetime×strptime(date_semaine, '%Y-%m-%d').date()
debut_semaine = date_obj - timedelta(days=date_obj.weekday())
fin_semaine = debut_semaine + timedelta(days=5)
query = """
DELETE FROM emploi_du_temps
WHERE classe = %s
AND date_semaine BETWEEN %s AND %s
"""
return db.execute_query(query, (classe, debut_semaine, fin_semaine))
```
[Link] :
```python
from database import db
from models import Matiere, Salle, EmploiDuTemps
from datetime import datetime, timedelta
from constraint import Problem, AllDifferentConstraint
import random
class EmploiGenerator:
def __init__(self):
[Link] = {
'Lundi': [('07:30', '09:30'), ('09:45', '11:30'), ('12:45', '14:45'), ('15:00', '16:45')],
'Mardi': [('07:30', '09:30'), ('09:45', '11:30'), ('12:45', '14:45'), ('15:00', '16:45')],
'Mercredi': [('07:30', '09:30'), ('09:45', '11:30')],
'Jeudi': [('07:30', '09:30'), ('09:45', '11:30'), ('12:45', '14:45'), ('15:00', '16:45')],
'Samedi': [('07:30', '09:30'), ('09:45', '11:30')]
}
# Convertir la date
date_debut = datetime×strptime(date_semaine, '%Y-%m-%d').date()
if matiere:
# Trouver une salle disponible
salle = self._trouver_salle_disponible(salles, date_courante,
creneau[0], creneau[1], classe)
return True
def _get_professeurs_disponibles(self):
"""Récupérer tous les professeurs"""
query = "SELECT id, nom_complet FROM professeurs"
return db.execute_query(query, fetch_all=True) or []
p = Problem()
# Récupérer données
matieres = self._get_matieres_avec_quota(classe)
salles = Salle.get_all()
# Créer variables
variables = []
for jour in [Link]():
for i, creneau in enumerate([Link][jour]):
var_name = f"{jour}_{i}"
[Link](var_name)
# Définir domaine
domaine = []
for matiere in matieres:
for salle in salles:
if self._salle_adequate(salle['id'], classe):
[Link]({
'matiere': matiere,
'salle': salle,
'creneau': creneau,
'jour': jour
})
[Link](var_name, domaine)
# Ajouter contraintes
# 1. Pas deux cours en même temps dans la même salle
# 2. Un professeur ne peut pas avoir deux cours simultanés
# 3. Respect des quotas
# Résoudre
solutions = p×getSolutions()
if solutions:
self._appliquer_solution(solutions[0], classe, date_semaine)
return True
return False
# Ajouter le cours
[Link](
jour=jour,
date_semaine=date_courante,
heure_debut=creneau[0],
heure_fin=creneau[1],
matiere_id=matiere['id'],
professeur_id=matiere['professeur_id'],
classe=classe,
salle_id=salle['id'],
duree_heures=2
)
[Link] :
```python
from flask import Flask, render_template, request, redirect, url_for, session, jsonify,
send_file
from datetime import datetime, timedelta
import io
from [Link] import canvas
from [Link] import A4
from [Link] import colors
from database import db
from models import Admin, Etudiant, Professeur, Matiere, Salle, EmploiDuTemps
from generator import EmploiGenerator
import os
app = Flask(__name__)
app.secret_key = [Link](24)
[Link]['SESSION_TYPE'] = 'filesystem'
# Initialiser le générateur
generator = EmploiGenerator()
@[Link]('/login', methods=['POST'])
def login():
email = request×form['email']
password = [Link]['password']
role = [Link]['role']
if role == 'admin':
user = [Link](email, password)
if user:
session['user_id'] = user['id']
session['user_email'] = user['email']
session['user_role'] = 'admin'
return redirect(url_for('admin_dashboard'))
@[Link]('/register/etudiant')
def register_etudiant_page():
return render_template('register_etudiant.html')
@[Link]('/register/etudiant', methods=['POST'])
def register_etudiant():
data = [Link]
try:
etudiant_id = Etudiant×inscrire(
nom=data['nom'],
prenom=data['prenom'],
matricule=data['matricule'],
telephone=data['telephone'],
email=data['email'],
niveau=int(data['niveau']),
classe=data['classe'],
password=data['password']
)
if etudiant_id:
return redirect(url_for('index'))
else:
return render_template('register_etudiant.html',
error="Erreur lors de l'inscription")
except Exception as e:
return render_template('register_etudiant.html',
error=f"Erreur: {str(e)}")
@[Link]('/register/professeur')
def register_professeur_page():
return render_template('register_professeur.html')
@[Link]('/register/professeur', methods=['POST'])
def register_professeur():
data = [Link]
try:
professeur_id = Professeur×inscrire(
nom_complet=data['nom_complet'],
matricule=data['matricule'],
telephone=data['telephone'],
email=data['email'],
password=data['password']
)
if professeur_id:
return redirect(url_for('index'))
else:
return render_template('register_professeur.html',
error="Erreur lors de l'inscription")
except Exception as e:
return render_template('register_professeur.html',
error=f"Erreur: {str(e)}")
@[Link]('/logout')
def logout():
[Link]()
return redirect(url_for('index'))
@[Link]('/admin/dashboard')
@admin_required
def admin_dashboard():
stats = Admin×get_stats()
return render_template('admin/[Link]', stats=stats)
@[Link]('/admin/professeurs')
@admin_required
def admin_professeurs():
query = "SELECT * FROM professeurs ORDER BY nom_complet"
professeurs = db.execute_query(query, fetch_all=True) or []
return render_template('admin/[Link]', professeurs=professeurs)
@[Link]('/admin/etudiants')
@admin_required
def admin_etudiants():
query = "SELECT * FROM etudiants ORDER BY nom, prenom"
etudiants = db.execute_query(query, fetch_all=True) or []
return render_template('admin/[Link]', etudiants=etudiants)
@[Link]('/admin/matieres')
@admin_required
def admin_matieres():
query = """
SELECT m.*, p.nom_complet as professeur_nom, [Link] as classe_nom
FROM matieres m
LEFT JOIN professeurs p ON m.professeur_id = p×id
LEFT JOIN classes c ON m×classe = [Link]
ORDER BY [Link], [Link]
"""
matieres = db.execute_query(query, fetch_all=True) or []
return render_template('admin/[Link]',
matieres=matieres,
classes=classes,
professeurs=professeurs)
@[Link]('/admin/matiere/ajouter', methods=['POST'])
@admin_required
def admin_ajouter_matiere():
data = [Link]
professeur_id = data['professeur_id'] if data['professeur_id'] else None
matiere_id = Matiere×ajouter(
code=data['code'],
nom=data['nom'],
quota_heures=int(data['quota_heures']),
classe=data['classe'],
professeur_id=professeur_id
)
return redirect(url_for('admin_matieres'))
@[Link]('/admin/salles')
@admin_required
def admin_salles():
query = "SELECT * FROM salles ORDER BY nom"
salles = db.execute_query(query, fetch_all=True) or []
return render_template('admin/[Link]', salles=salles)
@[Link]('/admin/salle/ajouter', methods=['POST'])
@admin_required
def admin_ajouter_salle():
data = [Link]
salle_id = Salle×ajouter(
nom=data['nom'],
capacite=int(data['capacite']),
type_salle=[Link]('type', 'Salle')
)
return redirect(url_for('admin_salles'))
try:
success = generator.generer_emploi(classe, date_semaine)
if success:
message = f"Emploi du temps généré avec succès pour la classe {classe}!"
message_type = "success"
else:
message = "Impossible de générer l'emploi du temps avec les contraintes
actuelles."
message_type = "error"
except Exception as e:
message = f"Erreur: {str(e)}"
message_type = "error"
# GET request
classes = db.execute_query("SELECT * FROM classes ORDER BY niveau, nom",
fetch_all=True) or []
return render_template('admin/[Link]', classes=classes)
@[Link]('/admin/visualiser')
@admin_required
def admin_visualiser():
# Récupérer tous les emplois du temps
query = """
SELECT e.*, [Link] as matiere_nom, p.nom_complet as professeur_nom,
[Link] as salle_nom, [Link] as classe_nom
FROM emploi_du_temps e
JOIN matieres m ON e.matiere_id = m×id
JOIN professeurs p ON e.professeur_id = p×id
JOIN salles s ON e.salle_id = s×id
JOIN classes c ON e×classe = [Link]
WHERE e.date_semaine >= CURDATE()
ORDER BY e.date_semaine, e.heure_debut
LIMIT 100
"""
cours = db.execute_query(query, fetch_all=True) or []
@[Link]('/etudiant/dashboard')
@etudiant_required
def etudiant_dashboard():
# Récupérer les informations de l'étudiant
query = "SELECT * FROM etudiants WHERE id = %s"
etudiant = db.execute_query(query, (session['user_id'],), fetch_one=True)
return render_template('etudiant/[Link]',
etudiant=etudiant,
emploi_par_jour=emploi_par_jour,
date_semaine=date_semaine)
@[Link]('/etudiant/emploi/pdf')
@etudiant_required
def etudiant_emploi_pdf():
# Créer un PDF de l'emploi du temps
buffer = io×BytesIO()
p = [Link](buffer, pagesize=A4)
# En-tête
[Link]("Helvetica-Bold", 16)
[Link](50, 800, "IAI-Schedul - Emploi du Temps")
[Link]("Helvetica", 12)
[Link](50, 780, f"Classe: {session['user_classe']}")
[Link](50, 760, f"Date: {[Link]().strftime('%d/%m/%Y %H:%M')}")
# Tableau
y = 700
jours = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi']
# Cours du jour
cours_du_jour = [c for c in emploi if c['jour'] == jour]
if cours_du_jour:
for cours in cours_du_jour:
[Link]("Helvetica", 10)
ligne = f"{cours['heure_debut']} - {cours['heure_fin']}: {cours['matiere_nom']}"
[Link](70, y, ligne)
y -= 15
y -= 10
[Link]()
[Link](0)
@[Link]('/professeur/dashboard')
@professeur_required
def professeur_dashboard():
# Récupérer les informations du professeur
query = "SELECT * FROM professeurs WHERE id = %s"
professeur = db.execute_query(query, (session['user_id'],), fetch_one=True)
return render_template('professeur/[Link]',
professeur=professeur,
emploi=emploi,
matieres=matieres,
date_semaine=date_semaine)
@[Link]('/api/emploi/<classe>')
def api_emploi_classe(classe):
date_semaine = [Link]('semaine', [Link]().strftime('%Y-%m-%d'))
emploi = Etudiant.get_emploi_du_temps(classe, date_semaine) or []
return jsonify(emploi)
@[Link]('/api/matieres/quota')
def api_matieres_quota():
query = """
SELECT m.*, [Link] as classe_nom,
ROUND((m.quota_heures - m.heures_restantes) * 100.0 / m.quota_heures, 1) as
pourcentage_utilise
FROM matieres m
JOIN classes c ON m×classe = [Link]
WHERE m.heures_restantes < m.quota_heures
ORDER BY [Link], [Link]
"""
matieres = db.execute_query(query, fetch_all=True) or []
return jsonify(matieres)
[Link]('static/css', exist_ok=True)
[Link]('static/js', exist_ok=True)
# Démarrer l'application
print("IAI-Schedul démarré sur [Link]
print("Super Admin: kenmognepenlap@[Link] / 123456789")
app×run(debug=True, port=5000)
```
6. Templates HTML
templates/[Link] :
```html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}IAI-Schedul{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/[Link]') }}">
<link rel="stylesheet" href="[Link]
css/[Link]">
{% block styles %}{% endblock %}
</head>
<body>
{% if session.user_role %}
<nav class="navbar">
<div class="nav-container">
<div class="nav-brand">
<i class="fas fa-calendar-alt"></i>
<span>IAI-Schedul</span>
</div>
<div class="nav-menu">
{% if session.user_role == 'admin' %}
<a href="{{ url_for('admin_dashboard') }}" class="nav-link">
<i class="fas fa-tachometer-alt"></i> Dashboard
</a>
<a href="{{ url_for('admin_professeurs') }}" class="nav-link">
<i class="fas fa-chalkboard-teacher"></i> Professeurs
</a>
<a href="{{ url_for('admin_matieres') }}" class="nav-link">
<i class="fas fa-book"></i> Matières
</a>
<a href="{{ url_for('admin_salles') }}" class="nav-link">
<i class="fas fa-door-open"></i> Salles
</a>
<a href="{{ url_for('admin_generer') }}" class="nav-link">
<i class="fas fa-magic"></i> Générer
</a>
{% elif session.user_role == 'etudiant' %}
<a href="{{ url_for('etudiant_dashboard') }}" class="nav-link">
<i class="fas fa-home"></i> Accueil
</a>
<a href="{{ url_for('etudiant_emploi_pdf') }}" class="nav-link">
<i class="fas fa-file-pdf"></i> Télécharger PDF
</a>
{% elif session.user_role == 'professeur' %}
<a href="{{ url_for('professeur_dashboard') }}" class="nav-link">
<i class="fas fa-home"></i> Accueil
</a>
{% endif %}
</div>
<div class="nav-user">
<span>{{ session.user_email }}</span>
<a href="{{ url_for('logout') }}" class="btn-logout">
<i class="fas fa-sign-out-alt"></i> Déconnexion
</a>
</div>
</div>
</nav>
{% endif %}
<main class="main-content">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<footer class="footer">
<p>IAI-Schedul © 2024 - IAI Cameroun</p>
<p>Système de gestion des emplois du temps</p>
</footer>
templates/[Link] :
```html
{% extends "[Link]" %}
{% if error %}
<div class="alert alert-error">
{{ error }}
</div>
{% endif %}
<div class="form-group">
<label for="password">
<i class="fas fa-lock"></i> Mot de passe
</label>
<input type="password" id="password" name="password" required
placeholder="Votre mot de passe">
</div>
<div class="form-group">
<label for="role">
<i class="fas fa-user-tag"></i> Rôle
</label>
<select id="role" name="role" required>
<option value="">Sélectionnez votre rôle</option>
<option value="admin">Administrateur</option>
<option value="etudiant">Étudiant</option>
<option value="professeur">Professeur</option>
</select>
</div>
<div class="login-links">
<p>Nouveau sur IAI-Schedul?</p>
<div class="link-buttons">
<a href="{{ url_for('register_etudiant_page') }}" class="btn btn-outline">
<i class="fas fa-user-graduate"></i> Inscription Étudiant
</a>
<a href="{{ url_for('register_professeur_page') }}" class="btn btn-outline">
<i class="fas fa-chalkboard-teacher"></i> Inscription Professeur
</a>
</div>
</div>
<div class="login-info">
<h3>Informations de test :</h3>
<p><strong>Super Admin :</strong> kenmognepenlap@[Link] / 123456789</p>
<p>Mot de passe par défaut pour les nouveaux comptes : <code>Password123</
code></p>
</div>
</div>
</div>
{% endblock %}
```
templates/admin/[Link] :
```html
{% extends "[Link]" %}
{% block content %}
<div class="dashboard-container">
<div class="dashboard-header">
<h1><i class="fas fa-tachometer-alt"></i> Tableau de Bord Administrateur</h1>
<p>Bienvenue, {{ session.user_email }}</p>
</div>
templates/etudiant/[Link] :
```html
{% extends "[Link]" %}
{% block content %}
<div class="emploi-container">
<div class="emploi-header">
<h1><i class="fas fa-calendar-week"></i> Mon Emploi du Temps</h1>
<div class="emploi-info">
<div class="info-card">
<i class="fas fa-user-graduate"></i>
<div>
<h3>{{ [Link] }} {{ [Link] }}</h3>
<p>Classe: {{ [Link] }} | Niveau: {{ [Link] }}</p>
</div>
</div>
<div class="info-card">
<i class="fas fa-calendar-day"></i>
<div>
<h3>Semaine du {{ date_semaine }}</h3>
<p>IAI Cameroun - Institut Africain d'Informatique</p>
</div>
</div>
</div>
</div>
<div class="day-courses">
{% if emploi_par_jour[jour] %}
{% for cours in emploi_par_jour[jour] %}
<div class="course-card">
<div class="course-time">
<i class="fas fa-clock"></i>
{{ cours.heure_debut[:5] }} - {{ cours.heure_fin[:5] }}
</div>
<div class="course-title">{{ cours.matiere_nom }}</div>
<div class="course-details">
<span class="detail-item">
<i class="fas fa-chalkboard-teacher"></i>
{{ cours.professeur_nom }}
</span>
<span class="detail-item">
<i class="fas fa-door-open"></i>
{{ cours.salle_nom }}
</span>
</div>
</div>
{% endfor %}
{% else %}
<div class="no-course">
<i class="fas fa-coffee"></i>
<p>Pas de cours aujourd'hui</p>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
7. Fichiers Statiques
static/css/[Link] :
```css
/* Variables */
:root {
--primary: #4f46e5;
--primary-dark: #4338ca;
--secondary: #10b981;
--danger: #ef4444;
--warning: #f59e0b;
--info: #3b82f6;
--dark: #1f2937;
--light: #f3f4f6;
--gray: #6b7280;
--white: #ffffff;
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--radius: 8px;
--transition: all 0.3s ease;
}
/* Reset et Base */
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: var(--dark);
line-height: 1.6;
}
/* Layout */
.navbar {
background: var(--white);
box-shadow: var(--shadow);
position: sticky;
top: 0;
z-index: 1000;
}
.nav-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
height: 70px;
}
.nav-brand {
display: flex;
align-items: center;
gap: 10px;
font-size: 1.5rem;
font-weight: bold;
color: var(--primary);
}
.nav-brand i {
font-size: 1.8rem;
}
.nav-menu {
display: flex;
gap: 20px;
}
.nav-link {
display: flex;
align-items: center;
gap: 8px;
color: var(--gray);
text-decoration: none;
padding: 10px 15px;
border-radius: var(--radius);
transition: var(--transition);
}
.nav-link:hover, .[Link] {
background: var(--light);
color: var(--primary);
}
.nav-user {
display: flex;
align-items: center;
gap: 20px;
}
.btn-logout {
display: flex;
align-items: center;
gap: 8px;
background: var(--danger);
color: var(--white);
padding: 8px 16px;
border-radius: var(--radius);
text-decoration: none;
transition: var(--transition);
}
.btn-logout:hover {
background: #dc2626;
}
.main-content {
max-width: 1200px;
margin: 30px auto;
padding: 0 20px;
}
/* Cartes et Conteneurs */
.dashboard-container, .emploi-container, .login-container {
background: var(--white);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 30px;
}
/* Formulaires */
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: var(--dark);
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 12px 16px;
border: 2px solid #e5e7eb;
border-radius: var(--radius);
font-size: 16px;
transition: var(--transition);
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
}
/* Boutons */
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
border: none;
border-radius: var(--radius);
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: var(--transition);
text-decoration: none;
text-align: center;
}
.btn-primary {
background: var(--primary);
color: var(--white);
}
.btn-primary:hover {
background: var(--primary-dark);
transform: translateY(-2px);
}
.btn-outline {
background: transparent;
color: var(--primary);
border: 2px solid var(--primary);
}
.btn-outline:hover {
background: var(--primary);
color: var(--white);
}
.btn-danger {
background: var(--danger);
color: var(--white);
}
.btn-danger:hover {
background: #dc2626;
}
.btn-block {
width: 100%;
justify-content: center;
}
/* Statistiques */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin: 30px 0;
}
.stat-card {
background: var(--white);
border-radius: var(--radius);
padding: 25px;
display: flex;
align-items: center;
gap: 20px;
box-shadow: var(--shadow);
transition: var(--transition);
}
.stat-card:hover {
transform: translateY(-5px);
}
.stat-icon {
width: 60px;
height: 60px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: var(--white);
}
.stat-content h3 {
font-size: 28px;
margin-bottom: 5px;
}
.stat-content p {
color: var(--gray);
font-size: 14px;
}
/* Emploi du temps */
.timetable-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin: 30px 0;
}
.timetable-day {
background: var(--light);
border-radius: var(--radius);
overflow: hidden;
}
.day-header {
background: var(--primary);
color: var(--white);
padding: 15px;
text-align: center;
}
.day-header h3 {
margin: 0;
font-size: 18px;
}
.day-courses {
padding: 15px;
}
.course-card {
background: var(--white);
border-radius: var(--radius);
padding: 15px;
margin-bottom: 10px;
border-left: 4px solid var(--primary);
}
.course-time {
color: var(--gray);
font-size: 14px;
margin-bottom: 8px;
}
.course-title {
font-weight: 600;
margin-bottom: 8px;
}
.course-details {
display: flex;
gap: 15px;
font-size: 14px;
color: var(--gray);
}
/* Alertes */
.alert {
padding: 15px 20px;
border-radius: var(--radius);
margin-bottom: 20px;
border-left: 4px solid;
}
.alert-success {
background: #d1fae5;
border-color: var(--secondary);
color: #065f46;
}
.alert-error {
background: #fee2e2;
border-color: var(--danger);
color: #991b1b;
}
.alert-warning {
background: #fef3c7;
border-color: var(--warning);
color: #92400e;
}
/* Login */
.login-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: none;
box-shadow: none;
}
.login-card {
background: var(--white);
border-radius: var(--radius);
padding: 40px;
width: 100%;
max-width: 500px;
box-shadow: var(--shadow);
}
.login-header {
text-align: center;
margin-bottom: 30px;
}
.login-header h1 {
color: var(--primary);
margin-bottom: 10px;
}
.login-links {
text-align: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e5e7eb;
}
.link-buttons {
display: flex;
gap: 10px;
margin-top: 15px;
}
/* Responsive */
@media (max-width: 768px) {
.nav-menu {
display: none;
}
.stats-grid {
grid-template-columns: 1fr;
}
.timetable-grid {
grid-template-columns: 1fr;
}
.course-details {
flex-direction: column;
gap: 5px;
}
.link-buttons {
flex-direction: column;
}
}
/* Animations */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
```
static/js/[Link] :
```javascript
// Fonctions utilitaires
function formatDate(date) {
return new Date(date).toLocaleDateString('fr-FR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
[Link](toast);
// Animation
setTimeout(() => [Link]('show'), 100);
setTimeout(() => {
[Link]('show');
setTimeout(() => [Link](), 300);
}, 3000);
}
[Link](field => {
if (![Link]()) {
[Link] = '#ef4444';
isValid = false;
}
});
if (!isValid) {
[Link]();
showToast('Veuillez remplir tous les champs obligatoires', 'error');
}
});
});
// Navigation semaine
const weekNav = [Link]('.week-navigation');
if (weekNav) {
[Link]('click', function(e) {
if (e×target×tagName === 'A') {
const btn = e×target;
btn×innerHTML = '<i class="fas fa-spinner fa-spin"></i> Chargement...';
btn×disabled = true;
}
});
}
// Auto-hide alerts
const alerts = [Link]('.alert');
[Link](alert => {
setTimeout(() => {
[Link] = '0';
setTimeout(() => [Link](), 300);
}, 5000);
});
// Gestion du PDF
function generatePDF() {
showToast('Génération du PDF en cours...', 'info');
// Simuler le téléchargement
setTimeout(() => {
showToast('PDF généré avec succès!', 'success');
}, 1500);
}
if (![Link]) {
throw new Error('Erreur réseau');
}
// Initialiser
checkNotifications();
.[Link] {
transform: translateX(0);
}
.toast-success {
border-left: 4px solid #10b981;
}
.toast-error {
border-left: 4px solid #ef4444;
}
.toast-info {
border-left: 4px solid #3b82f6;
}
.toast i {
font-size: 20px;
}
8. Fichier d'Environnement
.env :
```
DB_HOST=localhost
DB_NAME=iai_schedul
DB_USER=root
DB_PASSWORD=
FLASK_SECRET_KEY=votre_secret_key_ici
DEBUG=True
```
[Link] (Windows) :
```batch
@echo off
echo ========================================
echo INSTALLATION IAI-SCHEDUL (Python + MySQL)
echo ========================================
echo.
echo.
echo ========================================
echo INSTALLATION TERMINÉE!
echo ========================================
echo.
echo Pour démarrer l'application:
echo 1. Activer l'environnement: venv\Scripts\activate
echo 2. Lancer Flask: python [Link]
echo 3. Ouvrir: [Link]
echo.
echo Identifiants Super Admin:
echo Email: kenmognepenlap@[Link]
echo Matricule: 123456789
echo Mot de passe: Password123
echo.
pause
```
[Link] (Linux/Mac) :
```bash
#!/bin/bash
echo "========================================"
echo "INSTALLATION IAI-SCHEDUL (Python + MySQL)"
echo "========================================"
echo
# 1. Vérifier Python
if ! command -v python3 &> /dev/null; then
echo "Python n'est pas installé!"
echo "Installez Python 3.8+ : sudo apt install python3 python3-pip"
exit 1
fi
# 2. Créer l'environnement virtuel
echo "Création de l'environnement virtuel..."
python3 -m venv venv
source venv/bin/activate
# 4. Configurer MySQL
echo
echo "========================================"
echo "CONFIGURATION MYSQL"
echo "========================================"
echo "1. Assurez-vous que MySQL est installé et démarré"
echo "2. Connectez-vous à MySQL: mysql -u root -p"
echo "3. Créez la base: CREATE DATABASE iai_schedul;"
echo "4. Importez le fichier: mysql -u root -p iai_schedul < database_setup.sql"
echo
# 5. Initialiser
echo "Initialisation de l'application..."
python3 -c "from app import app; print('✓ Application prête!')"
echo
echo "========================================"
echo "INSTALLATION TERMINÉE!"
echo "========================================"
echo
echo "Pour démarrer l'application:"
echo "1. Activer l'environnement: source venv/bin/activate"
echo "2. Lancer Flask: python [Link]"
echo "3. Ouvrir: [Link]
echo
echo "Identifiants Super Admin:"
echo "Email: kenmognepenlap@[Link]"
echo "Matricule: 123456789"
echo "Mot de passe: Password123"
echo
```
10. Lancement de l'Application
Étapes finales :
```bash
# Windows
venv\Scripts\activate
python [Link]
# Linux/Mac
source venv/bin/activate
python [Link]
```
Fonctionnalités Implémentées :
Arborescence finale :
```
iai-schedul-python/
├── [Link]
├── [Link]
├── [Link]
├── [Link]
├── [Link]
├── .env
├── [Link]
├── [Link]
├── database_setup.sql
├── static/
│ ├── css/
│ │ └── [Link]
│ └── js/
│ └── [Link]
└── templates/
├── [Link]
├── [Link]
├── register_etudiant.html
├── register_professeur.html
├── admin/
│ ├── [Link]
│ ├── [Link]
│ ├── [Link]
│ ├── [Link]
│ ├── [Link]
│ ├── [Link]
│ └── [Link]
├── etudiant/
│ ├── [Link]
│ └── [Link]
└── professeur/
└── [Link]
```
Prêt à l'emploi !