refactor: standardize string quotes and improve code formatting
Updated both main.py and generate_math_exercises.py to use consistent double quotes for strings and improved code formatting with better line breaks for readability. Also added JSON import to main.py for bulk operations.
This commit is contained in:
+98
-59
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
import random
|
import random
|
||||||
import os
|
import os
|
||||||
import io
|
import io
|
||||||
@@ -7,8 +8,8 @@ import zipfile
|
|||||||
import tempfile
|
import tempfile
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
from typing import List
|
from typing import List
|
||||||
from fastapi import FastAPI, Request, Response, Form
|
from fastapi import FastAPI, Request, Form
|
||||||
from fastapi.responses import HTMLResponse, StreamingResponse
|
from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from fpdf import FPDF
|
from fpdf import FPDF
|
||||||
@@ -16,29 +17,30 @@ from fpdf import FPDF
|
|||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
# S3 Configuration
|
# S3 Configuration
|
||||||
S3_BUCKET_NAME = os.environ.get('S3_BUCKET_NAME', 'math-exercises')
|
S3_BUCKET_NAME = os.environ.get("S3_BUCKET_NAME", "math-exercises")
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="app/templates")
|
templates = Jinja2Templates(directory="app/templates")
|
||||||
|
|
||||||
|
|
||||||
def get_s3_client():
|
def get_s3_client():
|
||||||
"""Create and return an S3 client using environment variables"""
|
"""Create and return an S3 client using environment variables"""
|
||||||
s3_access_key = os.environ.get('S3_ACCESS_KEY')
|
s3_access_key = os.environ.get("S3_ACCESS_KEY")
|
||||||
s3_secret_key = os.environ.get('S3_SECRET_KEY')
|
s3_secret_key = os.environ.get("S3_SECRET_KEY")
|
||||||
s3_host_base = os.environ.get('S3_HOST_BASE')
|
s3_host_base = os.environ.get("S3_HOST_BASE")
|
||||||
s3_host_bucket = os.environ.get('S3_HOST_BUCKET')
|
|
||||||
|
|
||||||
if not all([s3_access_key, s3_secret_key, s3_host_base]):
|
if not all([s3_access_key, s3_secret_key, s3_host_base]):
|
||||||
raise ValueError("S3 environment variables not properly set")
|
raise ValueError("S3 environment variables not properly set")
|
||||||
|
|
||||||
s3 = boto3.client(
|
s3 = boto3.client(
|
||||||
's3',
|
"s3",
|
||||||
aws_access_key_id=s3_access_key,
|
aws_access_key_id=s3_access_key,
|
||||||
aws_secret_access_key=s3_secret_key,
|
aws_secret_access_key=s3_secret_key,
|
||||||
endpoint_url=s3_host_base,
|
endpoint_url=s3_host_base,
|
||||||
region_name='us-east-1' # Required but unused for Infomaniak
|
region_name="us-east-1", # Required but unused for Infomaniak
|
||||||
)
|
)
|
||||||
return s3
|
return s3
|
||||||
|
|
||||||
|
|
||||||
def create_bucket_if_not_exists(bucket_name):
|
def create_bucket_if_not_exists(bucket_name):
|
||||||
"""Create S3 bucket if it doesn't exist"""
|
"""Create S3 bucket if it doesn't exist"""
|
||||||
s3_client = get_s3_client()
|
s3_client = get_s3_client()
|
||||||
@@ -46,7 +48,7 @@ def create_bucket_if_not_exists(bucket_name):
|
|||||||
try:
|
try:
|
||||||
s3_client.head_bucket(Bucket=bucket_name)
|
s3_client.head_bucket(Bucket=bucket_name)
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
error_code = int(e.response['Error']['Code'])
|
error_code = int(e.response["Error"]["Code"])
|
||||||
if error_code == 404:
|
if error_code == 404:
|
||||||
# Bucket doesn't exist, create it
|
# Bucket doesn't exist, create it
|
||||||
try:
|
try:
|
||||||
@@ -60,13 +62,15 @@ def create_bucket_if_not_exists(bucket_name):
|
|||||||
print(f"Error checking bucket: {e}")
|
print(f"Error checking bucket: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
# Create bucket on startup
|
# Create bucket on startup
|
||||||
try:
|
try:
|
||||||
create_bucket_if_not_exists(S3_BUCKET_NAME)
|
create_bucket_if_not_exists(S3_BUCKET_NAME)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Could not create/check S3 bucket: {e}")
|
print(f"Warning: Could not create/check S3 bucket: {e}")
|
||||||
|
|
||||||
def upload_to_s3(file_data, bucket_name, object_name, content_type='application/pdf'):
|
|
||||||
|
def upload_to_s3(file_data, bucket_name, object_name, content_type="application/pdf"):
|
||||||
"""Upload file data to S3 bucket"""
|
"""Upload file data to S3 bucket"""
|
||||||
s3_client = get_s3_client()
|
s3_client = get_s3_client()
|
||||||
|
|
||||||
@@ -75,34 +79,38 @@ def upload_to_s3(file_data, bucket_name, object_name, content_type='application/
|
|||||||
Bucket=bucket_name,
|
Bucket=bucket_name,
|
||||||
Key=object_name,
|
Key=object_name,
|
||||||
Body=file_data,
|
Body=file_data,
|
||||||
ContentType=content_type
|
ContentType=content_type,
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
print(f"Error uploading to S3: {e}")
|
print(f"Error uploading to S3: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def download_from_s3(bucket_name, object_name):
|
def download_from_s3(bucket_name, object_name):
|
||||||
"""Download file data from S3 bucket"""
|
"""Download file data from S3 bucket"""
|
||||||
s3_client = get_s3_client()
|
s3_client = get_s3_client()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = s3_client.get_object(Bucket=bucket_name, Key=object_name)
|
response = s3_client.get_object(Bucket=bucket_name, Key=object_name)
|
||||||
return response['Body'].read()
|
return response["Body"].read()
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
print(f"Error downloading from S3: {e}")
|
print(f"Error downloading from S3: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def list_objects_in_s3(bucket_name):
|
def list_objects_in_s3(bucket_name):
|
||||||
"""List all objects in S3 bucket (sorted from newest to oldest)"""
|
"""List all objects in S3 bucket (sorted from newest to oldest)"""
|
||||||
s3_client = get_s3_client()
|
s3_client = get_s3_client()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = s3_client.list_objects_v2(Bucket=bucket_name)
|
response = s3_client.list_objects_v2(Bucket=bucket_name)
|
||||||
if 'Contents' in response:
|
if "Contents" in response:
|
||||||
# Filter for PDF files only and sort by last modified (newest first)
|
# Filter for PDF files only and sort by last modified (newest first)
|
||||||
pdf_files = [obj for obj in response['Contents'] if obj['Key'].endswith('.pdf')]
|
pdf_files = [
|
||||||
pdf_files.sort(key=lambda x: x['LastModified'], reverse=True)
|
obj for obj in response["Contents"] if obj["Key"].endswith(".pdf")
|
||||||
|
]
|
||||||
|
pdf_files.sort(key=lambda x: x["LastModified"], reverse=True)
|
||||||
return pdf_files
|
return pdf_files
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
@@ -110,6 +118,7 @@ def list_objects_in_s3(bucket_name):
|
|||||||
print(f"Error listing objects in S3: {e}")
|
print(f"Error listing objects in S3: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def delete_from_s3(bucket_name, object_name):
|
def delete_from_s3(bucket_name, object_name):
|
||||||
"""Delete file from S3 bucket"""
|
"""Delete file from S3 bucket"""
|
||||||
s3_client = get_s3_client()
|
s3_client = get_s3_client()
|
||||||
@@ -121,26 +130,42 @@ def delete_from_s3(bucket_name, object_name):
|
|||||||
print(f"Error deleting from S3: {e}")
|
print(f"Error deleting from S3: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class MathExercisesPDF(FPDF):
|
class MathExercisesPDF(FPDF):
|
||||||
def header(self):
|
def header(self):
|
||||||
self.set_font('Helvetica', 'B', 16)
|
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.cell(
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
"Exercices de Multiplication et Division",
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
"C",
|
||||||
|
new_x="LMARGIN",
|
||||||
|
new_y="NEXT",
|
||||||
|
)
|
||||||
self.ln(10)
|
self.ln(10)
|
||||||
|
|
||||||
def footer(self):
|
def footer(self):
|
||||||
self.set_y(-15)
|
self.set_y(-15)
|
||||||
self.set_font('Helvetica', 'I', 8)
|
self.set_font("Helvetica", "I", 8)
|
||||||
self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C', new_x="RIGHT", new_y="TOP")
|
self.cell(
|
||||||
|
0, 10, f"Page {self.page_no()}", 0, 0, "C", new_x="RIGHT", new_y="TOP"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ExerciseRequest(BaseModel):
|
class ExerciseRequest(BaseModel):
|
||||||
min_table: int
|
min_table: int
|
||||||
max_table: int
|
max_table: int
|
||||||
num_exercises: int = 15
|
num_exercises: int = 15
|
||||||
|
|
||||||
def generate_exercises(min_table: int, max_table: int, num_exercises: int = 15) -> List[str]:
|
|
||||||
|
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"""
|
"""Génère des exercices de multiplication et division aléatoires mélangés sans doublons"""
|
||||||
exercises = []
|
exercises: List[str] = []
|
||||||
used_operations = set() # Pour éviter les doublons
|
used_operations: set = set() # Pour éviter les doublons
|
||||||
|
|
||||||
# Générer le nombre exact d'exercices demandé
|
# Générer le nombre exact d'exercices demandé
|
||||||
attempts = 0
|
attempts = 0
|
||||||
@@ -155,9 +180,9 @@ def generate_exercises(min_table: int, max_table: int, num_exercises: int = 15)
|
|||||||
result = a * b
|
result = a * b
|
||||||
|
|
||||||
# Choisir aléatoirement le type d'exercice (seulement multiplication ou division)
|
# Choisir aléatoirement le type d'exercice (seulement multiplication ou division)
|
||||||
exercise_type = random.choice(['multiplication', 'division'])
|
exercise_type = random.choice(["multiplication", "division"])
|
||||||
|
|
||||||
if exercise_type == 'multiplication':
|
if exercise_type == "multiplication":
|
||||||
# Exercice de multiplication
|
# Exercice de multiplication
|
||||||
operation_key = f"mult_{a}_{b}" # Clé unique pour cette opération
|
operation_key = f"mult_{a}_{b}" # Clé unique pour cette opération
|
||||||
if operation_key not in used_operations:
|
if operation_key not in used_operations:
|
||||||
@@ -184,9 +209,9 @@ def generate_exercises(min_table: int, max_table: int, num_exercises: int = 15)
|
|||||||
result = a * b
|
result = a * b
|
||||||
|
|
||||||
# Choisir aléatoirement le type d'exercice
|
# Choisir aléatoirement le type d'exercice
|
||||||
exercise_type = random.choice(['multiplication', 'division'])
|
exercise_type = random.choice(["multiplication", "division"])
|
||||||
|
|
||||||
if exercise_type == 'multiplication':
|
if exercise_type == "multiplication":
|
||||||
exercise = f"{a} · {b} = ____"
|
exercise = f"{a} · {b} = ____"
|
||||||
else: # division
|
else: # division
|
||||||
divisor = random.choice([a, b])
|
divisor = random.choice([a, b])
|
||||||
@@ -196,7 +221,10 @@ def generate_exercises(min_table: int, max_table: int, num_exercises: int = 15)
|
|||||||
|
|
||||||
return exercises
|
return exercises
|
||||||
|
|
||||||
def create_math_exercises_pdf(min_table: int, max_table: int, num_exercises: int = 15) -> str:
|
|
||||||
|
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 et l'upload sur S3"""
|
"""Crée un fichier PDF avec des exercices de mathématiques mélangés en 3 colonnes et l'upload sur S3"""
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
@@ -205,17 +233,19 @@ def create_math_exercises_pdf(min_table: int, max_table: int, num_exercises: int
|
|||||||
|
|
||||||
# Ajouter des informations sur la plage de tables
|
# Ajouter des informations sur la plage de tables
|
||||||
if min_table == max_table:
|
if min_table == max_table:
|
||||||
table_info = f'Tables de multiplication et division pour {min_table}'
|
table_info = f"Tables de multiplication et division pour {min_table}"
|
||||||
pdf_filename = f'exercices_mathematiques_table_{min_table}_{num_exercises}_exercices_{timestamp}.pdf'
|
pdf_filename = f"exercices_mathematiques_table_{min_table}_{num_exercises}_exercices_{timestamp}.pdf"
|
||||||
else:
|
else:
|
||||||
table_info = f'Tables de multiplication et division de {min_table} à {max_table}'
|
table_info = (
|
||||||
pdf_filename = f'exercices_mathematiques_tables_{min_table}_a_{max_table}_{num_exercises}_exercices_{timestamp}.pdf'
|
f"Tables de multiplication et division de {min_table} à {max_table}"
|
||||||
|
)
|
||||||
|
pdf_filename = f"exercices_mathematiques_tables_{min_table}_a_{max_table}_{num_exercises}_exercices_{timestamp}.pdf"
|
||||||
|
|
||||||
pdf = MathExercisesPDF()
|
pdf = MathExercisesPDF()
|
||||||
pdf.add_page()
|
pdf.add_page()
|
||||||
pdf.set_font('Helvetica', '', 12)
|
pdf.set_font("Helvetica", "", 12)
|
||||||
|
|
||||||
pdf.cell(0, 10, table_info, 0, 1, 'C', new_x="LMARGIN", new_y="NEXT")
|
pdf.cell(0, 10, table_info, 0, 1, "C", new_x="LMARGIN", new_y="NEXT")
|
||||||
pdf.ln(5)
|
pdf.ln(5)
|
||||||
|
|
||||||
# Générer les exercices
|
# Générer les exercices
|
||||||
@@ -255,9 +285,9 @@ def create_math_exercises_pdf(min_table: int, max_table: int, num_exercises: int
|
|||||||
col3_text = ""
|
col3_text = ""
|
||||||
|
|
||||||
# Ajouter la ligne
|
# Ajouter la ligne
|
||||||
pdf.cell(60, 10, col1_text, 0, 0, 'L', new_x="RIGHT", new_y="TOP")
|
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, 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")
|
pdf.cell(60, 10, col3_text, 0, 1, "L", new_x="LMARGIN", new_y="NEXT")
|
||||||
|
|
||||||
# Sauvegarder le PDF en mémoire
|
# Sauvegarder le PDF en mémoire
|
||||||
pdf_data = pdf.output()
|
pdf_data = pdf.output()
|
||||||
@@ -270,11 +300,11 @@ def create_math_exercises_pdf(min_table: int, max_table: int, num_exercises: int
|
|||||||
|
|
||||||
return pdf_filename
|
return pdf_filename
|
||||||
|
|
||||||
|
|
||||||
@app.get("/", response_class=HTMLResponse)
|
@app.get("/", response_class=HTMLResponse)
|
||||||
async def read_root(request: Request):
|
async def read_root(request: Request):
|
||||||
return templates.TemplateResponse("index.html", {"request": request})
|
return templates.TemplateResponse("index.html", {"request": request})
|
||||||
|
|
||||||
from fastapi.responses import RedirectResponse
|
|
||||||
|
|
||||||
@app.post("/generate")
|
@app.post("/generate")
|
||||||
async def generate_exercises_endpoint(request: ExerciseRequest):
|
async def generate_exercises_endpoint(request: ExerciseRequest):
|
||||||
@@ -282,14 +312,14 @@ async def generate_exercises_endpoint(request: ExerciseRequest):
|
|||||||
if request.min_table < 1 or request.max_table < 1:
|
if request.min_table < 1 or request.max_table < 1:
|
||||||
return {"error": "Les tables doivent être supérieures à 0"}
|
return {"error": "Les tables doivent être supérieures à 0"}
|
||||||
if request.min_table > request.max_table:
|
if request.min_table > request.max_table:
|
||||||
return {"error": "La table minimale doit être inférieure ou égale à la table maximale"}
|
return {
|
||||||
|
"error": "La table minimale doit être inférieure ou égale à la table maximale"
|
||||||
|
}
|
||||||
if request.num_exercises < 1:
|
if request.num_exercises < 1:
|
||||||
return {"error": "Le nombre d'exercices doit être supérieur à 0"}
|
return {"error": "Le nombre d'exercices doit être supérieur à 0"}
|
||||||
|
|
||||||
pdf_filename = create_math_exercises_pdf(
|
pdf_filename = create_math_exercises_pdf(
|
||||||
request.min_table,
|
request.min_table, request.max_table, request.num_exercises
|
||||||
request.max_table,
|
|
||||||
request.num_exercises
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return redirect to automatically download the file
|
# Return redirect to automatically download the file
|
||||||
@@ -298,6 +328,7 @@ async def generate_exercises_endpoint(request: ExerciseRequest):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"error": f"Erreur lors de la création du PDF: {str(e)}"}
|
return {"error": f"Erreur lors de la création du PDF: {str(e)}"}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/download/{filename}")
|
@app.get("/download/{filename}")
|
||||||
async def download_pdf(filename: str):
|
async def download_pdf(filename: str):
|
||||||
# Download file from S3
|
# Download file from S3
|
||||||
@@ -309,10 +340,11 @@ async def download_pdf(filename: str):
|
|||||||
# Return streaming response with PDF data
|
# Return streaming response with PDF data
|
||||||
return StreamingResponse(
|
return StreamingResponse(
|
||||||
io.BytesIO(file_data),
|
io.BytesIO(file_data),
|
||||||
media_type='application/pdf',
|
media_type="application/pdf",
|
||||||
headers={'Content-Disposition': f'attachment; filename="{filename}"'}
|
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/list")
|
@app.get("/list")
|
||||||
async def list_pdfs(page: int = 1, page_size: int = 10):
|
async def list_pdfs(page: int = 1, page_size: int = 10):
|
||||||
"""List PDF files in the S3 bucket with pagination (sorted from newest to oldest)"""
|
"""List PDF files in the S3 bucket with pagination (sorted from newest to oldest)"""
|
||||||
@@ -342,33 +374,37 @@ async def list_pdfs(page: int = 1, page_size: int = 10):
|
|||||||
"total_files": total_files,
|
"total_files": total_files,
|
||||||
"total_pages": total_pages,
|
"total_pages": total_pages,
|
||||||
"has_next": page < total_pages,
|
"has_next": page < total_pages,
|
||||||
"has_prev": page > 1
|
"has_prev": page > 1,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"error": f"Error listing files: {str(e)}"}
|
return {"error": f"Error listing files: {str(e)}"}
|
||||||
|
|
||||||
|
|
||||||
@app.delete("/delete/{filename}")
|
@app.delete("/delete/{filename}")
|
||||||
async def delete_pdf(filename: str):
|
async def delete_pdf(filename: str):
|
||||||
"""Delete a PDF file from S3 bucket"""
|
"""Delete a PDF file from S3 bucket"""
|
||||||
try:
|
try:
|
||||||
# Decode URL encoded filename
|
# Decode URL encoded filename
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
decoded_filename = urllib.parse.unquote(filename)
|
decoded_filename = urllib.parse.unquote(filename)
|
||||||
|
|
||||||
success = delete_from_s3(S3_BUCKET_NAME, decoded_filename)
|
success = delete_from_s3(S3_BUCKET_NAME, decoded_filename)
|
||||||
if success:
|
if success:
|
||||||
return {"message": f"Fichier {decoded_filename} supprimé avec succès"}
|
return {"message": f"Fichier {decoded_filename} supprimé avec succès"}
|
||||||
else:
|
else:
|
||||||
return {"error": f"Erreur lors de la suppression du fichier {decoded_filename}"}
|
return {
|
||||||
|
"error": f"Erreur lors de la suppression du fichier {decoded_filename}"
|
||||||
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"error": f"Error deleting file: {str(e)}"}
|
return {"error": f"Error deleting file: {str(e)}"}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/bulk-delete")
|
@app.post("/bulk-delete")
|
||||||
async def bulk_delete(filenames: str = Form(...)):
|
async def bulk_delete(filenames: str = Form(...)):
|
||||||
"""Delete multiple PDF files from S3 bucket"""
|
"""Delete multiple PDF files from S3 bucket"""
|
||||||
try:
|
try:
|
||||||
import json
|
|
||||||
filename_list = json.loads(filenames)
|
filename_list = json.loads(filenames)
|
||||||
deleted_files = []
|
deleted_files = []
|
||||||
errors = []
|
errors = []
|
||||||
@@ -381,51 +417,54 @@ async def bulk_delete(filenames: str = Form(...)):
|
|||||||
errors.append(filename)
|
errors.append(filename)
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
return {"message": f"Fichiers supprimés: {len(deleted_files)}", "errors": errors}
|
return {
|
||||||
|
"message": f"Fichiers supprimés: {len(deleted_files)}",
|
||||||
|
"errors": errors,
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
return {"message": f"{len(deleted_files)} fichiers supprimés avec succès"}
|
return {"message": f"{len(deleted_files)} fichiers supprimés avec succès"}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"error": f"Error deleting files: {str(e)}"}
|
return {"error": f"Error deleting files: {str(e)}"}
|
||||||
|
|
||||||
from fastapi import Form
|
|
||||||
|
|
||||||
@app.post("/bulk-download")
|
@app.post("/bulk-download")
|
||||||
async def bulk_download(filenames: str = Form(...)):
|
async def bulk_download(filenames: str = Form(...)):
|
||||||
"""Download multiple PDF files as a zip archive"""
|
"""Download multiple PDF files as a zip archive"""
|
||||||
try:
|
try:
|
||||||
import zipfile
|
|
||||||
import tempfile
|
|
||||||
import json
|
|
||||||
|
|
||||||
# Parse the JSON string to get the list of filenames
|
# Parse the JSON string to get the list of filenames
|
||||||
filename_list = json.loads(filenames)
|
filename_list = json.loads(filenames)
|
||||||
|
|
||||||
# Create a temporary zip file
|
# Create a temporary zip file
|
||||||
with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as tmp_zip:
|
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_zip:
|
||||||
with zipfile.ZipFile(tmp_zip.name, 'w') as zipf:
|
with zipfile.ZipFile(tmp_zip.name, "w") as zipf:
|
||||||
for filename in filename_list:
|
for filename in filename_list:
|
||||||
file_data = download_from_s3(S3_BUCKET_NAME, filename)
|
file_data = download_from_s3(S3_BUCKET_NAME, filename)
|
||||||
if file_data:
|
if file_data:
|
||||||
zipf.writestr(filename, file_data)
|
zipf.writestr(filename, file_data)
|
||||||
|
|
||||||
# Read the zip file
|
# Read the zip file
|
||||||
with open(tmp_zip.name, 'rb') as f:
|
with open(tmp_zip.name, "rb") as f:
|
||||||
zip_data = f.read()
|
zip_data = f.read()
|
||||||
|
|
||||||
# Clean up temporary file
|
# Clean up temporary file
|
||||||
import os
|
import os
|
||||||
|
|
||||||
os.unlink(tmp_zip.name)
|
os.unlink(tmp_zip.name)
|
||||||
|
|
||||||
# Return streaming response with zip data
|
# Return streaming response with zip data
|
||||||
zip_filename = f"math_exercises_{len(filename_list)}_files.zip"
|
zip_filename = f"math_exercises_{len(filename_list)}_files.zip"
|
||||||
return StreamingResponse(
|
return StreamingResponse(
|
||||||
io.BytesIO(zip_data),
|
io.BytesIO(zip_data),
|
||||||
media_type='application/zip',
|
media_type="application/zip",
|
||||||
headers={'Content-Disposition': f'attachment; filename="{zip_filename}"'}
|
headers={
|
||||||
|
"Content-Disposition": f'attachment; filename="{zip_filename}"'
|
||||||
|
},
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"error": f"Error downloading files: {str(e)}"}
|
return {"error": f"Error downloading files: {str(e)}"}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
+57
-24
@@ -5,16 +5,29 @@ import subprocess
|
|||||||
import os
|
import os
|
||||||
from fpdf import FPDF
|
from fpdf import FPDF
|
||||||
|
|
||||||
|
|
||||||
class MathExercisesPDF(FPDF):
|
class MathExercisesPDF(FPDF):
|
||||||
def header(self):
|
def header(self):
|
||||||
self.set_font('Helvetica', 'B', 16)
|
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.cell(
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
"Exercices de Multiplication et Division",
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
"C",
|
||||||
|
new_x="LMARGIN",
|
||||||
|
new_y="NEXT",
|
||||||
|
)
|
||||||
self.ln(10)
|
self.ln(10)
|
||||||
|
|
||||||
def footer(self):
|
def footer(self):
|
||||||
self.set_y(-15)
|
self.set_y(-15)
|
||||||
self.set_font('Helvetica', 'I', 8)
|
self.set_font("Helvetica", "I", 8)
|
||||||
self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C', new_x="RIGHT", new_y="TOP")
|
self.cell(
|
||||||
|
0, 10, f"Page {self.page_no()}", 0, 0, "C", new_x="RIGHT", new_y="TOP"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def generate_exercises(min_table, max_table, num_exercises=15):
|
def generate_exercises(min_table, max_table, num_exercises=15):
|
||||||
"""Génère des exercices de multiplication et division aléatoires mélangés sans doublons"""
|
"""Génère des exercices de multiplication et division aléatoires mélangés sans doublons"""
|
||||||
@@ -34,9 +47,9 @@ def generate_exercises(min_table, max_table, num_exercises=15):
|
|||||||
result = a * b
|
result = a * b
|
||||||
|
|
||||||
# Choisir aléatoirement le type d'exercice (seulement multiplication ou division)
|
# Choisir aléatoirement le type d'exercice (seulement multiplication ou division)
|
||||||
exercise_type = random.choice(['multiplication', 'division'])
|
exercise_type = random.choice(["multiplication", "division"])
|
||||||
|
|
||||||
if exercise_type == 'multiplication':
|
if exercise_type == "multiplication":
|
||||||
# Exercice de multiplication
|
# Exercice de multiplication
|
||||||
operation_key = f"mult_{a}_{b}" # Clé unique pour cette opération
|
operation_key = f"mult_{a}_{b}" # Clé unique pour cette opération
|
||||||
if operation_key not in used_operations:
|
if operation_key not in used_operations:
|
||||||
@@ -63,9 +76,9 @@ def generate_exercises(min_table, max_table, num_exercises=15):
|
|||||||
result = a * b
|
result = a * b
|
||||||
|
|
||||||
# Choisir aléatoirement le type d'exercice
|
# Choisir aléatoirement le type d'exercice
|
||||||
exercise_type = random.choice(['multiplication', 'division'])
|
exercise_type = random.choice(["multiplication", "division"])
|
||||||
|
|
||||||
if exercise_type == 'multiplication':
|
if exercise_type == "multiplication":
|
||||||
exercise = f"{a} · {b} = ____"
|
exercise = f"{a} · {b} = ____"
|
||||||
else: # division
|
else: # division
|
||||||
divisor = random.choice([a, b])
|
divisor = random.choice([a, b])
|
||||||
@@ -75,19 +88,22 @@ def generate_exercises(min_table, max_table, num_exercises=15):
|
|||||||
|
|
||||||
return exercises
|
return exercises
|
||||||
|
|
||||||
|
|
||||||
def create_math_exercises_pdf(min_table, max_table, num_exercises=15):
|
def create_math_exercises_pdf(min_table, max_table, num_exercises=15):
|
||||||
"""Crée un fichier PDF avec des exercices de mathématiques mélangés en 3 colonnes"""
|
"""Crée un fichier PDF avec des exercices de mathématiques mélangés en 3 colonnes"""
|
||||||
pdf = MathExercisesPDF()
|
pdf = MathExercisesPDF()
|
||||||
pdf.add_page()
|
pdf.add_page()
|
||||||
pdf.set_font('Helvetica', '', 12)
|
pdf.set_font("Helvetica", "", 12)
|
||||||
|
|
||||||
# Ajouter des informations sur la plage de tables
|
# Ajouter des informations sur la plage de tables
|
||||||
if min_table == max_table:
|
if min_table == max_table:
|
||||||
table_info = f'Tables de multiplication et division pour {min_table}'
|
table_info = f"Tables de multiplication et division pour {min_table}"
|
||||||
else:
|
else:
|
||||||
table_info = f'Tables de multiplication et division de {min_table} à {max_table}'
|
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.cell(0, 10, table_info, 0, 1, "C", new_x="LMARGIN", new_y="NEXT")
|
||||||
pdf.ln(5)
|
pdf.ln(5)
|
||||||
|
|
||||||
# Générer les exercices
|
# Générer les exercices
|
||||||
@@ -127,18 +143,21 @@ def create_math_exercises_pdf(min_table, max_table, num_exercises=15):
|
|||||||
col3_text = ""
|
col3_text = ""
|
||||||
|
|
||||||
# Ajouter la ligne
|
# Ajouter la ligne
|
||||||
pdf.cell(60, 10, col1_text, 0, 0, 'L', new_x="RIGHT", new_y="TOP")
|
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, 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")
|
pdf.cell(60, 10, col3_text, 0, 1, "L", new_x="LMARGIN", new_y="NEXT")
|
||||||
|
|
||||||
# Sauvegarder le PDF
|
# Sauvegarder le PDF
|
||||||
if min_table == max_table:
|
if min_table == max_table:
|
||||||
filename = f'exercices_mathematiques_table_{min_table}_{num_exercises}_exercices.pdf'
|
filename = (
|
||||||
|
f"exercices_mathematiques_table_{min_table}_{num_exercises}_exercices.pdf"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
filename = f'exercices_mathematiques_tables_{min_table}_a_{max_table}_{num_exercises}_exercices.pdf'
|
filename = f"exercices_mathematiques_tables_{min_table}_a_{max_table}_{num_exercises}_exercices.pdf"
|
||||||
pdf.output(filename)
|
pdf.output(filename)
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
|
||||||
def open_pdf(filename):
|
def open_pdf(filename):
|
||||||
"""Ouvre le fichier PDF avec la commande système appropriée"""
|
"""Ouvre le fichier PDF avec la commande système appropriée"""
|
||||||
try:
|
try:
|
||||||
@@ -152,14 +171,21 @@ def open_pdf(filename):
|
|||||||
except (subprocess.CalledProcessError, FileNotFoundError, OSError):
|
except (subprocess.CalledProcessError, FileNotFoundError, OSError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Fonction principale"""
|
"""Fonction principale"""
|
||||||
# Vérifier les arguments de ligne de commande
|
# Vérifier les arguments de ligne de commande
|
||||||
if len(sys.argv) < 3:
|
if len(sys.argv) < 3:
|
||||||
print("Usage: python generate_math_exercises.py <min_table> <max_table> [num_exercises] [--open]")
|
print(
|
||||||
|
"Usage: python generate_math_exercises.py <min_table> <max_table> [num_exercises] [--open]"
|
||||||
|
)
|
||||||
print("Exemple: python generate_math_exercises.py 4 7 15")
|
print("Exemple: python generate_math_exercises.py 4 7 15")
|
||||||
print(" python generate_math_exercises.py 4 7 15 --open (pour ouvrir automatiquement le PDF)")
|
print(
|
||||||
print(" python generate_math_exercises.py 5 5 10 (pour seulement la table de 5)")
|
" python generate_math_exercises.py 4 7 15 --open (pour ouvrir automatiquement le PDF)"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
" python generate_math_exercises.py 5 5 10 (pour seulement la table de 5)"
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Vérifier si l'option --open est présente
|
# Vérifier si l'option --open est présente
|
||||||
@@ -173,7 +199,9 @@ def main():
|
|||||||
if min_table < 1 or max_table < 1:
|
if min_table < 1 or max_table < 1:
|
||||||
raise ValueError("Les tables doivent être supérieures à 0")
|
raise ValueError("Les tables doivent être supérieures à 0")
|
||||||
if min_table > max_table:
|
if min_table > max_table:
|
||||||
raise ValueError("La table minimale doit être inférieure ou égale à la table maximale")
|
raise ValueError(
|
||||||
|
"La table minimale doit être inférieure ou égale à la table maximale"
|
||||||
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print(f"Erreur: {e}")
|
print(f"Erreur: {e}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -197,7 +225,9 @@ def main():
|
|||||||
print(f"- Exercices pour la table de {min_table}")
|
print(f"- Exercices pour la table de {min_table}")
|
||||||
else:
|
else:
|
||||||
print(f"- Exercices pour les tables de {min_table} à {max_table}")
|
print(f"- Exercices pour les tables de {min_table} à {max_table}")
|
||||||
print(f"- {num_exercises} exercices MÉLANGÉS générés aléatoirement et numérotés de 1 à {num_exercises}")
|
print(
|
||||||
|
f"- {num_exercises} exercices MÉLANGÉS générés aléatoirement et numérotés de 1 à {num_exercises}"
|
||||||
|
)
|
||||||
print("- Types d'exercices : multiplications (·) et divisions (:) uniquement")
|
print("- Types d'exercices : multiplications (·) et divisions (:) uniquement")
|
||||||
print("- Présentés en 3 colonnes pour économiser l'espace")
|
print("- Présentés en 3 colonnes pour économiser l'espace")
|
||||||
print("- Aucune opération en double (quand c'est possible)")
|
print("- Aucune opération en double (quand c'est possible)")
|
||||||
@@ -205,13 +235,16 @@ def main():
|
|||||||
# Ouvrir le PDF si demandé
|
# Ouvrir le PDF si demandé
|
||||||
if open_after_creation:
|
if open_after_creation:
|
||||||
if open_pdf(filename):
|
if open_pdf(filename):
|
||||||
print(f"\nPDF ouvert automatiquement avec succès !")
|
print("\nPDF ouvert automatiquement avec succès !")
|
||||||
else:
|
else:
|
||||||
print(f"\nImpossible d'ouvrir automatiquement le PDF. Vous pouvez l'ouvrir manuellement.")
|
print(
|
||||||
|
"\nImpossible d'ouvrir automatiquement le PDF. Vous pouvez l'ouvrir manuellement."
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Erreur lors de la création du PDF: {e}")
|
print(f"Erreur lors de la création du PDF: {e}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
Reference in New Issue
Block a user