764 lines
30 KiB
Python
764 lines
30 KiB
Python
import re
|
|
import mimetypes
|
|
from datetime import date
|
|
from pathlib import Path
|
|
from urllib.parse import urlencode
|
|
|
|
from django.contrib import messages
|
|
from django.db.models import Q
|
|
from django.http import FileResponse, Http404, HttpRequest, HttpResponseForbidden
|
|
from django.shortcuts import redirect, render
|
|
|
|
from core.models import Aulario
|
|
from core.shell import require_axia_login, shell_context
|
|
|
|
from .models import ComedorMenu, ComedorMenuType, Cuaderno, StudentAttachment
|
|
|
|
|
|
def _safe_organization_id(value: str) -> str:
|
|
return re.sub(r"[^A-Za-z0-9._-]", "", (value or ""))
|
|
|
|
|
|
def _safe_aulario_id(value: str) -> str:
|
|
value = (value or "").split("/")[-1].split("\\")[-1]
|
|
return re.sub(r"[^A-Za-z0-9._-]", "", value)
|
|
|
|
|
|
def _safe_alumno_name(value: str) -> str:
|
|
value = (value or "").split("/")[-1].split("\\")[-1].strip()
|
|
value = re.sub(r"[\x00-\x1F\x7F]", "", value)
|
|
value = value.replace("/", "").replace("\\", "")
|
|
return value
|
|
|
|
|
|
def _valid_image(uploaded_file) -> bool:
|
|
content_type = (getattr(uploaded_file, "content_type", "") or "").lower()
|
|
if not content_type.startswith("image/"):
|
|
return False
|
|
allowed_ext = {".jpg", ".jpeg", ".png", ".gif", ".webp"}
|
|
ext = Path(uploaded_file.name or "").suffix.lower()
|
|
return ext in allowed_ext
|
|
|
|
|
|
def _alumnos_permission(request: HttpRequest) -> bool:
|
|
return (
|
|
request.axia_user.has_permission("aulatek:admin")
|
|
or request.axia_user.has_permission("aulatek:docente")
|
|
or request.axia_user.has_permission("entreaulas:docente")
|
|
)
|
|
|
|
|
|
def _cuadernos_permission(request: HttpRequest) -> bool:
|
|
return (
|
|
request.axia_user.has_permission("aulatek:docente")
|
|
or request.axia_user.has_permission("entreaulas:docente")
|
|
)
|
|
|
|
|
|
def _alumnos_access(request: HttpRequest):
|
|
gate = require_axia_login(request)
|
|
if gate:
|
|
return gate, None, None
|
|
if not _alumnos_permission(request):
|
|
return HttpResponseForbidden("No tienes permiso para gestionar alumnos."), None, None
|
|
org_id = _safe_organization_id(request.axia_user.active_org)
|
|
if not org_id:
|
|
return None, None, None
|
|
return None, request.axia_user, org_id
|
|
|
|
|
|
def _cuadernos_access(request: HttpRequest):
|
|
gate, user, org_id = _alumnos_access(request)
|
|
if gate:
|
|
return gate, None, None
|
|
if not _cuadernos_permission(request):
|
|
return HttpResponseForbidden("No tienes permiso para ver cuadernos."), None, None
|
|
return None, user, org_id
|
|
|
|
|
|
def _comedor_access(request: HttpRequest):
|
|
gate, user, org_id = _alumnos_access(request)
|
|
if gate:
|
|
return gate, None, None, None
|
|
aulario_id = _safe_aulario_id(request.GET.get("aulario", ""))
|
|
if not aulario_id or not org_id:
|
|
return None, user, org_id, None
|
|
aulario_row = Aulario.objects.filter(org_id=org_id, aulario_id=aulario_id).first()
|
|
if not aulario_row:
|
|
raise Http404("Aulario no encontrado")
|
|
return None, user, org_id, aulario_row
|
|
|
|
|
|
def _parse_menu_date(value: str):
|
|
text = (value or "").strip()
|
|
if not text:
|
|
return None
|
|
try:
|
|
return date.fromisoformat(text)
|
|
except ValueError:
|
|
return None
|
|
|
|
|
|
def _safe_titulo(value: str) -> str:
|
|
value = (value or "").strip()
|
|
value = re.sub(r"[\x00-\x1F\x7F]", "", value)
|
|
return value[:140]
|
|
|
|
|
|
def _signature_verified(request: HttpRequest) -> bool:
|
|
return (request.POST.get("_firma_signed") or "") == "1"
|
|
|
|
|
|
def aulatek_home(request: HttpRequest):
|
|
gate = require_axia_login(request)
|
|
if gate:
|
|
return gate
|
|
|
|
user = request.axia_user
|
|
aularios = []
|
|
if user.active_org:
|
|
rows = Aulario.objects.filter(org_id=user.active_org).order_by("aulario_id")
|
|
if user.aulas:
|
|
rows = rows.filter(aulario_id__in=user.aulas)
|
|
for row in rows:
|
|
payload = row.payload
|
|
if row.icon_file:
|
|
icon_url = row.icon_file.url
|
|
elif payload.get("icon"):
|
|
icon_url = payload.get("icon")
|
|
else:
|
|
icon_url = "/static/arasaac/aulario.png"
|
|
aularios.append(
|
|
{
|
|
"id": row.aulario_id,
|
|
"name": payload.get("name") or row.aulario_id,
|
|
"icon": icon_url,
|
|
}
|
|
)
|
|
|
|
context = shell_context(request, "aulatek", "AulaTek")
|
|
context["aularios"] = aularios
|
|
return render(request, "aulatek/index.html", context)
|
|
|
|
|
|
def aulatek_aulario(request: HttpRequest):
|
|
gate = require_axia_login(request)
|
|
if gate:
|
|
return gate
|
|
|
|
aulario_id = (request.GET.get("id") or "").strip()
|
|
row = Aulario.objects.filter(org_id=request.axia_user.active_org, aulario_id=aulario_id).first()
|
|
if not row:
|
|
raise Http404("Aulario no encontrado")
|
|
|
|
payload = row.payload
|
|
context = shell_context(request, "aulatek", f"Aulario: {payload.get('name') or aulario_id}")
|
|
context.update(
|
|
{
|
|
"aulario": {"id": aulario_id, "name": payload.get("name") or aulario_id},
|
|
"tiles": [
|
|
{"href": f"/aulatek/paneldiario/?aulario={aulario_id}", "label": "Panel Diario", "icon": "/static/arasaac/pdi.png", "variant": "primary"},
|
|
{
|
|
"href": f"/aulatek/alumnos/?aulario={aulario_id}",
|
|
"label": "Gestion de Alumnos",
|
|
"icon": "/static/arasaac/alumnos.png",
|
|
"variant": "info",
|
|
"visible": _alumnos_permission(request),
|
|
},
|
|
{
|
|
"href": f"/aulatek/cuadernos/?aulario={aulario_id}",
|
|
"label": "Cuadernos",
|
|
"icon": "/static/iconexperience/contract.png",
|
|
"variant": "dark",
|
|
"visible": _cuadernos_permission(request),
|
|
},
|
|
{
|
|
"href": f"/sysadmin/aularios/?aulario={aulario_id}",
|
|
"label": "Cambiar Ajustes",
|
|
"icon": "/static/iconexperience/gear_edit.png",
|
|
"variant": "secondary",
|
|
"visible": request.axia_user.has_permission("sysadmin:access"),
|
|
},
|
|
{"href": f"/aulatek/comedor/?aulario={aulario_id}", "label": "Menu del Comedor", "icon": "/static/arasaac/comedor.png", "variant": "success"},
|
|
{"href": f"/aulatek/proyectos/?aulario={aulario_id}", "label": "Proyectos", "icon": "", "variant": "warning"},
|
|
],
|
|
}
|
|
)
|
|
return render(request, "aulatek/aulario.html", context)
|
|
|
|
|
|
def _placeholder(request: HttpRequest, title: str):
|
|
gate = require_axia_login(request)
|
|
if gate:
|
|
return gate
|
|
context = shell_context(request, "aulatek", title)
|
|
context.update({"module_title": title, "module_description": "Modulo en migracion a Django.", "tiles": []})
|
|
return render(request, "core/module.html", context)
|
|
|
|
|
|
def paneldiario(request: HttpRequest):
|
|
return _placeholder(request, "Panel Diario")
|
|
|
|
|
|
def alumnos(request: HttpRequest):
|
|
gate, _, org_id = _alumnos_access(request)
|
|
if gate:
|
|
return gate
|
|
|
|
aulario_id = _safe_aulario_id(request.GET.get("aulario", ""))
|
|
if not aulario_id or not org_id:
|
|
context = shell_context(request, "aulatek", "Gestion de Alumnos")
|
|
context.update({"module_title": "Gestion de Alumnos", "aulario_id": "", "students": []})
|
|
return render(request, "aulatek/alumnos.html", context)
|
|
|
|
if not Aulario.objects.filter(org_id=org_id, aulario_id=aulario_id).exists():
|
|
raise Http404("Aulario no encontrado")
|
|
|
|
students = []
|
|
rows = StudentAttachment.objects.filter(org_id=org_id, aulario_id=aulario_id).order_by("alumno")
|
|
for row in rows:
|
|
students.append(
|
|
{
|
|
"id": row.id,
|
|
"name": row.alumno,
|
|
"has_photo": bool(row.photo),
|
|
}
|
|
)
|
|
|
|
context = shell_context(request, "aulatek", "Gestion de Alumnos")
|
|
context.update(
|
|
{
|
|
"module_title": "Gestion de Alumnos",
|
|
"aulario_id": aulario_id,
|
|
"students": students,
|
|
"can_view_cuadernos": _cuadernos_permission(request),
|
|
}
|
|
)
|
|
return render(request, "aulatek/alumnos.html", context)
|
|
|
|
|
|
def alumno_new(request: HttpRequest):
|
|
gate, _, org_id = _alumnos_access(request)
|
|
if gate:
|
|
return gate
|
|
|
|
aulario_id = _safe_aulario_id(request.GET.get("aulario", ""))
|
|
if not aulario_id or not org_id:
|
|
messages.error(request, "No se ha indicado un aulario valido.")
|
|
return redirect("/aulatek/")
|
|
if not Aulario.objects.filter(org_id=org_id, aulario_id=aulario_id).exists():
|
|
raise Http404("Aulario no encontrado")
|
|
|
|
if request.method == "POST":
|
|
nombre = _safe_alumno_name(request.POST.get("nombre", ""))
|
|
if not nombre:
|
|
messages.error(request, "El nombre no puede estar vacio.")
|
|
return redirect(f"/aulatek/alumnos/new/?aulario={aulario_id}")
|
|
if StudentAttachment.objects.filter(org_id=org_id, aulario_id=aulario_id, alumno=nombre).exists():
|
|
messages.error(request, "Ya existe un alumno con ese nombre.")
|
|
return redirect(f"/aulatek/alumnos/new/?aulario={aulario_id}")
|
|
|
|
student = StudentAttachment(org_id=org_id, aulario_id=aulario_id, alumno=nombre, data={})
|
|
photo = request.FILES.get("photo")
|
|
if photo and _valid_image(photo):
|
|
student.photo = photo
|
|
elif photo:
|
|
messages.warning(request, "La foto no se ha guardado porque no era una imagen valida.")
|
|
student.save()
|
|
messages.success(request, "Alumno anadido correctamente.")
|
|
return redirect(f"/aulatek/alumnos/?aulario={aulario_id}")
|
|
|
|
context = shell_context(request, "aulatek", "Nuevo Alumno")
|
|
context.update({"module_title": "Nuevo Alumno", "aulario_id": aulario_id})
|
|
return render(request, "aulatek/alumnos_new.html", context)
|
|
|
|
|
|
def alumno_edit(request: HttpRequest, student_id: int):
|
|
gate, _, org_id = _alumnos_access(request)
|
|
if gate:
|
|
return gate
|
|
|
|
student = StudentAttachment.objects.filter(id=student_id, org_id=org_id).first()
|
|
if not student:
|
|
raise Http404("Alumno no encontrado")
|
|
|
|
if request.method == "POST":
|
|
nombre_new = _safe_alumno_name(request.POST.get("nombre_new", ""))
|
|
if not nombre_new:
|
|
messages.error(request, "Nombre invalido.")
|
|
return redirect(f"/aulatek/alumnos/edit/{student.id}/")
|
|
|
|
if nombre_new != student.alumno and StudentAttachment.objects.filter(org_id=org_id, aulario_id=student.aulario_id, alumno=nombre_new).exists():
|
|
messages.error(request, "Ya existe un alumno con ese nombre.")
|
|
return redirect(f"/aulatek/alumnos/edit/{student.id}/")
|
|
|
|
student.alumno = nombre_new
|
|
|
|
photo = request.FILES.get("photo")
|
|
if photo and _valid_image(photo):
|
|
if student.photo:
|
|
student.photo.delete(save=False)
|
|
student.photo = photo
|
|
elif photo:
|
|
messages.warning(request, "La foto no se ha guardado porque no era una imagen valida.")
|
|
|
|
student.save()
|
|
messages.success(request, "Alumno actualizado correctamente.")
|
|
return redirect(f"/aulatek/alumnos/?aulario={student.aulario_id}")
|
|
|
|
context = shell_context(request, "aulatek", f"Editar Alumno: {student.alumno}")
|
|
context.update({"module_title": "Editar Alumno", "student": student})
|
|
return render(request, "aulatek/alumnos_edit.html", context)
|
|
|
|
|
|
def alumno_delete(request: HttpRequest, student_id: int):
|
|
gate, _, org_id = _alumnos_access(request)
|
|
if gate:
|
|
return gate
|
|
if request.method != "POST":
|
|
return HttpResponseForbidden("Metodo no permitido.")
|
|
if not _signature_verified(request):
|
|
return HttpResponseForbidden("Firma requerida para eliminar.")
|
|
|
|
student = StudentAttachment.objects.filter(id=student_id, org_id=org_id).first()
|
|
if not student:
|
|
raise Http404("Alumno no encontrado")
|
|
|
|
aulario_id = student.aulario_id
|
|
if student.photo:
|
|
student.photo.delete(save=False)
|
|
student.delete()
|
|
messages.success(request, "Alumno eliminado correctamente.")
|
|
return redirect(f"/aulatek/alumnos/?aulario={aulario_id}")
|
|
|
|
|
|
def alumno_photo(request: HttpRequest, student_id: int):
|
|
gate, _, org_id = _alumnos_access(request)
|
|
if gate:
|
|
return gate
|
|
if not org_id:
|
|
raise Http404("Foto no encontrada")
|
|
|
|
student = StudentAttachment.objects.filter(id=student_id, org_id=org_id).first()
|
|
if not student:
|
|
raise Http404("Foto no encontrada")
|
|
if not student.photo:
|
|
raise Http404("Foto no encontrada")
|
|
|
|
mime_type, _ = mimetypes.guess_type(student.photo.name)
|
|
return FileResponse(student.photo.open("rb"), content_type=mime_type or "application/octet-stream")
|
|
|
|
|
|
def cuadernos(request: HttpRequest):
|
|
gate, _, org_id = _cuadernos_access(request)
|
|
if gate:
|
|
return gate
|
|
|
|
aulario_id = _safe_aulario_id(request.GET.get("aulario", ""))
|
|
context = shell_context(request, "aulatek", "Cuadernos")
|
|
if not aulario_id or not org_id:
|
|
context.update(
|
|
{
|
|
"module_title": "Cuadernos",
|
|
"aulario_id": "",
|
|
"cuadernos": [],
|
|
"alumnos": [],
|
|
}
|
|
)
|
|
return render(request, "aulatek/cuadernos.html", context)
|
|
|
|
if not Aulario.objects.filter(org_id=org_id, aulario_id=aulario_id).exists():
|
|
raise Http404("Aulario no encontrado")
|
|
|
|
cuadernos_rows = Cuaderno.objects.filter(org_id=org_id, aulario_id=aulario_id).order_by("-updated_at", "-created_at")
|
|
context.update(
|
|
{
|
|
"module_title": "Cuadernos",
|
|
"aulario_id": aulario_id,
|
|
"cuadernos": cuadernos_rows,
|
|
}
|
|
)
|
|
return render(request, "aulatek/cuadernos.html", context)
|
|
|
|
|
|
def cuaderno_new(request: HttpRequest):
|
|
gate, user, org_id = _cuadernos_access(request)
|
|
if gate:
|
|
return gate
|
|
|
|
aulario_id = _safe_aulario_id(request.GET.get("aulario", ""))
|
|
if not aulario_id or not org_id:
|
|
messages.error(request, "No se ha indicado un aulario valido.")
|
|
return redirect("/aulatek/")
|
|
|
|
if not Aulario.objects.filter(org_id=org_id, aulario_id=aulario_id).exists():
|
|
raise Http404("Aulario no encontrado")
|
|
|
|
if request.method == "POST":
|
|
titulo = _safe_titulo(request.POST.get("titulo", ""))
|
|
alumno = _safe_alumno_name(request.POST.get("alumno", ""))
|
|
contenido = (request.POST.get("contenido") or "").strip()
|
|
|
|
if not titulo:
|
|
messages.error(request, "El titulo es obligatorio.")
|
|
return redirect(f"/aulatek/cuadernos/new/?aulario={aulario_id}")
|
|
if not contenido:
|
|
messages.error(request, "El contenido no puede estar vacio.")
|
|
return redirect(f"/aulatek/cuadernos/new/?aulario={aulario_id}")
|
|
Cuaderno.objects.create(
|
|
org_id=org_id,
|
|
aulario_id=aulario_id,
|
|
alumno=alumno,
|
|
titulo=titulo,
|
|
contenido_encriptado=contenido,
|
|
pin_hash="",
|
|
pin_salt="",
|
|
created_by=user.username if user else "",
|
|
updated_by=user.username if user else "",
|
|
)
|
|
messages.success(request, "Cuaderno creado correctamente.")
|
|
return redirect(f"/aulatek/cuadernos/?aulario={aulario_id}")
|
|
|
|
alumnos_rows = StudentAttachment.objects.filter(org_id=org_id, aulario_id=aulario_id).order_by("alumno")
|
|
context = shell_context(request, "aulatek", "Nuevo Cuaderno")
|
|
context.update(
|
|
{
|
|
"module_title": "Nuevo Cuaderno",
|
|
"aulario_id": aulario_id,
|
|
"alumnos": [row.alumno for row in alumnos_rows],
|
|
}
|
|
)
|
|
return render(request, "aulatek/cuaderno_new.html", context)
|
|
|
|
|
|
def cuaderno_edit(request: HttpRequest, cuaderno_id: int):
|
|
gate, user, org_id = _cuadernos_access(request)
|
|
if gate:
|
|
return gate
|
|
|
|
cuaderno = Cuaderno.objects.filter(id=cuaderno_id, org_id=org_id).first()
|
|
if not cuaderno:
|
|
raise Http404("Cuaderno no encontrado")
|
|
|
|
aulario_id = cuaderno.aulario_id
|
|
decrypted_content = cuaderno.contenido_encriptado or ""
|
|
|
|
if request.method == "POST":
|
|
action = (request.POST.get("action") or "unlock").strip().lower()
|
|
|
|
if action == "unlock":
|
|
return redirect(f"/aulatek/cuadernos/{cuaderno.id}/")
|
|
|
|
if action == "update":
|
|
titulo = _safe_titulo(request.POST.get("titulo", ""))
|
|
alumno = _safe_alumno_name(request.POST.get("alumno", ""))
|
|
contenido = (request.POST.get("contenido") or "").strip()
|
|
|
|
if not titulo:
|
|
messages.error(request, "El titulo es obligatorio.")
|
|
return redirect(f"/aulatek/cuadernos/{cuaderno.id}/")
|
|
if not contenido:
|
|
messages.error(request, "El contenido no puede estar vacio.")
|
|
return redirect(f"/aulatek/cuadernos/{cuaderno.id}/")
|
|
|
|
cuaderno.titulo = titulo
|
|
cuaderno.alumno = alumno
|
|
cuaderno.pin_salt = ""
|
|
cuaderno.pin_hash = ""
|
|
cuaderno.contenido_encriptado = contenido
|
|
cuaderno.updated_by = user.username if user else ""
|
|
cuaderno.save(update_fields=["titulo", "alumno", "pin_hash", "pin_salt", "contenido_encriptado", "updated_by", "updated_at"])
|
|
messages.success(request, "Cuaderno actualizado correctamente.")
|
|
return redirect(f"/aulatek/cuadernos/{cuaderno.id}/")
|
|
|
|
if action == "delete":
|
|
if not _signature_verified(request):
|
|
messages.error(request, "Firma requerida para eliminar el cuaderno.")
|
|
return redirect(f"/aulatek/cuadernos/{cuaderno.id}/")
|
|
cuaderno.delete()
|
|
messages.success(request, "Cuaderno eliminado.")
|
|
return redirect(f"/aulatek/cuadernos/?aulario={aulario_id}")
|
|
|
|
context = shell_context(request, "aulatek", f"Cuaderno: {cuaderno.titulo}")
|
|
context.update(
|
|
{
|
|
"module_title": "Editar Cuaderno",
|
|
"cuaderno": cuaderno,
|
|
"aulario_id": aulario_id,
|
|
"decrypted_content": decrypted_content,
|
|
}
|
|
)
|
|
return render(request, "aulatek/cuaderno_edit.html", context)
|
|
|
|
|
|
def comedor(request: HttpRequest):
|
|
gate, user, org_id, aulario_row = _comedor_access(request)
|
|
if gate:
|
|
return gate
|
|
|
|
context = shell_context(request, "aulatek", "Menu del Comedor")
|
|
if not org_id or not aulario_row:
|
|
context.update(
|
|
{
|
|
"module_title": "Menu del Comedor",
|
|
"aulario_id": "",
|
|
"menus": [],
|
|
"all_aularios": [],
|
|
}
|
|
)
|
|
return render(request, "aulatek/comedor.html", context)
|
|
|
|
all_aularios = list(Aulario.objects.filter(org_id=org_id).order_by("name", "aulario_id"))
|
|
selectable_shared = [row for row in all_aularios if row.id != aulario_row.id]
|
|
selected_date = _parse_menu_date(request.GET.get("date", "")) or date.today()
|
|
selected_menu_type = (request.GET.get("menu") or request.GET.get("type") or "").strip()
|
|
|
|
def comedor_url(target_date: date, target_type: str) -> str:
|
|
return "/aulatek/comedor/?" + urlencode(
|
|
{
|
|
"aulario": aulario_row.aulario_id,
|
|
"date": target_date.isoformat(),
|
|
"menu": target_type,
|
|
}
|
|
)
|
|
|
|
type_rows = list(
|
|
ComedorMenuType.objects.filter(org_id=org_id)
|
|
.filter(Q(source_aulario_id=aulario_row.aulario_id) | Q(shared_aularios=aulario_row))
|
|
.distinct()
|
|
.order_by("label")
|
|
)
|
|
|
|
if not selected_menu_type and type_rows:
|
|
selected_menu_type = type_rows[0].type_id
|
|
|
|
selected_type_row = next((row for row in type_rows if row.type_id == selected_menu_type), None)
|
|
if not selected_type_row and type_rows:
|
|
selected_type_row = type_rows[0]
|
|
selected_menu_type = selected_type_row.type_id
|
|
|
|
source_aulario_id = selected_type_row.source_aulario_id if selected_type_row else aulario_row.aulario_id
|
|
can_edit_selected_type = bool(selected_type_row and selected_type_row.source_aulario_id == aulario_row.aulario_id)
|
|
|
|
if request.method == "POST":
|
|
action = (request.POST.get("action") or "").strip()
|
|
|
|
if action == "add_type":
|
|
new_id = re.sub(r"[^a-z0-9_-]", "", (request.POST.get("new_type_id") or "").strip().lower())
|
|
new_label = (request.POST.get("new_type_label") or "").strip()
|
|
new_color = (request.POST.get("new_type_color") or "#0d6efd").strip() or "#0d6efd"
|
|
if new_id and new_label:
|
|
exists = ComedorMenuType.objects.filter(org_id=org_id, source_aulario_id=aulario_row.aulario_id, type_id=new_id).exists()
|
|
if exists:
|
|
messages.error(request, "Ese tipo ya existe.")
|
|
else:
|
|
ComedorMenuType.objects.create(
|
|
org_id=org_id,
|
|
source_aulario_id=aulario_row.aulario_id,
|
|
type_id=new_id,
|
|
label=new_label,
|
|
color=new_color,
|
|
created_by=user.username if user else "",
|
|
)
|
|
messages.success(request, "Tipo de menu creado.")
|
|
return redirect(comedor_url(selected_date, new_id))
|
|
|
|
if action == "rename_type":
|
|
rename_id = (request.POST.get("rename_type_id") or "").strip()
|
|
row = ComedorMenuType.objects.filter(org_id=org_id, source_aulario_id=aulario_row.aulario_id, type_id=rename_id).first()
|
|
if row:
|
|
new_label = (request.POST.get("rename_type_label") or "").strip()
|
|
new_color = (request.POST.get("rename_type_color") or "").strip()
|
|
if new_label:
|
|
row.label = new_label
|
|
if new_color:
|
|
row.color = new_color
|
|
row.save()
|
|
messages.success(request, "Tipo actualizado.")
|
|
return redirect(comedor_url(selected_date, row.type_id))
|
|
|
|
if action == "delete_type":
|
|
if not _signature_verified(request):
|
|
return HttpResponseForbidden("Firma requerida para eliminar tipo.")
|
|
delete_id = (request.POST.get("delete_type_id") or "").strip()
|
|
row = ComedorMenuType.objects.filter(org_id=org_id, source_aulario_id=aulario_row.aulario_id, type_id=delete_id).first()
|
|
if row:
|
|
ComedorMenu.objects.filter(org_id=org_id, aulario_id=aulario_row.aulario_id, menu_type=delete_id).delete()
|
|
row.delete()
|
|
messages.success(request, "Tipo eliminado.")
|
|
return redirect(comedor_url(selected_date, ""))
|
|
|
|
if action == "share_type":
|
|
if not selected_type_row or not can_edit_selected_type:
|
|
return HttpResponseForbidden("No tienes permiso para compartir este tipo.")
|
|
selected_ids = request.POST.getlist("shared_aularios")
|
|
rows = Aulario.objects.filter(org_id=org_id, id__in=selected_ids).exclude(id=aulario_row.id)
|
|
selected_type_row.shared_aularios.set(rows)
|
|
messages.success(request, "Comparticion de tipo actualizada.")
|
|
return redirect(comedor_url(selected_date, selected_type_row.type_id))
|
|
|
|
if action in {"save", "create", "update"}:
|
|
if not selected_type_row:
|
|
messages.error(request, "Selecciona un tipo valido.")
|
|
return redirect(comedor_url(selected_date, ""))
|
|
if not can_edit_selected_type:
|
|
return HttpResponseForbidden("No puedes editar un tipo compartido desde otro aulario.")
|
|
|
|
menu_date = _parse_menu_date(request.POST.get("menu_date", "")) or selected_date
|
|
first_name = (request.POST.get("first_name") or "").strip()
|
|
second_name = (request.POST.get("second_name") or "").strip()
|
|
dessert_name = (request.POST.get("dessert_name") or "").strip()
|
|
if not first_name or not second_name or not dessert_name:
|
|
messages.error(request, "Debes completar primero, segundo y postre.")
|
|
return redirect(comedor_url(menu_date, selected_type_row.type_id))
|
|
|
|
menu = ComedorMenu.objects.filter(
|
|
org_id=org_id,
|
|
aulario_id=selected_type_row.source_aulario_id,
|
|
menu_type=selected_type_row.type_id,
|
|
menu_date=menu_date,
|
|
).first()
|
|
if not menu:
|
|
menu = ComedorMenu(
|
|
org_id=org_id,
|
|
aulario_id=selected_type_row.source_aulario_id,
|
|
menu_name=selected_type_row.label,
|
|
menu_type=selected_type_row.type_id,
|
|
menu_date=menu_date,
|
|
created_by=user.username if user else "",
|
|
)
|
|
|
|
menu.first_name = first_name
|
|
menu.second_name = second_name
|
|
menu.dessert_name = dessert_name
|
|
|
|
first_photo = request.FILES.get(f"first_photo_{menu.id}") if menu.id else request.FILES.get("first_photo")
|
|
second_photo = request.FILES.get(f"second_photo_{menu.id}") if menu.id else request.FILES.get("second_photo")
|
|
dessert_photo = request.FILES.get(f"dessert_photo_{menu.id}") if menu.id else request.FILES.get("dessert_photo")
|
|
if first_photo and _valid_image(first_photo):
|
|
if menu.first_photo:
|
|
menu.first_photo.delete(save=False)
|
|
menu.first_photo = first_photo
|
|
if second_photo and _valid_image(second_photo):
|
|
if menu.second_photo:
|
|
menu.second_photo.delete(save=False)
|
|
menu.second_photo = second_photo
|
|
if dessert_photo and _valid_image(dessert_photo):
|
|
if menu.dessert_photo:
|
|
menu.dessert_photo.delete(save=False)
|
|
menu.dessert_photo = dessert_photo
|
|
|
|
menu.save()
|
|
messages.success(request, "Menu guardado correctamente.")
|
|
return redirect(comedor_url(menu_date, selected_type_row.type_id))
|
|
|
|
if action == "delete":
|
|
if not selected_type_row or not can_edit_selected_type:
|
|
return HttpResponseForbidden("No puedes eliminar menu de un tipo compartido.")
|
|
if not _signature_verified(request):
|
|
return HttpResponseForbidden("Firma requerida para eliminar menu.")
|
|
menu_id = int(request.POST.get("menu_id") or 0)
|
|
menu = ComedorMenu.objects.filter(
|
|
id=menu_id,
|
|
org_id=org_id,
|
|
aulario_id=selected_type_row.source_aulario_id,
|
|
menu_type=selected_type_row.type_id,
|
|
menu_date=selected_date,
|
|
).first()
|
|
if menu:
|
|
if menu.first_photo:
|
|
menu.first_photo.delete(save=False)
|
|
if menu.second_photo:
|
|
menu.second_photo.delete(save=False)
|
|
if menu.dessert_photo:
|
|
menu.dessert_photo.delete(save=False)
|
|
menu.delete()
|
|
messages.success(request, "Menu eliminado correctamente.")
|
|
return redirect(comedor_url(selected_date, selected_type_row.type_id))
|
|
|
|
# Refresca tras posibles acciones.
|
|
type_rows = list(
|
|
ComedorMenuType.objects.filter(org_id=org_id)
|
|
.filter(Q(source_aulario_id=aulario_row.aulario_id) | Q(shared_aularios=aulario_row))
|
|
.distinct()
|
|
.order_by("label")
|
|
)
|
|
if not selected_menu_type and type_rows:
|
|
selected_menu_type = type_rows[0].type_id
|
|
selected_type_row = next((row for row in type_rows if row.type_id == selected_menu_type), None)
|
|
if not selected_type_row and type_rows:
|
|
selected_type_row = type_rows[0]
|
|
selected_menu_type = selected_type_row.type_id
|
|
|
|
source_aulario_id = selected_type_row.source_aulario_id if selected_type_row else aulario_row.aulario_id
|
|
can_edit_selected_type = bool(selected_type_row and selected_type_row.source_aulario_id == aulario_row.aulario_id)
|
|
|
|
aularios_by_pk = {row.id: row for row in all_aularios}
|
|
selected_menu = None
|
|
if selected_type_row:
|
|
selected_menu = ComedorMenu.objects.filter(
|
|
org_id=org_id,
|
|
aulario_id=source_aulario_id,
|
|
menu_type=selected_type_row.type_id,
|
|
menu_date=selected_date,
|
|
).first()
|
|
|
|
selected_menu_data = None
|
|
shared_ids = []
|
|
if selected_type_row:
|
|
shared_ids = list(selected_type_row.shared_aularios.values_list("id", flat=True))
|
|
if selected_menu:
|
|
owner_row = Aulario.objects.filter(org_id=org_id, aulario_id=source_aulario_id).first()
|
|
selected_menu_data = {
|
|
"obj": selected_menu,
|
|
"is_owner": can_edit_selected_type,
|
|
"origin_label": (owner_row.name or owner_row.aulario_id) if owner_row else source_aulario_id,
|
|
"shared_ids": shared_ids,
|
|
"shared_labels": [
|
|
(aularios_by_pk[pk].name or aularios_by_pk[pk].aulario_id)
|
|
for pk in shared_ids
|
|
if pk in aularios_by_pk
|
|
],
|
|
}
|
|
|
|
menu_types = [
|
|
{
|
|
"id": row.type_id,
|
|
"label": row.label,
|
|
"color": row.color or "#0d6efd",
|
|
"active": row.type_id == selected_menu_type,
|
|
"is_owner": row.source_aulario_id == aulario_row.aulario_id,
|
|
"source_aulario_id": row.source_aulario_id,
|
|
}
|
|
for row in type_rows
|
|
]
|
|
prev_date = selected_date.fromordinal(selected_date.toordinal() - 1)
|
|
next_date = selected_date.fromordinal(selected_date.toordinal() + 1)
|
|
|
|
context.update(
|
|
{
|
|
"module_title": "Menu del Comedor",
|
|
"aulario_id": aulario_row.aulario_id,
|
|
"aulario_name": aulario_row.name or aulario_row.aulario_id,
|
|
"menu_types": menu_types,
|
|
"selected_menu_type": selected_menu_type,
|
|
"selected_date": selected_date,
|
|
"prev_date": prev_date,
|
|
"next_date": next_date,
|
|
"selected_menu": selected_menu_data,
|
|
"selected_type": selected_type_row,
|
|
"can_edit_selected_type": can_edit_selected_type,
|
|
"all_aularios": selectable_shared,
|
|
"aulario_options": all_aularios,
|
|
}
|
|
)
|
|
return render(request, "aulatek/comedor.html", context)
|
|
|
|
|
|
def proyectos(request: HttpRequest):
|
|
return _placeholder(request, "Proyectos")
|
|
|
|
|
|
def supercafe(request: HttpRequest):
|
|
return _placeholder(request, "SuperCafe")
|