+ {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} - {% for category, message in messages %} -
- {{ message }} -
- {% endfor %} +
+ {% for category, message in messages %} + + {% endfor %} +
{% endif %} {% endwith %} diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 0000000..04914d2 --- /dev/null +++ b/templates/error.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} + +{% block title %}Erreur - Gestion Scolaire{% endblock %} + +{% block content %} +
+
+
+ + + +
+

Une erreur s'est produite

+

{{ error or "Une erreur inattendue s'est produite." }}

+ + Retour à l'accueil + +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/exercise_detail.html b/templates/exercise_detail.html index d26b0af..8b6e079 100644 --- a/templates/exercise_detail.html +++ b/templates/exercise_detail.html @@ -13,18 +13,12 @@

{{ assessment.title }} - {{ assessment.class_group.name }}

- -
@@ -54,8 +48,8 @@
@@ -83,13 +77,9 @@
- - Modifier - - - + + Utilisez "Modifier l'évaluation complète" pour modifier +
@@ -103,8 +93,8 @@

Aucun élément de notation

Commencez par ajouter le premier élément de notation pour cet exercice.

diff --git a/templates/exercise_form.html b/templates/exercise_form.html deleted file mode 100644 index 3d80c34..0000000 --- a/templates/exercise_form.html +++ /dev/null @@ -1,74 +0,0 @@ -{% extends "base.html" %} - -{% block title %}{{ title }} - Gestion Scolaire{% endblock %} - -{% block content %} -
- - -
-
-

{{ title }}

-

Évaluation : {{ assessment.title }} ({{ assessment.class_group.name }})

-
- -
- {{ form.hidden_tag() }} - -
- - {{ form.title(class="block w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500") }} - {% if form.title.errors %} -
- {% for error in form.title.errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -
- -
- - {{ form.description(class="block w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500", rows="3") }} - {% if form.description.errors %} -
- {% for error in form.description.errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -
- -
- - {{ form.order(class="block w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500") }} - {% if form.order.errors %} -
- {% for error in form.order.errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -

L'ordre détermine l'affichage des exercices dans l'évaluation

-
- -
- - Annuler - - {{ form.submit(class="px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors") }} -
-
-
-
-{% endblock %} \ No newline at end of file diff --git a/templates/grading_element_form.html b/templates/grading_element_form.html deleted file mode 100644 index b42819e..0000000 --- a/templates/grading_element_form.html +++ /dev/null @@ -1,114 +0,0 @@ -{% extends "base.html" %} - -{% block title %}{{ title }} - Gestion Scolaire{% endblock %} - -{% block content %} -
- - -
-
-

{{ title }}

-

{{ assessment.title }} > {{ exercise.title }}

-
- -
- {{ form.hidden_tag() }} - -
- - {{ form.label(class="block w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500") }} - {% if form.label.errors %} -
- {% for error in form.label.errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -

Ex: "Calcul de base", "Méthode", "Présentation"

-
- -
- - {{ form.description(class="block w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500", rows="3") }} - {% if form.description.errors %} -
- {% for error in form.description.errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -
- -
- - {{ form.skill(class="block w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500") }} - {% if form.skill.errors %} -
- {% for error in form.skill.errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -

Ex: "Calculer", "Raisonner", "Communiquer"

-
- -
-
- - {{ form.max_points(class="block w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500", step="0.1") }} - {% if form.max_points.errors %} -
- {% for error in form.max_points.errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -
- -
- - {{ form.grading_type(class="block w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500") }} - {% if form.grading_type.errors %} -
- {% for error in form.grading_type.errors %} -

{{ error }}

- {% endfor %} -
- {% endif %} -
-
- - -
-

Types de notation

-
-

Points : Notation classique (ex: 2.5/4 points)

-

Score : Évaluation par niveaux (0=non acquis, 1=en cours, 2=acquis, 3=expert, .=non évalué)

-
-
- -
- - Annuler - - {{ form.submit(class="px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors") }} -
-
-
-
-{% endblock %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 79d64f9..78be5c8 100644 --- a/templates/index.html +++ b/templates/index.html @@ -114,7 +114,7 @@
Gérer les élèves
- +
diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..41c9c77 --- /dev/null +++ b/utils.py @@ -0,0 +1,94 @@ +from functools import wraps +from flask import current_app, flash, jsonify, request, render_template, redirect, url_for +from models import db +from sqlalchemy.exc import SQLAlchemyError, IntegrityError +from decimal import Decimal, InvalidOperation +import logging + +def handle_db_errors(f): + """Décorateur pour gérer les erreurs de base de données""" + @wraps(f) + def decorated_function(*args, **kwargs): + try: + result = f(*args, **kwargs) + # Vérifier que le résultat est une réponse Flask valide + if result is None: + current_app.logger.error(f'Fonction {f.__name__} a retourné None') + return render_template('error.html', error="Une erreur inattendue s'est produite."), 500 + return result + except IntegrityError as e: + db.session.rollback() + current_app.logger.error(f'Erreur d\'intégrité dans {f.__name__}: {e}') + + error_msg = "Une erreur s'est produite lors de l'enregistrement." + if "UNIQUE constraint failed" in str(e): + error_msg = "Cette donnée existe déjà." + elif "CHECK constraint failed" in str(e): + error_msg = "Les données saisies ne respectent pas les contraintes." + + if request.is_json: + return jsonify({'success': False, 'error': error_msg}), 400 + else: + flash(error_msg, 'error') + return render_template('error.html', error=error_msg), 400 + + except SQLAlchemyError as e: + db.session.rollback() + current_app.logger.error(f'Erreur SQLAlchemy dans {f.__name__}: {e}') + + error_msg = "Une erreur de base de données s'est produite." + if request.is_json: + return jsonify({'success': False, 'error': error_msg}), 500 + else: + flash(error_msg, 'error') + return render_template('error.html', error=error_msg), 500 + + except Exception as e: + db.session.rollback() + current_app.logger.error(f'Erreur inattendue dans {f.__name__}: {e}') + + error_msg = "Une erreur inattendue s'est produite." + if request.is_json: + return jsonify({'success': False, 'error': error_msg}), 500 + else: + flash(error_msg, 'error') + return render_template('error.html', error=error_msg), 500 + + return decorated_function + +def safe_int_conversion(value, field_name="valeur"): + """Conversion sécurisée en entier""" + if value is None: + return None + try: + return int(value) + except (ValueError, TypeError): + raise ValueError(f"La {field_name} doit être un nombre entier valide.") + +def safe_decimal_conversion(value, field_name="valeur"): + """Conversion sécurisée en décimal""" + if value is None: + return None + try: + return Decimal(str(value)) + except (ValueError, TypeError, InvalidOperation): + raise ValueError(f"La {field_name} doit être un nombre décimal valide.") + +def validate_json_data(data, required_fields): + """Valide les données JSON et vérifie les champs requis""" + if not data: + raise ValueError("Aucune donnée fournie.") + + missing_fields = [field for field in required_fields if field not in data or data[field] is None] + if missing_fields: + raise ValueError(f"Champs requis manquants: {', '.join(missing_fields)}") + + return True + +def log_user_action(action, details=None): + """Log les actions utilisateur pour audit""" + current_app.logger.info(f"Action utilisateur: {action}" + (f" - {details}" if details else "")) + +class ValidationError(Exception): + """Exception personnalisée pour les erreurs de validation""" + pass \ No newline at end of file