diff --git a/apps/member/signals.py b/apps/member/signals.py index b1b8cd82..f07c8896 100644 --- a/apps/member/signals.py +++ b/apps/member/signals.py @@ -17,7 +17,6 @@ def save_user_profile(instance, created, raw, **_kwargs): def update_wei_registration_fee_on_membership_creation(sender, instance, created, **kwargs): if not hasattr(instance, "_no_signal") and created: - print('update_wei_registration_fee_on_membership_creation') from wei.models import WEIRegistration if instance.club.id == 1 or instance.club.id == 2: registrations = WEIRegistration.objects.filter( diff --git a/apps/wei/forms/surveys/wei2025.py b/apps/wei/forms/surveys/wei2025.py index 33291bb1..a12063ec 100644 --- a/apps/wei/forms/surveys/wei2025.py +++ b/apps/wei/forms/surveys/wei2025.py @@ -10,145 +10,183 @@ from django import forms from django.db import transaction from django.db.models import Q from django.utils.translation import gettext_lazy as _ +from django.utils.safestring import mark_safe from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation from ...models import WEIMembership, Bus WORDS = { 'list': [ - '13 organisé', '3ième mi temps', 'Années 2000', 'Apéro', 'BBQ', 'BP', 'Beauf', 'Binge drinking', 'Bon enfant', - 'Cartouche', 'Catacombes', 'Chansons paillardes', 'Chansons populaires', 'Chanteur', 'Chartreuse', 'Chill', - 'Core', 'DJ', 'Dancefloor', 'Danse', 'David Guetta', 'Disco', 'Eau de vie', 'Électro', 'Escalade', 'Familial', - 'Fanfare', 'Fracassage', 'Féria', 'Hard rock', 'Hoeggarden', 'House', 'Huit-six', 'IPA', 'Inclusif', 'Inferno', - 'Introverti', 'Jager bomb', 'Jazz', 'Jeux d\'alcool', 'Jeux de rôles', 'Jeux vidéo', 'Jul', 'Jus de fruit', - 'Karaoké', 'LGBTQI+', 'Lady Gaga', 'Loup garou', 'Morning beer', 'Métal', 'Nuit blanche', 'Ovalie', 'Psychedelic', - 'Pétanque', 'Rave', 'Reggae', 'Rhum', 'Ricard', 'Rock', 'Rosé', 'Rétro', 'Séducteur', 'Techno', 'Thérapie taxi', - 'Théâtre', 'Trap', 'Turn up', 'Underground', 'Volley', 'Wati B', 'Zinédine Zidane', + 'Fiesta', 'Graillance', 'Move it move it', 'Calme', 'Nert et geek', 'Jeux de rôles et danse rock', + 'Strass et paillettes', 'Spectaculaire', 'Splendide', 'Flow inégalable', 'Rap', 'Battles légendaires', + 'Techno', 'Alcool', 'Kiffeur·euse', 'Rugby', 'Médiéval', 'Festif', + 'Stylé', 'Chipie', 'Rétro', 'Vache', 'Farfadet', 'Fanfare', ], 'questions': { - 'Question 1': [ - 'Description 1', + "alcool": [ + """Sur une échelle allant de 0 (= 0 alcool ou très peu) à 5 (= la fontaine de jouvence alcoolique), + quel niveau de consommation d’alcool souhaiterais-tu ?""", { - 3: 'Réponse 1 Madagas[car]', - 4: 'Réponse 1 Y2[KAR]', - 2: 'Réponse 1 Tcherno[bus]', - 5: 'Réponse 1 [Kar]tier', - 1: 'Réponse 1 [Car]cassonne', - 6: 'Réponse 1 O[car]ina', - 7: 'Réponse 1 Show[bus]', - 8: 'Réponse 1 [Car]ioca' + 42: "", + 47: "", + 48: "", + 45: "", + 44: "", + 46: "", + 43: "", + 49: "" } ], - 'Question 2': [ - 'Description 2', + "voie_post_bac": [ + """Si la DA du bus de ton choix correspondait à une voie post-bac, laquelle serait-elle ?""", { - 3: 'Réponse 2 Madagas[car]', - 4: 'Réponse 2 Y2[KAR]', - 2: 'Réponse 2 Tcherno[bus]', - 5: 'Réponse 2 [Kar]tier', - 1: 'Réponse 2 [Car]cassonne', - 6: 'Réponse 2 O[car]ina', - 7: 'Réponse 2 Show[bus]', - 8: 'Réponse 2 [Car]ioca' + 42: "", + 47: "", + 48: "", + 45: "", + 44: "", + 46: "", + 43: "", + 49: "" } ], - 'Question 3': [ - 'Description 3', + "boite": [ + """Tu es seul·e sur une île déserte et devant toi il y a une sombre boîte de taille raisonnable. + Qu’y a-t-il à l’intérieur ?""", { - 3: 'Réponse 3 Madagas[car]', - 4: 'Réponse 3 Y2[KAR]', - 2: 'Réponse 3 Tcherno[bus]', - 5: 'Réponse 3 [Kar]tier', - 1: 'Réponse 3 [Car]cassonne', - 6: 'Réponse 3 O[car]ina', - 7: 'Réponse 3 Show[bus]', - 8: 'Réponse 3 [Car]ioca' + 42: "", + 47: "", + 48: "", + 45: "", + 44: "", + 46: "", + 43: "", + 49: "" } ], - 'Question 4': [ - 'Description 4', + "tardif": [ + """Il est 00h, tu as passé la journée à la plage avec tes copains et iels te proposent de prolonger parce + qu’après tout, il n’y a plus personne sur la plage à cette heure-ci. Tu n’habites pas loin mais t’enchaînes + demain avec une journée similaire avec un autre groupe d’amis parce que t’es trop #busy. Que fais-tu ?""", { - 3: 'Réponse 4 Madagas[car]', - 4: 'Réponse 4 Y2[KAR]', - 2: 'Réponse 4 Tcherno[bus]', - 5: 'Réponse 4 [Kar]tier', - 1: 'Réponse 4 [Car]cassonne', - 6: 'Réponse 4 O[car]ina', - 7: 'Réponse 4 Show[bus]', - 8: 'Réponse 4 [Car]ioca' + 42: "", + 47: "", + 48: "", + 45: "", + 44: "", + 46: "", + 43: "", + 49: "" } ], - 'Question 5': [ - 'Description 5', + "cohesion": [ + """C’est la rentrée de Seconde et tu découvres ta classe, tes camarades et ta prof principale!!! + qui vous propose une activité de cohésion. Laquelle est-elle ?""", { - 3: 'Réponse 5 Madagas[car]', - 4: 'Réponse 5 Y2[KAR]', - 2: 'Réponse 5 Tcherno[bus]', - 5: 'Réponse 5 [Kar]tier', - 1: 'Réponse 5 [Car]cassonne', - 6: 'Réponse 5 O[car]ina', - 7: 'Réponse 5 Show[bus]', - 8: 'Réponse 5 [Car]ioca' + 42: "", + 47: "", + 48: "", + 45: "", + 44: "", + 46: "", + 43: "", + 49: "" } ], - 'Question 6': [ - 'Description 6', + "artiste": [ + """C’est l’été et la saison des festivals a commencé. Tu regardes la programmation du festival + pas loin de chez toi et tu découvres avec joie la présence d’un·e artiste. De qui s’agit-il ?""", { - 3: 'Réponse 6 Madagas[car]', - 4: 'Réponse 6 Y2[KAR]', - 2: 'Réponse 6 Tcherno[bus]', - 5: 'Réponse 6 [Kar]tier', - 1: 'Réponse 6 [Car]cassonne', - 6: 'Réponse 6 O[car]ina', - 7: 'Réponse 6 Show[bus]', - 8: 'Réponse 6 [Car]ioca' + 42: "", + 47: "", + 48: "", + 45: "", + 44: "", + 46: "", + 43: "", + 49: "" } ], - 'Question 7': [ - 'Description 7', + "annonce_noel": [ + """C’est Noël et tu revois toute ta famille, oncles, tantes, cousin·e·s, grands-parents, la totale. + D’un coup, tu te lèves, tapotes de manière pompeuse sur ton verre avec un de tes couverts. + Qu’annonces-tu ?""", { - 3: 'Réponse 7 Madagas[car]', - 4: 'Réponse 7 Y2[KAR]', - 2: 'Réponse 7 Tcherno[bus]', - 5: 'Réponse 7 [Kar]tier', - 1: 'Réponse 7 [Car]cassonne', - 6: 'Réponse 7 O[car]ina', - 7: 'Réponse 7 Show[bus]', - 8: 'Réponse 7 [Car]ioca' + 42: "", + 47: "", + 48: "", + 45: "", + 44: "", + 46: "", + 43: "", + 49: "" } ], - 'Question 8': [ - 'Description 8', + "vacances": [ + """Les vacances sont là et t’aimerais bien partir quelque part, mais où ?""", { - 3: 'Réponse 8 Madagas[car]', - 4: 'Réponse 8 Y2[KAR]', - 2: 'Réponse 8 Tcherno[bus]', - 5: 'Réponse 8 [Kar]tier', - 1: 'Réponse 8 [Car]cassonne', - 6: 'Réponse 8 O[car]ina', - 7: 'Réponse 8 Show[bus]', - 8: 'Réponse 8 [Car]ioca' + 42: "", + 47: "", + 48: "", + 45: "", + 44: "", + 46: "", + 43: "", + 49: "" } ], - 'Question 9': [ - 'Description 9', + "loisir": [ + """T’as fini ta journée de cours et tu t’apprêtes à profiter d’une activité/hobby/loisir de ton choix. + Laquelle est-ce ?""", { - 3: 'Réponse 9 Madagas[car]', - 4: 'Réponse 9 Y2[KAR]', - 2: 'Réponse 9 Tcherno[bus]', - 5: 'Réponse 9 [Kar]tier', - 1: 'Réponse 9 [Car]cassonne', - 6: 'Réponse 9 O[car]ina', - 7: 'Réponse 9 Show[bus]', - 8: 'Réponse 9 [Car]ioca' + 42: "", + 47: "", + 48: "", + 45: "", + 44: "", + 46: "", + 43: "", + 49: "" + } + ], + "plan": [ + """Tu reçois un message sur la conversation de groupe que tu partages avec tes potes : + vous êtes chaud·e·s pour vous retrouver. Quel plan t’attire le plus ?""", + { + 42: "", + 47: "", + 48: "", + 45: "", + 44: "", + 46: "", + 43: "", + 49: "" } ] } } +IMAGES = { +} + NB_WORDS = 5 +class OptionalImageRadioSelect(forms.RadioSelect): + def __init__(self, images=None, *args, **kwargs): + self.images = images or {} + super().__init__(*args, **kwargs) + + def create_option(self, name, value, label, selected, index, subindex=None, attrs=None): + option = super().create_option(name, value, label, selected, index, subindex=subindex, attrs=attrs) + img_url = self.images.get(value) + if img_url: + option['label'] = mark_safe(f'{label} ') + else: + option['label'] = label + return option + + class WEISurveyForm2025(forms.Form): """ Survey form for the year 2025. @@ -170,7 +208,7 @@ class WEISurveyForm2025(forms.Form): if information.step == 0: self.fields["words"] = forms.MultipleChoiceField( - label=_(f"Choose {NB_WORDS} words:"), + label=_(f"Select {NB_WORDS} words that describe the WEI experience you want to have."), choices=[(w, w) for w in WORDS['list']], widget=forms.CheckboxSelectMultiple(), required=True, @@ -178,23 +216,7 @@ class WEISurveyForm2025(forms.Form): if self.is_valid(): return - buses = WEISurveyAlgorithm2025.get_buses() - informations = {bus: WEIBusInformation2025(bus) for bus in buses} - scores = sum((list(informations[bus].scores.values()) for bus in buses), []) - if scores: - average_score = sum(scores) / len(scores) - else: - average_score = 0 - - preferred_words = { - bus: [word for word in WORDS['list'] if informations[bus].scores[word] >= average_score] - for bus in buses - } - - all_preferred_words = set() - for bus_words in preferred_words.values(): - all_preferred_words.update(bus_words) - all_preferred_words = list(all_preferred_words) + all_preferred_words = WORDS['list'] rng.shuffle(all_preferred_words) self.fields["words"].choices = [(w, w) for w in all_preferred_words] else: @@ -202,12 +224,15 @@ class WEISurveyForm2025(forms.Form): idx = information.step - 1 if idx < len(questions): q, (desc, answers) = questions[idx] - choices = [(k, v) for k, v in answers.items()] - rng.shuffle(choices) + if q == 'alcool': + choices = [(i / 2, str(i / 2)) for i in range(11)] + else: + choices = [(k, v) for k, v in answers.items()] + rng.shuffle(choices) self.fields[q] = forms.ChoiceField( label=desc, choices=choices, - widget=forms.RadioSelect, + widget=OptionalImageRadioSelect(images=IMAGES.get(q, {})), required=True, ) @@ -226,8 +251,6 @@ class WEIBusInformation2025(WEIBusInformation): def __init__(self, bus): self.scores = {} - for word in WORDS['list']: - self.scores[word] = 0 super().__init__(bus) @@ -235,7 +258,9 @@ class BusInformationForm2025(forms.ModelForm): class Meta: model = Bus fields = ['information_json'] - widgets = {} + widgets = { + 'information_json': forms.HiddenInput(), + } def __init__(self, *args, words=None, **kwargs): super().__init__(*args, **kwargs) @@ -257,7 +282,7 @@ class BusInformationForm2025(forms.ModelForm): label=word, choices=choices, coerce=int, - initial=initial_scores.get(word, 0), + initial=initial_scores.get(word, 0) if word in initial_scores else None, required=True, widget=forms.RadioSelect, help_text=_("Rate between 0 and 5."), @@ -285,7 +310,7 @@ class WEISurveyInformation2025(WEISurveyInformation): step = 0 def __init__(self, registration): - for i in range(1, 5): + for i in range(1, NB_WORDS + 1): setattr(self, "word" + str(i), None) for q in WORDS['questions']: setattr(self, q, None) @@ -297,7 +322,7 @@ class WEISurveyInformation2025(WEISurveyInformation): """ self.step = 0 self.seed = 0 - for i in range(1, 5): + for i in range(1, NB_WORDS + 1): setattr(self, f"word{i}", None) for q in WORDS['questions']: setattr(self, q, None) @@ -371,8 +396,9 @@ class WEISurvey2025(WEISurvey): """ if not self.is_complete(): raise ValueError("Survey is not ended, can't calculate score") - # Score is the given score by the bus subtracted to the mid-score of the buses. - s = sum(1 for q in WORDS['questions'] if getattr(self.information, q) == bus.pk) + s = sum(1 for q in WORDS['questions'] if q != 'alcool' and getattr(self.information, q) == bus.pk) + if 'alcool' in WORDS['questions'] and bus.pk in WORDS['questions']['alcool'][1] and hasattr(self.information, 'alcool'): + s -= abs(float(self.information.alcool) - float(WORDS['questions']['alcool'][1][bus.pk])) return s @lru_cache() @@ -396,7 +422,7 @@ class WEISurvey2025(WEISurvey): @lru_cache() def ordered_buses(self): """ - Force the choice of bus to be in the 3 preferred buses according to the words + Order the buses by the score_questions of the survey. """ values = list(self.scores_per_bus().items()) values.sort(key=lambda item: -item[1][0]) @@ -513,7 +539,7 @@ class WEISurveyAlgorithm2025(WEISurveyAlgorithm): for survey2 in surveys: if not survey2.information.valid or survey2.information.get_selected_bus() != bus: continue - score2 = survey2.score_questions(bus) + score2 = survey2.score_words(bus) if current_scores[1] <= score2: # Ignore better students continue if least_preferred_survey is None or score2 < least_score: diff --git a/apps/wei/templates/wei/bus_detail.html b/apps/wei/templates/wei/bus_detail.html index af4eaccb..0f521061 100644 --- a/apps/wei/templates/wei/bus_detail.html +++ b/apps/wei/templates/wei/bus_detail.html @@ -22,8 +22,8 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endif %} {% trans "Edit" %} - {% trans "Edit information" %} + {% trans "Edit information for survey" %} {% trans "Add team" %} diff --git a/apps/wei/templates/wei/weiclub_detail.html b/apps/wei/templates/wei/weiclub_detail.html index e4b0bfbb..84fcdf81 100644 --- a/apps/wei/templates/wei/weiclub_detail.html +++ b/apps/wei/templates/wei/weiclub_detail.html @@ -31,15 +31,22 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "Register to the WEI! – 1A" %} - {% endif %} + {% else %} - {% trans "Register to the WEI! – 2A+" %} + {% trans "Register to the WEI! – 2A+" %} + + {% endif %} {% else %} {% trans "Update my registration" %} {% if not not_first_year %} + {% if not survey_complete %} + + {% trans "Continue survey" %} + + {% endif %} {% trans "Restart survey" %} diff --git a/apps/wei/tests/test_wei_algorithm_2025.py b/apps/wei/tests/test_wei_algorithm_2025.py index cd6ad017..e1eac7e0 100644 --- a/apps/wei/tests/test_wei_algorithm_2025.py +++ b/apps/wei/tests/test_wei_algorithm_2025.py @@ -105,8 +105,6 @@ class TestWEIAlgorithm(TestCase): survey = WEISurvey2025(r) chosen_bus = survey.information.get_selected_bus() buses = survey.ordered_buses() - '''print(buses) - print(chosen_bus)''' self.assertIn(chosen_bus, [x[0] for x in buses]) score_questions, score_words = next(scores for bus, scores in buses if bus == chosen_bus) max_score_questions = max(buses[i][1][0] for i in range(len(buses))) diff --git a/apps/wei/views.py b/apps/wei/views.py index 782f264e..b18f7a25 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -166,6 +166,7 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, D my_registration = WEIRegistration.objects.filter(wei=club, user=self.request.user) if my_registration.exists(): my_registration = my_registration.get() + context["survey_complete"] = CurrentSurvey(my_registration).is_complete() else: my_registration = None context["my_registration"] = my_registration @@ -1231,7 +1232,6 @@ class WEIUpdateMembershipView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateVi return form def get_success_url(self): - print("get_success_url") return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.registration.wei.pk})