views.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. from django.shortcuts import render, get_object_or_404, redirect
  2. from django.contrib.auth.decorators import login_required, user_passes_test
  3. from django.contrib import messages
  4. from django.contrib.admin.views.decorators import staff_member_required
  5. from django.core.mail import EmailMultiAlternatives
  6. from django.conf import settings
  7. from django.template.loader import render_to_string
  8. from django.utils import timezone
  9. from datetime import date
  10. import logging
  11. import requests
  12. import os
  13. import json
  14. from django.core.mail import send_mass_mail
  15. from django.core.mail import BadHeaderError
  16. from smtplib import SMTPException
  17. from django.http import HttpResponse
  18. from rest_framework.response import Response
  19. from rest_framework.decorators import api_view
  20. from .serializers import EventoSerializer, ReservaSerializer, ListaEsperaSerializer, NoticiaSerializer, LugarSerializer
  21. from .models import Evento, Reserva, ListaEspera, Noticia, Lugar
  22. from .forms import ListaEsperaForm, EventoForm, MensajeCorreoForm, ReservaForm
  23. from django.contrib.auth import get_user_model
  24. from django.db.models import Count, Q
  25. User = get_user_model()
  26. logger = logging.getLogger(__name__)
  27. BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "8604655521:AAEREJsenslfLyZ4vpNs475BTI6rboJ9UY0")
  28. CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "-1003558251921") # chat_id del canal JugarAlPadel
  29. # helper for sending reservation emails
  30. def enviar_correo_reserva(evento, usuario, acompanante=None):
  31. """Envía los correos de confirmación al usuario (y acompañante) y notificación al admin.
  32. Parameters
  33. ----------
  34. evento : Evento
  35. Instancia del evento reservado.
  36. usuario : User
  37. Usuario que realiza la reserva.
  38. acompanante : User | None
  39. Si se trata de un evento para parejas, usuario acompañante opcional.
  40. """
  41. from_email = settings.DEFAULT_FROM_EMAIL
  42. to_email = usuario.email
  43. cc_email = []
  44. # cc siempre al administrador; al acompañante si existe
  45. # cc_email = [settings.ADMIN_EMAIL]
  46. if acompanante and acompanante.email:
  47. cc_email.append(acompanante.email)
  48. # cuerpo de texto plano
  49. texto = f'Hola {usuario.nombre}, \n\n'
  50. texto += f'Te has inscrito correctamente en el evento "{evento.nombre}".\n'
  51. texto += f'Detalles del evento:\n'
  52. texto += f'Nombre: {evento.nombre}\n'
  53. texto += f'Descripción: {evento.descripcion}\n'
  54. texto += f'Fecha: {evento.fecha.strftime("%d/%b/%y")} a las {evento.hora}\n'
  55. texto += f'Precio: {evento.precio} €'
  56. if acompanante:
  57. texto += f"\nHas indicado como pareja a {acompanante.nombre}.\n"
  58. texto += '\n¡Gracias por inscribirte!\n\n'
  59. texto += '\n\nIMPORTANTE: Para gestionar cambios o bajas NO contestes a este correo, escribe al administrador por WhatsApp\n\n'
  60. # plantilla HTML adecuada
  61. if acompanante:
  62. html_content = render_to_string(
  63. 'emails/confirmacion_reserva_parejas.html',
  64. {'evento': evento, 'usuario': usuario, 'acompanante': acompanante}
  65. )
  66. else:
  67. html_content = render_to_string(
  68. 'emails/confirmacion_reserva.html',
  69. {'evento': evento, 'usuario': usuario}
  70. )
  71. msg = EmailMultiAlternatives(
  72. f'Confirmación de Reserva para {evento.nombre}',
  73. texto,
  74. from_email,
  75. [to_email],
  76. cc=cc_email
  77. )
  78. msg.attach_alternative(html_content, "text/html")
  79. msg.send()
  80. # notificación al administrador (se envía siempre incluso si ya está
  81. # en cc porque el formato es distinto y queremos consolidar datos de plazas)
  82. asunto_admin = f'[{evento.plazas_restantes()}/{evento.plazas_disponibles}] Nueva Reserva para {evento.nombre}'
  83. mensaje_admin = (
  84. f'El usuario {usuario.nombre} ({usuario.email}) se ha inscrito en el evento "{evento.nombre}".\n'
  85. f'Quedan {evento.plazas_restantes()} plazas disponibles.'
  86. )
  87. html_content_admin = render_to_string(
  88. 'emails/notificacion_reserva_admin.html',
  89. {
  90. 'evento': evento,
  91. 'usuario': usuario,
  92. 'acompanante': acompanante,
  93. 'plazas_restantes': evento.plazas_restantes(),
  94. 'plazas_disponibles': evento.plazas_disponibles,
  95. },
  96. )
  97. email_admin = EmailMultiAlternatives(
  98. asunto_admin,
  99. mensaje_admin,
  100. settings.DEFAULT_FROM_EMAIL,
  101. [settings.ADMIN_EMAIL],
  102. )
  103. email_admin.attach_alternative(html_content_admin, "text/html")
  104. email_admin.send()
  105. def enviar_correo_lista_espera(evento, usuario, acompanante=None):
  106. """Envía correos cuando un usuario se apunta a la lista de espera.
  107. Parameters
  108. ----------
  109. evento : Evento
  110. usuario : User
  111. acompanante : User | None
  112. """
  113. from_email = settings.DEFAULT_FROM_EMAIL
  114. to_email = usuario.email
  115. cc_email = []
  116. if acompanante and acompanante.email:
  117. cc_email.append(acompanante.email)
  118. texto = f'Hola {usuario.nombre},\n\n'
  119. texto += f'Te has añadido a la lista de espera del evento "{evento.nombre}".\n'
  120. texto += f'Detalles del evento:\n'
  121. texto += f'Nombre: {evento.nombre}\n'
  122. texto += f'Descripción: {evento.descripcion}\n'
  123. texto += f'Fecha: {evento.fecha.strftime("%d/%b/%y")} a las {evento.hora}\n'
  124. if acompanante:
  125. texto += f"\nHas indicado como pareja a {acompanante.nombre}.\n"
  126. texto += '\nTe avisaremos si se libera alguna plaza.\n\n'
  127. texto += '\n\nIMPORTANTE: Para gestionar cambios o bajas NO contestes a este correo, escribe al administrador por WhatsApp\n\n'
  128. if acompanante:
  129. html = render_to_string(
  130. 'emails/confirmacion_lista_espera_parejas.html',
  131. {'evento': evento, 'usuario': usuario, 'acompanante': acompanante},
  132. )
  133. else:
  134. html = render_to_string(
  135. 'emails/confirmacion_lista_espera.html',
  136. {'evento': evento, 'usuario': usuario},
  137. )
  138. subject = f'Confirmación lista de espera para {evento.nombre}'
  139. msg = EmailMultiAlternatives(subject, texto, from_email, [to_email], cc=cc_email)
  140. msg.attach_alternative(html, 'text/html')
  141. msg.send()
  142. # notificar al admin
  143. asunto_admin = f'Nueva lista de espera para {evento.nombre}'
  144. mensaje_admin = (
  145. f'El usuario {usuario.nombre} ({usuario.email}) se ha añadido a la lista de espera.'
  146. )
  147. if acompanante:
  148. mensaje_admin += f' Acompañante: {acompanante.nombre} ({acompanante.email}).'
  149. html_admin = render_to_string(
  150. 'emails/notificacion_lista_espera_admin.html',
  151. {'evento': evento, 'usuario': usuario, 'acompanante': acompanante},
  152. )
  153. email_admin2 = EmailMultiAlternatives(
  154. asunto_admin,
  155. mensaje_admin,
  156. settings.DEFAULT_FROM_EMAIL,
  157. [settings.ADMIN_EMAIL],
  158. )
  159. email_admin2.attach_alternative(html_admin, 'text/html')
  160. email_admin2.send()
  161. @login_required
  162. def reservar_evento(request, evento_id):
  163. evento = get_object_or_404(Evento, id=evento_id)
  164. # Verificar si el usuario ya tiene una reserva para este evento
  165. if Reserva.objects.filter(evento=evento, usuario=request.user).exists():
  166. messages.error(request, 'Ya tienes una reserva para este evento.')
  167. return redirect('eventos:lista_eventos')
  168. # Verificar si quedan plazas disponibles
  169. if evento.plazas_restantes() > 0:
  170. Reserva.objects.create(evento=evento, usuario=request.user, iniciador=True)
  171. # separar el envío de correos a función reutilizable
  172. enviar_correo_reserva(evento, request.user)
  173. messages.success(request,
  174. f'Reserva realizada para el evento {evento.nombre}.')
  175. else:
  176. messages.error(request, 'Lo sentimos, no quedan plazas disponibles.')
  177. return redirect('eventos:lista_eventos')
  178. @login_required
  179. def reservar_evento_parejas(request, evento_id):
  180. evento = get_object_or_404(Evento, id=evento_id)
  181. # Si ya está inscrito
  182. if Reserva.objects.filter(evento=evento, usuario=request.user).exists():
  183. messages.warning(request, "Ya estás inscrito en este evento.")
  184. return redirect('eventos:detalle_evento', evento_id=evento.id)
  185. if request.method == 'POST':
  186. form = ReservaForm(request.POST, evento=evento, usuario=request.user)
  187. if form.is_valid():
  188. acompanante = form.cleaned_data.get('acompanante')
  189. # Crear la reserva principal
  190. Reserva.objects.create(
  191. evento=evento,
  192. usuario=request.user,
  193. iniciador=True,
  194. acompanante=acompanante if evento.para_parejas else None
  195. )
  196. # Si es un evento para parejas, crear también la reserva del acompañante
  197. if evento.para_parejas and acompanante:
  198. # Solo si el acompañante no está ya inscrito
  199. if not Reserva.objects.filter(evento=evento, usuario=acompanante).exists():
  200. Reserva.objects.create(
  201. evento=evento,
  202. usuario=acompanante,
  203. iniciador=False,
  204. acompanante=request.user
  205. )
  206. # Enviar correos de confirmación/aviso usando la función reutilizable
  207. enviar_correo_reserva(evento, request.user, acompanante=acompanante if evento.para_parejas else None)
  208. messages.success(request, "Reserva realizada correctamente.")
  209. return redirect('eventos:detalle_evento', evento_id=evento.id)
  210. else:
  211. form = ReservaForm(evento=evento, usuario=request.user)
  212. return render(request, 'eventos/reservar_evento.html', {'form': form, 'evento': evento})
  213. @user_passes_test(lambda u: u.is_staff)
  214. def publicar_evento(request, evento_id):
  215. evento = get_object_or_404(Evento, id=evento_id)
  216. if not evento.publicado:
  217. evento.publicado = True
  218. evento.save()
  219. # Envía mensajes al canal de Telegram
  220. if evento.para_parejas:
  221. send_photo_url("https://jugaralpadel.es/static/images/parejas.png", "Pozo %s publicado: https://jugaralpadel.es/eventos/eventos/%s/" % (evento.nombre, evento.id))
  222. else:
  223. send_photo_url("https://jugaralpadel.es/static/images//padel-court.jpg", "Pozo %s publicado: https://jugaralpadel.es/eventos/eventos/%s/" % (evento.nombre, evento.id))
  224. messages.success(request, 'El evento %s ha sido publicado' % evento.nombre)
  225. else:
  226. messages.info(request, 'El evento %s ya está publicado' % evento.nombre)
  227. return redirect('eventos:detalle_evento', evento_id=evento_id)
  228. @login_required
  229. def lista_eventos(request):
  230. # anno_actual = timezone.now().year
  231. # Solo mostrar los eventos publicados a los usuarios normales
  232. if request.user.is_staff:
  233. eventos = Evento.objects.all().order_by('-fecha', '-hora')
  234. else:
  235. eventos = Evento.objects.filter(publicado=True).order_by('-fecha', '-hora')
  236. # eventos = Evento.objects.filter(publicado=True, fecha__year=anno_actual) # Los usuarios ven solo eventos publicados y del año en curso
  237. # Crear un diccionario que mapea cada evento con un booleano indicando si el usuario ya ha reservado
  238. eventos_con_reserva = []
  239. for evento in eventos:
  240. inscrito = Reserva.objects.filter(evento=evento, usuario=request.user).exists() if request.user.is_authenticated else False
  241. if ListaEspera.objects.filter(evento=evento, usuario=request.user).exists():
  242. en_espera = True
  243. else:
  244. en_espera = False
  245. eventos_con_reserva.append({
  246. 'evento': evento,
  247. 'inscrito': inscrito,
  248. 'id': evento.id,
  249. 'nombre': evento.nombre,
  250. 'fecha': evento.fecha,
  251. 'hora': evento.hora,
  252. 'plazas_disponibles': evento.plazas_disponibles,
  253. 'plazas_restantes': evento.plazas_restantes,
  254. 'en_espera': en_espera,
  255. 'usuario': request.user,
  256. 'publicado': evento.publicado,
  257. 'url_imagen': evento.url_imagen,
  258. 'para_parejas': evento.para_parejas
  259. })
  260. return render(request, 'eventos/lista_eventos.html', {'eventos_con_reserva': eventos_con_reserva})
  261. def detalle_noticia(request, noticia_id):
  262. noticia = get_object_or_404(Noticia, id=noticia_id, publicado=True)
  263. return render(request, 'detalle_noticia.html', {'noticia': noticia})
  264. def ayuda(request):
  265. return render(request, 'eventos/help.html')
  266. @login_required
  267. def detalle_evento(request, evento_id):
  268. el_evento = []
  269. evento = get_object_or_404(Evento, pk=evento_id)
  270. reservas = Reserva.objects.filter(evento=evento_id)
  271. inscrito = Reserva.objects.filter(evento=evento, usuario=request.user).exists() if request.user.is_authenticated else False
  272. if ListaEspera.objects.filter(evento=evento, usuario=request.user).exists():
  273. en_espera = True
  274. else:
  275. en_espera = False
  276. lista_espera = ListaEspera.objects.filter(evento=evento_id)
  277. el_evento.append(
  278. {
  279. 'evento': evento,
  280. 'inscrito': inscrito,
  281. 'id': evento.id,
  282. 'nombre': evento.nombre,
  283. 'descripcion': evento.descripcion,
  284. 'fecha': evento.fecha,
  285. 'hora': evento.hora,
  286. 'precio': evento.precio,
  287. 'plazas_disponibles': evento.plazas_disponibles,
  288. 'plazas_restantes': evento.plazas_restantes,
  289. 'en_espera': en_espera,
  290. 'usuario': request.user,
  291. 'publicado': evento.publicado,
  292. 'url_imagen': evento.url_imagen,
  293. 'para_parejas': evento.para_parejas,
  294. 'lugar': evento.lugar
  295. }
  296. )
  297. return render(request, 'eventos/detalle_evento.html', {'eventos': el_evento, 'reservas': reservas, 'lista': lista_espera})
  298. # Verificar si el usuario es administrador
  299. def es_admin(user):
  300. return user.is_staff
  301. @user_passes_test(es_admin)
  302. def crear_evento(request):
  303. if request.method == 'POST':
  304. form = EventoForm(request.POST)
  305. if form.is_valid():
  306. form.save()
  307. messages.success(request, 'Evento creado exitosamente.')
  308. return redirect('eventos:lista_eventos') # Redirige a la lista de eventos
  309. else:
  310. form = EventoForm()
  311. return render(request, 'eventos/crear_evento.html', {'form': form})
  312. @user_passes_test(lambda u: u.is_staff)
  313. def editar_evento(request, evento_id):
  314. evento = get_object_or_404(Evento, id=evento_id)
  315. if request.method == 'POST':
  316. form = EventoForm(request.POST, instance=evento)
  317. if form.is_valid():
  318. form.save()
  319. return redirect('lista_eventos')
  320. else:
  321. form = EventoForm(instance=evento)
  322. return render(request, 'crear_evento.html', {'form': form, 'evento': evento})
  323. @login_required
  324. def apuntar_lista_espera(request, evento_id):
  325. evento = get_object_or_404(Evento, id=evento_id)
  326. # Verificar si el usuario ya está en la lista de espera
  327. if ListaEspera.objects.filter(evento=evento, usuario=request.user).exists():
  328. messages.error(request, 'Ya estás en la lista de espera para este evento.')
  329. return redirect('eventos:lista_eventos')
  330. # Crear una entrada en la lista de espera
  331. if request.method == 'POST':
  332. form = ListaEsperaForm(request.POST)
  333. if form.is_valid():
  334. ListaEspera.objects.create(evento=evento, usuario=request.user)
  335. # enviar correos de confirmación y aviso al administrador
  336. enviar_correo_lista_espera(evento, request.user)
  337. messages.success(request, 'Te has apuntado a la lista de espera para este evento.')
  338. return redirect('eventos:lista_eventos')
  339. form = ListaEsperaForm()
  340. return render(request, 'eventos/apuntar_lista_espera.html', {'form': form, 'evento': evento})
  341. @login_required
  342. def apuntar_lista_espera_parejas(request, evento_id):
  343. evento = get_object_or_404(Evento, id=evento_id)
  344. # Verificar si el usuario ya está en la lista de espera
  345. if ListaEspera.objects.filter(evento=evento, usuario=request.user).exists():
  346. messages.error(request, 'Ya estás en la lista de espera para este evento.')
  347. return redirect('eventos:lista_eventos')
  348. # Crear una entrada en la lista de espera
  349. if request.method == 'POST':
  350. form = ListaEsperaForm(request.POST, evento=evento, usuario=request.user)
  351. if form.is_valid():
  352. acompanante = form.cleaned_data.get('acompanante')
  353. ListaEspera.objects.create(
  354. evento=evento,
  355. usuario=request.user,
  356. acompanante=acompanante if evento.para_parejas else None
  357. )
  358. # enviar correos de confirmación y aviso al administrador
  359. enviar_correo_lista_espera(evento, request.user,
  360. acompanante=acompanante if evento.para_parejas else None)
  361. messages.success(request, 'Te has apuntado a la lista de espera para este evento.')
  362. return redirect('eventos:lista_eventos')
  363. else:
  364. form = ListaEsperaForm(evento=evento, usuario=request.user)
  365. return render(request, 'eventos/apuntar_lista_espera.html', {'form': form, 'evento': evento})
  366. #
  367. # API
  368. #
  369. @api_view(['GET'])
  370. def api_lista_eventos(request):
  371. """Devuelve la lista de todos los eventos."""
  372. eventos = Evento.objects.all()
  373. serializer = EventoSerializer(eventos, many=True)
  374. return Response(serializer.data)
  375. @api_view(['GET'])
  376. def api_detalle_evento(request, evento_id):
  377. """Devuelve los detalles de un evento específico."""
  378. try:
  379. evento = Evento.objects.get(id=evento_id)
  380. serializer = EventoSerializer(evento)
  381. return Response(serializer.data)
  382. except Evento.DoesNotExist:
  383. return Response({'error': 'Evento no encontrado'}, status=404)
  384. @api_view(['GET'])
  385. def api_lista_reservas(request):
  386. """Devuelve la lista de todos los reservas."""
  387. reservas = Reserva.objects.all()
  388. serializer = ReservaSerializer(reservas, many=True)
  389. return Response(serializer.data)
  390. @api_view(['GET'])
  391. def api_detalle_reserva(request, reserva_id):
  392. """Devuelve los detalles de un reserva específico."""
  393. try:
  394. reserva = Reserva.objects.get(id=reserva_id)
  395. serializer = ReservaSerializer(reserva)
  396. return Response(serializer.data)
  397. except Reserva.DoesNotExist:
  398. return Response({'error': 'Reserva no encontrado'}, status=404)
  399. @api_view(['GET'])
  400. def api_lista_listaespera(request):
  401. """Devuelve la lista de todos los listaespera."""
  402. listaesperas = ListaEspera.objects.all()
  403. serializer = ListaEsperaSerializer(listaesperas, many=True)
  404. return Response(serializer.data)
  405. @api_view(['GET'])
  406. def api_detalle_listaespera(request, listaespera_id):
  407. """Devuelve los detalles de un listaespera específico."""
  408. try:
  409. listaespera = ListaEspera.objects.get(id=listaespera_id)
  410. serializer = ListaEsperaSerializer(listaespera)
  411. return Response(serializer.data)
  412. except ListaEspera.DoesNotExist:
  413. return Response({'error': 'ListaEspera no encontrado'}, status=404)
  414. @api_view(['GET'])
  415. def api_lista_noticias(request):
  416. """Devuelve la lista de todos los noticias."""
  417. noticias = Noticia.objects.all()
  418. serializer = NoticiaSerializer(noticias, many=True)
  419. return Response(serializer.data)
  420. @api_view(['GET'])
  421. def api_detalle_noticia(request, noticia_id):
  422. """Devuelve los detalles de un noticia específico."""
  423. try:
  424. noticia = Noticia.objects.get(id=noticia_id)
  425. serializer = NoticiaSerializer(noticia)
  426. return Response(serializer.data)
  427. except Noticia.DoesNotExist:
  428. return Response({'error': 'Noticia no encontrado'}, status=404)
  429. @api_view(['GET'])
  430. def api_download_lugares(request):
  431. """Descarga la lista de lugares como archivo JSON."""
  432. lugares = Lugar.objects.all()
  433. serializer = LugarSerializer(lugares, many=True)
  434. response = HttpResponse(
  435. json.dumps(serializer.data, indent=2, ensure_ascii=False),
  436. content_type='application/json'
  437. )
  438. response['Content-Disposition'] = 'attachment; filename="lugares.json"'
  439. return response
  440. # Verifica si el usuario es administrador
  441. def es_admin(user):
  442. return user.is_staff
  443. # Enviar correo a los inscritos en formato html
  444. @user_passes_test(es_admin) # Solo administradores pueden acceder
  445. def enviar_correo_inscritos_html(request, evento_id):
  446. """
  447. Envía un correo HTML a los inscritos en un evento
  448. """
  449. evento = get_object_or_404(Evento, id=evento_id)
  450. inscritos = Reserva.objects.filter(evento=evento).select_related('usuario')
  451. if not inscritos:
  452. messages.warning(request, "No hay usuarios inscritos en este evento.")
  453. return redirect('eventos:detalle_evento', evento_id=evento.id)
  454. destinatarios = [reserva.usuario.email for reserva in inscritos if reserva.usuario.email]
  455. if not destinatarios:
  456. messages.warning(request, "No hay correos disponibles para los inscritos.")
  457. return redirect('eventos:detalle_evento', evento_id=evento.id)
  458. if request.method == "POST":
  459. form = MensajeCorreoForm(request.POST)
  460. if form.is_valid():
  461. asunto_usuario = form.cleaned_data['asunto']
  462. mensaje_usuario = form.cleaned_data['mensaje']
  463. asunto = f"{asunto_usuario} - {evento.nombre}"
  464. from_email = settings.DEFAULT_FROM_EMAIL
  465. for usuario in destinatarios:
  466. html_content = render_to_string(
  467. 'emails/correo_a_inscritos.html',
  468. {'evento': evento,
  469. 'usuario': usuario,
  470. 'mensaje_usuario': mensaje_usuario,
  471. 'asunto_usuario': asunto_usuario}
  472. )
  473. msg = EmailMultiAlternatives(
  474. asunto,
  475. mensaje_usuario,
  476. from_email,
  477. [usuario],
  478. )
  479. msg.attach_alternative(html_content, "text/html")
  480. msg.send()
  481. messages.success(request, f"Correo enviado a {len(destinatarios)} inscritos.")
  482. return redirect('eventos:detalle_evento', evento_id=evento.id)
  483. else:
  484. # Crear texto por defecto con los datos del evento (formato HTML)
  485. asunto_defecto = f"Información sobre {evento.nombre}"
  486. mensaje_defecto = f"""<p>Hola a todos,</p>
  487. <p>Os escribimos para daros información sobre el pozo:</p>
  488. <ul>
  489. <li><strong>Descripción:</strong> {evento.nombre}</li>
  490. <li><strong>Fecha:</strong> {evento.fecha.strftime('%d/%m/%Y')} a las {evento.hora}</li>
  491. <li><strong>Precio:</strong> {evento.precio} € por persona</li>
  492. </ul>
  493. """
  494. if evento.lugar:
  495. mensaje_defecto += f'<p><strong>Lugar:</strong> <a href="{ evento.lugar.url_ubicacion }" target="_blank">{ evento.lugar.nombre }</a> ({ evento.lugar.notas })</p>'
  496. mensaje_defecto += "<p>Por favor se ruega y se pide <strong>puntualidad</strong>.</p>"
  497. form = MensajeCorreoForm(initial={
  498. 'asunto': asunto_defecto,
  499. 'mensaje': mensaje_defecto
  500. })
  501. return render(request, 'eventos/enviar_correo_inscritos.html', {'form': form, 'evento': evento})
  502. # Muestra las estadísticas por usuario
  503. @login_required
  504. def estadisticas_por_usuario(request):
  505. today = date.today()
  506. mes_comienzo_temporada = settings.MES_COMIENZO_TEMPORADA
  507. if today.month < mes_comienzo_temporada:
  508. filtro_fecha = date(today.year - 1, mes_comienzo_temporada, 1)
  509. else:
  510. filtro_fecha = date(today.year, mes_comienzo_temporada, 1)
  511. reservas = Reserva.objects.filter(evento__fecha__gte=filtro_fecha)
  512. datos = (
  513. reservas.values('usuario__id', 'usuario__email', 'usuario__nombre')
  514. .annotate(eventos_inscritos=Count('evento', distinct=True))
  515. .order_by('-eventos_inscritos')
  516. )
  517. usuarios = User.objects.all().annotate(
  518. eventos_inscritos=Count(
  519. 'reserva__evento',
  520. filter=Q(reserva__evento__fecha__gte=filtro_fecha),
  521. distinct=True)
  522. ).order_by('-eventos_inscritos')
  523. return render(request, 'eventos/estadisticas_usuarios.html', {
  524. 'datos': usuarios,
  525. 'desde': filtro_fecha,
  526. })
  527. @login_required
  528. def eventos_por_usuario(request, usuario_id):
  529. usuario = get_object_or_404(User, pk=usuario_id)
  530. reservas = Reserva.objects.filter(usuario=usuario).select_related("evento")
  531. return render(request, "eventos/eventos_por_usuario.html", {
  532. "usuario": usuario,
  533. "reservas": reservas
  534. })
  535. @staff_member_required
  536. def cancelar_reserva_admin(request, reserva_id):
  537. reserva = get_object_or_404(Reserva, id=reserva_id)
  538. usuario_id = reserva.usuario.id
  539. if request.method == "POST":
  540. reserva.delete()
  541. return redirect("eventos:eventos_por_usuario", usuario_id)
  542. # Función para enviar mensaje a canal
  543. def send_message(text):
  544. url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
  545. params = {
  546. "chat_id": CHAT_ID,
  547. "text": text,
  548. }
  549. response = requests.get(url, params=params)
  550. return response.json()
  551. # Función para enviar mensaje al canal, con foto
  552. def send_photo_url(photo_url, caption=None):
  553. url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendPhoto"
  554. params = {
  555. "chat_id": CHAT_ID,
  556. "photo": photo_url,
  557. }
  558. if caption:
  559. params["caption"] = caption
  560. response = requests.get(url, params=params)
  561. return response.json()