Compare commits
3 Commits
7284c60b93
...
edefce9703
| Author | SHA1 | Date | |
|---|---|---|---|
|
edefce9703
|
|||
|
eb6da3d9cf
|
|||
|
bc55b95bda
|
+18
-22
@@ -1,28 +1,25 @@
|
|||||||
# Use Python 3.13 slim image as base
|
# Use Python 3.13 slim image as base
|
||||||
FROM python:3.13-slim
|
FROM python:3.13-slim AS builder
|
||||||
|
|
||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Set environment variables
|
# Create and set pip cache directory
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
ENV PIP_CACHE_DIR=/root/.cache/pip
|
||||||
PYTHONUNBUFFERED=1 \
|
|
||||||
POETRY_NO_INTERACTION=1 \
|
|
||||||
POETRY_VENV_IN_PROJECT=1 \
|
|
||||||
POETRY_CACHE_DIR=/tmp/poetry_cache
|
|
||||||
|
|
||||||
# Install system dependencies
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
build-essential \
|
|
||||||
curl \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Copy requirements file
|
# Copy requirements file
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
|
|
||||||
# Install Python dependencies
|
# Install Python dependencies with bind-mounted cache
|
||||||
RUN pip install --no-cache-dir --upgrade pip && \
|
RUN --mount=type=cache,id=pip,target=/root/.cache/pip \
|
||||||
pip install --no-cache-dir -r requirements.txt
|
pip install --no-deps \
|
||||||
|
--disable-pip-version-check \
|
||||||
|
--target=/app/site-packages \
|
||||||
|
-r requirements.txt
|
||||||
|
|
||||||
|
FROM python:3.13-slim
|
||||||
|
|
||||||
|
COPY --from=builder /app/site-packages /app/site-packages
|
||||||
|
|
||||||
# Create a non-root user
|
# Create a non-root user
|
||||||
RUN adduser --disabled-password --gecos '' appuser
|
RUN adduser --disabled-password --gecos '' appuser
|
||||||
@@ -31,8 +28,11 @@ RUN adduser --disabled-password --gecos '' appuser
|
|||||||
COPY ./app /app/app
|
COPY ./app /app/app
|
||||||
COPY ./generate_math_exercises.py /app/
|
COPY ./generate_math_exercises.py /app/
|
||||||
|
|
||||||
# Change ownership of the app directory to the non-root user
|
ENV PYTHONPATH=/app/site-packages
|
||||||
RUN chown -R appuser:appuser /app
|
ENV PATH=/app/site-packages/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
# Switch to non-root user
|
# Switch to non-root user
|
||||||
USER appuser
|
USER appuser
|
||||||
@@ -40,9 +40,5 @@ USER appuser
|
|||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
# Health check
|
|
||||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
|
|
||||||
CMD curl -f http://localhost:8000/health || exit 1
|
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
+39
-17
@@ -167,6 +167,7 @@ class ExerciseRequest(BaseModel):
|
|||||||
min_table: int
|
min_table: int
|
||||||
max_table: int
|
max_table: int
|
||||||
num_exercises: int = 15
|
num_exercises: int = 15
|
||||||
|
multiplication_only: bool = False
|
||||||
|
|
||||||
|
|
||||||
class OperationExerciseRequest(BaseModel):
|
class OperationExerciseRequest(BaseModel):
|
||||||
@@ -174,7 +175,7 @@ class OperationExerciseRequest(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
def generate_exercises(
|
def generate_exercises(
|
||||||
min_table: int, max_table: int, num_exercises: int = 15
|
min_table: int, max_table: int, num_exercises: int = 15, multiplication_only: bool = False
|
||||||
) -> List[str]:
|
) -> 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: List[str] = []
|
exercises: List[str] = []
|
||||||
@@ -192,8 +193,12 @@ def generate_exercises(
|
|||||||
b = random.randint(min_table, max_table)
|
b = random.randint(min_table, max_table)
|
||||||
result = a * b
|
result = a * b
|
||||||
|
|
||||||
# Choisir aléatoirement le type d'exercice (seulement multiplication ou division)
|
# Déterminer le type d'exercice
|
||||||
exercise_type = random.choice(["multiplication", "division"])
|
if multiplication_only:
|
||||||
|
exercise_type = "multiplication"
|
||||||
|
else:
|
||||||
|
# Choisir aléatoirement le type d'exercice (seulement multiplication ou division)
|
||||||
|
exercise_type = random.choice(["multiplication", "division"])
|
||||||
|
|
||||||
if exercise_type == "multiplication":
|
if exercise_type == "multiplication":
|
||||||
# Exercice de multiplication
|
# Exercice de multiplication
|
||||||
@@ -202,7 +207,7 @@ def generate_exercises(
|
|||||||
exercise = f"{a} · {b} = ____"
|
exercise = f"{a} · {b} = ____"
|
||||||
exercises.append(exercise)
|
exercises.append(exercise)
|
||||||
used_operations.add(operation_key)
|
used_operations.add(operation_key)
|
||||||
else: # division
|
elif not multiplication_only: # division
|
||||||
# Exercice de division
|
# Exercice de division
|
||||||
divisor = random.choice([a, b])
|
divisor = random.choice([a, b])
|
||||||
operation_key = f"div_{result}_{divisor}" # Clé unique pour cette opération
|
operation_key = f"div_{result}_{divisor}" # Clé unique pour cette opération
|
||||||
@@ -221,14 +226,21 @@ def generate_exercises(
|
|||||||
b = random.randint(min_table, max_table)
|
b = random.randint(min_table, max_table)
|
||||||
result = a * b
|
result = a * b
|
||||||
|
|
||||||
# Choisir aléatoirement le type d'exercice
|
# Déterminer le type d'exercice
|
||||||
exercise_type = random.choice(["multiplication", "division"])
|
if multiplication_only:
|
||||||
|
exercise_type = "multiplication"
|
||||||
|
else:
|
||||||
|
# Choisir aléatoirement le type d'exercice
|
||||||
|
exercise_type = random.choice(["multiplication", "division"])
|
||||||
|
|
||||||
if exercise_type == "multiplication":
|
if exercise_type == "multiplication":
|
||||||
exercise = f"{a} · {b} = ____"
|
exercise = f"{a} · {b} = ____"
|
||||||
else: # division
|
elif not multiplication_only: # division
|
||||||
divisor = random.choice([a, b])
|
divisor = random.choice([a, b])
|
||||||
exercise = f"{result} : {divisor} = ____"
|
exercise = f"{result} : {divisor} = ____"
|
||||||
|
else:
|
||||||
|
# Fallback to multiplication if multiplication_only is True
|
||||||
|
exercise = f"{a} · {b} = ____"
|
||||||
|
|
||||||
exercises.append(exercise)
|
exercises.append(exercise)
|
||||||
|
|
||||||
@@ -236,7 +248,7 @@ def generate_exercises(
|
|||||||
|
|
||||||
|
|
||||||
def create_math_exercises_pdf(
|
def create_math_exercises_pdf(
|
||||||
min_table: int, max_table: int, num_exercises: int = 15
|
min_table: int, max_table: int, num_exercises: int = 15, multiplication_only: bool = False
|
||||||
) -> str:
|
) -> 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
|
||||||
@@ -245,14 +257,24 @@ def create_math_exercises_pdf(
|
|||||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
|
||||||
# Ajouter des informations sur la plage de tables
|
# Ajouter des informations sur la plage de tables
|
||||||
if min_table == max_table:
|
if multiplication_only:
|
||||||
table_info = f"Tables de multiplication et division pour {min_table}"
|
if min_table == max_table:
|
||||||
pdf_filename = f"exercices_mathematiques_table_{min_table}_{num_exercises}_exercices_{timestamp}.pdf"
|
table_info = f"Tables de multiplication seulement pour {min_table}"
|
||||||
|
pdf_filename = f"exercices_multiplication_seulement_table_{min_table}_{num_exercises}_exercices_{timestamp}.pdf"
|
||||||
|
else:
|
||||||
|
table_info = (
|
||||||
|
f"Tables de multiplication seulement de {min_table} à {max_table}"
|
||||||
|
)
|
||||||
|
pdf_filename = f"exercices_multiplication_seulement_tables_{min_table}_a_{max_table}_{num_exercises}_exercices_{timestamp}.pdf"
|
||||||
else:
|
else:
|
||||||
table_info = (
|
if min_table == max_table:
|
||||||
f"Tables de multiplication et division de {min_table} à {max_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_tables_{min_table}_a_{max_table}_{num_exercises}_exercices_{timestamp}.pdf"
|
else:
|
||||||
|
table_info = (
|
||||||
|
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()
|
||||||
@@ -262,7 +284,7 @@ def create_math_exercises_pdf(
|
|||||||
pdf.ln(5)
|
pdf.ln(5)
|
||||||
|
|
||||||
# Générer les exercices
|
# Générer les exercices
|
||||||
exercises = generate_exercises(min_table, max_table, num_exercises)
|
exercises = generate_exercises(min_table, max_table, num_exercises, multiplication_only)
|
||||||
|
|
||||||
# Pas d'en-têtes de colonnes
|
# Pas d'en-têtes de colonnes
|
||||||
|
|
||||||
@@ -354,7 +376,7 @@ async def generate_exercises_endpoint(request: ExerciseRequest):
|
|||||||
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.max_table, request.num_exercises
|
request.min_table, request.max_table, request.num_exercises, request.multiplication_only
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return redirect to automatically download the file
|
# Return redirect to automatically download the file
|
||||||
|
|||||||
@@ -61,6 +61,11 @@
|
|||||||
<div class="form-text">Nombre total d'exercices à générer (entre 1 et 100)</div>
|
<div class="form-text">Nombre total d'exercices à générer (entre 1 et 100)</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="multiplicationOnly">
|
||||||
|
<label class="form-check-label" for="multiplicationOnly">Générer uniquement des multiplications</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<button type="submit" class="btn btn-primary btn-lg" id="generateBtn">
|
<button type="submit" class="btn btn-primary btn-lg" id="generateBtn">
|
||||||
<span id="buttonText">Générer le PDF (Mult/Div)</span>
|
<span id="buttonText">Générer le PDF (Mult/Div)</span>
|
||||||
@@ -511,6 +516,7 @@
|
|||||||
const minTable = parseInt(document.getElementById('minTable').value);
|
const minTable = parseInt(document.getElementById('minTable').value);
|
||||||
const maxTable = parseInt(document.getElementById('maxTable').value);
|
const maxTable = parseInt(document.getElementById('maxTable').value);
|
||||||
const numExercises = parseInt(document.getElementById('numExercises').value);
|
const numExercises = parseInt(document.getElementById('numExercises').value);
|
||||||
|
const multiplicationOnly = document.getElementById('multiplicationOnly').checked;
|
||||||
|
|
||||||
const generateBtn = document.getElementById('generateBtn');
|
const generateBtn = document.getElementById('generateBtn');
|
||||||
const buttonText = document.getElementById('buttonText');
|
const buttonText = document.getElementById('buttonText');
|
||||||
@@ -530,7 +536,8 @@
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
min_table: minTable,
|
min_table: minTable,
|
||||||
max_table: maxTable,
|
max_table: maxTable,
|
||||||
num_exercises: numExercises
|
num_exercises: numExercises,
|
||||||
|
multiplication_only: multiplicationOnly
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -545,13 +552,14 @@
|
|||||||
// Show success message
|
// Show success message
|
||||||
const resultContainer = document.getElementById('resultContainer');
|
const resultContainer = document.getElementById('resultContainer');
|
||||||
const resultMessage = document.getElementById('resultMessage');
|
const resultMessage = document.getElementById('resultMessage');
|
||||||
|
const exerciseType = multiplicationOnly ? "multiplications uniquement" : "multiplications et divisions";
|
||||||
resultMessage.innerHTML = `
|
resultMessage.innerHTML = `
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success">
|
||||||
<h6>PDF généré avec succès!</h6>
|
<h6>PDF généré avec succès!</h6>
|
||||||
<p>Le téléchargement devrait commencer automatiquement.</p>
|
<p>Le téléchargement devrait commencer automatiquement.</p>
|
||||||
<ul class="mb-0">
|
<ul class="mb-0">
|
||||||
<li>Tables de ${minTable} à ${maxTable}</li>
|
<li>Tables de ${minTable} à ${maxTable}</li>
|
||||||
<li>${numExercises} exercices générés</li>
|
<li>${numExercises} exercices générés (${exerciseType})</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user