1
0
mirror of https://gitlab.crans.org/bde/nk20-scripts synced 2025-06-22 16:28:19 +02:00

Compare commits

18 Commits

Author SHA1 Message Date
c1a6fd56eb Merge branch 'notes_report' into 'master'
send summary script

See merge request bde/nk20-scripts!5
2025-03-19 12:37:15 +01:00
043cc22f3c Merge branch 'merge_club' into 'master'
create script for fusion club

See merge request bde/nk20-scripts!10
2025-03-18 16:16:23 +01:00
57c0c253fe create script for fusion club 2025-03-18 12:42:38 +01:00
3dd5f6e3e0 Extend the possibility to send the list by email to the other newsletters 2025-03-04 16:50:49 +01:00
735d90e482 Add an option to send the list to an email 2025-03-04 16:39:04 +01:00
119c1edc2f Update intro_mail.html 2025-02-23 18:37:56 +01:00
47fc66a688 Merge branch 'ago' into 'master'
Ago

See merge request bde/nk20-scripts!9
2025-02-23 18:27:31 +01:00
21c102838b Merge branch 'ago' of https://gitlab.crans.org/bde/nk20-scripts into ago 2025-02-23 18:24:24 +01:00
0eb9ccd515 inclusive text 2025-02-23 18:21:42 +01:00
cea5f50e82 Merge branch 'ago' into 'master'
email templates for AGO

See merge request bde/nk20-scripts!8
2025-02-23 17:58:54 +01:00
6ef808bdd1 Merge branch 'master' into 'ago'
# Conflicts:
#   templates/scripts/intro_mail.html
#   templates/scripts/intro_mail.txt
2025-02-23 17:57:09 +01:00
4140966265 email templates for AGO 2025-02-23 17:54:13 +01:00
d1ebf893a7 Merge branch 'ago' into 'master'
email templates for AGO

See merge request bde/nk20-scripts!7
2025-02-23 17:47:16 +01:00
e2edf83347 email templates for AGO 2025-02-23 17:45:02 +01:00
a49f9fb94e Update extract_ml_registrations.py 2025-02-09 12:34:07 +01:00
f6819e1ea0 Merge branch 'Send_mail_NL_art' into 'master'
Update file extract_ml_registrations.py

See merge request bde/nk20-scripts!6
2025-01-25 14:16:20 +01:00
df9d765d53 Update file extract_ml_registrations.py 2025-01-25 14:14:23 +01:00
f76acb3248 send summary script 2023-10-05 16:46:03 +02:00
7 changed files with 653 additions and 9 deletions

View File

@ -6,6 +6,7 @@ from datetime import date
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.management import BaseCommand from django.core.management import BaseCommand
from member.models import Club, Membership from member.models import Club, Membership
from django.core.mail import send_mail
class Command(BaseCommand): class Command(BaseCommand):
@ -21,6 +22,8 @@ class Command(BaseCommand):
'events mailing list.') 'events mailing list.')
parser.add_argument('--years', '-y', type=int, default=0, parser.add_argument('--years', '-y', type=int, default=0,
help='Select the cumulative registred users of a membership from years ago. 0 means the current users') help='Select the cumulative registred users of a membership from years ago. 0 means the current users')
parser.add_argument('--email', '-e', type=str, default="",
help='Put the email supposed to receive the emails of the mailing list (only for art). If nothing is put, the script will just print the emails.')
def handle(self, *args, **options): def handle(self, *args, **options):
# TODO: Improve the mailing list extraction system, and link it automatically with Mailman. # TODO: Improve the mailing list extraction system, and link it automatically with Mailman.
@ -45,22 +48,89 @@ class Command(BaseCommand):
self.stdout.write(club.email) self.stdout.write(club.email)
return return
# Get the list of mails that want to be registered to the events mailing list. # Get the list of mails that want to be registered to the events mailing listn, as well as the number of mails.
# Print it or send it to the email provided by the user.
# Don't filter to valid members, old members can receive these mails as long as they want. # Don't filter to valid members, old members can receive these mails as long as they want.
if options["type"] == "events": if options["type"] == "events":
for user in User.objects.filter(profile__ml_events_registration=options["lang"]).all(): nb=0
self.stdout.write(user.email)
if options["email"] == "":
for user in User.objects.filter(profile__ml_events_registration=options["lang"]).all():
self.stdout.write(user.email)
nb+=1
self.stdout.write(str(nb))
else :
emails = []
for user in User.objects.filter(profile__ml_events_registration=options["lang"]).all():
emails.append(user.email)
nb+=1
subject = "Liste des abonnés à la newsletter BDE"
message = (
f"Voici la liste des utilisateurs abonnés à la newsletter BDE:\n\n"
+ "\n".join(emails)
+ f"\n\nTotal des abonnés : {nb}"
)
from_email = "Note Kfet 2020 <notekfet2020@crans.org>"
recipient_list = [options["email"]]
send_mail(subject, message, from_email, recipient_list)
return return
if options["type"] == "art": if options["type"] == "art":
nb=0 nb=0
for user in User.objects.filter(profile__ml_art_registration=True).all():
self.stdout.write(user.email) if options["email"] == "":
nb+=1 for user in User.objects.filter(profile__ml_art_registration=True).all():
self.stdout.write(str(nb)) self.stdout.write(user.email)
nb+=1
self.stdout.write(str(nb))
else :
emails = []
for user in User.objects.filter(profile__ml_art_registration=True).all():
emails.append(user.email)
nb+=1
subject = "Liste des abonnés à la newsletter BDA"
message = (
f"Voici la liste des utilisateurs abonnés à la newsletter BDA:\n\n"
+ "\n".join(emails)
+ f"\n\nTotal des abonnés : {nb}"
)
from_email = "Note Kfet 2020 <notekfet2020@crans.org>"
recipient_list = [options["email"]]
send_mail(subject, message, from_email, recipient_list)
return return
if options["type"] == "sport": if options["type"] == "sport":
for user in User.objects.filter(profile__ml_sport_registration=True).all(): nb=0
self.stdout.write(user.email)
if options["email"] == "":
for user in User.objects.filter(profile__ml_sport_registration=True).all():
self.stdout.write(user.email)
nb+=1
self.stdout.write(str(nb))
else :
emails = []
for user in User.objects.filter(profile__ml_sport_registration=True).all():
emails.append(user.email)
nb+=1
subject = "Liste des abonnés à la newsletter BDS"
message = (
f"Voici la liste des utilisateurs abonnés à la newsletter BDS:\n\n"
+ "\n".join(emails)
+ f"\n\nTotal des abonnés : {nb}"
)
from_email = "Note Kfet 2020 <notekfet2020@crans.org>"
recipient_list = [options["email"]]
send_mail(subject, message, from_email, recipient_list)
return return

View File

@ -0,0 +1,298 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import getpass
from time import sleep
from django.conf import settings
from django.core.mail import mail_admins
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
from django.db import transaction
from django.db.models import Q
from django.test import override_settings
from note.models import Alias, Transaction, TransactionTemplate
from member.models import Club, Membership
class Command(BaseCommand):
"""
This script is used to merge clubs.
THIS IS DANGEROUS SCRIPT, use it only if you know what you do !!!
"""
def add_arguments(self, parser):
parser.add_argument('--fake_club', '-c', type=str, nargs='+', help="Club id to merge and delete.")
parser.add_argument('--true_club', '-C', type=str, help="Club id will not be deleted.")
parser.add_argument('--force', '-f', action='store_true',
help="Force the script to have low verbosity.")
parser.add_argument('--doit', '-d', action='store_true',
help="Don't ask for a final confirmation and commit modification. "
"This option should really be used carefully.")
def handle(self, *args, **kwargs):
force = kwargs['force']
if not force:
self.stdout.write(self.style.WARNING("This is a dangerous script. "
"Please use --force to indicate that you known what you are doing. "
"Nothing will be deleted yet."))
sleep(5)
# We need to know who to blame.
qs = User.objects.filter(note__alias__normalized_name=Alias.normalize(getpass.getuser()))
if not qs.exists():
self.stderr.write(self.style.ERROR("I don't know who you are. Please add your linux id as an alias of "
"your own account."))
exit(2)
executor = qs.get()
deleted_clubs = []
deleted = []
created = []
edited = []
# Don't send mails during the process
with override_settings(EMAIL_BACKEND='django.core.mail.backends.dummy.EmailBackend'):
true_club_id = kwargs['true_club']
if true_club_id.isnumeric():
qs = Club.objects.filter(pk=int(true_club_id))
if not qs.exists():
self.stderr.write(self.style.WARNING(f"Club {true_club_id} was not found. Aborted…"))
exit(2)
true_club = qs.get()
else:
qs = Alias.objects.filter(normalized_name=Alias.normalize(true_club_id), note__noteclub__isnull=False)
if not qs.exists():
self.stderr.write(self.style.WARNING(f"Club {true_club_id} was not found. Aborted…"))
exit(2)
true_club = qs.get().note.club
fake_clubs = []
for fake_club_id in kwargs['fake_club']:
if fake_club_id.isnumeric():
qs = Club.objects.filter(pk=int(fake_club_id))
if not qs.exists():
self.stderr.write(self.style.WARNING(f"Club {fake_club_id} was not found. Ignoring…"))
continue
fake_clubs.append(qs.get())
else:
qs = Alias.objects.filter(normalized_name=Alias.normalize(fake_club_id), note__noteclub__isnull=False)
if not qs.exists():
self.stderr.write(self.style.WARNING(f"Club {fake_club_id} was not found. Ignoring…"))
continue
fake_clubs.append(qs.get().note.club)
clubs = fake_clubs.copy()
clubs.append(true_club)
for club in fake_clubs:
children = Club.objects.filter(parent_club=club)
for child in children:
if child not in fake_clubs:
self.stderr.write(self.style.ERROR(f"Club {club} has child club {child} which are not selected for merge. Aborted."))
exit(1)
with transaction.atomic():
local_deleted = []
local_created = []
local_edited = []
# Unlock note to enable modifications
for club in clubs:
if force and not club.note.is_active:
club.note.is_active = True
club.note.save()
# Deleting objects linked to fake_club and true_club
# Deleting transactions
# We delete transaction :
# fake_club_i <-> fake_club_j
# fake_club_i <-> true_club
transactions = Transaction.objects.filter(Q(source__noteclub__club__in=clubs)
& Q(destination__noteclub__club__in=clubs)).all()
local_deleted += list(transactions)
for tr in transactions:
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Removing {tr}")
if force:
tr.delete()
# Merge buttons
buttons = TransactionTemplate.objects.filter(destination__club__in=fake_clubs)
local_edited += list(buttons)
for b in buttons:
b.destination = true_club.note
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Edit {b}")
if force:
b.save()
# Merge transactions
transactions = Transaction.objects.filter(source__noteclub__club__in=fake_clubs)
local_deleted += list(transactions)
for tr in transactions:
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Removing {tr}")
tr_merge = tr
tr_merge.source = true_club.note
local_created.append(tr_merge)
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Creating {tr_merge}")
if force:
if not tr.destination.is_active:
tr.destination.is_active = True
tr.destination.save()
tr.delete()
tr_merge.save()
tr.destination.is_active = False
tr.destination.save()
else:
tr.delete()
tr_merge.save()
transactions = Transaction.objects.filter(destination__noteclub__club__in=fake_clubs)
local_deleted += list(transactions)
for tr in transactions:
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Removing {tr}")
tr_merge = tr
tr_merge.destination = true_club.note
local_created.append(tr_merge)
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Creating {tr_merge}")
if force:
if not tr.source.is_active:
tr.source.is_active = True
tr.source.save()
tr.delete()
tr_merge.save()
tr.source.is_active = False
tr.source.save()
else:
tr.delete()
tr_merge.save()
if 'permission' in settings.INSTALLED_APPS:
from permission.models import Role
r = Role.objects.filter(for_club__in=fake_clubs)
for role in r:
role.for_club = true_club
local_edited.append(role)
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Edit {role}")
if force:
role.save()
# Merge memberships
for club in fake_clubs:
memberships = Membership.objects.filter(club=club)
local_edited += list(memberships)
for membership in memberships:
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Edit {membership}")
if force:
membership.club = true_club
membership.save()
# Merging aliases
alias_list = []
for fake_club in fake_clubs:
alias_list += list(fake_club.note.alias.all())
local_deleted += alias_list
for alias in alias_list:
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Removing alias {alias}")
alias_merge = alias
alias_merge.note = true_club.note
local_created.append(alias_merge)
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Creating alias {alias_merge}")
if force:
alias.delete()
alias_merge.save()
if 'activity' in settings.INSTALLED_APPS:
from activity.models import Activity
# Merging activities
activities = Activity.objects.filter(organizer__in=fake_clubs)
for act in activities:
act.organizer = true_club
local_edited.append(act)
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Edit {act}")
if force:
act.save()
activities = Activity.objects.filter(attendees_club__in=fake_clubs)
for act in activities:
act.attendees_club = true_club
local_edited.append(act)
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Edit {act}")
if force:
act.save()
if 'food' in settings.INSTALLED_APPS:
from food.models import Food
foods = Food.objects.filter(owner__in=fake_clubs)
for f in foods:
f.owner = true_club
local_edited.append(f)
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Edit {f}")
if force:
f.save()
if 'wrapped' in settings.INSTALLED_APPS:
from wrapped.models import Wrapped
wraps = Wrapped.objects.filter(note__noteclub__club__in=fake_clubs)
local_deleted += list(wraps)
for w in wraps:
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Remove {w}")
if force:
w.delete()
# Deleting note
for club in fake_clubs:
local_deleted.append(club.note)
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Remove note of {club}")
if force:
club.note.delete()
# Finally deleting user
for club in fake_clubs:
local_deleted.append(club)
if kwargs['verbosity'] >= 1:
self.stdout.write(f"Remove {club}")
if force:
club.delete()
# This script should really not be used.
if not kwargs['doit'] and not input('You are about to delete real user data. '
'Are you really sure that it is what you want? [y/N] ')\
.lower().startswith('y'):
self.stdout.write(self.style.ERROR("Aborted."))
exit(1)
if kwargs['verbosity'] >= 1:
for club in fake_clubs:
self.stdout.write(self.style.SUCCESS(f"Club {club} deleted and merge in {true_club}."))
deleted_clubs.append(clubs)
deleted += local_deleted
created += local_created
edited += local_edited
if deleted_clubs:
message = f"Les clubs {deleted_clubs} ont été supprimé⋅es pour être fusionné dans le club {true_club} par {executor}.\n\n"
message += "Ont été supprimés en conséquence les objets suivants :\n\n"
for obj in deleted:
message += f"{repr(obj)} (pk: {obj.pk})\n"
message += "\n\nOnt été créés en conséquence les objects suivants :\n\n"
for obj in created:
message += f"{repr(obj)} (pk: {obj.pk})\n"
message += "\n\nOnt été édités en conséquence les objects suivants :\n\n"
for obj in edited:
message += f"{repr(obj)} (pk: {obj.pk})\n"
if force and kwargs['doit']:
mail_admins("Clubs fusionnés", message)

View File

@ -0,0 +1,144 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date
from django.core.mail import send_mail
from django.core.management import BaseCommand
from django.db.models import Q
from django.template.loader import render_to_string
from django.utils.translation import activate
from note.models import NoteUser, NoteClub
from treasury.models import NoteSummary
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument("--negative-amount", "-n", action='store', type=int, default=1000,
help="Maximum amount to be considered as very negative (inclusive)")
def handle(self, *args, **options):
activate('fr')
if options['negative_amount'] == 0:
# Don't log empty notes
options['negative_amount'] = 1
# User notes
positive_user_notes = NoteUser.objects.filter( Q(balance__gt=0) ).distinct()
positive_user_notes_bde = positive_user_notes.filter( Q(user__memberships__club__name = 'BDE') & Q(user__memberships__date_end__gte = date.today()) ).distinct()
zero_user_notes = NoteUser.objects.filter( Q(balance=0) ).distinct()
zero_user_notes_bde = zero_user_notes.filter( Q(user__memberships__club__name = 'BDE') & Q(user__memberships__date_end__gte = date.today()) ).distinct()
negative_user_notes = NoteUser.objects.filter( Q(balance__lt=0) ).distinct()
negative_user_notes_bde = negative_user_notes.filter( Q(user__memberships__club__name = 'BDE') & Q(user__memberships__date_end__gte = date.today()) ).distinct()
vnegative_user_notes = NoteUser.objects.filter( Q(balance__lte=-options["negative_amount"]) ).distinct()
vnegative_user_notes_bde = vnegative_user_notes.filter( Q(user__memberships__club__name = 'BDE') & Q(user__memberships__date_end__gte = date.today()) ).distinct()
total_positive_user = positive_user_notes.count()
balance_positive_user = sum(note.balance for note in positive_user_notes.all())
total_positive_user_bde = positive_user_notes_bde.count()
balance_positive_user_bde = sum(note.balance for note in positive_user_notes_bde.all())
total_zero_user = zero_user_notes.count()
total_zero_user_bde = zero_user_notes_bde.count()
total_negative_user = negative_user_notes.count()
balance_negative_user = sum(note.balance for note in negative_user_notes.all())
total_negative_user_bde = negative_user_notes_bde.count()
balance_negative_user_bde = sum(note.balance for note in negative_user_notes_bde.all())
total_vnegative_user = vnegative_user_notes.count()
balance_vnegative_user = sum(note.balance for note in vnegative_user_notes.all())
total_vnegative_user_bde = vnegative_user_notes_bde.count()
balance_vnegative_user_bde = sum(note.balance for note in vnegative_user_notes_bde.all())
#Club notes
positive_club_notes = NoteClub.objects.filter( Q(balance__gt=0) ).distinct()
positive_club_notes_nbde = positive_club_notes.filter( ~Q(club__name = 'BDE') & ~Q(club__name = 'Kfet') & ~Q(club__name__iendswith = '- BDE')).distinct()
zero_club_notes = NoteClub.objects.filter( Q(balance=0) ).distinct()
zero_club_notes_nbde = zero_club_notes.filter( ~Q(club__name = 'BDE') & ~Q(club__name = 'Kfet') & ~Q(club__name__iendswith = '- BDE')).distinct()
negative_club_notes = NoteClub.objects.filter( Q(balance__lt=0) ).distinct()
negative_club_notes_nbde = negative_club_notes.filter( ~Q(club__name = 'BDE') & ~Q(club__name = 'Kfet') & ~Q(club__name__iendswith = '- BDE')).distinct()
total_positive_club = positive_club_notes.count()
balance_positive_club = sum(note.balance for note in positive_club_notes.all())
total_positive_club_nbde = positive_club_notes_nbde.count()
balance_positive_club_nbde = sum(note.balance for note in positive_club_notes_nbde.all())
total_zero_club = zero_club_notes.count()
total_zero_club_nbde = zero_club_notes_nbde.count()
total_negative_club = negative_club_notes.count()
balance_negative_club = sum(note.balance for note in negative_club_notes.all())
total_negative_club_nbde = negative_club_notes_nbde.count()
balance_negative_club_nbde = sum(note.balance for note in negative_club_notes_nbde.all())
last_summary = NoteSummary.objects.order_by('-date').first()
summary = NoteSummary.objects.create(
total_positive_user=total_positive_user,
balance_positive_user=balance_positive_user,
total_positive_user_bde=total_positive_user_bde,
balance_positive_user_bde=balance_positive_user_bde,
total_zero_user=total_zero_user,
total_zero_user_bde=total_zero_user_bde,
total_negative_user=total_negative_user,
balance_negative_user=balance_negative_user,
total_negative_user_bde=total_negative_user_bde,
balance_negative_user_bde=balance_negative_user_bde,
total_vnegative_user=total_vnegative_user,
balance_vnegative_user=balance_vnegative_user,
total_vnegative_user_bde=total_vnegative_user_bde,
balance_vnegative_user_bde=balance_vnegative_user_bde,
total_positive_club=total_positive_club,
balance_positive_club=balance_positive_club,
total_positive_club_nbde=total_positive_club_nbde,
balance_positive_club_nbde=balance_positive_club_nbde,
total_zero_club=total_zero_club,
total_zero_club_nbde=total_zero_club_nbde,
total_negative_club=total_negative_club,
balance_negative_club=balance_negative_club,
total_negative_club_nbde=total_negative_club_nbde,
balance_negative_club_nbde=balance_negative_club_nbde,
)
balance_difference_user = (balance_positive_user - balance_negative_user) - (last_summary.balance_positive_user - last_summary.balance_negative_user)
balance_difference_club = (balance_positive_club - balance_negative_club) - (last_summary.balance_positive_club - last_summary.balance_negative_club)
plain_text = render_to_string("note/mails/summary_notes_report.txt", context=dict(summary=summary, balance_difference_user=balance_difference_user, balance_difference_club=balance_difference_club))
html = render_to_string("note/mails/summary_notes_report.html", context=dict(summary=summary, balance_difference_user=balance_difference_user, balance_difference_club=balance_difference_club))
send_mail("[Note Kfet] Récapitulatif de trésorerie", plain_text, "Note Kfet 2020 <notekfet2020@crans.org>",
recipient_list=["respo-info.bde@lists.crans.org", "tresorerie.bde@lists.crans.org"],
html_message=html)

View File

@ -0,0 +1,38 @@
{% load getenv %}
{% load i18n %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Horaire du vote : {{ election_name}}</title>
</head>
<body>
<p>
Bonjour {{ user.first_name }} {{ user.last_name }},
</p>
<p>
Nous t'informons que le vote : {{ election_name }}, sera ouvert de {{ time_start }} jusqu'à
{{ time_end }}.
</p>
<p>
Tu peux voter autant de fois que tu le souhaites tant que le vote est ouvert.
</p>
<p>
Le vote se déroulera sur la plateforme Belenios accessible via ce lien : <a href="{{ lien }}">{{ lien }}</a>
</p>
<p>
Ce vote est organisé par l'Amicale des Élèves de l'École Normale Supérieure Paris-Saclay.
</p>
<p>
En espérant que tu exerceras ton droit,<br>
Le BDE<br>
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
</p>
</body>
</html>

View File

@ -0,0 +1,17 @@
{% load getenv %}
{% load i18n %}
Bonjour {{ user.first_name }} {{ user.last_name }},
Nous t'informons que le vote : {{ election_name }}, sera ouvert de {{ time_start }} jusqu'à {{ time_end }}.
Tu peux voter autant de fois que tu le souhaites tant que le vote est ouvert.
Le vote se déroulera sur la plateforme Belenios accessible via ce lien : {{ lien }}
Ce vote est organisé par l'Amicale des Élèves de l'École Normale Supérieure Paris-Saclay.
En espérant que tu exerceras ton droit,
Le BDE
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}

View File

@ -0,0 +1,52 @@
{% load getenv %}
{% load i18n %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Information : {{ election_name }})</title>
</head>
<body>
<p>
Bonjour {{ user.first_name }} {{ user.last_name }},
</p>
<p>
Ce mail t'est envoyé car tu es inscrit·e sur la liste électorale pour le vote suivant : {{ election_name }}
</p>
<p>
Le vote se déroulera sur la plateforme Belenios accessible via ce lien :
<a href="{{ lien }}">{{ lien }}</a>
</p>
<p>
Voici ton code d'électeur·ice pour pouvoir voter : {{ code_electeur }}
</p>
<p>
Une authentification par la Note Kfet (avec ta note : {{ user.username }}) sera nécessaire à la fin du vote pour le valider, si tu rencontres des problèmes pour réinitialiser ton mot de passe en cas d'oubli, n'hésites pas à envoyer un mail à
<a href="mailto:respo-info.bde@lists.crans.org">respo-info.bde@lists.crans.org</a>.
</p>
<p>
Ce vote est organisé par l'Amicale des Élèves de l'École Normale Supérieure Paris-Saclay.
</p>
<p>
Les personnes possédant une partie de la clé de déchiffrement sont :
<ul>
{% for a in autority %}
<li>{{ a }}</li>
{% endfor %}
</ul>
</p>
<p>
En espérant que tu exerceras ce droit,<br>
Le BDE<br>
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
</p>
</body>
</html>

View File

@ -0,0 +1,25 @@
{% load getenv %}
{% load i18n %}
Bonjour {{ user.first_name }} {{ user.last_name }},
Ce mail t'est envoyé car tu es inscrit·e sur la liste électorale pour le vote suivant : {{ election_name }}
Le vote se déroulera sur la plateforme Belenios accessible via ce lien : {{ lien }}
Voici ton code d'électeur·ice pour pouvoir voter : {{ code_electeur }}
Une authentification par la Note Kfet (avec ta note : {{ user.username }}) sera nécessaire à la fin du vote pour le valider, si tu rencontres des problèmes pour réinitialiser ton mot de passe en cas d'oubli, n'hésites pas à envoyer un mail à respo-info.bde@lists.crans.org.
Ce vote est organisé par l'Amicale des Élèves de l'École Normale Supérieure Paris-Saclay.
Les personnes possédant une partie de la clé de déchiffrement sont :
{% for a in autority %}
{{ a }}
{% endfor %}
En espérant que tu exerceras ce droit,
Le BDE
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}