diff --git a/.env_example b/.env_example
index da0b4efa..499b6f6a 100644
--- a/.env_example
+++ b/.env_example
@@ -24,3 +24,7 @@ WIKI_PASSWORD=
# OIDC
OIDC_RSA_PRIVATE_KEY=CHANGE_ME
+
+# Activity configuration
+TRUSTED_ACTIVITY_MAIL=
+ACTIVITY_EMAIL_MANAGER=
diff --git a/apps/activity/forms.py b/apps/activity/forms.py
index a865ece6..cfa1a7fc 100644
--- a/apps/activity/forms.py
+++ b/apps/activity/forms.py
@@ -120,3 +120,12 @@ class GuestForm(forms.ModelForm):
},
),
}
+
+
+class EmailForm(forms.Form):
+ """
+ Form to export guest list by email
+ """
+ emails = forms.CharField()
+ emails.label = _("Emails")
+ emails.widget.attrs['placeholder'] = _("Emails, separated by a comma")
diff --git a/apps/activity/templates/activity/activity_detail.html b/apps/activity/templates/activity/activity_detail.html
index f119c797..63aba60d 100644
--- a/apps/activity/templates/activity/activity_detail.html
+++ b/apps/activity/templates/activity/activity_detail.html
@@ -2,7 +2,7 @@
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
-{% load i18n perms %}
+{% load i18n perms crispy_forms_tags %}
{% load render_table from django_tables2 %}
{% load static django_tables2 i18n %}
@@ -43,6 +43,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
+
+
+
{% endif %}
{% endif %}
@@ -120,5 +127,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
errMsg(xhr.responseJSON);
});
});
+ {% if mail %}
+ var mails = {{ mail|safe }};
+ for (const mail of mails) {
+ addMsg(gettext("An email has been sent to") + " " + mail, "success");
+ }
+ {% endif %}
{% endblock %}
diff --git a/apps/activity/templates/activity/guest_list.html b/apps/activity/templates/activity/guest_list.html
new file mode 100644
index 00000000..b5a98564
--- /dev/null
+++ b/apps/activity/templates/activity/guest_list.html
@@ -0,0 +1,24 @@
+{% load i18n %}
+{% now "Y-m-d" as today %}
+
+
+
+
+ [Note Kfet] Liste des invité·e·s à l'activité {{ activity.name }}
+
+
+
+Bonjour,
+
+Vous trouverez en pièce-jointe la liste des invité·e·s à l'activité : {{ activity.name }}
+Cette liste vous est partagée par {{ user_identity }} (en copie de ce mail).
+
+Bonne journée
+
+--
+
+ Le BDE
+ {% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
+
+
+
diff --git a/apps/activity/templates/activity/guest_list.txt b/apps/activity/templates/activity/guest_list.txt
new file mode 100644
index 00000000..36cc2798
--- /dev/null
+++ b/apps/activity/templates/activity/guest_list.txt
@@ -0,0 +1,13 @@
+{% load i18n %}
+
+Bonjour,
+
+Vous trouverez en pièce-jointe la liste des invité·e·s à l'activité : {{ activity.name }}
+Cette liste vous est partagée par {{ user_identity }} (en copie de ce mail).
+
+Bonne journée
+
+--
+Le BDE
+
+{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
diff --git a/apps/activity/templates/activity/guestlist_sample.tex b/apps/activity/templates/activity/guestlist_sample.tex
index ff60053c..9c6b5bec 100644
--- a/apps/activity/templates/activity/guestlist_sample.tex
+++ b/apps/activity/templates/activity/guestlist_sample.tex
@@ -17,11 +17,11 @@ jusqu'au {{ activity.date_end.astimezone.date }} à {{ activity.date_end.astimez
\begin{center}
\normalsize
- \begin{longtable}{c||c|c|c}
-& \textbf{Nom} & \textbf{Prénom} & \textbf{École} \\
+ \begin{longtable}{c||c|c|c|c|}
+& \textbf{Nom} & \textbf{Prénom} & \textbf{École} & \textbf{Entrée} \\
\hline\hline
{% for guest in guests %}
-{{ forloop.counter }} & {{ guest.last_name|safe }} & {{ guest.first_name|safe }} & {{ guest.school|safe }} \\
+{{ forloop.counter }} & {{ guest.last_name|safe }} & {{ guest.first_name|safe }} & {{ guest.school|safe }} & \\
\hline
{% endfor %}
\end{longtable}
diff --git a/apps/activity/views.py b/apps/activity/views.py
index 684e51ca..0263590d 100644
--- a/apps/activity/views.py
+++ b/apps/activity/views.py
@@ -11,11 +11,12 @@ from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
+from django.core.mail import EmailMultiAlternatives
from django.db import transaction
from django.db.models import F, Q
from django.db.models.functions.text import Lower
-from django.http import HttpResponse, JsonResponse
-from django.urls import reverse_lazy
+from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
+from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
@@ -31,7 +32,7 @@ from note.models import Alias, NoteSpecial, NoteUser
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
-from .forms import ActivityForm, GuestForm
+from .forms import ActivityForm, GuestForm, EmailForm
from .models import Activity, Entry, Guest, Opener
from .tables import ActivityTable, EntryTable, GuestTable, OpenerTable
@@ -199,6 +200,9 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMix
guests_view = guests.filter(PermissionBackend.filter_queryset(self.request, Guest, "view"))
if guests.exists() and guests.count() == guests_view.count():
context["export"] = True
+ context["email_form"] = EmailForm
+ if 'mail' in self.request.GET:
+ context["mail"] = self.request.GET['mail'].split(',')
return context
@@ -445,6 +449,29 @@ class GuestListRenderView(LoginRequiredMixin, View):
return qs.distinct()
def get(self, request, **kwargs):
+ pdf = self.generate_pdf(request)
+ return self.view_pdf(request, pdf)
+
+ def post(self, request, **kwargs):
+ recipients = []
+ emails = request.POST['emails'].split(',')
+ trust_address = os.getenv('TRUSTED_ACTIVITY_MAIL', '').split(',')
+ for email_address in emails:
+ if email_address in trust_address:
+ recipients.append(email_address)
+ # don't send email if no recipient
+ if not recipients:
+ raise PermissionDenied(_("Emails are not trusted!"))
+ pdf = self.generate_pdf(request)
+ self.send_pdf(request, recipients, pdf)
+ url = reverse('activity:activity_detail', kwargs={"pk": self.kwargs["activity_pk"]})
+ url += '?mail='
+ for email in recipients:
+ url += email + ','
+ url = url[:-1] # delete last comma
+ return HttpResponseRedirect(url)
+
+ def generate_pdf(self, request, **kwargs):
qs = self.get_queryset()
activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
@@ -480,19 +507,43 @@ class GuestListRenderView(LoginRequiredMixin, View):
log = f.read()
raise IOError("An error attempted while generating a Guest list (code=" + str(error) + ")\n\n" + log)
- # Display the generated pdf as a HTTP Response
with open("{}/guest-list.pdf".format(tmp_dir), 'rb') as f:
pdf = f.read()
- response = HttpResponse(pdf, content_type="application/pdf")
- response['Content-Disposition'] = "inline;filename=Liste des invité·e·s.pdf"
+ return pdf
+
except IOError as e:
raise e
finally:
# Delete all temporary files
shutil.rmtree(tmp_dir)
+ def view_pdf(self, request, pdf):
+ response = HttpResponse(pdf, content_type="application/pdf")
+ response['Content-Disposition'] = "inline;filename=Liste des invité·e·s.pdf"
return response
+ def send_pdf(self, request, recipients, pdf):
+ user_identity = request.user.first_name.capitalize() + ' ' + request.user.last_name.upper()
+ activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
+ subject = _(f"Guest list of the activity {activity.name} share by {user_identity}")
+ # add the user in cc
+ cc = [request.user.email]
+ context = {'activity': activity, 'user_identity': user_identity}
+ message = render_to_string("activity/guest_list.txt", context=context)
+ html_message = render_to_string("activity/guest_list.html", context=context)
+ if os.getenv('ACTIVITY_EMAIL_MANAGER', ''):
+ cc.append(os.getenv('ACTIVITY_EMAIL_MANAGER'))
+ email = EmailMultiAlternatives(
+ subject=subject,
+ to=recipients,
+ cc=cc,
+ body=message,
+ )
+ email.attach("Liste des invité·e·s.pdf", pdf)
+ email.attach_alternative(html_message, "text/html")
+ email.send()
+ return
+
# Cache for 1 hour
@method_decorator(cache_page(60 * 60), name='dispatch')
diff --git a/docs/apps/activity.rst b/docs/apps/activity.rst
index abdcd3e5..0282e1f1 100644
--- a/docs/apps/activity.rst
+++ b/docs/apps/activity.rst
@@ -107,6 +107,10 @@ N'importe qui peut inviter des ami⋅es non adhérent⋅es, tant que les contrai
trois personnes par activité et une personne ne peut pas être invitée plus de 5 fois par an). L'invitation est
facturée à l'entrée.
+Les ayant-droit peuvent également générer la liste des invité·e·s au format PDF afin de la transmettre
+aux vigiles. Iels peuvent aussi l’envoyer par mail (solution privilégiée), mais uniquement à une liste
+d’adresses mail bien précise, vérifiée régulièrement.
+
Entrées aux soirées
~~~~~~~~~~~~~~~~~~~