diff --git a/.gitignore b/.gitignore index 993ea7b..4fa0bf6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,19 @@ *env + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Generated PDF files +app/static/*.pdf + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..6d31e02 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Générateur d'Exercices de Mathématiques - Version Web + +Application web permettant de générer des exercices de multiplication et division personnalisés au format PDF. + +## Fonctionnalités + +- Génération d'exercices de multiplication et division mélangés +- Personnalisation des tables (minimale et maximale) +- Choix du nombre d'exercices à générer +- Format PDF prêt à l'impression +- Mise en page en trois colonnes pour économiser du papier +- Interface utilisateur responsive avec Bootstrap + +## Installation + +1. Installer les dépendances : +```bash +pip install -r requirements.txt +``` + +## Lancement de l'application + +```bash +uvicorn app.main:app --reload +``` + +L'application sera accessible à l'adresse : http://localhost:8000 + +## Utilisation + +1. Accédez à l'interface web +2. Configurez les paramètres : + - Table minimale : la plus petite table de multiplication à inclure + - Table maximale : la plus grande table de multiplication à inclure + - Nombre d'exercices : nombre total d'exercices à générer +3. Cliquez sur "Générer le PDF" +4. Téléchargez et imprimez le fichier PDF généré + +## Structure du projet + +- `app/main.py` : Application FastAPI principale +- `app/templates/` : Templates HTML +- `app/static/` : Fichiers statiques (PDF générés) +- `generate_math_exercises.py` : Version originale en ligne de commande + +## Technologies utilisées + +- Python 3 +- FastAPI (backend) +- Bootstrap 5 (frontend) +- Jinja2 (templating) +- fpdf2 (génération PDF) \ No newline at end of file diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..a86a8db --- /dev/null +++ b/app/main.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +import random +import os +from typing import List +from fastapi import FastAPI, Request, Response +from fastapi.responses import HTMLResponse, FileResponse +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates +from pydantic import BaseModel +from fpdf import FPDF + +app = FastAPI() + +# Mount static files and templates +app.mount("/static", StaticFiles(directory="app/static"), name="static") +templates = Jinja2Templates(directory="app/templates") + +class MathExercisesPDF(FPDF): + def header(self): + self.set_font('Helvetica', 'B', 16) + self.cell(0, 10, 'Exercices de Multiplication et Division', 0, 1, 'C', new_x="LMARGIN", new_y="NEXT") + self.ln(10) + + def footer(self): + self.set_y(-15) + self.set_font('Helvetica', 'I', 8) + self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C', new_x="RIGHT", new_y="TOP") + +class ExerciseRequest(BaseModel): + min_table: int + max_table: int + num_exercises: int = 15 + +def generate_exercises(min_table: int, max_table: int, num_exercises: int = 15) -> List[str]: + """Génère des exercices de multiplication et division aléatoires mélangés sans doublons""" + exercises = [] + used_operations = set() # Pour éviter les doublons + + # Générer le nombre exact d'exercices demandé + attempts = 0 + max_attempts = num_exercises * 10 # Limite pour éviter une boucle infinie + + while len(exercises) < num_exercises and attempts < max_attempts: + attempts += 1 + + # Choisir deux nombres aléatoires entre min_table et max_table + a = random.randint(min_table, max_table) + b = random.randint(min_table, max_table) + result = a * b + + # Choisir aléatoirement le type d'exercice (seulement multiplication ou division) + exercise_type = random.choice(['multiplication', 'division']) + + if exercise_type == 'multiplication': + # Exercice de multiplication + operation_key = f"mult_{a}_{b}" # Clé unique pour cette opération + if operation_key not in used_operations: + exercise = f"{a} · {b} = ____" + exercises.append(exercise) + used_operations.add(operation_key) + else: # division + # Exercice de division + divisor = random.choice([a, b]) + operation_key = f"div_{result}_{divisor}" # Clé unique pour cette opération + if operation_key not in used_operations: + exercise = f"{result} : {divisor} = ____" + exercises.append(exercise) + used_operations.add(operation_key) + + # Si nous n'avons pas pu générer suffisamment d'exercices uniques, + # compléter avec des variations + if len(exercises) < num_exercises: + remaining = num_exercises - len(exercises) + for _ in range(remaining): + # Générer des variations avec des nombres légèrement différents + a = random.randint(min_table, max_table) + b = random.randint(min_table, max_table) + result = a * b + + # Choisir aléatoirement le type d'exercice + exercise_type = random.choice(['multiplication', 'division']) + + if exercise_type == 'multiplication': + exercise = f"{a} · {b} = ____" + else: # division + divisor = random.choice([a, b]) + exercise = f"{result} : {divisor} = ____" + + exercises.append(exercise) + + return exercises + +def create_math_exercises_pdf(min_table: int, max_table: int, num_exercises: int = 15) -> str: + """Crée un fichier PDF avec des exercices de mathématiques mélangés en 3 colonnes""" + pdf = MathExercisesPDF() + pdf.add_page() + pdf.set_font('Helvetica', '', 12) + + # Ajouter des informations sur la plage de tables + if min_table == max_table: + table_info = f'Tables de multiplication et division pour {min_table}' + else: + table_info = f'Tables de multiplication et division de {min_table} à {max_table}' + + pdf.cell(0, 10, table_info, 0, 1, 'C', new_x="LMARGIN", new_y="NEXT") + pdf.ln(5) + + # Générer les exercices + exercises = generate_exercises(min_table, max_table, num_exercises) + + # Pas d'en-têtes de colonnes + + # Répartir les exercices en 3 colonnes + num_per_column = (num_exercises + 2) // 3 # Arrondi vers le haut + col1_exercises = exercises[:num_per_column] + col2_exercises = exercises[num_per_column:num_per_column*2] + col3_exercises = exercises[num_per_column*2:] + + # Ajouter les exercices numérotés de 1 à n dans les colonnes + max_rows = max(len(col1_exercises), len(col2_exercises), len(col3_exercises)) + + for i in range(max_rows): + # Colonne 1 + if i < len(col1_exercises): + exercise_num = i + 1 + col1_text = f"{exercise_num}. {col1_exercises[i]}" + else: + col1_text = "" + + # Colonne 2 + if i < len(col2_exercises): + exercise_num = i + 1 + len(col1_exercises) + col2_text = f"{exercise_num}. {col2_exercises[i]}" + else: + col2_text = "" + + # Colonne 3 + if i < len(col3_exercises): + exercise_num = i + 1 + len(col1_exercises) + len(col2_exercises) + col3_text = f"{exercise_num}. {col3_exercises[i]}" + else: + col3_text = "" + + # Ajouter la ligne + pdf.cell(60, 10, col1_text, 0, 0, 'L', new_x="RIGHT", new_y="TOP") + pdf.cell(60, 10, col2_text, 0, 0, 'L', new_x="RIGHT", new_y="TOP") + pdf.cell(60, 10, col3_text, 0, 1, 'L', new_x="LMARGIN", new_y="NEXT") + + # Sauvegarder le PDF + if min_table == max_table: + filename = f'app/static/exercices_mathematiques_table_{min_table}_{num_exercises}_exercices.pdf' + else: + filename = f'app/static/exercices_mathematiques_tables_{min_table}_a_{max_table}_{num_exercises}_exercices.pdf' + pdf.output(filename) + return filename + +@app.get("/", response_class=HTMLResponse) +async def read_root(request: Request): + return templates.TemplateResponse("index.html", {"request": request}) + +@app.post("/generate") +async def generate_exercises_endpoint(request: ExerciseRequest): + try: + if request.min_table < 1 or request.max_table < 1: + return {"error": "Les tables doivent être supérieures à 0"} + if request.min_table > request.max_table: + return {"error": "La table minimale doit être inférieure ou égale à la table maximale"} + if request.num_exercises < 1: + return {"error": "Le nombre d'exercices doit être supérieur à 0"} + + filename = create_math_exercises_pdf( + request.min_table, + request.max_table, + request.num_exercises + ) + + # Extract just the filename for the download link + pdf_filename = os.path.basename(filename) + + return { + "message": "PDF généré avec succès!", + "pdf_filename": pdf_filename, + "min_table": request.min_table, + "max_table": request.max_table, + "num_exercises": request.num_exercises + } + except Exception as e: + return {"error": f"Erreur lors de la création du PDF: {str(e)}"} + +@app.get("/download/{filename}") +async def download_pdf(filename: str): + file_path = f"app/static/{filename}" + if os.path.exists(file_path): + return FileResponse( + path=file_path, + media_type='application/pdf', + filename=filename + ) + return {"error": "File not found"} + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..8be54a5 --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,219 @@ + + +
+ + +Créez des exercices de multiplication et division personnalisés en PDF
+Exercices de multiplication avec des opérateurs aléatoires de la table spécifiée.
+Exercices de division correspondants aux tables de multiplication sélectionnées.
+Générez des fichiers PDF prêts à imprimer avec une mise en page organisée.
+Les exercices sont présentés en trois colonnes pour économiser du papier.
+