404 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			404 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from flask import Blueprint, render_template, request, flash, redirect, url_for, jsonify
 | |
| from app_config import config_manager
 | |
| from models import db, AppConfig, CompetenceScaleValue, Competence
 | |
| from utils import handle_error
 | |
| import logging
 | |
| 
 | |
| bp = Blueprint('config', __name__, url_prefix='/config')
 | |
| 
 | |
| @bp.route('/')
 | |
| def index():
 | |
|     """Page principale de configuration."""
 | |
|     try:
 | |
|         return render_template('config/index.html', app_config=config_manager)
 | |
|     except Exception as e:
 | |
|         return handle_error(e, "Erreur lors du chargement de la configuration")
 | |
| 
 | |
| @bp.route('/api/get')
 | |
| def api_get_config():
 | |
|     """API pour récupérer la configuration actuelle."""
 | |
|     try:
 | |
|         config_data = {
 | |
|             'context': {
 | |
|                 'school_year': config_manager.get('context.school_year')
 | |
|             },
 | |
|             'evaluations': {
 | |
|                 'default_grading_system': config_manager.get('evaluations.default_grading_system'),
 | |
|                 'competence_scale': config_manager.get_competence_scale_values(),
 | |
|                 'competences': config_manager.get_competences_list()
 | |
|             }
 | |
|         }
 | |
|         return jsonify(config_data)
 | |
|     except Exception as e:
 | |
|         logging.error(f"Erreur API configuration: {e}")
 | |
|         return jsonify({'error': 'Erreur lors de la récupération de la configuration'}), 500
 | |
| 
 | |
| @bp.route('/api/update', methods=['POST'])
 | |
| def api_update_config():
 | |
|     """API pour mettre à jour la configuration."""
 | |
|     try:
 | |
|         data = request.get_json()
 | |
|         
 | |
|         if not data:
 | |
|             return jsonify({'error': 'Données manquantes'}), 400
 | |
|         
 | |
|         # Mettre à jour la configuration
 | |
|         for section, values in data.items():
 | |
|             if isinstance(values, dict):
 | |
|                 for key, value in values.items():
 | |
|                     config_manager.set(f"{section}.{key}", value)
 | |
|             else:
 | |
|                 config_manager.set(section, values)
 | |
|         
 | |
|         # Sauvegarder
 | |
|         if config_manager.save():
 | |
|             flash('Configuration mise à jour avec succès', 'success')
 | |
|             return jsonify({'success': True, 'message': 'Configuration sauvegardée'})
 | |
|         else:
 | |
|             return jsonify({'error': 'Erreur lors de la sauvegarde'}), 500
 | |
|             
 | |
|     except Exception as e:
 | |
|         logging.error(f"Erreur mise à jour configuration: {e}")
 | |
|         return jsonify({'error': 'Erreur lors de la mise à jour'}), 500
 | |
| 
 | |
| @bp.route('/competences')
 | |
| def competences():
 | |
|     """Page de configuration des compétences."""
 | |
|     try:
 | |
|         competences_list = config_manager.get_competences_list()
 | |
|         competence_scale = config_manager.get_competence_scale_values()
 | |
|         return render_template('config/competences.html', 
 | |
|                              competences=competences_list,
 | |
|                              competence_scale=competence_scale)
 | |
|     except Exception as e:
 | |
|         return handle_error(e, "Erreur lors du chargement des compétences")
 | |
| 
 | |
| @bp.route('/competences/add', methods=['POST'])
 | |
| def add_competence():
 | |
|     """Ajouter une nouvelle compétence."""
 | |
|     try:
 | |
|         name = request.form.get('name')
 | |
|         color = request.form.get('color', '#3b82f6')
 | |
|         icon = request.form.get('icon', 'star')
 | |
|         
 | |
|         if not name:
 | |
|             flash('Le nom de la compétence est requis', 'error')
 | |
|             return redirect(url_for('config.competences'))
 | |
|         
 | |
|         # Validation de la couleur hexadécimale
 | |
|         import re
 | |
|         if not re.match(r'^#[0-9a-fA-F]{6}$', color):
 | |
|             flash('Format de couleur invalide', 'error')
 | |
|             return redirect(url_for('config.competences'))
 | |
|         
 | |
|         # Vérifier si le nom existe déjà
 | |
|         if Competence.query.filter_by(name=name).first():
 | |
|             flash(f'Une compétence "{name}" existe déjà', 'error')
 | |
|             return redirect(url_for('config.competences'))
 | |
|         
 | |
|         if config_manager.add_competence(name, color, icon):
 | |
|             flash(f'Compétence "{name}" ajoutée avec succès', 'success')
 | |
|         else:
 | |
|             flash('Erreur lors de la sauvegarde', 'error')
 | |
|             
 | |
|     except Exception as e:
 | |
|         logging.error(f"Erreur ajout compétence: {e}")
 | |
|         flash('Erreur lors de l\'ajout de la compétence', 'error')
 | |
|     
 | |
|     return redirect(url_for('config.competences'))
 | |
| 
 | |
| @bp.route('/competences/update', methods=['POST'])
 | |
| def update_competence():
 | |
|     """Modifier une compétence existante."""
 | |
|     try:
 | |
|         edit_index = request.form.get('edit_index')
 | |
|         name = request.form.get('name')
 | |
|         color = request.form.get('color', '#3b82f6')
 | |
|         icon = request.form.get('icon', 'star')
 | |
|         
 | |
|         if not edit_index or not name:
 | |
|             flash('Données manquantes pour la modification', 'error')
 | |
|             return redirect(url_for('config.competences'))
 | |
|         
 | |
|         index = int(edit_index)
 | |
|         competences = Competence.query.order_by(Competence.order_index).all()
 | |
|         
 | |
|         if 0 <= index < len(competences):
 | |
|             # Validation de la couleur hexadécimale
 | |
|             import re
 | |
|             if not re.match(r'^#[0-9a-fA-F]{6}$', color):
 | |
|                 flash('Format de couleur invalide', 'error')
 | |
|                 return redirect(url_for('config.competences'))
 | |
|             
 | |
|             competence = competences[index]
 | |
|             
 | |
|             # Vérifier si le nom existe déjà (sauf pour l'élément en cours d'édition)
 | |
|             existing = Competence.query.filter(Competence.name == name, Competence.id != competence.id).first()
 | |
|             if existing:
 | |
|                 flash(f'Une compétence "{name}" existe déjà', 'error')
 | |
|                 return redirect(url_for('config.competences'))
 | |
|             
 | |
|             old_name = competence.name
 | |
|             
 | |
|             if config_manager.update_competence(competence.id, name, color, icon):
 | |
|                 flash(f'Compétence "{old_name}" modifiée en "{name}"', 'success')
 | |
|             else:
 | |
|                 flash('Erreur lors de la sauvegarde', 'error')
 | |
|         else:
 | |
|             flash('Compétence introuvable', 'error')
 | |
|             
 | |
|     except Exception as e:
 | |
|         logging.error(f"Erreur modification compétence: {e}")
 | |
|         flash('Erreur lors de la modification', 'error')
 | |
|     
 | |
|     return redirect(url_for('config.competences'))
 | |
| 
 | |
| @bp.route('/competences/delete/<int:index>', methods=['POST'])
 | |
| def delete_competence(index):
 | |
|     """Supprimer une compétence."""
 | |
|     try:
 | |
|         competences = Competence.query.order_by(Competence.order_index).all()
 | |
|         
 | |
|         if 0 <= index < len(competences):
 | |
|             competence = competences[index]
 | |
|             deleted_name = competence.name
 | |
|             
 | |
|             if config_manager.delete_competence(competence.id):
 | |
|                 flash(f'Compétence "{deleted_name}" supprimée', 'success')
 | |
|             else:
 | |
|                 flash('Erreur lors de la sauvegarde', 'error')
 | |
|         else:
 | |
|             flash('Compétence introuvable', 'error')
 | |
|             
 | |
|     except Exception as e:
 | |
|         logging.error(f"Erreur suppression compétence: {e}")
 | |
|         flash('Erreur lors de la suppression', 'error')
 | |
|     
 | |
|     return redirect(url_for('config.competences'))
 | |
| 
 | |
| @bp.route('/scale')
 | |
| def scale():
 | |
|     """Page de configuration de l'échelle des compétences."""
 | |
|     try:
 | |
|         competence_scale = config_manager.get_competence_scale_values()
 | |
|         return render_template('config/scale.html', competence_scale=competence_scale)
 | |
|     except Exception as e:
 | |
|         return handle_error(e, "Erreur lors du chargement de l'échelle")
 | |
| 
 | |
| @bp.route('/scale/update', methods=['POST'])
 | |
| def update_scale():
 | |
|     """Mettre à jour l'échelle des compétences."""
 | |
|     try:
 | |
|         scale_data = {}
 | |
|         
 | |
|         # Récupérer tous les champs du formulaire
 | |
|         for key in request.form.keys():
 | |
|             if key.startswith('scale_'):
 | |
|                 parts = key.split('_', 2)  # scale_VALUE_FIELD
 | |
|                 if len(parts) == 3:
 | |
|                     value, field = parts[1], parts[2]
 | |
|                     
 | |
|                     if value not in scale_data:
 | |
|                         scale_data[value] = {}
 | |
|                     
 | |
|                     form_value = request.form[key]
 | |
|                     if field == 'included_in_total':
 | |
|                         form_value = form_value.lower() == 'true'
 | |
|                     
 | |
|                     scale_data[value][field] = form_value
 | |
|         
 | |
|         # Validation des données
 | |
|         import re
 | |
|         for value, config in scale_data.items():
 | |
|             # Vérifier que tous les champs requis sont présents
 | |
|             if not all(field in config for field in ['label', 'color', 'included_in_total']):
 | |
|                 flash(f'Données manquantes pour la valeur {value}', 'error')
 | |
|                 return redirect(url_for('config.scale'))
 | |
|             
 | |
|             # Valider le format de couleur
 | |
|             if not re.match(r'^#[0-9a-fA-F]{6}$', config['color']):
 | |
|                 flash(f'Format de couleur invalide pour la valeur {value}', 'error')
 | |
|                 return redirect(url_for('config.scale'))
 | |
|             
 | |
|             # Valider que le libellé n'est pas vide
 | |
|             if not config['label'].strip():
 | |
|                 flash(f'Le libellé ne peut pas être vide pour la valeur {value}', 'error')
 | |
|                 return redirect(url_for('config.scale'))
 | |
|         
 | |
|         # Mettre à jour chaque valeur
 | |
|         success_count = 0
 | |
|         for value, config in scale_data.items():
 | |
|             if config_manager.update_scale_value(
 | |
|                 value, 
 | |
|                 config['label'], 
 | |
|                 config['color'], 
 | |
|                 config['included_in_total']
 | |
|             ):
 | |
|                 success_count += 1
 | |
|         
 | |
|         if success_count == len(scale_data):
 | |
|             flash('Échelle des compétences mise à jour avec succès', 'success')
 | |
|         else:
 | |
|             flash(f'Mise à jour partielle : {success_count}/{len(scale_data)} valeurs mises à jour', 'warning')
 | |
|             
 | |
|     except Exception as e:
 | |
|         logging.error(f"Erreur mise à jour échelle: {e}")
 | |
|         flash('Erreur lors de la mise à jour', 'error')
 | |
|     
 | |
|     return redirect(url_for('config.scale'))
 | |
| 
 | |
| @bp.route('/scale/add', methods=['POST'])
 | |
| def add_scale_value():
 | |
|     """Ajouter une nouvelle valeur à l'échelle."""
 | |
|     try:
 | |
|         value = request.form.get('value')
 | |
|         label = request.form.get('label')
 | |
|         color = request.form.get('color', '#6b7280')
 | |
|         included_in_total = request.form.get('included_in_total', 'true').lower() == 'true'
 | |
|         
 | |
|         if not value or not label:
 | |
|             flash('La valeur et le libellé sont requis', 'error')
 | |
|             return redirect(url_for('config.scale'))
 | |
|         
 | |
|         # Validation de la couleur hexadécimale
 | |
|         import re
 | |
|         if not re.match(r'^#[0-9a-fA-F]{6}$', color):
 | |
|             flash('Format de couleur invalide', 'error')
 | |
|             return redirect(url_for('config.scale'))
 | |
|         
 | |
|         # Vérifier si la valeur existe déjà
 | |
|         if CompetenceScaleValue.query.filter_by(value=value).first():
 | |
|             flash(f'La valeur "{value}" existe déjà dans l\'échelle', 'error')
 | |
|             return redirect(url_for('config.scale'))
 | |
|         
 | |
|         if config_manager.add_scale_value(value, label.strip(), color, included_in_total):
 | |
|             flash(f'Valeur "{value}" ajoutée à l\'échelle avec succès', 'success')
 | |
|         else:
 | |
|             flash('Erreur lors de la sauvegarde', 'error')
 | |
|             
 | |
|     except Exception as e:
 | |
|         logging.error(f"Erreur ajout valeur échelle: {e}")
 | |
|         flash('Erreur lors de l\'ajout de la valeur', 'error')
 | |
|     
 | |
|     return redirect(url_for('config.scale'))
 | |
| 
 | |
| @bp.route('/scale/delete/<path:value>', methods=['POST'])
 | |
| def delete_scale_value(value):
 | |
|     """Supprimer une valeur de l'échelle."""
 | |
|     try:
 | |
|         # Vérifier si la valeur existe
 | |
|         scale_value = CompetenceScaleValue.query.filter_by(value=value).first()
 | |
|         if not scale_value:
 | |
|             flash(f'La valeur "{value}" n\'existe pas dans l\'échelle', 'error')
 | |
|             return redirect(url_for('config.scale'))
 | |
|         
 | |
|         # Empêcher la suppression des valeurs de base (0, 1, 2, 3, .)
 | |
|         base_values = ['0', '1', '2', '3', '.']
 | |
|         if value in base_values:
 | |
|             flash(f'La valeur "{value}" est une valeur de base et ne peut pas être supprimée', 'error')
 | |
|             return redirect(url_for('config.scale'))
 | |
|         
 | |
|         label = scale_value.label
 | |
|         
 | |
|         if config_manager.delete_scale_value(value):
 | |
|             flash(f'Valeur "{value}" ({label}) supprimée de l\'échelle', 'success')
 | |
|         else:
 | |
|             flash('Erreur lors de la sauvegarde', 'error')
 | |
|             
 | |
|     except Exception as e:
 | |
|         logging.error(f"Erreur suppression valeur échelle: {e}")
 | |
|         flash('Erreur lors de la suppression', 'error')
 | |
|     
 | |
|     return redirect(url_for('config.scale'))
 | |
| 
 | |
| @bp.route('/scale/reset', methods=['POST'])
 | |
| def reset_scale():
 | |
|     """Réinitialiser l'échelle aux valeurs par défaut."""
 | |
|     try:
 | |
|         # Supprimer toutes les valeurs existantes
 | |
|         CompetenceScaleValue.query.delete()
 | |
|         
 | |
|         # Réajouter les valeurs par défaut
 | |
|         default_scale = config_manager.default_config['evaluations']['competence_scale']['values']
 | |
|         for value, config in default_scale.items():
 | |
|             scale_value = CompetenceScaleValue(
 | |
|                 value=value,
 | |
|                 label=config['label'],
 | |
|                 color=config['color'],
 | |
|                 included_in_total=config['included_in_total']
 | |
|             )
 | |
|             db.session.add(scale_value)
 | |
|         
 | |
|         db.session.commit()
 | |
|         flash('Échelle réinitialisée aux valeurs par défaut', 'success')
 | |
|             
 | |
|     except Exception as e:
 | |
|         db.session.rollback()
 | |
|         logging.error(f"Erreur réinitialisation échelle: {e}")
 | |
|         flash('Erreur lors de la réinitialisation', 'error')
 | |
|     
 | |
|     return redirect(url_for('config.scale'))
 | |
| 
 | |
| @bp.route('/general')
 | |
| def general():
 | |
|     """Page de configuration générale."""
 | |
|     try:
 | |
|         return render_template('config/general.html', 
 | |
|                              app_config=config_manager,
 | |
|                              school_year=config_manager.get_school_year(),
 | |
|                              default_grading_system=config_manager.get_default_grading_system())
 | |
|     except Exception as e:
 | |
|         return handle_error(e, "Erreur lors du chargement de la configuration générale")
 | |
| 
 | |
| @bp.route('/general/update', methods=['POST'])
 | |
| def update_general():
 | |
|     """Mettre à jour la configuration générale."""
 | |
|     try:
 | |
|         school_year = request.form.get('school_year')
 | |
|         default_grading_system = request.form.get('default_grading_system')
 | |
|         
 | |
|         if not school_year:
 | |
|             flash('L\'année scolaire est requise', 'error')
 | |
|             return redirect(url_for('config.general'))
 | |
|         
 | |
|         # Validation de l'année scolaire (format YYYY-YYYY)
 | |
|         import re
 | |
|         if not re.match(r'^\d{4}-\d{4}$', school_year):
 | |
|             flash('Format d\'année scolaire invalide (attendu: YYYY-YYYY)', 'error')
 | |
|             return redirect(url_for('config.general'))
 | |
|         
 | |
|         # Mettre à jour la configuration
 | |
|         config_manager.set('context.school_year', school_year)
 | |
|         config_manager.set('evaluations.default_grading_system', default_grading_system)
 | |
|         
 | |
|         if config_manager.save():
 | |
|             flash('Configuration générale mise à jour avec succès', 'success')
 | |
|         else:
 | |
|             flash('Erreur lors de la sauvegarde', 'error')
 | |
|             
 | |
|     except Exception as e:
 | |
|         logging.error(f"Erreur mise à jour config générale: {e}")
 | |
|         flash('Erreur lors de la mise à jour', 'error')
 | |
|     
 | |
|     return redirect(url_for('config.general'))
 | |
| 
 | |
| @bp.route('/reset', methods=['POST'])
 | |
| def reset_config():
 | |
|     """Réinitialise la configuration aux valeurs par défaut."""
 | |
|     try:
 | |
|         # Supprimer toute la configuration existante
 | |
|         AppConfig.query.delete()
 | |
|         CompetenceScaleValue.query.delete()
 | |
|         Competence.query.delete()
 | |
|         
 | |
|         # Réinitialiser avec les valeurs par défaut
 | |
|         config_manager.initialize_default_config()
 | |
|         
 | |
|         flash('Configuration réinitialisée aux valeurs par défaut', 'success')
 | |
|             
 | |
|     except Exception as e:
 | |
|         db.session.rollback()
 | |
|         logging.error(f"Erreur réinitialisation: {e}")
 | |
|         flash('Erreur lors de la réinitialisation', 'error')
 | |
|     
 | |
|     return redirect(url_for('config.index')) |