mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-12-06 20:07:43 +01:00
add email feature
This commit is contained in:
@@ -24,3 +24,7 @@ WIKI_PASSWORD=
|
|||||||
|
|
||||||
# OIDC
|
# OIDC
|
||||||
OIDC_RSA_PRIVATE_KEY=CHANGE_ME
|
OIDC_RSA_PRIVATE_KEY=CHANGE_ME
|
||||||
|
|
||||||
|
# Activity configuration
|
||||||
|
TRUSTED_ACTIVITY_MAIL=
|
||||||
|
ACTIVITY_EMAIL_MANAGER=
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
{% comment %}
|
{% comment %}
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
{% load i18n perms %}
|
{% load i18n perms crispy_forms_tags %}
|
||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
{% load static django_tables2 i18n %}
|
{% load static django_tables2 i18n %}
|
||||||
|
|
||||||
@@ -43,6 +43,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<button class="btn btn-block btn-danger"><i class="fa fa-file-pdf-o"></i> {% trans "Export to PDF" %}</button>
|
<button class="btn btn-block btn-danger"><i class="fa fa-file-pdf-o"></i> {% trans "Export to PDF" %}</button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="{% url 'activity:guest_pdf' activity_pk=activity.pk %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ email_form|crispy }}
|
||||||
|
<button class="btn btn-primary" type="submit">{% trans "Share" %}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -120,5 +127,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
errMsg(xhr.responseJSON);
|
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 %}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
24
apps/activity/templates/activity/guest_list.html
Normal file
24
apps/activity/templates/activity/guest_list.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% now "Y-m-d" as today %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>[Note Kfet] Liste des invité·e·s à l'activité {{ activity.name }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
--
|
||||||
|
<p>
|
||||||
|
Le BDE<br>
|
||||||
|
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
apps/activity/templates/activity/guest_list.txt
Normal file
13
apps/activity/templates/activity/guest_list.txt
Normal file
@@ -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" %}
|
||||||
@@ -17,11 +17,11 @@ jusqu'au {{ activity.date_end.astimezone.date }} à {{ activity.date_end.astimez
|
|||||||
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\normalsize
|
\normalsize
|
||||||
\begin{longtable}{c||c|c|c}
|
\begin{longtable}{c||c|c|c|c|}
|
||||||
& \textbf{Nom} & \textbf{Prénom} & \textbf{École} \\
|
& \textbf{Nom} & \textbf{Prénom} & \textbf{École} & \textbf{Entrée} \\
|
||||||
\hline\hline
|
\hline\hline
|
||||||
{% for guest in guests %}
|
{% 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
|
\hline
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
\end{longtable}
|
\end{longtable}
|
||||||
|
|||||||
@@ -11,11 +11,12 @@ from django.conf import settings
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.core.mail import EmailMultiAlternatives
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import F, Q
|
from django.db.models import F, Q
|
||||||
from django.db.models.functions.text import Lower
|
from django.db.models.functions.text import Lower
|
||||||
from django.http import HttpResponse, JsonResponse
|
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import gettext_lazy as _
|
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.backends import PermissionBackend
|
||||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
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 .models import Activity, Entry, Guest, Opener
|
||||||
from .tables import ActivityTable, EntryTable, GuestTable, OpenerTable
|
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"))
|
guests_view = guests.filter(PermissionBackend.filter_queryset(self.request, Guest, "view"))
|
||||||
if guests.exists() and guests.count() == guests_view.count():
|
if guests.exists() and guests.count() == guests_view.count():
|
||||||
context["export"] = True
|
context["export"] = True
|
||||||
|
context["email_form"] = EmailForm
|
||||||
|
if 'mail' in self.request.GET:
|
||||||
|
context["mail"] = self.request.GET['mail'].split(',')
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@@ -445,6 +449,29 @@ class GuestListRenderView(LoginRequiredMixin, View):
|
|||||||
return qs.distinct()
|
return qs.distinct()
|
||||||
|
|
||||||
def get(self, request, **kwargs):
|
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()
|
qs = self.get_queryset()
|
||||||
|
|
||||||
activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
|
activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
|
||||||
@@ -480,19 +507,43 @@ class GuestListRenderView(LoginRequiredMixin, View):
|
|||||||
log = f.read()
|
log = f.read()
|
||||||
raise IOError("An error attempted while generating a Guest list (code=" + str(error) + ")\n\n" + log)
|
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:
|
with open("{}/guest-list.pdf".format(tmp_dir), 'rb') as f:
|
||||||
pdf = f.read()
|
pdf = f.read()
|
||||||
response = HttpResponse(pdf, content_type="application/pdf")
|
return pdf
|
||||||
response['Content-Disposition'] = "inline;filename=Liste des invité·e·s.pdf"
|
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
raise e
|
raise e
|
||||||
finally:
|
finally:
|
||||||
# Delete all temporary files
|
# Delete all temporary files
|
||||||
shutil.rmtree(tmp_dir)
|
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
|
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
|
# Cache for 1 hour
|
||||||
@method_decorator(cache_page(60 * 60), name='dispatch')
|
@method_decorator(cache_page(60 * 60), name='dispatch')
|
||||||
|
|||||||
@@ -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
|
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.
|
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
|
Entrées aux soirées
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user