Add students assistance data in PDF file
Browse files- app/main.py +64 -1
- app/requirements.txt +1 -0
- app/templates/course_details.html +11 -4
app/main.py
CHANGED
|
@@ -14,9 +14,11 @@ import threading
|
|
| 14 |
import time
|
| 15 |
import base64
|
| 16 |
import csv
|
|
|
|
| 17 |
import requests
|
| 18 |
import datetime
|
| 19 |
-
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash
|
|
|
|
| 20 |
from flask_socketio import SocketIO, emit
|
| 21 |
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
|
| 22 |
from werkzeug.security import generate_password_hash, check_password_hash
|
|
@@ -1114,6 +1116,67 @@ def api_attendance():
|
|
| 1114 |
|
| 1115 |
return jsonify({"status": "error", "message": "No label provided"}), 400
|
| 1116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1117 |
@app.route('/repos')
|
| 1118 |
def repos():
|
| 1119 |
GIT_TOKEN = os.getenv("GITLAB_TOKEN")
|
|
|
|
| 14 |
import time
|
| 15 |
import base64
|
| 16 |
import csv
|
| 17 |
+
import io
|
| 18 |
import requests
|
| 19 |
import datetime
|
| 20 |
+
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, send_file
|
| 21 |
+
from fpdf import FPDF
|
| 22 |
from flask_socketio import SocketIO, emit
|
| 23 |
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
|
| 24 |
from werkzeug.security import generate_password_hash, check_password_hash
|
|
|
|
| 1116 |
|
| 1117 |
return jsonify({"status": "error", "message": "No label provided"}), 400
|
| 1118 |
|
| 1119 |
+
class AttendancePDF(FPDF):
|
| 1120 |
+
def header(self):
|
| 1121 |
+
self.set_font('Arial', 'B', 15)
|
| 1122 |
+
self.cell(0, 10, 'MAKER SPACE - REPORTE DE ASISTENCIA', 0, 1, 'C')
|
| 1123 |
+
self.ln(5)
|
| 1124 |
+
|
| 1125 |
+
def footer(self):
|
| 1126 |
+
self.set_y(-15)
|
| 1127 |
+
self.set_font('Arial', 'I', 8)
|
| 1128 |
+
self.cell(0, 10, f'Página {self.page_no()}', 0, 0, 'C')
|
| 1129 |
+
|
| 1130 |
+
@app.route('/export/attendance/<student_id>')
|
| 1131 |
+
@login_required
|
| 1132 |
+
def export_attendance(student_id):
|
| 1133 |
+
# Buscar registros del estudiante
|
| 1134 |
+
all_logs = []
|
| 1135 |
+
if isinstance(face_mgr.faces, dict):
|
| 1136 |
+
all_logs = face_mgr.faces.get("attendance_log", [])
|
| 1137 |
+
|
| 1138 |
+
student_logs = [log for log in all_logs if log.get('student_id') == student_id]
|
| 1139 |
+
|
| 1140 |
+
if not student_logs:
|
| 1141 |
+
flash("No hay registros de asistencia para este estudiante.", "orange")
|
| 1142 |
+
return redirect(request.referrer or url_for('classroom_dashboard'))
|
| 1143 |
+
|
| 1144 |
+
student_name = student_logs[0].get('student_name', 'Estudiante')
|
| 1145 |
+
|
| 1146 |
+
# Crear PDF
|
| 1147 |
+
pdf = AttendancePDF()
|
| 1148 |
+
pdf.add_page()
|
| 1149 |
+
pdf.set_font("Arial", size=12)
|
| 1150 |
+
|
| 1151 |
+
# Info Estudiante
|
| 1152 |
+
pdf.set_font("Arial", 'B', 12)
|
| 1153 |
+
pdf.cell(0, 10, f"Estudiante: {student_name}", 0, 1)
|
| 1154 |
+
pdf.cell(0, 10, f"ID: {student_id}", 0, 1)
|
| 1155 |
+
pdf.ln(5)
|
| 1156 |
+
|
| 1157 |
+
# Tabla (Estilo Excel)
|
| 1158 |
+
pdf.set_fill_color(200, 220, 255)
|
| 1159 |
+
pdf.set_font("Arial", 'B', 10)
|
| 1160 |
+
pdf.cell(40, 10, "Fecha", 1, 0, 'C', 1)
|
| 1161 |
+
pdf.cell(40, 10, "Hora", 1, 0, 'C', 1)
|
| 1162 |
+
pdf.cell(40, 10, "Estado", 1, 0, 'C', 1)
|
| 1163 |
+
pdf.cell(70, 10, "Curso ID", 1, 1, 'C', 1)
|
| 1164 |
+
|
| 1165 |
+
pdf.set_font("Arial", size=10)
|
| 1166 |
+
for log in student_logs:
|
| 1167 |
+
pdf.cell(40, 10, log.get('date', '-'), 1)
|
| 1168 |
+
pdf.cell(40, 10, log.get('time', '-'), 1)
|
| 1169 |
+
pdf.cell(40, 10, log.get('status', 'PRESENTE'), 1)
|
| 1170 |
+
pdf.cell(70, 10, log.get('course_id', '-'), 1, 1)
|
| 1171 |
+
|
| 1172 |
+
output = io.BytesIO()
|
| 1173 |
+
pdf_content = pdf.output()
|
| 1174 |
+
output.write(pdf_content)
|
| 1175 |
+
output.seek(0)
|
| 1176 |
+
|
| 1177 |
+
filename = f"Asistencia_{student_name.replace(' ', '_')}.pdf"
|
| 1178 |
+
return send_file(output, as_attachment=True, download_name=filename, mimetype='application/pdf')
|
| 1179 |
+
|
| 1180 |
@app.route('/repos')
|
| 1181 |
def repos():
|
| 1182 |
GIT_TOKEN = os.getenv("GITLAB_TOKEN")
|
app/requirements.txt
CHANGED
|
@@ -13,3 +13,4 @@ datasets
|
|
| 13 |
huggingface_hub
|
| 14 |
mediapipe
|
| 15 |
opencv-python-headless
|
|
|
|
|
|
| 13 |
huggingface_hub
|
| 14 |
mediapipe
|
| 15 |
opencv-python-headless
|
| 16 |
+
fpdf2
|
app/templates/course_details.html
CHANGED
|
@@ -67,10 +67,17 @@
|
|
| 67 |
<tr style="border-bottom: 1px solid rgba(255,255,255,0.05);">
|
| 68 |
<td style="padding: 1rem; font-weight: bold;">{{ student.name }}</td>
|
| 69 |
<td style="padding: 1rem;">
|
| 70 |
-
<
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
</td>
|
| 75 |
<td style="padding: 1rem;" class="text-dim">
|
| 76 |
{% if student.attendance %}
|
|
|
|
| 67 |
<tr style="border-bottom: 1px solid rgba(255,255,255,0.05);">
|
| 68 |
<td style="padding: 1rem; font-weight: bold;">{{ student.name }}</td>
|
| 69 |
<td style="padding: 1rem;">
|
| 70 |
+
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
| 71 |
+
<span class="badge"
|
| 72 |
+
style="background: rgba(16, 185, 129, 0.2); color: #10b981; padding: 0.2rem 0.5rem; border-radius: 0.5rem;">
|
| 73 |
+
{{ student.attendance|length }}
|
| 74 |
+
</span>
|
| 75 |
+
<a href="{{ url_for('export_attendance', student_id=student.id) }}" class="btn glass"
|
| 76 |
+
style="padding: 0.2rem 0.5rem; font-size: 0.8rem; background: rgba(239, 68, 68, 0.1); color: #ef4444;"
|
| 77 |
+
title="Descargar PDF">
|
| 78 |
+
<i class="fas fa-file-pdf"></i> PDF
|
| 79 |
+
</a>
|
| 80 |
+
</div>
|
| 81 |
</td>
|
| 82 |
<td style="padding: 1rem;" class="text-dim">
|
| 83 |
{% if student.attendance %}
|