1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-08-21 12:57:23 +02:00

Survey questions

This commit is contained in:
Ehouarn
2025-08-20 22:59:37 +02:00
parent 251bb933da
commit 4da87872bd
6 changed files with 167 additions and 137 deletions

View File

@@ -17,7 +17,6 @@ def save_user_profile(instance, created, raw, **_kwargs):
def update_wei_registration_fee_on_membership_creation(sender, instance, created, **kwargs): def update_wei_registration_fee_on_membership_creation(sender, instance, created, **kwargs):
if not hasattr(instance, "_no_signal") and created: if not hasattr(instance, "_no_signal") and created:
print('update_wei_registration_fee_on_membership_creation')
from wei.models import WEIRegistration from wei.models import WEIRegistration
if instance.club.id == 1 or instance.club.id == 2: if instance.club.id == 1 or instance.club.id == 2:
registrations = WEIRegistration.objects.filter( registrations = WEIRegistration.objects.filter(

View File

@@ -10,145 +10,183 @@ from django import forms
from django.db import transaction from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.utils.safestring import mark_safe
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation
from ...models import WEIMembership, Bus from ...models import WEIMembership, Bus
WORDS = { WORDS = {
'list': [ 'list': [
'13 organisé', '3ième mi temps', 'Années 2000', 'Apéro', 'BBQ', 'BP', 'Beauf', 'Binge drinking', 'Bon enfant', 'Fiesta', 'Graillance', 'Move it move it', 'Calme', 'Nert et geek', 'Jeux de rôles et danse rock',
'Cartouche', 'Catacombes', 'Chansons paillardes', 'Chansons populaires', 'Chanteur', 'Chartreuse', 'Chill', 'Strass et paillettes', 'Spectaculaire', 'Splendide', 'Flow inégalable', 'Rap', 'Battles légendaires',
'Core', 'DJ', 'Dancefloor', 'Danse', 'David Guetta', 'Disco', 'Eau de vie', 'Électro', 'Escalade', 'Familial', 'Techno', 'Alcool', 'Kiffeur·euse', 'Rugby', 'Médiéval', 'Festif',
'Fanfare', 'Fracassage', 'Féria', 'Hard rock', 'Hoeggarden', 'House', 'Huit-six', 'IPA', 'Inclusif', 'Inferno', 'Stylé', 'Chipie', 'Rétro', 'Vache', 'Farfadet', 'Fanfare',
'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',
], ],
'questions': { 'questions': {
'Question 1': [ "alcool": [
'Description 1', """Sur une échelle allant de 0 (= 0 alcool ou très peu) à 5 (= la fontaine de jouvence alcoolique),
quel niveau de consommation dalcool souhaiterais-tu ?""",
{ {
3: 'Réponse 1 Madagas[car]', 42: "",
4: 'Réponse 1 Y2[KAR]', 47: "",
2: 'Réponse 1 Tcherno[bus]', 48: "",
5: 'Réponse 1 [Kar]tier', 45: "",
1: 'Réponse 1 [Car]cassonne', 44: "",
6: 'Réponse 1 O[car]ina', 46: "",
7: 'Réponse 1 Show[bus]', 43: "",
8: 'Réponse 1 [Car]ioca' 49: ""
} }
], ],
'Question 2': [ "voie_post_bac": [
'Description 2', """Si la DA du bus de ton choix correspondait à une voie post-bac, laquelle serait-elle ?""",
{ {
3: 'Réponse 2 Madagas[car]', 42: "",
4: 'Réponse 2 Y2[KAR]', 47: "",
2: 'Réponse 2 Tcherno[bus]', 48: "",
5: 'Réponse 2 [Kar]tier', 45: "",
1: 'Réponse 2 [Car]cassonne', 44: "",
6: 'Réponse 2 O[car]ina', 46: "",
7: 'Réponse 2 Show[bus]', 43: "",
8: 'Réponse 2 [Car]ioca' 49: ""
} }
], ],
'Question 3': [ "boite": [
'Description 3', """Tu es seul·e sur une île déserte et devant toi il y a une sombre boîte de taille raisonnable.
Quy a-t-il à lintérieur ?""",
{ {
3: 'Réponse 3 Madagas[car]', 42: "",
4: 'Réponse 3 Y2[KAR]', 47: "",
2: 'Réponse 3 Tcherno[bus]', 48: "",
5: 'Réponse 3 [Kar]tier', 45: "",
1: 'Réponse 3 [Car]cassonne', 44: "",
6: 'Réponse 3 O[car]ina', 46: "",
7: 'Réponse 3 Show[bus]', 43: "",
8: 'Réponse 3 [Car]ioca' 49: ""
} }
], ],
'Question 4': [ "tardif": [
'Description 4', """Il est 00h, tu as passé la journée à la plage avec tes copains et iels te proposent de prolonger parce
quaprès tout, il ny a plus personne sur la plage à cette heure-ci. Tu nhabites pas loin mais tenchaînes
demain avec une journée similaire avec un autre groupe damis parce que tes trop #busy. Que fais-tu ?""",
{ {
3: 'Réponse 4 Madagas[car]', 42: "",
4: 'Réponse 4 Y2[KAR]', 47: "",
2: 'Réponse 4 Tcherno[bus]', 48: "",
5: 'Réponse 4 [Kar]tier', 45: "",
1: 'Réponse 4 [Car]cassonne', 44: "",
6: 'Réponse 4 O[car]ina', 46: "",
7: 'Réponse 4 Show[bus]', 43: "",
8: 'Réponse 4 [Car]ioca' 49: ""
} }
], ],
'Question 5': [ "cohesion": [
'Description 5', """Cest 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]', 42: "",
4: 'Réponse 5 Y2[KAR]', 47: "",
2: 'Réponse 5 Tcherno[bus]', 48: "",
5: 'Réponse 5 [Kar]tier', 45: "",
1: 'Réponse 5 [Car]cassonne', 44: "",
6: 'Réponse 5 O[car]ina', 46: "",
7: 'Réponse 5 Show[bus]', 43: "",
8: 'Réponse 5 [Car]ioca' 49: ""
} }
], ],
'Question 6': [ "artiste": [
'Description 6', """Cest 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 dun·e artiste. De qui sagit-il ?""",
{ {
3: 'Réponse 6 Madagas[car]', 42: "",
4: 'Réponse 6 Y2[KAR]', 47: "",
2: 'Réponse 6 Tcherno[bus]', 48: "",
5: 'Réponse 6 [Kar]tier', 45: "",
1: 'Réponse 6 [Car]cassonne', 44: "",
6: 'Réponse 6 O[car]ina', 46: "",
7: 'Réponse 6 Show[bus]', 43: "",
8: 'Réponse 6 [Car]ioca' 49: ""
} }
], ],
'Question 7': [ "annonce_noel": [
'Description 7', """Cest Noël et tu revois toute ta famille, oncles, tantes, cousin·e·s, grands-parents, la totale.
Dun coup, tu te lèves, tapotes de manière pompeuse sur ton verre avec un de tes couverts.
Quannonces-tu ?""",
{ {
3: 'Réponse 7 Madagas[car]', 42: "",
4: 'Réponse 7 Y2[KAR]', 47: "",
2: 'Réponse 7 Tcherno[bus]', 48: "",
5: 'Réponse 7 [Kar]tier', 45: "",
1: 'Réponse 7 [Car]cassonne', 44: "",
6: 'Réponse 7 O[car]ina', 46: "",
7: 'Réponse 7 Show[bus]', 43: "",
8: 'Réponse 7 [Car]ioca' 49: ""
} }
], ],
'Question 8': [ "vacances": [
'Description 8', """Les vacances sont là et taimerais bien partir quelque part, mais où ?""",
{ {
3: 'Réponse 8 Madagas[car]', 42: "",
4: 'Réponse 8 Y2[KAR]', 47: "",
2: 'Réponse 8 Tcherno[bus]', 48: "",
5: 'Réponse 8 [Kar]tier', 45: "",
1: 'Réponse 8 [Car]cassonne', 44: "",
6: 'Réponse 8 O[car]ina', 46: "",
7: 'Réponse 8 Show[bus]', 43: "",
8: 'Réponse 8 [Car]ioca' 49: ""
} }
], ],
'Question 9': [ "loisir": [
'Description 9', """Tas fini ta journée de cours et tu tapprêtes à profiter dune activité/hobby/loisir de ton choix.
Laquelle est-ce ?""",
{ {
3: 'Réponse 9 Madagas[car]', 42: "",
4: 'Réponse 9 Y2[KAR]', 47: "",
2: 'Réponse 9 Tcherno[bus]', 48: "",
5: 'Réponse 9 [Kar]tier', 45: "",
1: 'Réponse 9 [Car]cassonne', 44: "",
6: 'Réponse 9 O[car]ina', 46: "",
7: 'Réponse 9 Show[bus]', 43: "",
8: 'Réponse 9 [Car]ioca' 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 tattire le plus ?""",
{
42: "",
47: "",
48: "",
45: "",
44: "",
46: "",
43: "",
49: ""
} }
] ]
} }
} }
IMAGES = {
}
NB_WORDS = 5 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} <img src="{img_url}" style="height:32px;vertical-align:middle;">')
else:
option['label'] = label
return option
class WEISurveyForm2025(forms.Form): class WEISurveyForm2025(forms.Form):
""" """
Survey form for the year 2025. Survey form for the year 2025.
@@ -170,7 +208,7 @@ class WEISurveyForm2025(forms.Form):
if information.step == 0: if information.step == 0:
self.fields["words"] = forms.MultipleChoiceField( 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']], choices=[(w, w) for w in WORDS['list']],
widget=forms.CheckboxSelectMultiple(), widget=forms.CheckboxSelectMultiple(),
required=True, required=True,
@@ -178,23 +216,7 @@ class WEISurveyForm2025(forms.Form):
if self.is_valid(): if self.is_valid():
return return
buses = WEISurveyAlgorithm2025.get_buses() all_preferred_words = WORDS['list']
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)
rng.shuffle(all_preferred_words) rng.shuffle(all_preferred_words)
self.fields["words"].choices = [(w, w) for w in all_preferred_words] self.fields["words"].choices = [(w, w) for w in all_preferred_words]
else: else:
@@ -202,12 +224,15 @@ class WEISurveyForm2025(forms.Form):
idx = information.step - 1 idx = information.step - 1
if idx < len(questions): if idx < len(questions):
q, (desc, answers) = questions[idx] q, (desc, answers) = questions[idx]
choices = [(k, v) for k, v in answers.items()] if q == 'alcool':
rng.shuffle(choices) 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( self.fields[q] = forms.ChoiceField(
label=desc, label=desc,
choices=choices, choices=choices,
widget=forms.RadioSelect, widget=OptionalImageRadioSelect(images=IMAGES.get(q, {})),
required=True, required=True,
) )
@@ -226,8 +251,6 @@ class WEIBusInformation2025(WEIBusInformation):
def __init__(self, bus): def __init__(self, bus):
self.scores = {} self.scores = {}
for word in WORDS['list']:
self.scores[word] = 0
super().__init__(bus) super().__init__(bus)
@@ -235,7 +258,9 @@ class BusInformationForm2025(forms.ModelForm):
class Meta: class Meta:
model = Bus model = Bus
fields = ['information_json'] fields = ['information_json']
widgets = {} widgets = {
'information_json': forms.HiddenInput(),
}
def __init__(self, *args, words=None, **kwargs): def __init__(self, *args, words=None, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -257,7 +282,7 @@ class BusInformationForm2025(forms.ModelForm):
label=word, label=word,
choices=choices, choices=choices,
coerce=int, coerce=int,
initial=initial_scores.get(word, 0), initial=initial_scores.get(word, 0) if word in initial_scores else None,
required=True, required=True,
widget=forms.RadioSelect, widget=forms.RadioSelect,
help_text=_("Rate between 0 and 5."), help_text=_("Rate between 0 and 5."),
@@ -285,7 +310,7 @@ class WEISurveyInformation2025(WEISurveyInformation):
step = 0 step = 0
def __init__(self, registration): def __init__(self, registration):
for i in range(1, 5): for i in range(1, NB_WORDS + 1):
setattr(self, "word" + str(i), None) setattr(self, "word" + str(i), None)
for q in WORDS['questions']: for q in WORDS['questions']:
setattr(self, q, None) setattr(self, q, None)
@@ -297,7 +322,7 @@ class WEISurveyInformation2025(WEISurveyInformation):
""" """
self.step = 0 self.step = 0
self.seed = 0 self.seed = 0
for i in range(1, 5): for i in range(1, NB_WORDS + 1):
setattr(self, f"word{i}", None) setattr(self, f"word{i}", None)
for q in WORDS['questions']: for q in WORDS['questions']:
setattr(self, q, None) setattr(self, q, None)
@@ -371,8 +396,9 @@ class WEISurvey2025(WEISurvey):
""" """
if not self.is_complete(): if not self.is_complete():
raise ValueError("Survey is not ended, can't calculate score") 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 q != 'alcool' and getattr(self.information, q) == bus.pk)
s = sum(1 for q in WORDS['questions'] if 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 return s
@lru_cache() @lru_cache()
@@ -396,7 +422,7 @@ class WEISurvey2025(WEISurvey):
@lru_cache() @lru_cache()
def ordered_buses(self): 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 = list(self.scores_per_bus().items())
values.sort(key=lambda item: -item[1][0]) values.sort(key=lambda item: -item[1][0])
@@ -513,7 +539,7 @@ class WEISurveyAlgorithm2025(WEISurveyAlgorithm):
for survey2 in surveys: for survey2 in surveys:
if not survey2.information.valid or survey2.information.get_selected_bus() != bus: if not survey2.information.valid or survey2.information.get_selected_bus() != bus:
continue continue
score2 = survey2.score_questions(bus) score2 = survey2.score_words(bus)
if current_scores[1] <= score2: # Ignore better students if current_scores[1] <= score2: # Ignore better students
continue continue
if least_preferred_survey is None or score2 < least_score: if least_preferred_survey is None or score2 < least_score:

View File

@@ -22,8 +22,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endif %} {% endif %}
<a class="btn btn-primary btn-sm my-1" href="{% url 'wei:update_bus' pk=object.pk %}" <a class="btn btn-primary btn-sm my-1" href="{% url 'wei:update_bus' pk=object.pk %}"
data-turbolinks="false">{% trans "Edit" %}</a> data-turbolinks="false">{% trans "Edit" %}</a>
<a class="btn btn-primary btn-sm my-1" href="{% url 'wei:update_bus_info' pk=object.pk %}" <a class="btn btn-primary btn-sm my-1" href="{% url 'wei:update_bus_info' pk=object.pk %}"
data-turbolinks="false">{% trans "Edit information" %}</a> data-turbolinks="false">{% trans "Edit information for survey" %}</a>
<a class="btn btn-primary btn-sm my-1" href="{% url 'wei:add_team' pk=object.pk %}" <a class="btn btn-primary btn-sm my-1" href="{% url 'wei:add_team' pk=object.pk %}"
data-turbolinks="false">{% trans "Add team" %}</a> data-turbolinks="false">{% trans "Add team" %}</a>
</div> </div>

View File

@@ -31,15 +31,22 @@ SPDX-License-Identifier: GPL-3.0-or-later
<a class="btn btn-success" href="{% url "wei:wei_register_1A_myself" wei_pk=club.pk %}" data-turbolinks="false"> <a class="btn btn-success" href="{% url "wei:wei_register_1A_myself" wei_pk=club.pk %}" data-turbolinks="false">
{% trans "Register to the WEI! 1A" %} {% trans "Register to the WEI! 1A" %}
</a> </a>
{% endif %} {% else %}
<a class="btn btn-success" href="{% url "wei:wei_register_2A_myself" wei_pk=club.pk %}" data-turbolinks="false"> <a class="btn btn-success" href="{% url "wei:wei_register_2A_myself" wei_pk=club.pk %}" data-turbolinks="false">
{% trans "Register to the WEI! 2A+" %}</a> {% trans "Register to the WEI! 2A+" %}
</a>
{% endif %}
{% else %} {% else %}
<a class="btn btn-warning" href="{% url "wei:wei_update_registration" pk=my_registration.pk %}" <a class="btn btn-warning" href="{% url "wei:wei_update_registration" pk=my_registration.pk %}"
data-turbolinks="false"> data-turbolinks="false">
{% trans "Update my registration" %} {% trans "Update my registration" %}
</a> </a>
{% if not not_first_year %} {% if not not_first_year %}
{% if not survey_complete %}
<a class="btn btn-warning" href="{% url "wei:wei_survey" pk=my_registration.pk %}" data-turbolinks="false">
{% trans "Continue survey" %}
</a>
{% endif %}
<a class="btn btn-warning" href="{% url "wei:wei_survey" pk=my_registration.pk %}?reset=true" data-turbolinks="false"> <a class="btn btn-warning" href="{% url "wei:wei_survey" pk=my_registration.pk %}?reset=true" data-turbolinks="false">
{% trans "Restart survey" %} {% trans "Restart survey" %}
</a> </a>

View File

@@ -105,8 +105,6 @@ class TestWEIAlgorithm(TestCase):
survey = WEISurvey2025(r) survey = WEISurvey2025(r)
chosen_bus = survey.information.get_selected_bus() chosen_bus = survey.information.get_selected_bus()
buses = survey.ordered_buses() buses = survey.ordered_buses()
'''print(buses)
print(chosen_bus)'''
self.assertIn(chosen_bus, [x[0] for x in buses]) 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) 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))) max_score_questions = max(buses[i][1][0] for i in range(len(buses)))

View File

@@ -166,6 +166,7 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, D
my_registration = WEIRegistration.objects.filter(wei=club, user=self.request.user) my_registration = WEIRegistration.objects.filter(wei=club, user=self.request.user)
if my_registration.exists(): if my_registration.exists():
my_registration = my_registration.get() my_registration = my_registration.get()
context["survey_complete"] = CurrentSurvey(my_registration).is_complete()
else: else:
my_registration = None my_registration = None
context["my_registration"] = my_registration context["my_registration"] = my_registration
@@ -1231,7 +1232,6 @@ class WEIUpdateMembershipView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateVi
return form return form
def get_success_url(self): def get_success_url(self):
print("get_success_url")
return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.registration.wei.pk}) return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.registration.wei.pk})