feat: completar fases 3 y 4 del roadmap mvp (gradebook, drag-and-drop, impersonacion) y actualizar README
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
from datetime import datetime
|
||||
from app import db
|
||||
|
||||
|
||||
class MilestoneGrade(db.Model):
|
||||
"""
|
||||
Calificación y seguimiento pedagógico individual de un alumno en un hito evaluativo (Fase 3 MVP).
|
||||
Vincula un AcademicMilestone con un estudiante matriculado (StudentEnrollment / User).
|
||||
"""
|
||||
__tablename__ = 'milestone_grades'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
milestone_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('academic_milestones.id', ondelete='CASCADE'),
|
||||
nullable=False,
|
||||
index=True
|
||||
)
|
||||
enrollment_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('student_enrollments.id', ondelete='CASCADE'),
|
||||
nullable=False,
|
||||
index=True
|
||||
)
|
||||
student_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('users.id', ondelete='CASCADE'),
|
||||
nullable=False,
|
||||
index=True
|
||||
)
|
||||
score = db.Column(db.Float, nullable=True) # Calificación numérica de 1.0 a 10.0
|
||||
is_absent = db.Column(db.Boolean, default=False) # Flag de Ausente
|
||||
feedback = db.Column(db.Text, nullable=True) # Devolución formativa/pedagógica
|
||||
graded_by_user_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('users.id', ondelete='SET NULL'),
|
||||
nullable=True
|
||||
)
|
||||
graded_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# Restricción de unicidad: un estudiante tiene una única nota por hito
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint('milestone_id', 'student_id', name='uq_milestone_student'),
|
||||
)
|
||||
|
||||
# Relaciones
|
||||
milestone = db.relationship('AcademicMilestone', backref=db.backref('grades', lazy='dynamic', cascade='all, delete-orphan'))
|
||||
enrollment = db.relationship('StudentEnrollment', backref=db.backref('milestone_grades', lazy='dynamic', cascade='all, delete-orphan'))
|
||||
student = db.relationship('User', foreign_keys=[student_id], backref=db.backref('received_grades', lazy='dynamic'))
|
||||
graded_by = db.relationship('User', foreign_keys=[graded_by_user_id])
|
||||
|
||||
@property
|
||||
def is_passed(self) -> bool:
|
||||
"""Determina si la nota está aprobada (>= 4.00 y no ausente)."""
|
||||
if self.is_absent or self.score is None:
|
||||
return False
|
||||
return self.score >= 4.0
|
||||
|
||||
@property
|
||||
def display_score(self) -> str:
|
||||
"""Representación textual estándar: 'Ausente', '7.5' o '-'."""
|
||||
if self.is_absent:
|
||||
return 'Ausente'
|
||||
if self.score is not None:
|
||||
return f'{self.score:.1f}' if self.score % 1 != 0 else f'{int(self.score)}'
|
||||
return '-'
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'milestone_id': self.milestone_id,
|
||||
'milestone_title': self.milestone.title if self.milestone else None,
|
||||
'milestone_type': self.milestone.tipo if self.milestone else None,
|
||||
'milestone_color': self.milestone.color if self.milestone else None,
|
||||
'enrollment_id': self.enrollment_id,
|
||||
'student_id': self.student_id,
|
||||
'student_name': self.student.name if self.student else None,
|
||||
'student_email': self.student.email if self.student else None,
|
||||
'score': self.score,
|
||||
'display_score': self.display_score,
|
||||
'is_absent': bool(self.is_absent),
|
||||
'is_passed': self.is_passed,
|
||||
'feedback': self.feedback or '',
|
||||
'graded_by_user_id': self.graded_by_user_id,
|
||||
'graded_by_name': self.graded_by.name if self.graded_by else None,
|
||||
'graded_at': self.graded_at.isoformat() if self.graded_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return f'<MilestoneGrade milestone={self.milestone_id} student={self.student_id} score={self.display_score}>'
|
||||
Reference in New Issue
Block a user