Files
pagasis/backend/authentication/models.py
T
2025-10-20 16:14:08 -06:00

217 lines
6.9 KiB
Python

from django.contrib.auth.models import User
from django.db import models
from django.core.validators import RegexValidator, MinValueValidator, MaxValueValidator
from django.utils import timezone
from django.core.exceptions import ValidationError
class UserProfile(models.Model):
USER_TYPES = (
('student', 'Estudiante'),
('assistant', 'Asistente'),
)
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True)
account_number = models.CharField(
max_length=8,
unique=True,
validators=[RegexValidator(
regex=r'^\d{8}$',
message='El número de cuenta debe tener exactamente 8 dígitos.'
)],
verbose_name="Número de cuenta"
)
user_type = models.CharField(
max_length=10,
choices=USER_TYPES,
blank=False,
null=False,
verbose_name="Tipo de usuario"
)
full_name = models.CharField(
max_length=200,
verbose_name="Nombre completo"
)
def __str__(self):
return f"{self.account_number} - {self.full_name}"
class Asistente(models.Model):
user_profile = models.OneToOneField(UserProfile, on_delete=models.CASCADE, limit_choices_to={'user_type': 'assistant'})
can_manage_events = models.BooleanField(default=True, verbose_name="Puede gestionar eventos")
class Meta:
verbose_name = "Permiso de Asistente"
verbose_name_plural = "Permisos de Asistentes"
def clean(self):
"""Validar que el user_profile sea de tipo assistant"""
from django.core.exceptions import ValidationError
if self.user_profile and self.user_profile.user_type != 'assistant':
raise ValidationError({
'user_profile': 'Solo se pueden asignar permisos a usuarios de tipo Asistente. Este usuario es un Estudiante.'
})
def save(self, *args, **kwargs):
self.full_clean()
super().save(*args, **kwargs)
def __str__(self):
return f"Permisos: {self.user_profile.full_name}"
class ExternalUser(models.Model):
"""Usuarios externos"""
APPROVAL_STATUS = (
('pending', 'Pendiente'),
('approved', 'Aprobado'),
('rejected', 'Rechazado'),
)
full_name = models.CharField(max_length=200, verbose_name="Nombre completo")
account_number = models.CharField(
max_length=8,
unique=True,
verbose_name="Número de cuenta"
)
status = models.CharField(
max_length=10,
choices=APPROVAL_STATUS,
default='approved',
verbose_name="Estado de aprobación"
)
approved_by = models.ForeignKey(
UserProfile,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name="Aprobado por"
)
rejection_reason = models.TextField(
blank=True,
null=True,
verbose_name="Motivo de rechazo"
)
created_at = models.DateTimeField(auto_now_add=True)
processed_at = models.DateTimeField(null=True, blank=True)
class Meta:
verbose_name = "Usuario Externo"
verbose_name_plural = "Usuarios Externos"
ordering = ['-created_at']
@property
def is_approved(self):
return self.status == 'approved'
@property
def is_pending(self):
return self.status == 'pending'
def approve(self, approved_by_user):
"""Aprobar usuario externo"""
self.status = 'approved'
self.approved_by = approved_by_user
self.processed_at = timezone.now()
self.save()
def reject(self, rejected_by_user, reason=""):
"""Rechazar usuario externo"""
self.status = 'rejected'
self.approved_by = rejected_by_user
self.rejection_reason = reason
self.processed_at = timezone.now()
self.save()
def __str__(self):
status_icons = {
'pending': '⏳',
'approved': '✅',
'rejected': '❌'
}
icon = status_icons.get(self.status, '?')
return f"{icon} {self.full_name} - {self.account_number}"
class SystemConfiguration(models.Model):
"""Configuración global del sistema"""
minimum_attendance_percentage = models.FloatField(
default=80.0,
validators=[MinValueValidator(0.0), MaxValueValidator(100.0)],
verbose_name="Porcentaje mínimo de asistencia",
help_text="Porcentaje mínimo de asistencia requerido para obtener constancia (0-100)"
)
# Configuración de tiempos de registro
minutes_before_event = models.IntegerField(
default=10,
validators=[MinValueValidator(0), MaxValueValidator(60)],
verbose_name="Minutos antes del evento",
help_text="Tiempo en minutos antes del inicio del evento para permitir el registro de asistencia (0-60)"
)
minutes_after_start = models.IntegerField(
default=25,
validators=[MinValueValidator(0), MaxValueValidator(120)],
verbose_name="Minutos después del inicio",
help_text="Tiempo límite en minutos después del inicio del evento para registrar asistencia (0-120)"
)
updated_at = models.DateTimeField(auto_now=True, verbose_name="Última actualización")
updated_by = models.ForeignKey(
UserProfile,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name="Actualizado por"
)
class Meta:
verbose_name = "Configuración del Sistema"
verbose_name_plural = "Configuración del Sistema"
def save(self, *args, **kwargs):
# Asegurar que solo exista una instancia de configuración
if not self.pk and SystemConfiguration.objects.exists():
raise ValidationError("Solo puede existir una configuración del sistema.")
super().save(*args, **kwargs)
@classmethod
def get_config(cls):
"""Obtener o crear la configuración del sistema"""
config, created = cls.objects.get_or_create(
pk=1,
defaults={
'minimum_attendance_percentage': 80.0,
'minutes_before_event': 10,
'minutes_after_start': 25
}
)
return config
def __str__(self):
return f"Configuración del Sistema - Asistencia mínima: {self.minimum_attendance_percentage}%"
# ===== PROXY MODELS PARA SEPARAR ESTUDIANTES Y ASISTENTES EN EL ADMIN =====
class Student(UserProfile):
"""Proxy model para estudiantes"""
class Meta:
proxy = True
verbose_name = "Estudiante"
verbose_name_plural = "Estudiantes"
def save(self, *args, **kwargs):
self.user_type = 'student'
super().save(*args, **kwargs)
class AssistantProfile(UserProfile):
"""Proxy model para asistentes"""
class Meta:
proxy = True
verbose_name = "Asistente (Perfil)"
verbose_name_plural = "Asistentes (Perfiles)"
def save(self, *args, **kwargs):
self.user_type = 'assistant'
super().save(*args, **kwargs)