from django.contrib import admin from django.contrib.auth.models import User, Group from django import forms from django.utils.html import format_html from django.http import HttpResponse from import_export import resources, fields from import_export.admin import ImportExportModelAdmin from import_export.widgets import ForeignKeyWidget from .models import UserProfile, Asistente, ExternalUser, SystemConfiguration, Student, AssistantProfile from .audit import AuditLog # Ocultar modelos de Django que no se usan admin.site.unregister(User) admin.site.unregister(Group) # ===== RECURSOS PARA IMPORT/EXPORT ===== class StudentResource(resources.ModelResource): """Recurso para importar/exportar SOLO estudiantes""" class Meta: model = UserProfile fields = ('account_number', 'full_name') export_order = ('account_number', 'full_name') import_id_fields = ['account_number'] skip_unchanged = True report_skipped = True def before_import_row(self, row, **kwargs): """Validar y limpiar datos antes de importar""" # Limpiar espacios row['account_number'] = str(row.get('account_number', '')).strip() row['full_name'] = str(row.get('full_name', '')).strip() # Asignar automáticamente user_type como student row['user_type'] = 'student' def before_save_instance(self, instance, row, using_transactions, dry_run, **kwargs): """Crear usuario de Django si no existe y asegurar que sea estudiante""" # Asegurar que sea estudiante instance.user_type = 'student' # Crear usuario de Django si no existe if not instance.user_id: # Buscar si ya existe el usuario try: user = User.objects.get(username=instance.account_number) except User.DoesNotExist: if not dry_run: # Solo crear en la base de datos si no es dry_run user = User.objects.create_user( username=instance.account_number, first_name=instance.full_name, password=None ) user.set_unusable_password() user.save() else: # En dry_run, dejar user como None (ahora permitido) user = None if user: instance.user = user class AssistantResource(resources.ModelResource): """Recurso para importar/exportar SOLO asistentes""" class Meta: model = UserProfile fields = ('account_number', 'full_name') export_order = ('account_number', 'full_name') import_id_fields = ['account_number'] skip_unchanged = True report_skipped = True def before_import_row(self, row, **kwargs): """Validar y limpiar datos antes de importar""" # Limpiar espacios row['account_number'] = str(row.get('account_number', '')).strip() row['full_name'] = str(row.get('full_name', '')).strip() # Asignar automáticamente user_type como assistant row['user_type'] = 'assistant' def before_save_instance(self, instance, row, using_transactions, dry_run, **kwargs): """Crear usuario de Django si no existe y asegurar que sea asistente""" # Asegurar que sea asistente instance.user_type = 'assistant' # Crear usuario de Django si no existe if not instance.user_id: # Buscar si ya existe el usuario try: user = User.objects.get(username=instance.account_number) except User.DoesNotExist: if not dry_run: # Solo crear en la base de datos si no es dry_run user = User.objects.create_user( username=instance.account_number, first_name=instance.full_name, password=None ) user.set_unusable_password() user.save() else: # En dry_run, dejar user como None (ahora permitido) user = None if user: instance.user = user def after_save_instance(self, instance, using_transactions, dry_run): """Crear automáticamente el registro de Asistente después de guardar""" if not dry_run: # Crear automáticamente el registro de Asistente si no existe from .models import Asistente Asistente.objects.get_or_create( user_profile=instance, defaults={'can_manage_events': True} ) class UserProfileResource(resources.ModelResource): """Recurso para importar/exportar UserProfile (LEGACY - no usar)""" class Meta: model = UserProfile fields = ('account_number', 'full_name', 'user_type') import_id_fields = ['account_number'] skip_unchanged = True report_skipped = True def before_import_row(self, row, **kwargs): """Validar y limpiar datos antes de importar""" # Limpiar espacios row['account_number'] = str(row.get('account_number', '')).strip() row['full_name'] = str(row.get('full_name', '')).strip() row['user_type'] = str(row.get('user_type', '')).strip().lower() def after_save_instance(self, instance, using_transactions, dry_run): """Crear usuario de Django si no existe""" if not dry_run and not instance.user_id: user = User.objects.create_user( username=instance.account_number, first_name=instance.full_name, password=None ) user.set_unusable_password() user.save() instance.user = user instance.save() class ExternalUserResource(resources.ModelResource): """Recurso para exportar usuarios externos""" approved_by_name = fields.Field() class Meta: model = ExternalUser fields = ('account_number', 'full_name', 'status', 'approved_by_name', 'created_at', 'rejection_reason') export_order = fields def dehydrate_approved_by_name(self, external_user): """Obtener nombre del aprobador""" return external_user.approved_by.full_name if external_user.approved_by else '-' class UserProfileForm(forms.ModelForm): """Formulario personalizado para crear UserProfile sin contraseña""" class Meta: model = UserProfile fields = ['account_number', 'full_name', 'user_type'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Hacer el campo user_type obligatorio self.fields['user_type'].required = True self.fields['user_type'].empty_label = None # Quitar opción vacía del dropdown def save(self, commit=True): profile = super().save(commit=False) # Si el perfil no tiene usuario asociado, crear uno automáticamente if not profile.user_id: # Crear usuario con username = número de cuenta, sin contraseña user = User.objects.create_user( username=profile.account_number, first_name=profile.full_name, password=None # Sin contraseña ) # Desactivar contraseña usable user.set_unusable_password() user.save() profile.user = user if commit: profile.save() return profile class StudentAdmin(ImportExportModelAdmin): """Admin SOLO para estudiantes""" resource_class = StudentResource list_display = ['account_number', 'full_name'] search_fields = ['account_number', 'full_name'] actions = ['export_selected_students'] fieldsets = ( ('Información del Estudiante', { 'fields': ('account_number', 'full_name'), 'description': '📚 Importa el Excel con solo 2 columnas: account_number y full_name. El sistema automáticamente los creará como estudiantes.' }), ) def get_queryset(self, request): """Mostrar SOLO estudiantes""" qs = super().get_queryset(request) return qs.filter(user_type='student') def get_import_formats(self): """Formatos permitidos para importar""" from import_export.formats.base_formats import XLSX, CSV return [XLSX, CSV] def get_export_formats(self): """Formatos permitidos para exportar""" from import_export.formats.base_formats import XLSX, CSV return [XLSX, CSV] def export_selected_students(self, request, queryset): """Acción para exportar estudiantes seleccionados""" resource = StudentResource() dataset = resource.export(queryset) from import_export.formats.base_formats import XLSX xlsx_format = XLSX() export_data = xlsx_format.export_data(dataset) response = HttpResponse( export_data, content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ) response['Content-Disposition'] = 'attachment; filename="estudiantes.xlsx"' self.message_user(request, f'Se exportaron {queryset.count()} estudiantes.') return response export_selected_students.short_description = "📊 Exportar estudiantes seleccionados" def save_model(self, request, obj, form, change): """Asegurar que siempre sea estudiante y crear usuario si no existe""" obj.user_type = 'student' # Si no tiene usuario, crear uno if not obj.user_id: user = User.objects.create_user( username=obj.account_number, first_name=obj.full_name, password=None ) user.set_unusable_password() user.save() obj.user = user super().save_model(request, obj, form, change) class AssistantProfileAdmin(ImportExportModelAdmin): """Admin SOLO para asistentes""" resource_class = AssistantResource list_display = ['account_number', 'full_name'] search_fields = ['account_number', 'full_name'] actions = ['export_selected_assistants'] fieldsets = ( ('Información del Asistente', { 'fields': ('account_number', 'full_name'), 'description': '👨🏫 Importa el Excel con solo 2 columnas: account_number y full_name. El sistema automáticamente los creará como asistentes.' }), ) def get_queryset(self, request): """Mostrar SOLO asistentes""" qs = super().get_queryset(request) return qs.filter(user_type='assistant') def get_import_formats(self): """Formatos permitidos para importar""" from import_export.formats.base_formats import XLSX, CSV return [XLSX, CSV] def get_export_formats(self): """Formatos permitidos para exportar""" from import_export.formats.base_formats import XLSX, CSV return [XLSX, CSV] def export_selected_assistants(self, request, queryset): """Acción para exportar asistentes seleccionados""" resource = AssistantResource() dataset = resource.export(queryset) from import_export.formats.base_formats import XLSX xlsx_format = XLSX() export_data = xlsx_format.export_data(dataset) response = HttpResponse( export_data, content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ) response['Content-Disposition'] = 'attachment; filename="asistentes.xlsx"' self.message_user(request, f'Se exportaron {queryset.count()} asistentes.') return response export_selected_assistants.short_description = "📊 Exportar asistentes seleccionados" def save_model(self, request, obj, form, change): """Asegurar que siempre sea asistente y crear usuario si no existe""" obj.user_type = 'assistant' # Si no tiene usuario, crear uno if not obj.user_id: user = User.objects.create_user( username=obj.account_number, first_name=obj.full_name, password=None ) user.set_unusable_password() user.save() obj.user = user super().save_model(request, obj, form, change) # Crear automáticamente el registro de Asistente si no existe Asistente.objects.get_or_create( user_profile=obj, defaults={'can_manage_events': True} ) # Registrar los admins separados con proxy models admin.site.register(Student, StudentAdmin) admin.site.register(AssistantProfile, AssistantProfileAdmin) # UserProfile está oculto del admin - se usan Student y AssistantProfile en su lugar @admin.register(Asistente) class AsistenteAdmin(admin.ModelAdmin): list_display = ['get_asistente_info', 'get_numero_cuenta', 'get_registros_realizados', 'ver_alumnos_registrados', 'can_manage_events'] list_filter = ['can_manage_events'] search_fields = ['user_profile__full_name', 'user_profile__account_number'] readonly_fields = ['user_profile', 'get_registros_realizados', 'get_ultimos_registros'] actions = ['ver_reporte_registros'] fieldsets = ( ('Asistente', { 'fields': ('user_profile',), 'description': '📋 Los permisos de asistente se crean automáticamente cuando se registra un nuevo asistente.' }), ('Permisos y Configuración', { 'fields': ('can_manage_events',) }), ('Estadísticas de Registros', { 'fields': ('get_registros_realizados', 'get_ultimos_registros'), 'classes': ('collapse',) }), ) def has_add_permission(self, request): """Ocultar botón de agregar - se crean automáticamente""" return False def has_delete_permission(self, request, obj=None): """No permitir eliminar permisos de asistente""" return False def get_asistente_info(self, obj): """Mostrar nombre del asistente""" return obj.user_profile.full_name get_asistente_info.short_description = 'Asistente' def get_numero_cuenta(self, obj): """Mostrar número de cuenta""" return obj.user_profile.account_number get_numero_cuenta.short_description = 'Número de Cuenta' def formfield_for_foreignkey(self, db_field, request, **kwargs): """Filtrar el campo user_profile para mostrar solo asistentes""" if db_field.name == "user_profile": kwargs["queryset"] = UserProfile.objects.filter(user_type='assistant') return super().formfield_for_foreignkey(db_field, request, **kwargs) def get_registros_realizados(self, obj): """Muestra el total de registros realizados por este asistente""" from attendance.models import Attendance count = Attendance.objects.filter(registered_by=obj.user_profile).count() return f"📊 {count} registros" get_registros_realizados.short_description = 'Total de registros' def get_ultimos_registros(self, obj): """Muestra los últimos 5 registros realizados por este asistente""" from django.utils.html import format_html from attendance.models import Attendance registros = Attendance.objects.filter( registered_by=obj.user_profile ).select_related('student', 'external_user', 'event').order_by('-timestamp')[:5] if not registros: return "Sin registros" html = '
| Fecha | Asistente | Evento |
|---|---|---|
| {reg.timestamp.strftime("%d/%m/%Y %H:%M")} | {attendee} | {reg.event.title[:30]}... |