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 : + {% if my_choices %} + <li>Inscrit à {{ my_choices|length }} activités : <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 : + {% if my_choices %} + <li>{{ my_choices|length }} activités souhaitées : <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> • {}: {} inscrits (maximum {})".format( - act.title, total, max + slot, total, max ) if min > total: min_fails += "<br> • {}: {} 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> •  {} 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