feat: add exercises and scoring
This commit is contained in:
		
							
								
								
									
										280
									
								
								app.py
									
									
									
									
									
								
							
							
						
						
									
										280
									
								
								app.py
									
									
									
									
									
								
							| @@ -1,9 +1,9 @@ | |||||||
| from flask import Flask, render_template, request, redirect, url_for, flash, jsonify | from flask import Flask, render_template, request, redirect, url_for, flash, jsonify | ||||||
| from flask_sqlalchemy import SQLAlchemy | from flask_sqlalchemy import SQLAlchemy | ||||||
| from flask_wtf import FlaskForm | from flask_wtf import FlaskForm | ||||||
| from wtforms import StringField, TextAreaField, FloatField, SelectField, DateField, IntegerField | from wtforms import StringField, TextAreaField, FloatField, SelectField, DateField, IntegerField, SubmitField | ||||||
| from wtforms.validators import DataRequired, Email, NumberRange, Optional | from wtforms.validators import DataRequired, Email, NumberRange, Optional, Length | ||||||
| from datetime import datetime | from datetime import datetime, date | ||||||
|  |  | ||||||
| app = Flask(__name__) | app = Flask(__name__) | ||||||
| app.config['SECRET_KEY'] = 'your-secret-key-here' | app.config['SECRET_KEY'] = 'your-secret-key-here' | ||||||
| @@ -80,6 +80,52 @@ class Grade(db.Model): | |||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return f'<Grade {self.value} for {self.student.first_name}>' |         return f'<Grade {self.value} for {self.student.first_name}>' | ||||||
|  |  | ||||||
|  | # Forms | ||||||
|  | class AssessmentForm(FlaskForm): | ||||||
|  |     title = StringField('Titre', validators=[DataRequired(), Length(max=200)]) | ||||||
|  |     description = TextAreaField('Description', validators=[Optional()]) | ||||||
|  |     date = DateField('Date', validators=[DataRequired()], default=date.today) | ||||||
|  |     class_group_id = SelectField('Classe', validators=[DataRequired()], coerce=int) | ||||||
|  |     coefficient = FloatField('Coefficient', validators=[DataRequired(), NumberRange(min=0.1, max=10)], default=1.0) | ||||||
|  |     submit = SubmitField('Enregistrer') | ||||||
|  |      | ||||||
|  |     def __init__(self, *args, **kwargs): | ||||||
|  |         super(AssessmentForm, self).__init__(*args, **kwargs) | ||||||
|  |         self.class_group_id.choices = [(cg.id, cg.name) for cg in ClassGroup.query.order_by(ClassGroup.name).all()] | ||||||
|  |  | ||||||
|  | class ClassGroupForm(FlaskForm): | ||||||
|  |     name = StringField('Nom de la classe', validators=[DataRequired(), Length(max=100)]) | ||||||
|  |     description = TextAreaField('Description', validators=[Optional()]) | ||||||
|  |     year = StringField('Année scolaire', validators=[DataRequired(), Length(max=20)], default="2024-2025") | ||||||
|  |     submit = SubmitField('Enregistrer') | ||||||
|  |  | ||||||
|  | class StudentForm(FlaskForm): | ||||||
|  |     first_name = StringField('Prénom', validators=[DataRequired(), Length(max=100)]) | ||||||
|  |     last_name = StringField('Nom', validators=[DataRequired(), Length(max=100)]) | ||||||
|  |     email = StringField('Email', validators=[Optional(), Email(), Length(max=120)]) | ||||||
|  |     class_group_id = SelectField('Classe', validators=[DataRequired()], coerce=int) | ||||||
|  |     submit = SubmitField('Enregistrer') | ||||||
|  |      | ||||||
|  |     def __init__(self, *args, **kwargs): | ||||||
|  |         super(StudentForm, self).__init__(*args, **kwargs) | ||||||
|  |         self.class_group_id.choices = [(cg.id, cg.name) for cg in ClassGroup.query.order_by(ClassGroup.name).all()] | ||||||
|  |  | ||||||
|  | class ExerciseForm(FlaskForm): | ||||||
|  |     title = StringField('Titre', validators=[DataRequired(), Length(max=200)]) | ||||||
|  |     description = TextAreaField('Description', validators=[Optional()]) | ||||||
|  |     order = IntegerField('Ordre', validators=[DataRequired(), NumberRange(min=1)], default=1) | ||||||
|  |     submit = SubmitField('Enregistrer') | ||||||
|  |  | ||||||
|  | class GradingElementForm(FlaskForm): | ||||||
|  |     label = StringField('Libellé', validators=[DataRequired(), Length(max=200)]) | ||||||
|  |     description = TextAreaField('Description', validators=[Optional()]) | ||||||
|  |     skill = StringField('Compétence', validators=[Optional(), Length(max=200)]) | ||||||
|  |     max_points = FloatField('Barème (points max)', validators=[DataRequired(), NumberRange(min=0.1)], default=1.0) | ||||||
|  |     grading_type = SelectField('Type de notation', validators=[DataRequired()],  | ||||||
|  |                               choices=[('points', 'Points (ex: 2.5/4)'), ('score', 'Score (0, 1, 2, 3, .)')], | ||||||
|  |                               default='points') | ||||||
|  |     submit = SubmitField('Enregistrer') | ||||||
|  |  | ||||||
| @app.route('/') | @app.route('/') | ||||||
| def index(): | def index(): | ||||||
|     recent_assessments = Assessment.query.order_by(Assessment.date.desc()).limit(5).all() |     recent_assessments = Assessment.query.order_by(Assessment.date.desc()).limit(5).all() | ||||||
| @@ -107,6 +153,234 @@ def assessments(): | |||||||
|     assessments = Assessment.query.join(ClassGroup).order_by(Assessment.date.desc()).all() |     assessments = Assessment.query.join(ClassGroup).order_by(Assessment.date.desc()).all() | ||||||
|     return render_template('assessments.html', assessments=assessments) |     return render_template('assessments.html', assessments=assessments) | ||||||
|  |  | ||||||
|  | @app.route('/assessments/new', methods=['GET', 'POST']) | ||||||
|  | def new_assessment(): | ||||||
|  |     form = AssessmentForm() | ||||||
|  |     if form.validate_on_submit(): | ||||||
|  |         assessment = Assessment( | ||||||
|  |             title=form.title.data, | ||||||
|  |             description=form.description.data, | ||||||
|  |             date=form.date.data, | ||||||
|  |             class_group_id=form.class_group_id.data, | ||||||
|  |             coefficient=form.coefficient.data | ||||||
|  |         ) | ||||||
|  |         db.session.add(assessment) | ||||||
|  |         db.session.commit() | ||||||
|  |         flash('Évaluation créée avec succès !', 'success') | ||||||
|  |         return redirect(url_for('assessment_detail', id=assessment.id)) | ||||||
|  |     return render_template('assessment_form.html', form=form, title='Nouvelle évaluation') | ||||||
|  |  | ||||||
|  | @app.route('/assessments/<int:id>') | ||||||
|  | def assessment_detail(id): | ||||||
|  |     assessment = Assessment.query.get_or_404(id) | ||||||
|  |     return render_template('assessment_detail.html', assessment=assessment) | ||||||
|  |  | ||||||
|  | @app.route('/assessments/<int:id>/edit', methods=['GET', 'POST']) | ||||||
|  | def edit_assessment(id): | ||||||
|  |     assessment = Assessment.query.get_or_404(id) | ||||||
|  |     form = AssessmentForm(obj=assessment) | ||||||
|  |     if form.validate_on_submit(): | ||||||
|  |         assessment.title = form.title.data | ||||||
|  |         assessment.description = form.description.data | ||||||
|  |         assessment.date = form.date.data | ||||||
|  |         assessment.class_group_id = form.class_group_id.data | ||||||
|  |         assessment.coefficient = form.coefficient.data | ||||||
|  |         db.session.commit() | ||||||
|  |         flash('Évaluation modifiée avec succès !', 'success') | ||||||
|  |         return redirect(url_for('assessment_detail', id=assessment.id)) | ||||||
|  |     return render_template('assessment_form.html', form=form, title='Modifier l\'évaluation', assessment=assessment) | ||||||
|  |  | ||||||
|  | @app.route('/assessments/<int:id>/delete', methods=['POST']) | ||||||
|  | def delete_assessment(id): | ||||||
|  |     assessment = Assessment.query.get_or_404(id) | ||||||
|  |     db.session.delete(assessment) | ||||||
|  |     db.session.commit() | ||||||
|  |     flash('Évaluation supprimée avec succès !', 'success') | ||||||
|  |     return redirect(url_for('assessments')) | ||||||
|  |  | ||||||
|  | # Exercise routes | ||||||
|  | @app.route('/assessments/<int:assessment_id>/exercises/new', methods=['GET', 'POST']) | ||||||
|  | def new_exercise(assessment_id): | ||||||
|  |     assessment = Assessment.query.get_or_404(assessment_id) | ||||||
|  |     form = ExerciseForm() | ||||||
|  |      | ||||||
|  |     # Set default order to next available | ||||||
|  |     if form.order.data == 1:  # Only if it's the default value | ||||||
|  |         max_order = db.session.query(db.func.max(Exercise.order)).filter_by(assessment_id=assessment_id).scalar() | ||||||
|  |         form.order.data = (max_order or 0) + 1 | ||||||
|  |      | ||||||
|  |     if form.validate_on_submit(): | ||||||
|  |         exercise = Exercise( | ||||||
|  |             assessment_id=assessment_id, | ||||||
|  |             title=form.title.data, | ||||||
|  |             description=form.description.data, | ||||||
|  |             order=form.order.data | ||||||
|  |         ) | ||||||
|  |         db.session.add(exercise) | ||||||
|  |         db.session.commit() | ||||||
|  |         flash('Exercice créé avec succès !', 'success') | ||||||
|  |         return redirect(url_for('exercise_detail', assessment_id=assessment_id, id=exercise.id)) | ||||||
|  |      | ||||||
|  |     return render_template('exercise_form.html', form=form, assessment=assessment, title='Nouvel exercice') | ||||||
|  |  | ||||||
|  | @app.route('/assessments/<int:assessment_id>/exercises/<int:id>') | ||||||
|  | def exercise_detail(assessment_id, id): | ||||||
|  |     assessment = Assessment.query.get_or_404(assessment_id) | ||||||
|  |     exercise = Exercise.query.filter_by(id=id, assessment_id=assessment_id).first_or_404() | ||||||
|  |     return render_template('exercise_detail.html', assessment=assessment, exercise=exercise) | ||||||
|  |  | ||||||
|  | @app.route('/assessments/<int:assessment_id>/exercises/<int:id>/edit', methods=['GET', 'POST']) | ||||||
|  | def edit_exercise(assessment_id, id): | ||||||
|  |     assessment = Assessment.query.get_or_404(assessment_id) | ||||||
|  |     exercise = Exercise.query.filter_by(id=id, assessment_id=assessment_id).first_or_404() | ||||||
|  |     form = ExerciseForm(obj=exercise) | ||||||
|  |      | ||||||
|  |     if form.validate_on_submit(): | ||||||
|  |         exercise.title = form.title.data | ||||||
|  |         exercise.description = form.description.data | ||||||
|  |         exercise.order = form.order.data | ||||||
|  |         db.session.commit() | ||||||
|  |         flash('Exercice modifié avec succès !', 'success') | ||||||
|  |         return redirect(url_for('exercise_detail', assessment_id=assessment_id, id=exercise.id)) | ||||||
|  |      | ||||||
|  |     return render_template('exercise_form.html', form=form, assessment=assessment, exercise=exercise, title='Modifier l\'exercice') | ||||||
|  |  | ||||||
|  | @app.route('/assessments/<int:assessment_id>/exercises/<int:id>/delete', methods=['POST']) | ||||||
|  | def delete_exercise(assessment_id, id): | ||||||
|  |     assessment = Assessment.query.get_or_404(assessment_id) | ||||||
|  |     exercise = Exercise.query.filter_by(id=id, assessment_id=assessment_id).first_or_404() | ||||||
|  |     db.session.delete(exercise) | ||||||
|  |     db.session.commit() | ||||||
|  |     flash('Exercice supprimé avec succès !', 'success') | ||||||
|  |     return redirect(url_for('assessment_detail', id=assessment_id)) | ||||||
|  |  | ||||||
|  | # GradingElement routes | ||||||
|  | @app.route('/assessments/<int:assessment_id>/exercises/<int:exercise_id>/elements/new', methods=['GET', 'POST']) | ||||||
|  | def new_grading_element(assessment_id, exercise_id): | ||||||
|  |     assessment = Assessment.query.get_or_404(assessment_id) | ||||||
|  |     exercise = Exercise.query.filter_by(id=exercise_id, assessment_id=assessment_id).first_or_404() | ||||||
|  |     form = GradingElementForm() | ||||||
|  |      | ||||||
|  |     if form.validate_on_submit(): | ||||||
|  |         element = GradingElement( | ||||||
|  |             exercise_id=exercise_id, | ||||||
|  |             label=form.label.data, | ||||||
|  |             description=form.description.data, | ||||||
|  |             skill=form.skill.data, | ||||||
|  |             max_points=form.max_points.data, | ||||||
|  |             grading_type=form.grading_type.data | ||||||
|  |         ) | ||||||
|  |         db.session.add(element) | ||||||
|  |         db.session.commit() | ||||||
|  |         flash('Élément de notation créé avec succès !', 'success') | ||||||
|  |         return redirect(url_for('exercise_detail', assessment_id=assessment_id, id=exercise_id)) | ||||||
|  |      | ||||||
|  |     return render_template('grading_element_form.html', form=form, assessment=assessment, exercise=exercise, title='Nouvel élément de notation') | ||||||
|  |  | ||||||
|  | @app.route('/assessments/<int:assessment_id>/exercises/<int:exercise_id>/elements/<int:id>/edit', methods=['GET', 'POST']) | ||||||
|  | def edit_grading_element(assessment_id, exercise_id, id): | ||||||
|  |     assessment = Assessment.query.get_or_404(assessment_id) | ||||||
|  |     exercise = Exercise.query.filter_by(id=exercise_id, assessment_id=assessment_id).first_or_404() | ||||||
|  |     element = GradingElement.query.filter_by(id=id, exercise_id=exercise_id).first_or_404() | ||||||
|  |     form = GradingElementForm(obj=element) | ||||||
|  |      | ||||||
|  |     if form.validate_on_submit(): | ||||||
|  |         element.label = form.label.data | ||||||
|  |         element.description = form.description.data | ||||||
|  |         element.skill = form.skill.data | ||||||
|  |         element.max_points = form.max_points.data | ||||||
|  |         element.grading_type = form.grading_type.data | ||||||
|  |         db.session.commit() | ||||||
|  |         flash('Élément de notation modifié avec succès !', 'success') | ||||||
|  |         return redirect(url_for('exercise_detail', assessment_id=assessment_id, id=exercise_id)) | ||||||
|  |      | ||||||
|  |     return render_template('grading_element_form.html', form=form, assessment=assessment, exercise=exercise, element=element, title='Modifier l\'élément de notation') | ||||||
|  |  | ||||||
|  | @app.route('/assessments/<int:assessment_id>/exercises/<int:exercise_id>/elements/<int:id>/delete', methods=['POST']) | ||||||
|  | def delete_grading_element(assessment_id, exercise_id, id): | ||||||
|  |     assessment = Assessment.query.get_or_404(assessment_id) | ||||||
|  |     exercise = Exercise.query.filter_by(id=exercise_id, assessment_id=assessment_id).first_or_404() | ||||||
|  |     element = GradingElement.query.filter_by(id=id, exercise_id=exercise_id).first_or_404() | ||||||
|  |     db.session.delete(element) | ||||||
|  |     db.session.commit() | ||||||
|  |     flash('Élément de notation supprimé avec succès !', 'success') | ||||||
|  |     return redirect(url_for('exercise_detail', assessment_id=assessment_id, id=exercise_id)) | ||||||
|  |  | ||||||
|  | # Grading routes | ||||||
|  | @app.route('/assessments/<int:assessment_id>/grading') | ||||||
|  | def assessment_grading(assessment_id): | ||||||
|  |     assessment = Assessment.query.get_or_404(assessment_id) | ||||||
|  |     students = Student.query.filter_by(class_group_id=assessment.class_group_id).order_by(Student.last_name, Student.first_name).all() | ||||||
|  |      | ||||||
|  |     # Get all grading elements for this assessment | ||||||
|  |     grading_elements = [] | ||||||
|  |     for exercise in assessment.exercises: | ||||||
|  |         for element in exercise.grading_elements: | ||||||
|  |             grading_elements.append(element) | ||||||
|  |      | ||||||
|  |     # Get existing grades | ||||||
|  |     existing_grades = {} | ||||||
|  |     for grade in Grade.query.join(GradingElement).join(Exercise).filter_by(assessment_id=assessment_id).all(): | ||||||
|  |         key = f"{grade.student_id}_{grade.grading_element_id}" | ||||||
|  |         existing_grades[key] = grade | ||||||
|  |      | ||||||
|  |     return render_template('assessment_grading.html',  | ||||||
|  |                          assessment=assessment,  | ||||||
|  |                          students=students,  | ||||||
|  |                          grading_elements=grading_elements, | ||||||
|  |                          existing_grades=existing_grades) | ||||||
|  |  | ||||||
|  | @app.route('/assessments/<int:assessment_id>/grading/save', methods=['POST']) | ||||||
|  | def save_grades(assessment_id): | ||||||
|  |     assessment = Assessment.query.get_or_404(assessment_id) | ||||||
|  |      | ||||||
|  |     for key, value in request.form.items(): | ||||||
|  |         if key.startswith('grade_'): | ||||||
|  |             # Parse key: grade_<student_id>_<element_id> | ||||||
|  |             parts = key.split('_') | ||||||
|  |             if len(parts) == 3: | ||||||
|  |                 student_id = int(parts[1]) | ||||||
|  |                 element_id = int(parts[2]) | ||||||
|  |                  | ||||||
|  |                 # Find or create grade | ||||||
|  |                 grade = Grade.query.filter_by( | ||||||
|  |                     student_id=student_id, | ||||||
|  |                     grading_element_id=element_id | ||||||
|  |                 ).first() | ||||||
|  |                  | ||||||
|  |                 if value.strip():  # If value is not empty | ||||||
|  |                     if not grade: | ||||||
|  |                         grade = Grade( | ||||||
|  |                             student_id=student_id, | ||||||
|  |                             grading_element_id=element_id, | ||||||
|  |                             value=value | ||||||
|  |                         ) | ||||||
|  |                         db.session.add(grade) | ||||||
|  |                     else: | ||||||
|  |                         grade.value = value | ||||||
|  |                 elif grade:  # If value is empty but grade exists, delete it | ||||||
|  |                     db.session.delete(grade) | ||||||
|  |      | ||||||
|  |     # Handle comments | ||||||
|  |     for key, value in request.form.items(): | ||||||
|  |         if key.startswith('comment_'): | ||||||
|  |             parts = key.split('_') | ||||||
|  |             if len(parts) == 3: | ||||||
|  |                 student_id = int(parts[1]) | ||||||
|  |                 element_id = int(parts[2]) | ||||||
|  |                  | ||||||
|  |                 grade = Grade.query.filter_by( | ||||||
|  |                     student_id=student_id, | ||||||
|  |                     grading_element_id=element_id | ||||||
|  |                 ).first() | ||||||
|  |                  | ||||||
|  |                 if grade: | ||||||
|  |                     grade.comment = value.strip() if value.strip() else None | ||||||
|  |      | ||||||
|  |     db.session.commit() | ||||||
|  |     flash('Notes sauvegardées avec succès !', 'success') | ||||||
|  |     return redirect(url_for('assessment_grading', assessment_id=assessment_id)) | ||||||
|  |  | ||||||
| @app.cli.command() | @app.cli.command() | ||||||
| def init_db(): | def init_db(): | ||||||
|     """Initialize the database with sample data.""" |     """Initialize the database with sample data.""" | ||||||
|   | |||||||
							
								
								
									
										142
									
								
								templates/assessment_detail.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								templates/assessment_detail.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,142 @@ | |||||||
|  | {% extends "base.html" %} | ||||||
|  |  | ||||||
|  | {% block title %}{{ assessment.title }} - Gestion Scolaire{% endblock %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <div class="space-y-6"> | ||||||
|  |     <div class="flex justify-between items-center"> | ||||||
|  |         <div> | ||||||
|  |             <a href="{{ url_for('assessments') }}" class="text-blue-600 hover:text-blue-800 text-sm font-medium mb-2 inline-block"> | ||||||
|  |                 ← Retour aux évaluations | ||||||
|  |             </a> | ||||||
|  |             <h1 class="text-2xl font-bold text-gray-900">{{ assessment.title }}</h1> | ||||||
|  |             <p class="text-gray-600">{{ assessment.class_group.name }} - {{ assessment.date.strftime('%d/%m/%Y') }}</p> | ||||||
|  |         </div> | ||||||
|  |         <div class="flex space-x-3"> | ||||||
|  |             <a href="{{ url_for('edit_assessment', id=assessment.id) }}" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors"> | ||||||
|  |                 Modifier | ||||||
|  |             </a> | ||||||
|  |             <button onclick="if(confirm('Êtes-vous sûr de vouloir supprimer cette évaluation ?')) { document.getElementById('delete-form').submit(); }"  | ||||||
|  |                     class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors"> | ||||||
|  |                 Supprimer | ||||||
|  |             </button> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <form id="delete-form" method="POST" action="{{ url_for('delete_assessment', id=assessment.id) }}" style="display: none;"></form> | ||||||
|  |      | ||||||
|  |     <!-- Informations de l'évaluation --> | ||||||
|  |     <div class="bg-white shadow rounded-lg"> | ||||||
|  |         <div class="px-6 py-4 border-b border-gray-200"> | ||||||
|  |             <h2 class="text-lg font-medium text-gray-900">Informations générales</h2> | ||||||
|  |         </div> | ||||||
|  |         <div class="px-6 py-4"> | ||||||
|  |             <dl class="grid grid-cols-1 md:grid-cols-2 gap-6"> | ||||||
|  |                 <div> | ||||||
|  |                     <dt class="text-sm font-medium text-gray-500">Classe</dt> | ||||||
|  |                     <dd class="mt-1 text-sm text-gray-900">{{ assessment.class_group.name }}</dd> | ||||||
|  |                 </div> | ||||||
|  |                 <div> | ||||||
|  |                     <dt class="text-sm font-medium text-gray-500">Date</dt> | ||||||
|  |                     <dd class="mt-1 text-sm text-gray-900">{{ assessment.date.strftime('%d/%m/%Y') }}</dd> | ||||||
|  |                 </div> | ||||||
|  |                 <div> | ||||||
|  |                     <dt class="text-sm font-medium text-gray-500">Coefficient</dt> | ||||||
|  |                     <dd class="mt-1 text-sm text-gray-900">{{ assessment.coefficient }}</dd> | ||||||
|  |                 </div> | ||||||
|  |                 <div> | ||||||
|  |                     <dt class="text-sm font-medium text-gray-500">Nombre d'exercices</dt> | ||||||
|  |                     <dd class="mt-1 text-sm text-gray-900">{{ assessment.exercises|length }}</dd> | ||||||
|  |                 </div> | ||||||
|  |                 {% if assessment.description %} | ||||||
|  |                 <div class="md:col-span-2"> | ||||||
|  |                     <dt class="text-sm font-medium text-gray-500">Description</dt> | ||||||
|  |                     <dd class="mt-1 text-sm text-gray-900">{{ assessment.description }}</dd> | ||||||
|  |                 </div> | ||||||
|  |                 {% endif %} | ||||||
|  |             </dl> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <!-- Exercices --> | ||||||
|  |     <div class="bg-white shadow rounded-lg"> | ||||||
|  |         <div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center"> | ||||||
|  |             <h2 class="text-lg font-medium text-gray-900">Exercices</h2> | ||||||
|  |             <a href="{{ url_for('new_exercise', assessment_id=assessment.id) }}" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors"> | ||||||
|  |                 Ajouter un exercice | ||||||
|  |             </a> | ||||||
|  |         </div> | ||||||
|  |         <div class="px-6 py-4"> | ||||||
|  |             {% if assessment.exercises %} | ||||||
|  |                 <div class="space-y-4"> | ||||||
|  |                     {% for exercise in assessment.exercises|sort(attribute='order') %} | ||||||
|  |                         <div class="border border-gray-200 rounded-lg p-4"> | ||||||
|  |                             <div class="flex justify-between items-start"> | ||||||
|  |                                 <div class="flex-1"> | ||||||
|  |                                     <h3 class="text-sm font-medium text-gray-900">{{ exercise.title }}</h3> | ||||||
|  |                                     {% if exercise.description %} | ||||||
|  |                                         <p class="text-sm text-gray-600 mt-1">{{ exercise.description }}</p> | ||||||
|  |                                     {% endif %} | ||||||
|  |                                     <div class="text-xs text-gray-500 mt-2"> | ||||||
|  |                                         {{ exercise.grading_elements|length }} élément(s) de notation | ||||||
|  |                                     </div> | ||||||
|  |                                 </div> | ||||||
|  |                                 <div class="flex space-x-2 ml-4"> | ||||||
|  |                                     <a href="{{ url_for('exercise_detail', assessment_id=assessment.id, id=exercise.id) }}" class="text-blue-600 hover:text-blue-800 text-sm font-medium"> | ||||||
|  |                                         Voir détails | ||||||
|  |                                     </a> | ||||||
|  |                                     <a href="{{ url_for('edit_exercise', assessment_id=assessment.id, id=exercise.id) }}" class="text-gray-600 hover:text-gray-800 text-sm font-medium"> | ||||||
|  |                                         Modifier | ||||||
|  |                                     </a> | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     {% endfor %} | ||||||
|  |                 </div> | ||||||
|  |             {% else %} | ||||||
|  |                 <div class="text-center py-8"> | ||||||
|  |                     <svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48"> | ||||||
|  |                         <path d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8m9-16v20m5-14H9m1 4h8m1 2h8m-8 6h8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> | ||||||
|  |                     </svg> | ||||||
|  |                     <h3 class="mt-2 text-sm font-medium text-gray-900">Aucun exercice</h3> | ||||||
|  |                     <p class="mt-1 text-sm text-gray-500">Commencez par ajouter le premier exercice de cette évaluation.</p> | ||||||
|  |                     <div class="mt-6"> | ||||||
|  |                         <a href="{{ url_for('new_exercise', assessment_id=assessment.id) }}" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors"> | ||||||
|  |                             Ajouter un exercice | ||||||
|  |                         </a> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             {% endif %} | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <!-- Actions rapides --> | ||||||
|  |     <div class="bg-white shadow rounded-lg"> | ||||||
|  |         <div class="px-6 py-4 border-b border-gray-200"> | ||||||
|  |             <h2 class="text-lg font-medium text-gray-900">Actions</h2> | ||||||
|  |         </div> | ||||||
|  |         <div class="px-6 py-4"> | ||||||
|  |             <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> | ||||||
|  |                 <a href="{{ url_for('assessment_grading', assessment_id=assessment.id) }}" class="flex items-center justify-center px-4 py-3 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"> | ||||||
|  |                     <svg class="w-5 h-5 mr-2 text-green-600" fill="currentColor" viewBox="0 0 20 20"> | ||||||
|  |                         <path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zm0 4a1 1 0 011-1h12a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1V8z" clip-rule="evenodd"/> | ||||||
|  |                     </svg> | ||||||
|  |                     Saisir les notes | ||||||
|  |                 </a> | ||||||
|  |                 <button class="flex items-center justify-center px-4 py-3 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"> | ||||||
|  |                     <svg class="w-5 h-5 mr-2 text-purple-600" fill="currentColor" viewBox="0 0 20 20"> | ||||||
|  |                         <path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 011 1v1a1 1 0 01-1 1H4a1 1 0 01-1-1v-1zM3 4a1 1 0 011-1h12a1 1 0 011 1v1a1 1 0 01-1 1H4a1 1 0 01-1-1V4z" clip-rule="evenodd"/> | ||||||
|  |                     </svg> | ||||||
|  |                     Voir les résultats | ||||||
|  |                 </button> | ||||||
|  |                 <button class="flex items-center justify-center px-4 py-3 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"> | ||||||
|  |                     <svg class="w-5 h-5 mr-2 text-blue-600" fill="currentColor" viewBox="0 0 20 20"> | ||||||
|  |                         <path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 011 1v1a1 1 0 01-1 1H4a1 1 0 01-1-1v-1zM3 4a1 1 0 011-1h12a1 1 0 011 1v1a1 1 0 01-1 1H4a1 1 0 01-1-1V4z" clip-rule="evenodd"/> | ||||||
|  |                     </svg> | ||||||
|  |                     Exporter | ||||||
|  |                 </button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										102
									
								
								templates/assessment_form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								templates/assessment_form.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | |||||||
|  | {% extends "base.html" %} | ||||||
|  |  | ||||||
|  | {% block title %}{{ title }} - Gestion Scolaire{% endblock %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <div class="max-w-2xl mx-auto"> | ||||||
|  |     <div class="mb-6"> | ||||||
|  |         <a href="{{ url_for('assessments') }}" class="text-blue-600 hover:text-blue-800 text-sm font-medium"> | ||||||
|  |             ← Retour aux évaluations | ||||||
|  |         </a> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <div class="bg-white shadow rounded-lg"> | ||||||
|  |         <div class="px-6 py-4 border-b border-gray-200"> | ||||||
|  |             <h1 class="text-xl font-semibold text-gray-900">{{ title }}</h1> | ||||||
|  |         </div> | ||||||
|  |          | ||||||
|  |         <form method="POST" class="px-6 py-4 space-y-6"> | ||||||
|  |             {{ form.hidden_tag() }} | ||||||
|  |              | ||||||
|  |             <div> | ||||||
|  |                 <label for="{{ form.title.id }}" class="block text-sm font-medium text-gray-700 mb-1"> | ||||||
|  |                     {{ form.title.label.text }} | ||||||
|  |                 </label> | ||||||
|  |                 {{ 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 %} | ||||||
|  |                     <div class="mt-1 text-sm text-red-600"> | ||||||
|  |                         {% for error in form.title.errors %} | ||||||
|  |                             <p>{{ error }}</p> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </div> | ||||||
|  |                 {% endif %} | ||||||
|  |             </div> | ||||||
|  |              | ||||||
|  |             <div> | ||||||
|  |                 <label for="{{ form.description.id }}" class="block text-sm font-medium text-gray-700 mb-1"> | ||||||
|  |                     {{ form.description.label.text }} | ||||||
|  |                 </label> | ||||||
|  |                 {{ 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 %} | ||||||
|  |                     <div class="mt-1 text-sm text-red-600"> | ||||||
|  |                         {% for error in form.description.errors %} | ||||||
|  |                             <p>{{ error }}</p> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </div> | ||||||
|  |                 {% endif %} | ||||||
|  |             </div> | ||||||
|  |              | ||||||
|  |             <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | ||||||
|  |                 <div> | ||||||
|  |                     <label for="{{ form.date.id }}" class="block text-sm font-medium text-gray-700 mb-1"> | ||||||
|  |                         {{ form.date.label.text }} | ||||||
|  |                     </label> | ||||||
|  |                     {{ form.date(class="block w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500") }} | ||||||
|  |                     {% if form.date.errors %} | ||||||
|  |                         <div class="mt-1 text-sm text-red-600"> | ||||||
|  |                             {% for error in form.date.errors %} | ||||||
|  |                                 <p>{{ error }}</p> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </div> | ||||||
|  |                     {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                  | ||||||
|  |                 <div> | ||||||
|  |                     <label for="{{ form.coefficient.id }}" class="block text-sm font-medium text-gray-700 mb-1"> | ||||||
|  |                         {{ form.coefficient.label.text }} | ||||||
|  |                     </label> | ||||||
|  |                     {{ form.coefficient(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.coefficient.errors %} | ||||||
|  |                         <div class="mt-1 text-sm text-red-600"> | ||||||
|  |                             {% for error in form.coefficient.errors %} | ||||||
|  |                                 <p>{{ error }}</p> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </div> | ||||||
|  |                     {% endif %} | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |              | ||||||
|  |             <div> | ||||||
|  |                 <label for="{{ form.class_group_id.id }}" class="block text-sm font-medium text-gray-700 mb-1"> | ||||||
|  |                     {{ form.class_group_id.label.text }} | ||||||
|  |                 </label> | ||||||
|  |                 {{ form.class_group_id(class="block w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500") }} | ||||||
|  |                 {% if form.class_group_id.errors %} | ||||||
|  |                     <div class="mt-1 text-sm text-red-600"> | ||||||
|  |                         {% for error in form.class_group_id.errors %} | ||||||
|  |                             <p>{{ error }}</p> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </div> | ||||||
|  |                 {% endif %} | ||||||
|  |             </div> | ||||||
|  |              | ||||||
|  |             <div class="flex justify-end space-x-3 pt-4 border-t border-gray-200"> | ||||||
|  |                 <a href="{{ url_for('assessments') }}" class="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"> | ||||||
|  |                     Annuler | ||||||
|  |                 </a> | ||||||
|  |                 {{ form.submit(class="px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors") }} | ||||||
|  |             </div> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										159
									
								
								templates/assessment_grading.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								templates/assessment_grading.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | |||||||
|  | {% extends "base.html" %} | ||||||
|  |  | ||||||
|  | {% block title %}Saisie des notes - {{ assessment.title }} - Gestion Scolaire{% endblock %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <div class="space-y-6"> | ||||||
|  |     <div class="flex justify-between items-center"> | ||||||
|  |         <div> | ||||||
|  |             <a href="{{ url_for('assessment_detail', id=assessment.id) }}" class="text-blue-600 hover:text-blue-800 text-sm font-medium mb-2 inline-block"> | ||||||
|  |                 ← Retour à l'évaluation | ||||||
|  |             </a> | ||||||
|  |             <h1 class="text-2xl font-bold text-gray-900">Saisie des notes</h1> | ||||||
|  |             <p class="text-gray-600">{{ assessment.title }} - {{ assessment.class_group.name }}</p> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     {% if not grading_elements %} | ||||||
|  |         <div class="bg-yellow-50 border border-yellow-200 rounded-md p-4"> | ||||||
|  |             <div class="flex"> | ||||||
|  |                 <div class="flex-shrink-0"> | ||||||
|  |                     <svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor"> | ||||||
|  |                         <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" /> | ||||||
|  |                     </svg> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="ml-3"> | ||||||
|  |                     <h3 class="text-sm font-medium text-yellow-800">Aucun élément de notation</h3> | ||||||
|  |                     <div class="mt-2 text-sm text-yellow-700"> | ||||||
|  |                         <p>Cette évaluation n'a pas encore d'éléments de notation configurés. Vous devez d'abord créer des exercices et leurs éléments de notation.</p> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="mt-4"> | ||||||
|  |                         <a href="{{ url_for('assessment_detail', id=assessment.id) }}" class="text-sm font-medium text-yellow-800 underline hover:text-yellow-900"> | ||||||
|  |                             Configurer l'évaluation → | ||||||
|  |                         </a> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     {% else %} | ||||||
|  |         <form method="POST" action="{{ url_for('save_grades', assessment_id=assessment.id) }}" class="space-y-6"> | ||||||
|  |             <!-- Informations sur les types de notation --> | ||||||
|  |             <div class="bg-blue-50 border border-blue-200 rounded-md p-4"> | ||||||
|  |                 <h3 class="text-sm font-medium text-blue-900 mb-2">Guide de saisie</h3> | ||||||
|  |                 <div class="text-xs text-blue-800 space-y-1"> | ||||||
|  |                     <p><strong>Points :</strong> Saisissez une valeur numérique (ex: 2.5, 3, 0)</p> | ||||||
|  |                     <p><strong>Score :</strong> 0=non acquis, 1=en cours, 2=acquis, 3=expert, .=non évalué</p> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |              | ||||||
|  |             <!-- Tableau de saisie --> | ||||||
|  |             <div class="bg-white shadow rounded-lg overflow-hidden"> | ||||||
|  |                 <div class="px-6 py-4 border-b border-gray-200"> | ||||||
|  |                     <h2 class="text-lg font-medium text-gray-900">Grille de notation</h2> | ||||||
|  |                 </div> | ||||||
|  |                  | ||||||
|  |                 <div class="overflow-x-auto"> | ||||||
|  |                     <table class="min-w-full divide-y divide-gray-200"> | ||||||
|  |                         <thead class="bg-gray-50"> | ||||||
|  |                             <tr> | ||||||
|  |                                 <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider sticky left-0 bg-gray-50"> | ||||||
|  |                                     Élève | ||||||
|  |                                 </th> | ||||||
|  |                                 {% for element in grading_elements %} | ||||||
|  |                                     <th scope="col" class="px-3 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider min-w-32"> | ||||||
|  |                                         <div>{{ element.label }}</div> | ||||||
|  |                                         <div class="font-normal text-xs mt-1"> | ||||||
|  |                                             <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium | ||||||
|  |                                                 {% if element.grading_type == 'score' %}bg-purple-100 text-purple-800{% else %}bg-green-100 text-green-800{% endif %}"> | ||||||
|  |                                                 {% if element.grading_type == 'score' %}Score/{{ element.max_points|int }}{% else %}/{{ element.max_points }}{% endif %} | ||||||
|  |                                             </span> | ||||||
|  |                                         </div> | ||||||
|  |                                         {% if element.skill %} | ||||||
|  |                                             <div class="text-xs text-gray-400 mt-1">{{ element.skill }}</div> | ||||||
|  |                                         {% endif %} | ||||||
|  |                                     </th> | ||||||
|  |                                 {% endfor %} | ||||||
|  |                             </tr> | ||||||
|  |                         </thead> | ||||||
|  |                         <tbody class="bg-white divide-y divide-gray-200"> | ||||||
|  |                             {% for student in students %} | ||||||
|  |                                 <tr class="hover:bg-gray-50"> | ||||||
|  |                                     <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 sticky left-0 bg-white"> | ||||||
|  |                                         {{ student.first_name }} {{ student.last_name }} | ||||||
|  |                                     </td> | ||||||
|  |                                     {% for element in grading_elements %} | ||||||
|  |                                         {% set grade_key = student.id ~ '_' ~ element.id %} | ||||||
|  |                                         {% set existing_grade = existing_grades.get(grade_key) %} | ||||||
|  |                                         <td class="px-3 py-4 whitespace-nowrap text-center"> | ||||||
|  |                                             <div class="space-y-2"> | ||||||
|  |                                                 {% if element.grading_type == 'score' %} | ||||||
|  |                                                     <select name="grade_{{ student.id }}_{{ element.id }}" class="block w-full text-sm border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"> | ||||||
|  |                                                         <option value="">-</option> | ||||||
|  |                                                         <option value="." {% if existing_grade and existing_grade.value == '.' %}selected{% endif %}>. (non évalué)</option> | ||||||
|  |                                                         <option value="0" {% if existing_grade and existing_grade.value == '0' %}selected{% endif %}>0 (non acquis)</option> | ||||||
|  |                                                         <option value="1" {% if existing_grade and existing_grade.value == '1' %}selected{% endif %}>1 (en cours)</option> | ||||||
|  |                                                         <option value="2" {% if existing_grade and existing_grade.value == '2' %}selected{% endif %}>2 (acquis)</option> | ||||||
|  |                                                         <option value="3" {% if existing_grade and existing_grade.value == '3' %}selected{% endif %}>3 (expert)</option> | ||||||
|  |                                                     </select> | ||||||
|  |                                                 {% else %} | ||||||
|  |                                                     <input type="number" step="0.1" min="0" max="{{ element.max_points }}"  | ||||||
|  |                                                            name="grade_{{ student.id }}_{{ element.id }}"  | ||||||
|  |                                                            value="{% if existing_grade %}{{ existing_grade.value }}{% endif %}" | ||||||
|  |                                                            class="block w-full text-sm border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500 text-center" | ||||||
|  |                                                            placeholder="0"> | ||||||
|  |                                                 {% endif %} | ||||||
|  |                                                 <input type="text"  | ||||||
|  |                                                        name="comment_{{ student.id }}_{{ element.id }}"  | ||||||
|  |                                                        value="{% if existing_grade and existing_grade.comment %}{{ existing_grade.comment }}{% endif %}" | ||||||
|  |                                                        class="block w-full text-xs border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" | ||||||
|  |                                                        placeholder="Commentaire (optionnel)"> | ||||||
|  |                                             </div> | ||||||
|  |                                         </td> | ||||||
|  |                                     {% endfor %} | ||||||
|  |                                 </tr> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </tbody> | ||||||
|  |                     </table> | ||||||
|  |                 </div> | ||||||
|  |                  | ||||||
|  |                 <div class="px-6 py-4 bg-gray-50 border-t border-gray-200 flex justify-end space-x-3"> | ||||||
|  |                     <a href="{{ url_for('assessment_detail', id=assessment.id) }}" class="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"> | ||||||
|  |                         Annuler | ||||||
|  |                     </a> | ||||||
|  |                     <button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors"> | ||||||
|  |                         Sauvegarder les notes | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </form> | ||||||
|  |          | ||||||
|  |         <!-- Légende --> | ||||||
|  |         <div class="bg-white shadow rounded-lg"> | ||||||
|  |             <div class="px-6 py-4 border-b border-gray-200"> | ||||||
|  |                 <h3 class="text-lg font-medium text-gray-900">Légende des exercices</h3> | ||||||
|  |             </div> | ||||||
|  |             <div class="px-6 py-4"> | ||||||
|  |                 <div class="space-y-4"> | ||||||
|  |                     {% for exercise in assessment.exercises|sort(attribute='order') %} | ||||||
|  |                         <div class="border-l-4 border-blue-500 pl-4"> | ||||||
|  |                             <h4 class="font-medium text-gray-900">{{ exercise.title }}</h4> | ||||||
|  |                             {% if exercise.description %} | ||||||
|  |                                 <p class="text-sm text-gray-600 mt-1">{{ exercise.description }}</p> | ||||||
|  |                             {% endif %} | ||||||
|  |                             <div class="mt-2 space-y-1"> | ||||||
|  |                                 {% for element in exercise.grading_elements %} | ||||||
|  |                                     <div class="text-sm text-gray-700"> | ||||||
|  |                                         <span class="font-medium">{{ element.label }}</span> | ||||||
|  |                                         {% if element.skill %} - {{ element.skill }}{% endif %} | ||||||
|  |                                         {% if element.description %} : {{ element.description }}{% endif %} | ||||||
|  |                                     </div> | ||||||
|  |                                 {% endfor %} | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     {% endfor %} | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     {% endif %} | ||||||
|  | </div> | ||||||
|  | {% endblock %} | ||||||
| @@ -6,9 +6,9 @@ | |||||||
| <div class="space-y-6"> | <div class="space-y-6"> | ||||||
|     <div class="flex justify-between items-center"> |     <div class="flex justify-between items-center"> | ||||||
|         <h1 class="text-2xl font-bold text-gray-900">Gestion des évaluations</h1> |         <h1 class="text-2xl font-bold text-gray-900">Gestion des évaluations</h1> | ||||||
|         <button class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition-colors"> |         <a href="{{ url_for('new_assessment') }}" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition-colors"> | ||||||
|             Nouvelle évaluation |             Nouvelle évaluation | ||||||
|         </button> |         </a> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     {% if assessments %} |     {% if assessments %} | ||||||
| @@ -42,15 +42,15 @@ | |||||||
|                                     </div> |                                     </div> | ||||||
|                                 </div> |                                 </div> | ||||||
|                                 <div class="flex space-x-2"> |                                 <div class="flex space-x-2"> | ||||||
|                                     <button class="text-indigo-600 hover:text-indigo-900 text-sm font-medium"> |                                     <a href="{{ url_for('assessment_detail', id=assessment.id) }}" class="text-indigo-600 hover:text-indigo-900 text-sm font-medium"> | ||||||
|                                         Voir exercices |                                         Voir détails | ||||||
|                                     </button> |                                     </a> | ||||||
|                                     <button class="text-green-600 hover:text-green-900 text-sm font-medium"> |                                     <a href="{{ url_for('assessment_grading', assessment_id=assessment.id) }}" class="text-green-600 hover:text-green-900 text-sm font-medium"> | ||||||
|                                         Saisir notes |                                         Saisir notes | ||||||
|                                     </button> |                                     </a> | ||||||
|                                     <button class="text-gray-600 hover:text-gray-900 text-sm font-medium"> |                                     <a href="{{ url_for('edit_assessment', id=assessment.id) }}" class="text-gray-600 hover:text-gray-900 text-sm font-medium"> | ||||||
|                                         Modifier |                                         Modifier | ||||||
|                                     </button> |                                     </a> | ||||||
|                                 </div> |                                 </div> | ||||||
|                             </div> |                             </div> | ||||||
|                         </div> |                         </div> | ||||||
| @@ -66,9 +66,9 @@ | |||||||
|             <h3 class="mt-2 text-sm font-medium text-gray-900">Aucune évaluation</h3> |             <h3 class="mt-2 text-sm font-medium text-gray-900">Aucune évaluation</h3> | ||||||
|             <p class="mt-1 text-sm text-gray-500">Commencez par créer votre première évaluation.</p> |             <p class="mt-1 text-sm text-gray-500">Commencez par créer votre première évaluation.</p> | ||||||
|             <div class="mt-6"> |             <div class="mt-6"> | ||||||
|                 <button class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition-colors"> |                 <a href="{{ url_for('new_assessment') }}" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition-colors"> | ||||||
|                     Nouvelle évaluation |                     Nouvelle évaluation | ||||||
|                 </button> |                 </a> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|     {% endif %} |     {% endif %} | ||||||
|   | |||||||
							
								
								
									
										138
									
								
								templates/exercise_detail.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								templates/exercise_detail.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | |||||||
|  | {% extends "base.html" %} | ||||||
|  |  | ||||||
|  | {% block title %}{{ exercise.title }} - Gestion Scolaire{% endblock %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <div class="space-y-6"> | ||||||
|  |     <div class="flex justify-between items-center"> | ||||||
|  |         <div> | ||||||
|  |             <a href="{{ url_for('assessment_detail', id=assessment.id) }}" class="text-blue-600 hover:text-blue-800 text-sm font-medium mb-2 inline-block"> | ||||||
|  |                 ← Retour à l'évaluation "{{ assessment.title }}" | ||||||
|  |             </a> | ||||||
|  |             <h1 class="text-2xl font-bold text-gray-900">{{ exercise.title }}</h1> | ||||||
|  |             <p class="text-gray-600">{{ assessment.title }} - {{ assessment.class_group.name }}</p> | ||||||
|  |         </div> | ||||||
|  |         <div class="flex space-x-3"> | ||||||
|  |             <a href="{{ url_for('edit_exercise', assessment_id=assessment.id, id=exercise.id) }}" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors"> | ||||||
|  |                 Modifier | ||||||
|  |             </a> | ||||||
|  |             <button onclick="if(confirm('Êtes-vous sûr de vouloir supprimer cet exercice ?')) { document.getElementById('delete-form').submit(); }"  | ||||||
|  |                     class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors"> | ||||||
|  |                 Supprimer | ||||||
|  |             </button> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <form id="delete-form" method="POST" action="{{ url_for('delete_exercise', assessment_id=assessment.id, id=exercise.id) }}" style="display: none;"></form> | ||||||
|  |      | ||||||
|  |     <!-- Informations de l'exercice --> | ||||||
|  |     <div class="bg-white shadow rounded-lg"> | ||||||
|  |         <div class="px-6 py-4 border-b border-gray-200"> | ||||||
|  |             <h2 class="text-lg font-medium text-gray-900">Informations de l'exercice</h2> | ||||||
|  |         </div> | ||||||
|  |         <div class="px-6 py-4"> | ||||||
|  |             <dl class="grid grid-cols-1 md:grid-cols-2 gap-6"> | ||||||
|  |                 <div> | ||||||
|  |                     <dt class="text-sm font-medium text-gray-500">Ordre</dt> | ||||||
|  |                     <dd class="mt-1 text-sm text-gray-900">{{ exercise.order }}</dd> | ||||||
|  |                 </div> | ||||||
|  |                 <div> | ||||||
|  |                     <dt class="text-sm font-medium text-gray-500">Nombre d'éléments de notation</dt> | ||||||
|  |                     <dd class="mt-1 text-sm text-gray-900">{{ exercise.grading_elements|length }}</dd> | ||||||
|  |                 </div> | ||||||
|  |                 {% if exercise.description %} | ||||||
|  |                 <div class="md:col-span-2"> | ||||||
|  |                     <dt class="text-sm font-medium text-gray-500">Description</dt> | ||||||
|  |                     <dd class="mt-1 text-sm text-gray-900">{{ exercise.description }}</dd> | ||||||
|  |                 </div> | ||||||
|  |                 {% endif %} | ||||||
|  |             </dl> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <!-- Éléments de notation --> | ||||||
|  |     <div class="bg-white shadow rounded-lg"> | ||||||
|  |         <div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center"> | ||||||
|  |             <h2 class="text-lg font-medium text-gray-900">Éléments de notation</h2> | ||||||
|  |             <a href="{{ url_for('new_grading_element', assessment_id=assessment.id, exercise_id=exercise.id) }}" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors"> | ||||||
|  |                 Ajouter un élément | ||||||
|  |             </a> | ||||||
|  |         </div> | ||||||
|  |         <div class="px-6 py-4"> | ||||||
|  |             {% if exercise.grading_elements %} | ||||||
|  |                 <div class="space-y-4"> | ||||||
|  |                     {% for element in exercise.grading_elements %} | ||||||
|  |                         <div class="border border-gray-200 rounded-lg p-4"> | ||||||
|  |                             <div class="flex justify-between items-start"> | ||||||
|  |                                 <div class="flex-1"> | ||||||
|  |                                     <div class="flex items-center space-x-3"> | ||||||
|  |                                         <h3 class="text-sm font-medium text-gray-900">{{ element.label }}</h3> | ||||||
|  |                                         <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium | ||||||
|  |                                             {% if element.grading_type == 'score' %}bg-purple-100 text-purple-800{% else %}bg-green-100 text-green-800{% endif %}"> | ||||||
|  |                                             {% if element.grading_type == 'score' %}Score (0-3){% else %}Points{% endif %} | ||||||
|  |                                         </span> | ||||||
|  |                                     </div> | ||||||
|  |                                     {% if element.description %} | ||||||
|  |                                         <p class="text-sm text-gray-600 mt-1">{{ element.description }}</p> | ||||||
|  |                                     {% endif %} | ||||||
|  |                                     <div class="flex items-center space-x-4 text-xs text-gray-500 mt-2"> | ||||||
|  |                                         {% if element.skill %} | ||||||
|  |                                             <span><strong>Compétence:</strong> {{ element.skill }}</span> | ||||||
|  |                                         {% endif %} | ||||||
|  |                                         <span><strong>Barème:</strong> {{ element.max_points }} {% if element.grading_type == 'points' %}point(s){% else %}max{% endif %}</span> | ||||||
|  |                                     </div> | ||||||
|  |                                 </div> | ||||||
|  |                                 <div class="flex space-x-2 ml-4"> | ||||||
|  |                                     <a href="{{ url_for('edit_grading_element', assessment_id=assessment.id, exercise_id=exercise.id, id=element.id) }}" class="text-gray-600 hover:text-gray-800 text-sm font-medium"> | ||||||
|  |                                         Modifier | ||||||
|  |                                     </a> | ||||||
|  |                                     <button onclick="if(confirm('Êtes-vous sûr de vouloir supprimer cet élément ?')) { document.getElementById('delete-element-{{ element.id }}').submit(); }" class="text-red-600 hover:text-red-800 text-sm font-medium"> | ||||||
|  |                                         Supprimer | ||||||
|  |                                     </button> | ||||||
|  |                                     <form id="delete-element-{{ element.id }}" method="POST" action="{{ url_for('delete_grading_element', assessment_id=assessment.id, exercise_id=exercise.id, id=element.id) }}" style="display: none;"></form> | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     {% endfor %} | ||||||
|  |                 </div> | ||||||
|  |             {% else %} | ||||||
|  |                 <div class="text-center py-8"> | ||||||
|  |                     <svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48"> | ||||||
|  |                         <path d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8m9-16v20m5-14H9m1 4h8m1 2h8m-8 6h8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> | ||||||
|  |                     </svg> | ||||||
|  |                     <h3 class="mt-2 text-sm font-medium text-gray-900">Aucun élément de notation</h3> | ||||||
|  |                     <p class="mt-1 text-sm text-gray-500">Commencez par ajouter le premier élément de notation pour cet exercice.</p> | ||||||
|  |                     <div class="mt-6"> | ||||||
|  |                         <a href="{{ url_for('new_grading_element', assessment_id=assessment.id, exercise_id=exercise.id) }}" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors"> | ||||||
|  |                             Ajouter un élément | ||||||
|  |                         </a> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             {% endif %} | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <!-- Actions rapides --> | ||||||
|  |     <div class="bg-white shadow rounded-lg"> | ||||||
|  |         <div class="px-6 py-4 border-b border-gray-200"> | ||||||
|  |             <h2 class="text-lg font-medium text-gray-900">Actions</h2> | ||||||
|  |         </div> | ||||||
|  |         <div class="px-6 py-4"> | ||||||
|  |             <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> | ||||||
|  |                 <a href="{{ url_for('assessment_grading', assessment_id=assessment.id) }}" class="flex items-center justify-center px-4 py-3 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"> | ||||||
|  |                     <svg class="w-5 h-5 mr-2 text-green-600" fill="currentColor" viewBox="0 0 20 20"> | ||||||
|  |                         <path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zm0 4a1 1 0 011-1h12a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1V8z" clip-rule="evenodd"/> | ||||||
|  |                     </svg> | ||||||
|  |                     Saisir les notes | ||||||
|  |                 </a> | ||||||
|  |                 <button class="flex items-center justify-center px-4 py-3 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"> | ||||||
|  |                     <svg class="w-5 h-5 mr-2 text-purple-600" fill="currentColor" viewBox="0 0 20 20"> | ||||||
|  |                         <path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 011 1v1a1 1 0 01-1 1H4a1 1 0 01-1-1v-1zM3 4a1 1 0 011-1h12a1 1 0 011 1v1a1 1 0 01-1 1H4a1 1 0 01-1-1V4z" clip-rule="evenodd"/> | ||||||
|  |                     </svg> | ||||||
|  |                     Voir les résultats | ||||||
|  |                 </button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										74
									
								
								templates/exercise_form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								templates/exercise_form.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | {% extends "base.html" %} | ||||||
|  |  | ||||||
|  | {% block title %}{{ title }} - Gestion Scolaire{% endblock %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <div class="max-w-2xl mx-auto"> | ||||||
|  |     <div class="mb-6"> | ||||||
|  |         <a href="{{ url_for('assessment_detail', id=assessment.id) }}" class="text-blue-600 hover:text-blue-800 text-sm font-medium"> | ||||||
|  |             ← Retour à l'évaluation "{{ assessment.title }}" | ||||||
|  |         </a> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <div class="bg-white shadow rounded-lg"> | ||||||
|  |         <div class="px-6 py-4 border-b border-gray-200"> | ||||||
|  |             <h1 class="text-xl font-semibold text-gray-900">{{ title }}</h1> | ||||||
|  |             <p class="text-sm text-gray-600 mt-1">Évaluation : {{ assessment.title }} ({{ assessment.class_group.name }})</p> | ||||||
|  |         </div> | ||||||
|  |          | ||||||
|  |         <form method="POST" class="px-6 py-4 space-y-6"> | ||||||
|  |             {{ form.hidden_tag() }} | ||||||
|  |              | ||||||
|  |             <div> | ||||||
|  |                 <label for="{{ form.title.id }}" class="block text-sm font-medium text-gray-700 mb-1"> | ||||||
|  |                     {{ form.title.label.text }} | ||||||
|  |                 </label> | ||||||
|  |                 {{ 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 %} | ||||||
|  |                     <div class="mt-1 text-sm text-red-600"> | ||||||
|  |                         {% for error in form.title.errors %} | ||||||
|  |                             <p>{{ error }}</p> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </div> | ||||||
|  |                 {% endif %} | ||||||
|  |             </div> | ||||||
|  |              | ||||||
|  |             <div> | ||||||
|  |                 <label for="{{ form.description.id }}" class="block text-sm font-medium text-gray-700 mb-1"> | ||||||
|  |                     {{ form.description.label.text }} | ||||||
|  |                 </label> | ||||||
|  |                 {{ 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 %} | ||||||
|  |                     <div class="mt-1 text-sm text-red-600"> | ||||||
|  |                         {% for error in form.description.errors %} | ||||||
|  |                             <p>{{ error }}</p> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </div> | ||||||
|  |                 {% endif %} | ||||||
|  |             </div> | ||||||
|  |              | ||||||
|  |             <div> | ||||||
|  |                 <label for="{{ form.order.id }}" class="block text-sm font-medium text-gray-700 mb-1"> | ||||||
|  |                     {{ form.order.label.text }} | ||||||
|  |                 </label> | ||||||
|  |                 {{ 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 %} | ||||||
|  |                     <div class="mt-1 text-sm text-red-600"> | ||||||
|  |                         {% for error in form.order.errors %} | ||||||
|  |                             <p>{{ error }}</p> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </div> | ||||||
|  |                 {% endif %} | ||||||
|  |                 <p class="mt-1 text-xs text-gray-500">L'ordre détermine l'affichage des exercices dans l'évaluation</p> | ||||||
|  |             </div> | ||||||
|  |              | ||||||
|  |             <div class="flex justify-end space-x-3 pt-4 border-t border-gray-200"> | ||||||
|  |                 <a href="{{ url_for('assessment_detail', id=assessment.id) }}" class="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"> | ||||||
|  |                     Annuler | ||||||
|  |                 </a> | ||||||
|  |                 {{ form.submit(class="px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors") }} | ||||||
|  |             </div> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										114
									
								
								templates/grading_element_form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								templates/grading_element_form.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | |||||||
|  | {% extends "base.html" %} | ||||||
|  |  | ||||||
|  | {% block title %}{{ title }} - Gestion Scolaire{% endblock %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <div class="max-w-2xl mx-auto"> | ||||||
|  |     <div class="mb-6"> | ||||||
|  |         <a href="{{ url_for('exercise_detail', assessment_id=assessment.id, id=exercise.id) }}" class="text-blue-600 hover:text-blue-800 text-sm font-medium"> | ||||||
|  |             ← Retour à l'exercice "{{ exercise.title }}" | ||||||
|  |         </a> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <div class="bg-white shadow rounded-lg"> | ||||||
|  |         <div class="px-6 py-4 border-b border-gray-200"> | ||||||
|  |             <h1 class="text-xl font-semibold text-gray-900">{{ title }}</h1> | ||||||
|  |             <p class="text-sm text-gray-600 mt-1">{{ assessment.title }} > {{ exercise.title }}</p> | ||||||
|  |         </div> | ||||||
|  |          | ||||||
|  |         <form method="POST" class="px-6 py-4 space-y-6"> | ||||||
|  |             {{ form.hidden_tag() }} | ||||||
|  |              | ||||||
|  |             <div> | ||||||
|  |                 <label for="{{ form.label.id }}" class="block text-sm font-medium text-gray-700 mb-1"> | ||||||
|  |                     {{ form.label.label.text }} | ||||||
|  |                 </label> | ||||||
|  |                 {{ 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 %} | ||||||
|  |                     <div class="mt-1 text-sm text-red-600"> | ||||||
|  |                         {% for error in form.label.errors %} | ||||||
|  |                             <p>{{ error }}</p> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </div> | ||||||
|  |                 {% endif %} | ||||||
|  |                 <p class="mt-1 text-xs text-gray-500">Ex: "Calcul de base", "Méthode", "Présentation"</p> | ||||||
|  |             </div> | ||||||
|  |              | ||||||
|  |             <div> | ||||||
|  |                 <label for="{{ form.description.id }}" class="block text-sm font-medium text-gray-700 mb-1"> | ||||||
|  |                     {{ form.description.label.text }} | ||||||
|  |                 </label> | ||||||
|  |                 {{ 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 %} | ||||||
|  |                     <div class="mt-1 text-sm text-red-600"> | ||||||
|  |                         {% for error in form.description.errors %} | ||||||
|  |                             <p>{{ error }}</p> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </div> | ||||||
|  |                 {% endif %} | ||||||
|  |             </div> | ||||||
|  |              | ||||||
|  |             <div> | ||||||
|  |                 <label for="{{ form.skill.id }}" class="block text-sm font-medium text-gray-700 mb-1"> | ||||||
|  |                     {{ form.skill.label.text }} | ||||||
|  |                 </label> | ||||||
|  |                 {{ 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 %} | ||||||
|  |                     <div class="mt-1 text-sm text-red-600"> | ||||||
|  |                         {% for error in form.skill.errors %} | ||||||
|  |                             <p>{{ error }}</p> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </div> | ||||||
|  |                 {% endif %} | ||||||
|  |                 <p class="mt-1 text-xs text-gray-500">Ex: "Calculer", "Raisonner", "Communiquer"</p> | ||||||
|  |             </div> | ||||||
|  |              | ||||||
|  |             <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | ||||||
|  |                 <div> | ||||||
|  |                     <label for="{{ form.max_points.id }}" class="block text-sm font-medium text-gray-700 mb-1"> | ||||||
|  |                         {{ form.max_points.label.text }} | ||||||
|  |                     </label> | ||||||
|  |                     {{ 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 %} | ||||||
|  |                         <div class="mt-1 text-sm text-red-600"> | ||||||
|  |                             {% for error in form.max_points.errors %} | ||||||
|  |                                 <p>{{ error }}</p> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </div> | ||||||
|  |                     {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                  | ||||||
|  |                 <div> | ||||||
|  |                     <label for="{{ form.grading_type.id }}" class="block text-sm font-medium text-gray-700 mb-1"> | ||||||
|  |                         {{ form.grading_type.label.text }} | ||||||
|  |                     </label> | ||||||
|  |                     {{ 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 %} | ||||||
|  |                         <div class="mt-1 text-sm text-red-600"> | ||||||
|  |                             {% for error in form.grading_type.errors %} | ||||||
|  |                                 <p>{{ error }}</p> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </div> | ||||||
|  |                     {% endif %} | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |              | ||||||
|  |             <!-- Aide sur les types de notation --> | ||||||
|  |             <div class="bg-blue-50 border border-blue-200 rounded-md p-4"> | ||||||
|  |                 <h4 class="text-sm font-medium text-blue-900 mb-2">Types de notation</h4> | ||||||
|  |                 <div class="text-xs text-blue-800 space-y-1"> | ||||||
|  |                     <p><strong>Points :</strong> Notation classique (ex: 2.5/4 points)</p> | ||||||
|  |                     <p><strong>Score :</strong> Évaluation par niveaux (0=non acquis, 1=en cours, 2=acquis, 3=expert, .=non évalué)</p> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |              | ||||||
|  |             <div class="flex justify-end space-x-3 pt-4 border-t border-gray-200"> | ||||||
|  |                 <a href="{{ url_for('exercise_detail', assessment_id=assessment.id, id=exercise.id) }}" class="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"> | ||||||
|  |                     Annuler | ||||||
|  |                 </a> | ||||||
|  |                 {{ form.submit(class="px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors") }} | ||||||
|  |             </div> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | {% endblock %} | ||||||
		Reference in New Issue
	
	Block a user