Files
notytex/routes/config.py

403 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, handle_db_errors
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'])
@handle_db_errors
def add_competence():
"""Ajouter une nouvelle compétence."""
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')
return redirect(url_for('config.competences'))
@bp.route('/competences/update', methods=['POST'])
@handle_db_errors
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'])
@handle_db_errors
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 de réussite."""
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 de réussite."""
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 de réussite 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'])
@handle_db_errors
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'))