from django.shortcuts import render, get_object_or_404, redirect from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib import messages from django.contrib.admin.views.decorators import staff_member_required from django.core.mail import EmailMultiAlternatives from django.conf import settings from django.template.loader import render_to_string from django.utils import timezone from datetime import date import logging import requests import os import json from django.core.mail import send_mass_mail from django.core.mail import BadHeaderError from smtplib import SMTPException from django.http import HttpResponse from rest_framework.response import Response from rest_framework.decorators import api_view from .serializers import EventoSerializer, ReservaSerializer, ListaEsperaSerializer, NoticiaSerializer, LugarSerializer from .models import Evento, Reserva, ListaEspera, Noticia, Lugar from .forms import ListaEsperaForm, EventoForm, MensajeCorreoForm, ReservaForm from django.contrib.auth import get_user_model from django.db.models import Count, Q User = get_user_model() logger = logging.getLogger(__name__) BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "8604655521:AAEREJsenslfLyZ4vpNs475BTI6rboJ9UY0") CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "-1003558251921") # chat_id del canal JugarAlPadel # helper for sending reservation emails def enviar_correo_reserva(evento, usuario, acompanante=None): """Envía los correos de confirmación al usuario (y acompañante) y notificación al admin. Parameters ---------- evento : Evento Instancia del evento reservado. usuario : User Usuario que realiza la reserva. acompanante : User | None Si se trata de un evento para parejas, usuario acompañante opcional. """ from_email = settings.DEFAULT_FROM_EMAIL to_email = usuario.email cc_email = [] # cc siempre al administrador; al acompañante si existe # cc_email = [settings.ADMIN_EMAIL] if acompanante and acompanante.email: cc_email.append(acompanante.email) # cuerpo de texto plano texto = f'Hola {usuario.nombre}, \n\n' texto += f'Te has inscrito correctamente en el evento "{evento.nombre}".\n' texto += f'Detalles del evento:\n' texto += f'Nombre: {evento.nombre}\n' texto += f'Descripción: {evento.descripcion}\n' texto += f'Fecha: {evento.fecha.strftime("%d/%b/%y")} a las {evento.hora}\n' texto += f'Precio: {evento.precio} €' if acompanante: texto += f"\nHas indicado como pareja a {acompanante.nombre}.\n" texto += '\n¡Gracias por inscribirte!\n\n' texto += '\n\nIMPORTANTE: Para gestionar cambios o bajas NO contestes a este correo, escribe al administrador por WhatsApp\n\n' # plantilla HTML adecuada if acompanante: html_content = render_to_string( 'emails/confirmacion_reserva_parejas.html', {'evento': evento, 'usuario': usuario, 'acompanante': acompanante} ) else: html_content = render_to_string( 'emails/confirmacion_reserva.html', {'evento': evento, 'usuario': usuario} ) msg = EmailMultiAlternatives( f'Confirmación de Reserva para {evento.nombre}', texto, from_email, [to_email], cc=cc_email ) msg.attach_alternative(html_content, "text/html") msg.send() # notificación al administrador (se envía siempre incluso si ya está # en cc porque el formato es distinto y queremos consolidar datos de plazas) asunto_admin = f'[{evento.plazas_restantes()}/{evento.plazas_disponibles}] Nueva Reserva para {evento.nombre}' mensaje_admin = ( f'El usuario {usuario.nombre} ({usuario.email}) se ha inscrito en el evento "{evento.nombre}".\n' f'Quedan {evento.plazas_restantes()} plazas disponibles.' ) html_content_admin = render_to_string( 'emails/notificacion_reserva_admin.html', { 'evento': evento, 'usuario': usuario, 'acompanante': acompanante, 'plazas_restantes': evento.plazas_restantes(), 'plazas_disponibles': evento.plazas_disponibles, }, ) email_admin = EmailMultiAlternatives( asunto_admin, mensaje_admin, settings.DEFAULT_FROM_EMAIL, [settings.ADMIN_EMAIL], ) email_admin.attach_alternative(html_content_admin, "text/html") email_admin.send() def enviar_correo_lista_espera(evento, usuario, acompanante=None): """Envía correos cuando un usuario se apunta a la lista de espera. Parameters ---------- evento : Evento usuario : User acompanante : User | None """ from_email = settings.DEFAULT_FROM_EMAIL to_email = usuario.email cc_email = [] if acompanante and acompanante.email: cc_email.append(acompanante.email) texto = f'Hola {usuario.nombre},\n\n' texto += f'Te has añadido a la lista de espera del evento "{evento.nombre}".\n' texto += f'Detalles del evento:\n' texto += f'Nombre: {evento.nombre}\n' texto += f'Descripción: {evento.descripcion}\n' texto += f'Fecha: {evento.fecha.strftime("%d/%b/%y")} a las {evento.hora}\n' if acompanante: texto += f"\nHas indicado como pareja a {acompanante.nombre}.\n" texto += '\nTe avisaremos si se libera alguna plaza.\n\n' texto += '\n\nIMPORTANTE: Para gestionar cambios o bajas NO contestes a este correo, escribe al administrador por WhatsApp\n\n' if acompanante: html = render_to_string( 'emails/confirmacion_lista_espera_parejas.html', {'evento': evento, 'usuario': usuario, 'acompanante': acompanante}, ) else: html = render_to_string( 'emails/confirmacion_lista_espera.html', {'evento': evento, 'usuario': usuario}, ) subject = f'Confirmación lista de espera para {evento.nombre}' msg = EmailMultiAlternatives(subject, texto, from_email, [to_email], cc=cc_email) msg.attach_alternative(html, 'text/html') msg.send() # notificar al admin asunto_admin = f'Nueva lista de espera para {evento.nombre}' mensaje_admin = ( f'El usuario {usuario.nombre} ({usuario.email}) se ha añadido a la lista de espera.' ) if acompanante: mensaje_admin += f' Acompañante: {acompanante.nombre} ({acompanante.email}).' html_admin = render_to_string( 'emails/notificacion_lista_espera_admin.html', {'evento': evento, 'usuario': usuario, 'acompanante': acompanante}, ) email_admin2 = EmailMultiAlternatives( asunto_admin, mensaje_admin, settings.DEFAULT_FROM_EMAIL, [settings.ADMIN_EMAIL], ) email_admin2.attach_alternative(html_admin, 'text/html') email_admin2.send() @login_required def reservar_evento(request, evento_id): evento = get_object_or_404(Evento, id=evento_id) # Verificar si el usuario ya tiene una reserva para este evento if Reserva.objects.filter(evento=evento, usuario=request.user).exists(): messages.error(request, 'Ya tienes una reserva para este evento.') return redirect('eventos:lista_eventos') # Verificar si quedan plazas disponibles if evento.plazas_restantes() > 0: Reserva.objects.create(evento=evento, usuario=request.user, iniciador=True) # separar el envío de correos a función reutilizable enviar_correo_reserva(evento, request.user) messages.success(request, f'Reserva realizada para el evento {evento.nombre}.') else: messages.error(request, 'Lo sentimos, no quedan plazas disponibles.') return redirect('eventos:lista_eventos') @login_required def reservar_evento_parejas(request, evento_id): evento = get_object_or_404(Evento, id=evento_id) # Si ya está inscrito if Reserva.objects.filter(evento=evento, usuario=request.user).exists(): messages.warning(request, "Ya estás inscrito en este evento.") return redirect('eventos:detalle_evento', evento_id=evento.id) if request.method == 'POST': form = ReservaForm(request.POST, evento=evento, usuario=request.user) if form.is_valid(): acompanante = form.cleaned_data.get('acompanante') # Crear la reserva principal Reserva.objects.create( evento=evento, usuario=request.user, iniciador=True, acompanante=acompanante if evento.para_parejas else None ) # Si es un evento para parejas, crear también la reserva del acompañante if evento.para_parejas and acompanante: # Solo si el acompañante no está ya inscrito if not Reserva.objects.filter(evento=evento, usuario=acompanante).exists(): Reserva.objects.create( evento=evento, usuario=acompanante, iniciador=False, acompanante=request.user ) # Enviar correos de confirmación/aviso usando la función reutilizable enviar_correo_reserva(evento, request.user, acompanante=acompanante if evento.para_parejas else None) messages.success(request, "Reserva realizada correctamente.") return redirect('eventos:detalle_evento', evento_id=evento.id) else: form = ReservaForm(evento=evento, usuario=request.user) return render(request, 'eventos/reservar_evento.html', {'form': form, 'evento': evento}) @user_passes_test(lambda u: u.is_staff) def publicar_evento(request, evento_id): evento = get_object_or_404(Evento, id=evento_id) if not evento.publicado: evento.publicado = True evento.save() # Envía mensajes al canal de Telegram if evento.para_parejas: send_photo_url("https://jugaralpadel.es/static/images/parejas.png", "Pozo %s publicado: https://jugaralpadel.es/eventos/eventos/%s/" % (evento.nombre, evento.id)) else: send_photo_url("https://jugaralpadel.es/static/images//padel-court.jpg", "Pozo %s publicado: https://jugaralpadel.es/eventos/eventos/%s/" % (evento.nombre, evento.id)) messages.success(request, 'El evento %s ha sido publicado' % evento.nombre) else: messages.info(request, 'El evento %s ya está publicado' % evento.nombre) return redirect('eventos:detalle_evento', evento_id=evento_id) @login_required def lista_eventos(request): # anno_actual = timezone.now().year # Solo mostrar los eventos publicados a los usuarios normales if request.user.is_staff: eventos = Evento.objects.all().order_by('-fecha', '-hora') else: eventos = Evento.objects.filter(publicado=True).order_by('-fecha', '-hora') # eventos = Evento.objects.filter(publicado=True, fecha__year=anno_actual) # Los usuarios ven solo eventos publicados y del año en curso # Crear un diccionario que mapea cada evento con un booleano indicando si el usuario ya ha reservado eventos_con_reserva = [] for evento in eventos: inscrito = Reserva.objects.filter(evento=evento, usuario=request.user).exists() if request.user.is_authenticated else False if ListaEspera.objects.filter(evento=evento, usuario=request.user).exists(): en_espera = True else: en_espera = False eventos_con_reserva.append({ 'evento': evento, 'inscrito': inscrito, 'id': evento.id, 'nombre': evento.nombre, 'fecha': evento.fecha, 'hora': evento.hora, 'plazas_disponibles': evento.plazas_disponibles, 'plazas_restantes': evento.plazas_restantes, 'en_espera': en_espera, 'usuario': request.user, 'publicado': evento.publicado, 'url_imagen': evento.url_imagen, 'para_parejas': evento.para_parejas }) return render(request, 'eventos/lista_eventos.html', {'eventos_con_reserva': eventos_con_reserva}) def detalle_noticia(request, noticia_id): noticia = get_object_or_404(Noticia, id=noticia_id, publicado=True) return render(request, 'detalle_noticia.html', {'noticia': noticia}) def ayuda(request): return render(request, 'eventos/help.html') @login_required def detalle_evento(request, evento_id): el_evento = [] evento = get_object_or_404(Evento, pk=evento_id) reservas = Reserva.objects.filter(evento=evento_id) inscrito = Reserva.objects.filter(evento=evento, usuario=request.user).exists() if request.user.is_authenticated else False if ListaEspera.objects.filter(evento=evento, usuario=request.user).exists(): en_espera = True else: en_espera = False lista_espera = ListaEspera.objects.filter(evento=evento_id) el_evento.append( { 'evento': evento, 'inscrito': inscrito, 'id': evento.id, 'nombre': evento.nombre, 'descripcion': evento.descripcion, 'fecha': evento.fecha, 'hora': evento.hora, 'precio': evento.precio, 'plazas_disponibles': evento.plazas_disponibles, 'plazas_restantes': evento.plazas_restantes, 'en_espera': en_espera, 'usuario': request.user, 'publicado': evento.publicado, 'url_imagen': evento.url_imagen, 'para_parejas': evento.para_parejas, 'lugar': evento.lugar } ) return render(request, 'eventos/detalle_evento.html', {'eventos': el_evento, 'reservas': reservas, 'lista': lista_espera}) # Verificar si el usuario es administrador def es_admin(user): return user.is_staff @user_passes_test(es_admin) def crear_evento(request): if request.method == 'POST': form = EventoForm(request.POST) if form.is_valid(): form.save() messages.success(request, 'Evento creado exitosamente.') return redirect('eventos:lista_eventos') # Redirige a la lista de eventos else: form = EventoForm() return render(request, 'eventos/crear_evento.html', {'form': form}) @user_passes_test(lambda u: u.is_staff) def editar_evento(request, evento_id): evento = get_object_or_404(Evento, id=evento_id) if request.method == 'POST': form = EventoForm(request.POST, instance=evento) if form.is_valid(): form.save() return redirect('lista_eventos') else: form = EventoForm(instance=evento) return render(request, 'crear_evento.html', {'form': form, 'evento': evento}) @login_required def apuntar_lista_espera(request, evento_id): evento = get_object_or_404(Evento, id=evento_id) # Verificar si el usuario ya está en la lista de espera if ListaEspera.objects.filter(evento=evento, usuario=request.user).exists(): messages.error(request, 'Ya estás en la lista de espera para este evento.') return redirect('eventos:lista_eventos') # Crear una entrada en la lista de espera if request.method == 'POST': form = ListaEsperaForm(request.POST) if form.is_valid(): ListaEspera.objects.create(evento=evento, usuario=request.user) # enviar correos de confirmación y aviso al administrador enviar_correo_lista_espera(evento, request.user) messages.success(request, 'Te has apuntado a la lista de espera para este evento.') return redirect('eventos:lista_eventos') form = ListaEsperaForm() return render(request, 'eventos/apuntar_lista_espera.html', {'form': form, 'evento': evento}) @login_required def apuntar_lista_espera_parejas(request, evento_id): evento = get_object_or_404(Evento, id=evento_id) # Verificar si el usuario ya está en la lista de espera if ListaEspera.objects.filter(evento=evento, usuario=request.user).exists(): messages.error(request, 'Ya estás en la lista de espera para este evento.') return redirect('eventos:lista_eventos') # Crear una entrada en la lista de espera if request.method == 'POST': form = ListaEsperaForm(request.POST, evento=evento, usuario=request.user) if form.is_valid(): acompanante = form.cleaned_data.get('acompanante') ListaEspera.objects.create( evento=evento, usuario=request.user, acompanante=acompanante if evento.para_parejas else None ) # enviar correos de confirmación y aviso al administrador enviar_correo_lista_espera(evento, request.user, acompanante=acompanante if evento.para_parejas else None) messages.success(request, 'Te has apuntado a la lista de espera para este evento.') return redirect('eventos:lista_eventos') else: form = ListaEsperaForm(evento=evento, usuario=request.user) return render(request, 'eventos/apuntar_lista_espera.html', {'form': form, 'evento': evento}) # # API # @api_view(['GET']) def api_lista_eventos(request): """Devuelve la lista de todos los eventos.""" eventos = Evento.objects.all() serializer = EventoSerializer(eventos, many=True) return Response(serializer.data) @api_view(['GET']) def api_detalle_evento(request, evento_id): """Devuelve los detalles de un evento específico.""" try: evento = Evento.objects.get(id=evento_id) serializer = EventoSerializer(evento) return Response(serializer.data) except Evento.DoesNotExist: return Response({'error': 'Evento no encontrado'}, status=404) @api_view(['GET']) def api_lista_reservas(request): """Devuelve la lista de todos los reservas.""" reservas = Reserva.objects.all() serializer = ReservaSerializer(reservas, many=True) return Response(serializer.data) @api_view(['GET']) def api_detalle_reserva(request, reserva_id): """Devuelve los detalles de un reserva específico.""" try: reserva = Reserva.objects.get(id=reserva_id) serializer = ReservaSerializer(reserva) return Response(serializer.data) except Reserva.DoesNotExist: return Response({'error': 'Reserva no encontrado'}, status=404) @api_view(['GET']) def api_lista_listaespera(request): """Devuelve la lista de todos los listaespera.""" listaesperas = ListaEspera.objects.all() serializer = ListaEsperaSerializer(listaesperas, many=True) return Response(serializer.data) @api_view(['GET']) def api_detalle_listaespera(request, listaespera_id): """Devuelve los detalles de un listaespera específico.""" try: listaespera = ListaEspera.objects.get(id=listaespera_id) serializer = ListaEsperaSerializer(listaespera) return Response(serializer.data) except ListaEspera.DoesNotExist: return Response({'error': 'ListaEspera no encontrado'}, status=404) @api_view(['GET']) def api_lista_noticias(request): """Devuelve la lista de todos los noticias.""" noticias = Noticia.objects.all() serializer = NoticiaSerializer(noticias, many=True) return Response(serializer.data) @api_view(['GET']) def api_detalle_noticia(request, noticia_id): """Devuelve los detalles de un noticia específico.""" try: noticia = Noticia.objects.get(id=noticia_id) serializer = NoticiaSerializer(noticia) return Response(serializer.data) except Noticia.DoesNotExist: return Response({'error': 'Noticia no encontrado'}, status=404) @api_view(['GET']) def api_download_lugares(request): """Descarga la lista de lugares como archivo JSON.""" lugares = Lugar.objects.all() serializer = LugarSerializer(lugares, many=True) response = HttpResponse( json.dumps(serializer.data, indent=2, ensure_ascii=False), content_type='application/json' ) response['Content-Disposition'] = 'attachment; filename="lugares.json"' return response # Verifica si el usuario es administrador def es_admin(user): return user.is_staff # Enviar correo a los inscritos en formato html @user_passes_test(es_admin) # Solo administradores pueden acceder def enviar_correo_inscritos_html(request, evento_id): """ Envía un correo HTML a los inscritos en un evento """ evento = get_object_or_404(Evento, id=evento_id) inscritos = Reserva.objects.filter(evento=evento).select_related('usuario') if not inscritos: messages.warning(request, "No hay usuarios inscritos en este evento.") return redirect('eventos:detalle_evento', evento_id=evento.id) destinatarios = [reserva.usuario.email for reserva in inscritos if reserva.usuario.email] if not destinatarios: messages.warning(request, "No hay correos disponibles para los inscritos.") return redirect('eventos:detalle_evento', evento_id=evento.id) if request.method == "POST": form = MensajeCorreoForm(request.POST) if form.is_valid(): asunto_usuario = form.cleaned_data['asunto'] mensaje_usuario = form.cleaned_data['mensaje'] asunto = f"{asunto_usuario} - {evento.nombre}" from_email = settings.DEFAULT_FROM_EMAIL for usuario in destinatarios: html_content = render_to_string( 'emails/correo_a_inscritos.html', {'evento': evento, 'usuario': usuario, 'mensaje_usuario': mensaje_usuario, 'asunto_usuario': asunto_usuario} ) msg = EmailMultiAlternatives( asunto, mensaje_usuario, from_email, [usuario], ) msg.attach_alternative(html_content, "text/html") msg.send() messages.success(request, f"Correo enviado a {len(destinatarios)} inscritos.") return redirect('eventos:detalle_evento', evento_id=evento.id) else: # Crear texto por defecto con los datos del evento (formato HTML) asunto_defecto = f"Información sobre {evento.nombre}" mensaje_defecto = f"""

Hola a todos,

Os escribimos para daros información sobre el pozo:

""" if evento.lugar: mensaje_defecto += f'

Lugar: { evento.lugar.nombre } ({ evento.lugar.notas })

' mensaje_defecto += "

Por favor se ruega y se pide puntualidad.

" form = MensajeCorreoForm(initial={ 'asunto': asunto_defecto, 'mensaje': mensaje_defecto }) return render(request, 'eventos/enviar_correo_inscritos.html', {'form': form, 'evento': evento}) # Muestra las estadísticas por usuario @login_required def estadisticas_por_usuario(request): today = date.today() mes_comienzo_temporada = settings.MES_COMIENZO_TEMPORADA if today.month < mes_comienzo_temporada: filtro_fecha = date(today.year - 1, mes_comienzo_temporada, 1) else: filtro_fecha = date(today.year, mes_comienzo_temporada, 1) reservas = Reserva.objects.filter(evento__fecha__gte=filtro_fecha) datos = ( reservas.values('usuario__id', 'usuario__email', 'usuario__nombre') .annotate(eventos_inscritos=Count('evento', distinct=True)) .order_by('-eventos_inscritos') ) usuarios = User.objects.all().annotate( eventos_inscritos=Count( 'reserva__evento', filter=Q(reserva__evento__fecha__gte=filtro_fecha), distinct=True) ).order_by('-eventos_inscritos') return render(request, 'eventos/estadisticas_usuarios.html', { 'datos': usuarios, 'desde': filtro_fecha, }) @login_required def eventos_por_usuario(request, usuario_id): usuario = get_object_or_404(User, pk=usuario_id) reservas = Reserva.objects.filter(usuario=usuario).select_related("evento") return render(request, "eventos/eventos_por_usuario.html", { "usuario": usuario, "reservas": reservas }) @staff_member_required def cancelar_reserva_admin(request, reserva_id): reserva = get_object_or_404(Reserva, id=reserva_id) usuario_id = reserva.usuario.id if request.method == "POST": reserva.delete() return redirect("eventos:eventos_por_usuario", usuario_id) # Función para enviar mensaje a canal def send_message(text): url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage" params = { "chat_id": CHAT_ID, "text": text, } response = requests.get(url, params=params) return response.json() # Función para enviar mensaje al canal, con foto def send_photo_url(photo_url, caption=None): url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendPhoto" params = { "chat_id": CHAT_ID, "photo": photo_url, } if caption: params["caption"] = caption response = requests.get(url, params=params) return response.json()