diff --git a/home/models.py b/home/models.py
index 67f000409365f1e3454430f8a40a23a227b8bdfa..11fbceceb5b81b7cf335725794c680c5e72416ad 100644
--- a/home/models.py
+++ b/home/models.py
@@ -42,7 +42,9 @@ class InterludesActivity(models.Model):
 		"Nombre minimum de participants"
 	)
 	display = models.BooleanField("afficher dans la liste d'activités", default=False)
-	must_subscribe = models.BooleanField("sur inscription", default=False)
+	must_subscribe = models.BooleanField("sur inscription", default=False,
+		help_text="Une activité doit être affichée dans la liste également pour que l'on puisse si inscrire"
+	)
 	host_name = models.CharField("nom de l'organisateur", max_length=50)
 	host_email = models.EmailField("email de l'organisateur")
 	description = models.TextField("description", max_length=2000)
@@ -82,6 +84,14 @@ class InterludesActivity(models.Model):
 	def pretty_type(self) -> str:
 		return self.Types(self.act_type).label
 
+	def conflicts(self, other: "InterludesActivity") -> bool:
+		"""Check whether these activites overlap"""
+		if self.end is None or other.end is None:
+			return False
+		if self.start <= other.start:
+			return other.start <= self.end
+		return self.start <= other.end
+
 	def __str__(self):
 		return self.title
 
diff --git a/home/static/css/style.css b/home/static/css/style.css
index 60b79e07cc2ac772f998cb81acd3bb8efe35f09b..6e5e2b669b4bec5e6534648c2f510c9db7055cb3 100644
--- a/home/static/css/style.css
+++ b/home/static/css/style.css
@@ -355,7 +355,7 @@ ul.messagelist li.info:before {
 // Location
 // =========================== */
 
-#mailing-address {
+.centered {
 	font-size: 1.5rem;
 	text-align: center;
 }
@@ -421,7 +421,7 @@ ul.messagelist li.info:before {
 }
 
 @media (max-width: 800px) {
-	#mailing-address {
+	.centered {
 		font-size: 1.2rem;
 	}
 	#public-transport-info {
diff --git a/home/templates/admin.html b/home/templates/admin.html
index 00b8530845a2d802841def0d594d13d7a47b6581..71a169cb2bc71166156a0176da945968573bbdd7 100644
--- a/home/templates/admin.html
+++ b/home/templates/admin.html
@@ -107,4 +107,32 @@
 			<div class="nb_small">{{ metrics.granted }}</div>
 		</div>
 	</div>
+
+	<h2>Répartition des activités</h2>
+
+	<p>La répartition se fait depuis la <a href="{% url 'admin:index' %}">page d'administration de django</a>,
+	dans la rebrique "Choix d'activités" via la colonne "obtenue".</p>
+
+	{{ validations|safe }}
+
+	<script type="text/javascript">
+		function mail_inscrits() {
+			if (confirm(
+				`Cette action va envoyer 20 emails.\nÊtes-vous sur de vouloir continuer ?`
+			))
+				window.location = "{% url 'home' %}";
+		}
+
+		function mail_orgas() {
+			if (confirm(
+				`Cette action va envoyer 20 emails.\nÊtes-vous sur de vouloir continuer ?`
+			))
+				window.location = "{% url 'home' %}";
+		}
+	</script>
+	<p class="centered"><strong>N'ENVOYER LES EMAILS QUE SI VOUS ÊTES SUR DE VOUS !</strong></p>
+	<div class="flex wrap">
+		<button class="button" onclick="mail_inscrits();">Email aux inscrits</button>
+		<button class="button" onclick="mail_inscrits();">Email aux orgas</button>
+	</div>
 {% endblock %}
\ No newline at end of file
diff --git a/home/templates/faq.html b/home/templates/faq.html
index 3786dcf18759d41631628ebc415797f3e762864b..cbe2a676befab1db91ec540d40d2f854eee1f738 100644
--- a/home/templates/faq.html
+++ b/home/templates/faq.html
@@ -33,7 +33,7 @@
 
 <h2>Comment se rendre à l'ENS Ulm ?</h2>
 	<div>
-		<p id="mailing-address">
+		<p class="centered">
 			<i class="fas fa-map-marker-alt"></i>
 			45 rue d'Ulm, 75005 Paris
 		</p>
diff --git a/home/views.py b/home/views.py
index 30d081f3aebb5fe07e8eee3cdf7cd5adb3e5021d..2389090baf7d7af06b79cc6fa9ac299b957e8ddf 100644
--- a/home/views.py
+++ b/home/views.py
@@ -3,6 +3,7 @@ from datetime import timedelta
 from django.contrib import messages
 from django.contrib.auth.mixins import LoginRequiredMixin
 from django.contrib.sitemaps import Sitemap
+from django.db.models import Count
 from django.forms import formset_factory
 from django.shortcuts import redirect, render
 from django.urls import reverse
@@ -164,11 +165,95 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
 			wish = wishes.count()
 			granted = wishes.filter(accepted=True).count()
 
+		# validation de la repartition des activités
+		accepted = wishes.filter(accepted=True)
+		# order_by is useless but required
+		counts = accepted.values("activity").annotate(total=Count("id")).order_by("activity")
+
 		return metrics
 
+	def validate_activity_participant_nb(self):
+		""" Vérifie que le nombre de participant inscrit
+		à chaque activité est compris entre le min et le max"""
+		activities = InterludesActivity.objects.filter(must_subscribe=True, display=True)
+		min_fails = ""
+		max_fails = ""
+		for act in activities:
+			total = ActivityList.objects.filter(
+				activity=act, accepted=True, participant__is_registered=True
+			).aggregate(total=Count("id"))["total"]
+			max = act.max_participants
+			min = act.min_participants
+			if max != 0 and max < total:
+				max_fails += "<br> &bullet;&ensp;{}: {} inscrits (maximum {})".format(
+					act.title, total, max
+				)
+			if min > total:
+				min_fails += "<br> &bullet;&ensp;{}: {} inscrits (minimum {})".format(
+					act.title, total, max
+				)
+		message = ""
+		if min_fails:
+			message += '<li class="error">Activités en sous-effectif : {}</li>'.format(min_fails)
+		else:
+			message += '<li class="success">Aucune activité en sous-effectif</li>'
+		if max_fails:
+			message += '<li class="error">Activités en sur-effectif : {}</li>'.format(max_fails)
+		else:
+			message += '<li class="success">Aucune activité en sur-effectif</li>'
+		return message
+
+	def validate_activity_conflicts(self):
+		"""Vérifie que personne n'est inscrit à des activités simultanées"""
+		activities = InterludesActivity.objects.filter(must_subscribe=True, display=True)
+		conflicts = []
+		for i, act1 in enumerate(activities):
+			for act2 in activities[i+1:]:
+				if act1.conflicts(act2):
+					conflicts.append((act1, act2))
+		base_qs = ActivityList.objects.filter(accepted=True, participant__is_registered=True)
+		errors = ""
+		for act1, act2 in conflicts:
+			participants1 = base_qs.filter(activity=act1).values("participant")
+			participants2 = base_qs.filter(activity=act1).values("participant")
+			intersection = participants1 & participants2
+			if intersection:
+				print(intersection)
+				errors += '<br> &bullet;&ensp; {} participe(nt) à la fois à "{}" et à "{}"'.format(
+					",".join(str(InterludesParticipant.objects.get(id=x["participant"])) for x in intersection),
+					act1.title, act2.title
+				)
+
+		if errors:
+			return '<li class="error">Des participants ont plusieurs activités au même moment :{}</li>'.format(
+				errors
+			)
+		return '<li class="success">Aucun inscrit à plusieurs activités simultanées</li>'
+
+	def validate_activity_allocation(self):
+		settings = SiteSettings.load()
+		validations = '<ul class="messagelist">'
+
+		# validate global settings
+		if not settings.inscriptions_open:
+			validations += '<li class="success">Les inscriptions sont fermées</li>'
+		else:
+			validations += '<li class="error">Les inscriptions sont encores ouvertes</li>'
+		if settings.activities_allocated:
+			validations += '<li class="success">La répartition est marquée comme effectuée</li>'
+		else:
+			validations += '<li class="error">La répartition n\'est pas marquée comme effectuée</li>'
+
+		# longer validations
+		validations += self.validate_activity_participant_nb()
+		validations += self.validate_activity_conflicts()
+		validations += '</ul>'
+		return validations
+
 	def get_context_data(self, *args, **kwargs):
 		context = super().get_context_data(*args, **kwargs)
 		context["metrics"] = self.get_metrics()
+		context["validations"] = self.validate_activity_allocation()
 		return context