| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653 |
- 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
- 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
- from .models import Evento, Reserva, ListaEspera, Noticia
- 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__)
- # 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()
- 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)
- # Verifica si el usuario es administrador
- def es_admin(user):
- return user.is_staff
- @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"""<p>Hola a todos,</p>
- <p>Os escribimos para daros información sobre el pozo:</p>
- <ul>
- <li><strong>Descripción:</strong> {evento.nombre}</li>
- <li><strong>Fecha:</strong> {evento.fecha.strftime('%d/%m/%Y')} a las {evento.hora}</li>
- <li><strong>Precio:</strong> {evento.precio} € por persona</li>
- </ul>
- """
-
- if evento.lugar:
- mensaje_defecto += f'<p><strong>Lugar:</strong> <a href="{ evento.lugar.url_ubicacion }" target="_blank">{ evento.lugar.nombre }</a> ({ evento.lugar.notas })</p>'
- mensaje_defecto += "<p>Por favor se ruega y se pide <strong>puntualidad</strong>.</p>"
-
- form = MensajeCorreoForm(initial={
- 'asunto': asunto_defecto,
- 'mensaje': mensaje_defecto
- })
- return render(request, 'eventos/enviar_correo_inscritos.html', {'form': form, 'evento': evento})
- @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)
|