feat: add csv import
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, current_app, abort
|
||||
from werkzeug.utils import secure_filename
|
||||
from models import db, ClassGroup, Student, Assessment, StudentEnrollment
|
||||
from forms import ClassGroupForm
|
||||
from utils import handle_db_errors, ValidationError
|
||||
from repositories.class_repository import ClassRepository
|
||||
from services.csv_import_service import CSVImportService
|
||||
from datetime import date, datetime
|
||||
|
||||
bp = Blueprint('classes', __name__, url_prefix='/classes')
|
||||
@@ -793,4 +795,105 @@ def cancel_departure():
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f'Erreur annulation départ: {e}')
|
||||
flash('Erreur lors de l\'annulation du départ', 'error')
|
||||
return redirect(request.referrer or url_for('classes'))
|
||||
return redirect(request.referrer or url_for('classes'))
|
||||
|
||||
@bp.route('/<int:id>/import-students-csv', methods=['POST'])
|
||||
@handle_db_errors
|
||||
def import_students_csv(id):
|
||||
"""Importe plusieurs élèves depuis un fichier CSV dans une classe."""
|
||||
class_repo = ClassRepository()
|
||||
class_group = class_repo.get_or_404(id)
|
||||
|
||||
try:
|
||||
# Vérifier qu'un fichier a été envoyé
|
||||
if 'csv_file' not in request.files:
|
||||
if request.is_json:
|
||||
return jsonify({'success': False, 'error': 'Aucun fichier fourni'}), 400
|
||||
flash('Aucun fichier sélectionné', 'error')
|
||||
return redirect(request.referrer or url_for('classes.students', id=id))
|
||||
|
||||
csv_file = request.files['csv_file']
|
||||
|
||||
if csv_file.filename == '':
|
||||
if request.is_json:
|
||||
return jsonify({'success': False, 'error': 'Aucun fichier sélectionné'}), 400
|
||||
flash('Aucun fichier sélectionné', 'error')
|
||||
return redirect(request.referrer or url_for('classes.students', id=id))
|
||||
|
||||
# Vérifier l'extension du fichier
|
||||
if not csv_file.filename.lower().endswith('.csv'):
|
||||
if request.is_json:
|
||||
return jsonify({'success': False, 'error': 'Le fichier doit être au format CSV'}), 400
|
||||
flash('Le fichier doit être au format CSV', 'error')
|
||||
return redirect(request.referrer or url_for('classes.students', id=id))
|
||||
|
||||
# Lire le contenu du fichier
|
||||
csv_content = csv_file.read().decode('utf-8-sig') # utf-8-sig pour gérer BOM
|
||||
|
||||
if not csv_content.strip():
|
||||
if request.is_json:
|
||||
return jsonify({'success': False, 'error': 'Le fichier CSV est vide'}), 400
|
||||
flash('Le fichier CSV est vide', 'error')
|
||||
return redirect(request.referrer or url_for('classes.students', id=id))
|
||||
|
||||
# Paramètres d'import
|
||||
enrollment_date_str = request.form.get('enrollment_date')
|
||||
if enrollment_date_str:
|
||||
enrollment_date = datetime.strptime(enrollment_date_str, '%Y-%m-%d').date()
|
||||
else:
|
||||
enrollment_date = date.today()
|
||||
|
||||
skip_duplicates = request.form.get('skip_duplicates', 'true').lower() == 'true'
|
||||
|
||||
# Lancer l'import via le service
|
||||
csv_service = CSVImportService()
|
||||
result = csv_service.import_students_to_class(
|
||||
csv_content=csv_content,
|
||||
class_group_id=id,
|
||||
enrollment_date=enrollment_date,
|
||||
skip_duplicates=skip_duplicates
|
||||
)
|
||||
|
||||
current_app.logger.info(f'Import CSV classe {id}: {result.imported_count} importés, {result.skipped_count} ignorés, {result.error_count} erreurs')
|
||||
|
||||
if request.is_json:
|
||||
return jsonify({
|
||||
'success': result.success,
|
||||
'message': result.message,
|
||||
'stats': {
|
||||
'total_lines': result.total_lines,
|
||||
'imported_count': result.imported_count,
|
||||
'skipped_count': result.skipped_count,
|
||||
'error_count': result.error_count
|
||||
},
|
||||
'imported_students': [
|
||||
{
|
||||
'name': f"{s.first_name} {s.last_name}",
|
||||
'line': s.line_number,
|
||||
'raw_name': s.raw_name
|
||||
} for s in result.imported_students
|
||||
],
|
||||
'skipped_students': result.skipped_students,
|
||||
'errors': result.errors
|
||||
})
|
||||
else:
|
||||
if result.success:
|
||||
if result.imported_count > 0:
|
||||
flash(f'{result.imported_count} élève(s) importé(s) avec succès en {class_group.name}', 'success')
|
||||
if result.skipped_count > 0:
|
||||
flash(f'{result.skipped_count} élève(s) ignoré(s) (doublons)', 'info')
|
||||
else:
|
||||
flash('Aucun élève importé', 'info')
|
||||
else:
|
||||
flash(f'Erreur lors de l\'import: {result.message}', 'error')
|
||||
|
||||
return redirect(url_for('classes.students', id=id) + '?reload=1')
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'Erreur import CSV classe {id}: {e}')
|
||||
|
||||
if request.is_json:
|
||||
return jsonify({'success': False, 'error': 'Erreur lors de l\'import du fichier CSV'}), 500
|
||||
else:
|
||||
flash('Erreur lors de l\'import du fichier CSV', 'error')
|
||||
return redirect(request.referrer or url_for('classes.students', id=id))
|
||||
Reference in New Issue
Block a user