From 2c45e274abfd48092bd43056437ecebbd2ad34fd Mon Sep 17 00:00:00 2001
From: Dorian Lesbre <dorian.lesbre@gmail.com>
Date: Tue, 6 Apr 2021 20:12:42 +0200
Subject: [PATCH] Moves slots to a separate table (cleaner)

---
 accounts/templates/profile.html |  24 +++---
 accounts/views.py               |   8 +-
 home/admin.py                   |  39 ++++++----
 home/forms.py                   |  16 ++--
 home/models.py                  | 102 +++++++++++++------------
 home/templates/_planning.html   |  36 ++++-----
 home/templates/activites.html   |   6 +-
 home/templates/admin.html       |  27 ++++---
 home/urls.py                    |   1 +
 home/views.py                   | 128 +++++++++++++++++---------------
 shared/views.py                 |   1 -
 11 files changed, 207 insertions(+), 181 deletions(-)

diff --git a/accounts/templates/profile.html b/accounts/templates/profile.html
index cc0bc6a..fcab35b 100644
--- a/accounts/templates/profile.html
+++ b/accounts/templates/profile.html
@@ -23,13 +23,13 @@
 		<!-- <li>{% if user.profile.mug %}Commandse une tasse{% else %}Ne commande pas de tasse{% endif %}</li> -->
 		<!-- <li>Inscrit à {{ user.profile.nb_meals }} repas.</li> -->
 		{% if settings.activities_allocated %}
-			{% if my_activities %}
-			<li>Inscrit à {{ my_activities|length }} activités&nbsp;:
+			{% if my_choices %}
+			<li>Inscrit à {{ my_choices|length }} activités&nbsp;:
 				<ul>
-					{% for activity in my_activities %}
-					<li><a href="{% url 'activites' %}#{{ activity.activity.slug }}">{{ activity.activity.title }}</a>
-						{% if activity.activity.on_planning %}
-							(le {{ activity.activity.start|date:"l à H:i" }}<!--  en {{ activity.activity.room }} -->)
+					{% for choice in my_choices %}
+					<li><a href="{% url 'activites' %}#{{ choice.slot.activity.slug }}">{{ choice.slot }}</a>
+						{% if choice.slot.on_planning %}
+							(le {{ choice.slot.start|date:"l à H:i" }}<!--  en {{ choice.slot.room }} -->)
 						{% endif %}
 					</li>
 					{% endfor %}
@@ -39,13 +39,13 @@
 			<li>Inscrit à aucune activité</li>
 			{% endif %}
 		{% else %}
-			{% if my_activities %}
-			<li>{{ my_activities|length }} activités souhaitées&nbsp;:
+			{% if my_choices %}
+			<li>{{ my_choices|length }} activités souhaitées&nbsp;:
 				<ol>
-					{% for activity in my_activities %}
-					<li><a href="{% url 'activites' %}#{{ activity.activity.slug }}">{{ activity.activity.title }}</a>
-						{% if activity.activity.on_planning %}
-							(le {{ activity.activity.start|date:"l à H:i" }}<!-- en {{ activity.activity.room }} -->)
+					{% for choice in my_choices %}
+					<li><a href="{% url 'activites' %}#{{ choice.slot.activity.slug }}">{{ choice.slot }}</a>
+						{% if choice.slot.on_planning %}
+							(le {{ choice.slot.start|date:"l à H:i" }}<!--  en {{ choice.slot.room }} -->)
 						{% endif %}
 					</li>
 					{% endfor %}
diff --git a/accounts/views.py b/accounts/views.py
index aba8c83..84643cb 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -14,7 +14,7 @@ from django.shortcuts import render, redirect
 from accounts import forms
 from accounts.models import EmailUser
 from accounts.tokens import email_token_generator
-from home.models import ActivityList
+from home.models import InterludesActivityChoices
 from site_settings.models import SiteSettings
 
 def send_validation_email(request, user, subject, template):
@@ -58,14 +58,14 @@ class ProfileView(LoginRequiredMixin, TemplateView):
 		context = super().get_context_data(**kwargs)
 		settings = SiteSettings.load()
 		if settings.activities_allocated:
-			my_activities = ActivityList.objects.filter(
+			my_choices = InterludesActivityChoices.objects.filter(
 				participant=self.request.user.profile,
 				accepted=True
 			)
 		else:
-			my_activities = ActivityList.objects.filter(participant=self.request.user.profile)
+			my_choices = InterludesActivityChoices.objects.filter(participant=self.request.user.profile)
 
-		context["my_activities"] = my_activities
+		context["my_choices"] = my_choices
 		return context
 
 
diff --git a/home/admin.py b/home/admin.py
index cd9858c..6ac49af 100644
--- a/home/admin.py
+++ b/home/admin.py
@@ -1,6 +1,6 @@
 from django.contrib import admin
 
-from home.models import InterludesActivity, InterludesParticipant, ActivityList
+from home import models
 from shared.admin import ExportCsvMixin
 
 # Titre de la vue (tag <h1>)
@@ -9,32 +9,38 @@ admin.site.site_header = "Administration site interludes"
 admin.site.site_title = "Admin Interludes"
 
 
-@admin.register(InterludesActivity)
+@admin.register(models.InterludesActivity)
 class InterludesActivityAdmin(ExportCsvMixin, admin.ModelAdmin):
 	"""option d'affichage des activités dans la vue django admin"""
 	filename = "export_activites.csv"
-	list_display = ("title", "host_name", "display", "must_subscribe", "subscribing_open","on_planning")
-	list_filter = ("display", "must_subscribe", "subscribing_open", "on_planning", "status")
+	list_display = ("title", "host_name", "display", "must_subscribe",)
+	list_filter = ("display", "must_subscribe", "status",)
 	ordering = ("title", "host_name",)
-	list_editable = ("display", "subscribing_open",)
+	list_editable = ("display",)
 	fields = (
 		"title",
 		("host_name", "host_email"),
 		"status", "act_type", "duration",
 		("min_participants", "max_participants"),
-		("must_subscribe", "subscribing_open"),
+		"must_subscribe",
 		"communicate_participants",
 		"description", "desc_as_html",
 		"display",
-		"room", "start",
-		"on_planning",
 		"notes",
-		"canonical",
 	)
 	list_per_page = 100
-	save_as = True # Allow to duplicate models
 
-@admin.register(InterludesParticipant)
+
+@admin.register(models.InterludesSlot)
+class InterludesSlotAdmin(ExportCsvMixin, admin.ModelAdmin):
+	"""option d'affichage des crénaux dans la vue d'admin"""
+	filename = "export_slots.csv"
+	list_display = ("__str__", "start", "room", "subscribing_open", "on_planning",)
+	list_filter = ("subscribing_open", "on_planning", "activity__display",)
+	list_editable = ("subscribing_open", "on_planning",)
+
+
+@admin.register(models.InterludesParticipant)
 class InterludesParticipantAdmin(ExportCsvMixin, admin.ModelAdmin):
 	"""option d'affichage des participant dans la vue django admin"""
 	filename = "export_participants.csv"
@@ -47,15 +53,16 @@ class InterludesParticipantAdmin(ExportCsvMixin, admin.ModelAdmin):
 	ordering = ("user",)
 	list_per_page = 200
 
-@admin.register(ActivityList)
+
+@admin.register(models.InterludesActivityChoices)
 class ActivityListAdmin(ExportCsvMixin, admin.ModelAdmin):
 	"""option d'affichage des choix d'activités dans la vue django admin"""
 	filename = "export_choix_activite.csv"
-	list_display = ("activity", "participant", "priority", "accepted")
+	list_display = ("slot", "participant", "priority", "accepted")
 	list_filter = (
-		"activity", "participant__is_registered", "activity__display",
-		"accepted", "activity__subscribing_open",
+		"slot__activity", "participant__is_registered", "slot__activity__display",
+		"accepted", "slot__subscribing_open",
 	)
 	list_editable = ("accepted",)
-	ordering = ("activity", "priority", "participant",)
+	ordering = ("slot", "priority", "participant",)
 	list_per_page = 400
diff --git a/home/forms.py b/home/forms.py
index a246a72..7d993db 100644
--- a/home/forms.py
+++ b/home/forms.py
@@ -1,14 +1,14 @@
 from django import forms
 from django.core.exceptions import ValidationError
 
-from home.models import ActivityList, InterludesParticipant, InterludesActivity
+from home import models
 from shared.forms import FormRenderMixin
 
 
 class InscriptionForm(FormRenderMixin, forms.ModelForm):
 
 	class Meta:
-		model = InterludesParticipant
+		model = models.InterludesParticipant
 		fields = (
 			"school", "sleeps", # "mug",
 			"meal_friday_evening", "meal_saturday_morning", "meal_saturday_midday",
@@ -31,14 +31,14 @@ class InscriptionForm(FormRenderMixin, forms.ModelForm):
 
 class ActivityForm(FormRenderMixin, forms.ModelForm):
 	class Meta:
-		model = ActivityList
-		fields = ("activity",)
-		labels = {"activity":""}
+		model = models.InterludesActivityChoices
+		fields = ("slot",)
+		labels = {"slot":""}
 
 	def __init__(self, *args, **kwargs):
 		super(ActivityForm, self).__init__(*args, **kwargs)
-		activities = InterludesActivity.objects.filter(subscribing_open=True)
-		self.fields['activity'].queryset = activities
+		slots = models.InterludesSlot.objects.filter(subscribing_open=True)
+		self.fields['slot'].queryset = slots
 
 class BaseActivityFormSet(forms.BaseFormSet):
 	"""Form set that fails if duplicate activities"""
@@ -51,7 +51,7 @@ class BaseActivityFormSet(forms.BaseFormSet):
 		for form in self.forms:
 			if self.can_delete and self._should_delete_form(form):
 				continue
-			activity = form.cleaned_data.get('activity')
+			activity = form.cleaned_data.get('slot')
 			if activity is None:
 				continue
 			if activity in activities:
diff --git a/home/models.py b/home/models.py
index c760de5..a69d74a 100644
--- a/home/models.py
+++ b/home/models.py
@@ -47,10 +47,7 @@ class InterludesActivity(models.Model):
 		help_text="Si vrai, s'affiche sur la page activités"
 	)
 	must_subscribe = models.BooleanField("sur inscription", default=False,
-		help_text="Informatif, il faut utiliser 'ouverte aux inscriptions' pour ajouter dans la liste d'inscription"
-	)
-	subscribing_open = models.BooleanField("ouverte aux inscriptions", default=False,
-		help_text="Si vrai, apparaît dans la liste du formulaire d'inscription"
+		help_text="Informatif, il faut utiliser les crénaux pour ajouter dans la liste d'inscription"
 	)
 	host_name = models.CharField("nom de l'organisateur", max_length=50)
 	host_email = models.EmailField("email de l'organisateur")
@@ -62,29 +59,8 @@ class InterludesActivity(models.Model):
 		help_text="Assurer vous que le texte est bien formaté, cette option peut casser la page activités."
 	)
 
-	on_planning = models.BooleanField(
-		"afficher sur le planning", default=False,
-		help_text="Nécessite de salle et heure de début non vide"
-	)
-	start = models.DateTimeField("début", null=True, blank=True)
-	room = models.CharField("salle", max_length=100, null=True, blank=True)
-
-	canonical = models.ForeignKey("self",
-		on_delete=models.SET_NULL, null=True, blank=True,
-		verbose_name="Représentant canonique",
-		help_text="Si plusieurs copie d'une activité existe (pour plusieurs crénaux), "
-			"et une seule est affichée, sélectionner là dans les copie pour réparer les liens "
-			"du planning vers la description"
-	)
-
 	notes = models.TextField("Notes privées", max_length=2000, blank=True)
 
-	@property
-	def end(self):
-		if (not self.start) or (not self.duration):
-			return None
-		return self.start + self.duration
-
 	@property
 	def nb_participants(self) -> str:
 		if self.max_participants == 0:
@@ -118,34 +94,64 @@ class InterludesActivity(models.Model):
 	@property
 	def slug(self) -> str:
 		"""Returns the planning/display slug for this activity"""
-		id = self.id
-		if self.canonical:
-			id = self.canonical.id
-		return "act-{}".format(id)
+		return "act-{}".format(self.id)
 
 	@property
-	def times_and_places(self) -> str:
-		"""Returns a list of start times and place related to self
-		(check canonical links for multiple timetable,
-		only displays if on_planning is true)"""
-		objects = InterludesActivity.objects.filter(
-			models.Q(id=self.id) | models.Q(canonical=self)
-		).filter(on_planning=True).values("start", "room").order_by("start")
-		return objects
-
-	def conflicts(self, other: "InterludesActivity") -> bool:
-		"""Check whether these activites overlap"""
+	def slots(self):
+		"""Returns a list of slots related to self"""
+		return InterludesSlot.objects.filter(activity=self, on_planning=True).order_by("start")
+
+	def __str__(self):
+		return self.title
+
+	class Meta:
+		verbose_name = "activité"
+
+
+class InterludesSlot(models.Model):
+	"""Crénaux indiquant ou une activité se place dans le planning
+	Dans une table à part car un activité peut avoir plusieurs crénaux.
+	Les inscriptions se font à des crénaux et non des activités"""
+
+	TITLE_SPECIFIER = "{act_title}"
+
+	activity = models.ForeignKey(InterludesActivity, on_delete=models.CASCADE, verbose_name="Activité")
+	title = models.CharField(
+		"Titre", max_length=200, default=TITLE_SPECIFIER,
+		help_text="Utilisez '{}' pour insérer le titre de l'activité correspondante".format(
+			TITLE_SPECIFIER),
+	)
+	start = models.DateTimeField("début", null=True, blank=True)
+	room = models.CharField("salle", max_length=100, null=True, blank=True)
+	on_planning = models.BooleanField(
+		"afficher sur le planning", default=False,
+		help_text="Nécessite de salle et heure de début non vide",
+	)
+	subscribing_open = models.BooleanField("ouvert aux inscriptions", default=False,
+		help_text="Si vrai, apparaît dans la liste du formulaire d'inscription"
+	)
+
+	@property
+	def end(self):
+		"""Heure de fin du crénau"""
+		if (not self.start) or (not self.activity.duration):
+			return None
+		return self.start + self.activity.duration
+
+	def conflicts(self, other: "InterludesSlot") -> bool:
+		"""Check whether these slots 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
+	def __str__(self) -> str:
+		return self.title.replace(self.TITLE_SPECIFIER, self.activity.title)
 
 	class Meta:
-		verbose_name = "activité"
+		verbose_name = "crénau"
+		verbose_name_plural = "crénaux"
 
 
 class InterludesParticipant(models.Model):
@@ -189,21 +195,21 @@ class InterludesParticipant(models.Model):
 		verbose_name = "participant"
 
 
-class ActivityList(models.Model):
+class InterludesActivityChoices(models.Model):
 	"""liste d'activités souhaitée de chaque participant,
 	avec un order de priorité"""
 	priority = models.PositiveIntegerField("priorité")
 	participant = models.ForeignKey(
-		InterludesParticipant, on_delete=models.CASCADE, db_column="participant"
+		InterludesParticipant, on_delete=models.CASCADE, verbose_name="participant",
 	)
-	activity = models.ForeignKey(
-		InterludesActivity, on_delete=models.CASCADE, db_column="activité"
+	slot = models.ForeignKey(
+		InterludesSlot, on_delete=models.CASCADE, verbose_name="crénau",
 	)
 	accepted = models.BooleanField("Obtenue", default=False)
 
 	class Meta:
 		# couples uniques
-		unique_together = (("priority", "participant"), ("participant", "activity"))
+		unique_together = (("priority", "participant"), ("participant", "slot"))
 		ordering = ("participant", "priority")
 		verbose_name = "choix d'activités"
 		verbose_name_plural = "choix d'activités"
diff --git a/home/templates/_planning.html b/home/templates/_planning.html
index 08baa1c..229fee0 100644
--- a/home/templates/_planning.html
+++ b/home/templates/_planning.html
@@ -16,35 +16,35 @@ I.E we set all dates to the first day (Friday) and set groups allowing vertical
 
 	// Items in the timeline
 	const items = new vis.DataSet([
-	{% for act in planning %}
-		{% if act.start|date:"d" == act.end|date:"d" %}
+	{% for slot in planning %}
+		{% if slot.start|date:"d" == slot.end|date:"d" %}
 			{
-				content: '<a class="hidden" href="#{{ act.slug }}"><div><strong>{{ act.title }}</strong><br>{{ act.room }}</div></a>',
-				title: '<strong>{{ act.title }}</strong><br>{{ act.room }}',
-				start: '{{ settings.date_start|date:"Y-m-d"}} {{ act.start|date:"H:i:s" }}',
+				content: '<a class="hidden" href="#{{ slot.activity.slug }}"><div><strong>{{ slot }}</strong><br>{{ slot.room }}</div></a>',
+				title: '<strong>{{ slot }}</strong><br>{{ slot.room }}',
+				start: '{{ settings.date_start|date:"Y-m-d"}} {{ slot.start|date:"H:i:s" }}',
 				align: 'left',
-				group: {{ act.start|date:"d" }},
-				subgroup: '{{ act.room }}',
-				end:'{{ settings.date_start|date:"Y-m-d"}} {{ act.end|date:"H:i:s" }}'
+				group: {{ slot.start|date:"d" }},
+				subgroup: '{{ slot.room }}',
+				end:'{{ settings.date_start|date:"Y-m-d"}} {{ slot.end|date:"H:i:s" }}'
 			},
 		{% else %} // activity spans multiple days
 			{
-				content: '<a class="hidden" href="#{{ act.slug }}"><div><strong>{{ act.title }}</strong><br>{{ act.room }}</div></a>',
-				title: '<strong>{{ act.title }}</strong><br>{{ act.room }}',
-				start: '{{ settings.date_start|date:"Y-m-d"}} {{ act.start|date:"H:i:s" }}',
+				content: '<a class="hidden" href="#{{ slot.activity.slug }}"><div><strong>{{ slot }}</strong><br>{{ slot.room }}</div></a>',
+				title: '<strong>{{ slot }}</strong><br>{{ slot.room }}',
+				start: '{{ settings.date_start|date:"Y-m-d"}} {{ slot.start|date:"H:i:s" }}',
 				align: 'left',
-				group: {{ act.start|date:"d" }},
-				subgroup: '{{ act.room }}',
+				group: {{ slot.start|date:"d" }},
+				subgroup: '{{ slot.room }}',
 				end:'{{ settings.date_start|date:"Y-m-d"}} 23:59:59'
 			},
 			{
-				content: '<a class="hidden" href="#{{ act.slug }}"><div><strong>{{ act.title }}</strong><br>{{ act.room }}</div></a>',
-				title: '<strong>{{ act.title }}</strong><br>{{ act.room }}',
+				content: '<a class="hidden" href="#{{ slot.slug }}"><div><strong>{{ slot }}</strong><br>{{ slot.room }}</div></a>',
+				title: '<strong>{{ slot }}</strong><br>{{ slot.room }}',
 				start: '{{ settings.date_start|date:"Y-m-d"}} 00:00:00',
 				align: 'left',
-				group: {{ act.start|date:"d"|add:"1" }},
-				subgroup: '{{ act.room }}',
-				end:'{{ settings.date_start|date:"Y-m-d"}} {{ act.end|date:"H:i:s" }}'
+				group: {{ slot.start|date:"d"|add:"1" }},
+				subgroup: '{{ slot.room }}',
+				end:'{{ settings.date_start|date:"Y-m-d"}} {{ slot.end|date:"H:i:s" }}'
 			},
 		{% endif %}
 	{% endfor %}
diff --git a/home/templates/activites.html b/home/templates/activites.html
index c3a8069..12e2726 100644
--- a/home/templates/activites.html
+++ b/home/templates/activites.html
@@ -38,9 +38,9 @@
 			<dt>Orga :</dt><dd>{{ activity.host_name }}</dd>
 			<dt>Type :</dt><dd>{{ activity.pretty_type }}</dd>
 			<dt>Places :</dt><dd>{{ activity.nb_participants }}</dd>
-			{% if activity.times_and_places %}
-			<dt>Heure/Lieu :</dt><dd>{% for act in activity.times_and_places %}
-				{{ act.start|date:"l H:i" }}<!-- {{ act.room }} -->{% if not forloop.last %},<br> {% endif %}
+			{% if activity.slots %}
+			<dt>Heure/Lieu :</dt><dd>{% for slot in activity.slots %}
+				{{ slot.start|date:"l H:i" }}<!-- {{ slot.room }} -->{% if not forloop.last %},<br> {% endif %}
 				{% endfor %}
 			</dd>
 			{% endif %}
diff --git a/home/templates/admin.html b/home/templates/admin.html
index 0ec089c..be62fb2 100644
--- a/home/templates/admin.html
+++ b/home/templates/admin.html
@@ -10,8 +10,9 @@
 	<h2>Page d'administration</h2>
 	<p><strong>Version {{ constants.WEBSITE_FULL_VERSION }}</strong></p>
 	<div class="flex wrap">
-		<a class="button" href="{% url 'participants.csv' %}"><i class="fa fa-download"></i> Liste des participants</a>
-		<a class="button" href="{% url 'activities.csv' %}"><i class="fa fa-download"></i> Liste des activités</a>
+		<a class="button" href="{% url 'participants.csv' %}"><i class="fa fa-download"></i> Participants</a>
+		<a class="button" href="{% url 'activities.csv' %}"><i class="fa fa-download"></i> Activités</a>
+		<a class="button" href="{% url 'slots.csv' %}"><i class="fa fa-download"></i> Crénaux</a>
 		<a class="button" href="{% url 'activity_choices.csv' %}"><i class="fa fa-download"></i> Choix d'activités</a>
 	</div>
 	<ul class="messagelist">
@@ -109,14 +110,14 @@
 			<div class="qty">Affichées</div>
 			<div class="nb_small">{{ metrics.displayed }}</div>
 		</div>
-		<div class="stat">
-			<div class="qty">Planning</div>
-			<div class="nb_small">{{ metrics.planning }}</div>
-		</div>
 		<div class="stat">
 			<div class="qty">Inscription*</div>
 			<div class="nb_small">{{ metrics.act_ins }}</div>
 		</div>
+		<div class="stat">
+			<div class="qty">Mail orga</div>
+			<div class="nb_small">{{ metrics.communicate }}</div>
+		</div>
 		<div class="stat">
 			<div class="qty">Présentiel</div>
 			<div class="nb_small">{{ metrics.st_present }}</div>
@@ -133,8 +134,12 @@
 
 	<div class="flex wrap lines">
 		<div class="stat">
-			<div class="qty">Créneaux*</div>
-			<div class="nb_big">{{ metrics.true_ins }}</div>
+			<div class="qty">Planning*</div>
+			<div class="nb_big">{{ metrics.slots }}</div>
+		</div>
+		<div class="stat">
+			<div class="qty">Inscriptions</div>
+			<div class="nb_small">{{ metrics.true_ins }}</div>
 		</div>
 		<div class="stat">
 			<div class="qty">Souhaits</div>
@@ -150,10 +155,8 @@
 		</div>
 	</div>
 
-	<p>*Le nombre d'activité "inscription" est le nombre d'activités affichée (dans la liste de la page
-		<a href="{% url 'activites' %}">activités</a>), tandis que le nombre de Créneaux est le nombre d'activités
-		qui apparaissent dans la liste du formulaire d'inscription (une activité avec plusieurs créneaux peut y
-		apparaître plusieures fois)
+	<p>*Une activité peut avoir plusieurs crénaux sur le planning. Les inscriptions se font par crénaux donc le nombre
+		d'inscriptions de la catégorie "Activités" est seulement informatif .
 	</p>
 
 	{% if metrics.malformed %}
diff --git a/home/urls.py b/home/urls.py
index 0d7ca31..28bf00d 100644
--- a/home/urls.py
+++ b/home/urls.py
@@ -15,6 +15,7 @@ urlpatterns = [
 	path('favicon.ico', RedirectView.as_view(url='/static/imgs/favicon.ico')),
 	path('admin_site/', views.AdminView.as_view(), name="site_admin"),
 	path('export/activities/', views.ExportActivities.as_view(), name="activities.csv"),
+	path('export/slots/', views.ExportSlots.as_view(), name="slots.csv"),
 	path('export/participants/', views.ExportParticipants.as_view(), name="participants.csv"),
 	path('export/activity_choices/', views.ExportActivityChoices.as_view(), name="activity_choices.csv"),
 	path('email/send_user_emails_0564946523/', views.SendUserEmail.as_view(), name="email_users"),
diff --git a/home/views.py b/home/views.py
index 72fc9c0..8fd563c 100644
--- a/home/views.py
+++ b/home/views.py
@@ -12,7 +12,7 @@ from django.urls import reverse
 from django.views.generic import RedirectView, UpdateView, TemplateView, View
 
 from accounts.models import EmailUser
-from home.models import ActivityList, InterludesActivity, InterludesParticipant
+from home import models
 from home.forms import ActivityForm, BaseActivityFormSet, InscriptionForm
 from site_settings.models import SiteSettings
 from shared.views import CSVWriteView, SuperuserRequiredMixin
@@ -31,7 +31,7 @@ def get_planning_context():
 	"""Returns the context dict needed to display the planning"""
 	settings = SiteSettings.load()
 	context = dict()
-	context['planning'] = InterludesActivity.objects.filter(on_planning=True).order_by("title")
+	context['planning'] = models.InterludesSlot.objects.filter(on_planning=True).order_by("title")
 	if settings.date_start is not None:
 		context['friday'] = settings.date_start.day
 		context['saturday'] = (settings.date_start + timedelta(days=1)).day
@@ -49,7 +49,7 @@ class ActivityView(TemplateView):
 	def get_context_data(self, **kwargs):
 		"""ajoute la liste des activités au contexte"""
 		context = super(ActivityView, self).get_context_data(**kwargs)
-		context['activities'] = InterludesActivity.objects.filter(display=True).order_by("title")
+		context['activities'] = models.InterludesActivity.objects.filter(display=True).order_by("title")
 		context.update(get_planning_context())
 		return context
 
@@ -80,28 +80,30 @@ class RegisterUpdateView(LoginRequiredMixin, TemplateView):
 	formset_class = formset_factory(form=ActivityForm, extra=3, formset=BaseActivityFormSet)
 
 	@staticmethod
-	def get_activities(participant):
-		activities = ActivityList.objects.filter(participant=participant).order_by("priority")
-		return [{"activity": act.activity} for act in activities]
+	def get_slots(participant):
+		activities = models.InterludesActivityChoices.objects.filter(participant=participant).order_by("priority")
+		return [{"slot": act.slot} for act in activities]
 
 	@staticmethod
 	def set_activities(participant, formset):
 		# delete old activites
-		ActivityList.objects.filter(participant=participant).delete()
+		models.InterludesActivityChoices.objects.filter(participant=participant).delete()
 
 		priority = 0
 		for form in formset:
 			data = form.cleaned_data
 			if data:
-				activity = data["activity"]
-				ActivityList(priority=priority, participant=participant, activity=activity).save()
+				slot = data["slot"]
+				models.InterludesActivityChoices(
+					priority=priority, participant=participant, slot=slot
+				).save()
 				priority += 1
 
 	def get(self, request, *args, **kwargs):
 		participant = request.user.profile
-		activities = self.get_activities(participant)
+		slots = self.get_slots(participant)
 		form = self.form_class(instance=participant)
-		formset = self.formset_class(initial=activities)
+		formset = self.formset_class(initial=slots)
 		context = {"form": form, "formset": formset}
 		return render(request, self.template_name, context)
 
@@ -150,9 +152,10 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
 	template_name = "admin.html"
 
 	def get_metrics(self):
-		registered = InterludesParticipant.objects.filter(is_registered = True)
-		acts = InterludesActivity.objects.all()
-		wishes = ActivityList.objects.filter(participant__is_registered=True)
+		registered = models.InterludesParticipant.objects.filter(is_registered = True)
+		acts = models.InterludesActivity.objects.all()
+		slots_in = models.InterludesSlot.objects.all()
+		wishes = models.InterludesActivityChoices.objects.filter(participant__is_registered=True)
 		class metrics:
 			participants = registered.count()
 			ulm = registered.filter(school="U").count()
@@ -173,43 +176,39 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
 
 			activites = acts.count()
 			displayed = acts.filter(display=True).count()
-			planning = acts.filter(on_planning=True).count()
 			act_ins = acts.filter(display=True, must_subscribe=True).count()
-			st_present = acts.filter(display=True, status=InterludesActivity.Status.PRESENT).count()
-			st_distant = acts.filter(display=True, status=InterludesActivity.Status.DISTANT).count()
-			st_both = acts.filter(display=True, status=InterludesActivity.Status.BOTH).count()
+			communicate = acts.filter(communicate_participants=True).count()
+			st_present = acts.filter(display=True, status=models.InterludesActivity.Status.PRESENT).count()
+			st_distant = acts.filter(display=True, status=models.InterludesActivity.Status.DISTANT).count()
+			st_both = acts.filter(display=True, status=models.InterludesActivity.Status.BOTH).count()
 
-			true_ins = acts.filter(subscribing_open=True).count()
+			slots = slots_in.count()
+			true_ins = slots_in.filter(subscribing_open=True).count()
 			wish = wishes.count()
 			granted = wishes.filter(accepted=True).count()
-			malformed = ActivityList.objects.filter(activity__subscribing_open=False).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")
+			malformed = models.InterludesActivityChoices.objects.filter(slot__subscribing_open=False).count()
 
 		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(subscribing_open=True)
+		slots = models.InterludesSlot.objects.filter(subscribing_open=True)
 		min_fails = ""
 		max_fails = ""
-		for act in activities:
-			total = ActivityList.objects.filter(
-				activity=act, accepted=True, participant__is_registered=True
+		for slot in slots:
+			total = models.InterludesActivityChoices.objects.filter(
+				slot=slot, accepted=True, participant__is_registered=True
 			).aggregate(total=Count("id"))["total"]
-			max = act.max_participants
-			min = act.min_participants
+			max = slot.activity.max_participants
+			min = slot.activity.min_participants
 			if max != 0 and max < total:
 				max_fails += "<br> &bullet;&ensp;{}: {} inscrits (maximum {})".format(
-					act.title, total, max
+					slot, total, max
 				)
 			if min > total:
 				min_fails += "<br> &bullet;&ensp;{}: {} inscrits (minimum {})".format(
-					act.title, total, min
+					slot, total, min
 				)
 		message = ""
 		if min_fails:
@@ -224,23 +223,23 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
 
 	def validate_activity_conflicts(self):
 		"""Vérifie que personne n'est inscrit à des activités simultanées"""
-		activities = InterludesActivity.objects.filter(subscribing_open=True)
+		slots = models.InterludesSlot.objects.filter(subscribing_open=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)
+		for i, slot_1 in enumerate(slots):
+			for slot_2 in slots[i+1:]:
+				if slot_1.conflicts(slot_2):
+					conflicts.append((slot_1, slot_2))
+		base_qs = models.InterludesActivityChoices.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
+		for slot_1, slot_2 in conflicts:
+			participants_1 = {x.participant for x in base_qs.filter(slot=slot_1)}
+			participants_2 = {x.participant for x in base_qs.filter(slot=slot_2)}
+			intersection = participants_1.intersection(participants_2)
 			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
+					", ".join(str(x) for x in intersection), slot_1, slot_2
 				)
 
 		if errors:
@@ -274,9 +273,9 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
 
 		validations += '</ul>'
 
-		user_email_nb = InterludesParticipant.objects.filter(is_registered=True).count()
-		orga_email_nb = InterludesActivity.objects.filter(
-			subscribing_open=True, communicate_participants=True
+		user_email_nb = models.InterludesParticipant.objects.filter(is_registered=True).count()
+		orga_email_nb = models.InterludesSlot.objects.filter(
+			subscribing_open=True, activity__communicate_participants=True
 		).count()
 
 		return {
@@ -296,7 +295,18 @@ class AdminView(SuperuserRequiredMixin, TemplateView):
 
 class ExportActivities(SuperuserRequiredMixin, CSVWriteView):
 	filename = "activites_interludes"
-	model = InterludesActivity
+	model = models.InterludesActivity
+
+class ExportSlots(SuperuserRequiredMixin, CSVWriteView):
+	filename = "crénaux_interludes"
+	headers = ["Titre", "Début", "Salle", "Ouverte aux inscriptions", "Affichée sur le planning"]
+
+	def get_rows(self):
+		slots = models.InterludesSlot.objects.all()
+		rows = []
+		for slot in slots:
+			rows.append([str(slot), slot.start, slot.room, slot.subscribing_open, slot.on_planning])
+		return rows
 
 class ExportParticipants(SuperuserRequiredMixin, CSVWriteView):
 	filename = "participants_interludes"
@@ -306,7 +316,7 @@ class ExportParticipants(SuperuserRequiredMixin, CSVWriteView):
 		"Repas D matin", "Repas D soir"
 	]
 	def get_rows(self):
-		profiles = InterludesParticipant.objects.filter(is_registered=True).all()
+		profiles = models.InterludesParticipant.objects.filter(is_registered=True).all()
 		rows = []
 		for profile in profiles:
 			rows.append([
@@ -328,17 +338,17 @@ class ExportParticipants(SuperuserRequiredMixin, CSVWriteView):
 
 class ExportActivityChoices(SuperuserRequiredMixin, CSVWriteView):
 	filename = "choix_activite_interludes"
-	model = ActivityList
-	headers = ["id_participant", "nom_participant", "priorité", "obtenu", "nom_activité", "id_activité"]
+	model = models.InterludesActivityChoices
+	headers = ["id_participant", "nom_participant", "priorité", "obtenu", "nom_crénau", "id_crénau"]
 
 	def get_rows(self):
-		activities = ActivityList.objects.all()
+		activities = models.InterludesActivityChoices.objects.all()
 		rows = []
 		for act in activities:
 			if act.participant.is_registered:
 				rows.append([
 					act.participant.id, str(act.participant), act.priority,
-					act.accepted, str(act.activity), act.activity.id
+					act.accepted, str(act.slot), act.slot.id
 				])
 		return rows
 
@@ -363,11 +373,11 @@ class SendUserEmail(SendEmailBase):
 
 	def get_emails(self):
 		"""genere les mails a envoyer"""
-		participants = InterludesParticipant.objects.filter(is_registered=True)
+		participants = models.InterludesParticipant.objects.filter(is_registered=True)
 		emails = []
 		settings = SiteSettings.load()
 		for participant in participants:
-			my_activities = ActivityList.objects.filter(participant=participant)
+			my_activities = models.InterludesActivityChoices.objects.filter(participant=participant)
 			message = render_to_string("email/user.html", {
 				"user": participant.user,
 				"settings": settings,
@@ -408,13 +418,13 @@ class SendOrgaEmail(SendEmailBase):
 
 	def get_emails(self):
 		"""genere les mails a envoyer"""
-		activities = InterludesActivity.objects.filter(
+		activities = models.InterludesActivity.objects.filter(
 			subscribing_open=True, communicate_participants=True
 		)
 		emails = []
 		settings = SiteSettings.load()
 		for act in activities:
-			participants = ActivityList.objects.filter(activity=act, accepted=True)
+			participants = models.InterludesActivityChoices.objects.filter(activity=act, accepted=True)
 			message = render_to_string("email/orga.html", {
 				"activity": act,
 				"settings": settings,
diff --git a/shared/views.py b/shared/views.py
index 5bbc0c8..a245d71 100644
--- a/shared/views.py
+++ b/shared/views.py
@@ -70,5 +70,4 @@ class CSVWriteView(View):
 
 		for row in self.get_rows():
 			writer.writerow(row)
-		print(writer)
 		return response
-- 
GitLab