From 7af2ebba409f82300768ab191526912ce5afb988 Mon Sep 17 00:00:00 2001 From: mcngnt Date: Wed, 3 Jul 2024 18:55:41 +0200 Subject: [PATCH 1/8] basic survey --- apps/wei/forms/surveys/__init__.py | 4 +- apps/wei/forms/surveys/wei2024.py | 303 +++++++++++++++++++++++++++++ 2 files changed, 305 insertions(+), 2 deletions(-) create mode 100644 apps/wei/forms/surveys/wei2024.py diff --git a/apps/wei/forms/surveys/__init__.py b/apps/wei/forms/surveys/__init__.py index 0e0c37e2..733f7c37 100644 --- a/apps/wei/forms/surveys/__init__.py +++ b/apps/wei/forms/surveys/__init__.py @@ -2,11 +2,11 @@ # SPDX-License-Identifier: GPL-3.0-or-later from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm -from .wei2023 import WEISurvey2023 +from .wei2024 import WEISurvey2024 __all__ = [ 'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey', ] -CurrentSurvey = WEISurvey2023 +CurrentSurvey = WEISurvey2024 diff --git a/apps/wei/forms/surveys/wei2024.py b/apps/wei/forms/surveys/wei2024.py new file mode 100644 index 00000000..7e8c576e --- /dev/null +++ b/apps/wei/forms/surveys/wei2024.py @@ -0,0 +1,303 @@ +# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from functools import lru_cache + +from django import forms +from django.db import transaction +from django.db.models import Q + +from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation +from ...models import WEIMembership + + + + +buses_descr = [ +["Magi[Kar]p","#ef5568", +"bus faible en alcool mais fort en connerie avec une partie calme pour les amateurs de sieste et de jeux de société. non discriminant il accepte tout le monde y compris le plus nulle des pokémons (magicarpe !!!!!!). Malgré les accusations mensongères, il n'y a aucun weeb dans le Magi[Kar]p"], +["Va[car]me","#fd7a28", +"descr"], +["[Kar]aïbes","#a5cfdd", +"descr"], +["[Kar]di [Bus]","#e46398", +"descr"], +["Sparta[bus] 🐺🐒🏉","#ebdac2", +"descr"], +["Zanzo[Bus]","#FFFF", +"descr"], +["Bran[Kar]","#6da1ac", +"Si vous ne connaissez pas le Bran[Kar], c’est comme une grande famille qui fait un apéro, qui se bourre un peu la gueule en discutant des heures autour d’une table remplie de bouffe et de super bons cocktails (la plupart des barmen/barwomen du bus sont les barmans de Shakens), sauf qu’on est un bus du Wei (vous comprendrez bien le nom de notre bus en voyant l’état de certain·e·s). Il nous arrive de faire quelques conneries, mais surtout de jouer au Bière-pong en musique !"], +["Techno [kar]ade","#8065a3", +"descr"], +["[Bus]ka-P","#7c4768", +"descr"] +] + +def get_survey_info(id): + s = {"recap": { + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0 + }} + s_ = {f"bus{id}" : {f"{i}" : 0 for i in range(1,5+1)} for id in range(len(buses_descr))} + s.update(s_) + s.update({f"bus{id}" : {f"{i}" : i for i in range(1,5+1)}}) + return {"scores" : s} + + +def print_bus(id): + return buses_descr[id][0] + "\n\n" + buses_descr[id][2] + +def print_all_buses(): + l = [print_bus(id) for id in range(len(buses_descr))] + return "\n\n---------\n\n".join(l) + +WORDS = {"recap": ["Cher(e) 1A, te voilà arrivé(e) devant un choix fatidique, le choix de ton bus....... \n (Musique effrayante) \n Peite blagounette évidemment, chacun des bus te permettra de passer un excellent WEI ! Mais quitte à avoir le choix, voici la liste de tous les bus ainsi qu'une description détaillée de ces derniers ! Prends ton temps, observe les bien et quand tu te sens prêt(e), appuye sur le bouton 'Noter les bus' pour continuer (pas besoin d'apprendre par coeur les bus, la descirption du bus te sera rappeler avant de le noter !) \n\n\n" + print_all_buses(), { + 1: "Noter les bus :" +}]} + +WORDS.update({ + f"bus{id}" : [print_bus(id),{i : f"Noter {i}/5" for i in range(1,5+1)}] for id in range(len(buses_descr)) +}) + + + +class WEISurveyForm2024(forms.Form): + """ + Survey form for the year 2024. + Members answer 20 questions, from which we calculate the best associated bus. + """ + def set_registration(self, registration): + """ + Filter the bus selector with the buses of the current WEI. + """ + information = WEISurveyInformation2024(registration) + + question = information.questions[information.step] + self.fields[question] = forms.ChoiceField( + label=WORDS[question][0], + widget=forms.RadioSelect(), + ) + answers = [(answer, WORDS[question][1][answer]) for answer in WORDS[question][1]] + self.fields[question].choices = answers + + +class WEIBusInformation2024(WEIBusInformation): + """ + For each question, the bus has ordered answers + """ + scores: dict + + def __init__(self, bus): + self.scores = {} + for question in WORDS: + self.scores[question] = [] + super().__init__(bus) + + +class WEISurveyInformation2024(WEISurveyInformation): + """ + We store the id of the selected bus. We store only the name, but is not used in the selection: + that's only for humans that try to read data. + """ + + step = 0 + questions = list(WORDS.keys()) + + def __init__(self, registration): + for question in WORDS: + setattr(self, str(question), None) + super().__init__(registration) + + +class WEISurvey2024(WEISurvey): + """ + Survey for the year 2024. + """ + + @classmethod + def get_year(cls): + return 2024 + + @classmethod + def get_survey_information_class(cls): + return WEISurveyInformation2024 + + def get_form_class(self): + return WEISurveyForm2024 + + def update_form(self, form): + """ + Filter the bus selector with the buses of the WEI. + """ + form.set_registration(self.registration) + + @transaction.atomic + def form_valid(self, form): + self.information.step += 1 + for question in WORDS: + if question in form.cleaned_data: + answer = form.cleaned_data[question] + setattr(self.information, question, answer) + self.save() + + @classmethod + def get_algorithm_class(cls): + return WEISurveyAlgorithm2024 + + def is_complete(self) -> bool: + """ + The survey is complete once the bus is chosen. + """ + for question in WORDS: + if not getattr(self.information, question): + return False + return True + + @lru_cache() + def score(self, bus): + if not self.is_complete(): + raise ValueError("Survey is not ended, can't calculate score") + + bus_info = self.get_algorithm_class().get_bus_information(bus) + # Score is the given score by the bus subtracted to the mid-score of the buses. + s = 0 + for question in WORDS: + s += bus_info.scores[question][str(getattr(self.information, question))] + return s + + @lru_cache() + def scores_per_bus(self): + return {bus: self.score(bus) for bus in self.get_algorithm_class().get_buses()} + + @lru_cache() + def ordered_buses(self): + values = list(self.scores_per_bus().items()) + values.sort(key=lambda item: -item[1]) + return values + + @classmethod + def clear_cache(cls): + return super().clear_cache() + + +class WEISurveyAlgorithm2024(WEISurveyAlgorithm): + """ + The algorithm class for the year 2024. + We use Gale-Shapley algorithm to attribute 1y students into buses. + """ + + @classmethod + def get_survey_class(cls): + return WEISurvey2024 + + @classmethod + def get_bus_information_class(cls): + return WEIBusInformation2024 + + def run_algorithm(self, display_tqdm=False): + """ + Gale-Shapley algorithm implementation. + We modify it to allow buses to have multiple "weddings". + """ + surveys = list(self.get_survey_class()(r) for r in self.get_registrations()) # All surveys + surveys = [s for s in surveys if s.is_complete()] # Don't consider invalid surveys + # Don't manage hardcoded people + surveys = [s for s in surveys if not hasattr(s.information, 'hardcoded') or not s.information.hardcoded] + + # Reset previous algorithm run + for survey in surveys: + survey.free() + survey.save() + + non_men = [s for s in surveys if s.registration.gender != 'male'] + men = [s for s in surveys if s.registration.gender == 'male'] + + quotas = {} + registrations = self.get_registrations() + non_men_total = registrations.filter(~Q(gender='male')).count() + for bus in self.get_buses(): + free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count() + # Remove hardcoded people + free_seats -= WEIMembership.objects.filter(bus=bus, registration__first_year=True, + registration__information_json__icontains="hardcoded").count() + quotas[bus] = 4 + int(non_men_total / registrations.count() * free_seats) + + tqdm_obj = None + if display_tqdm: + from tqdm import tqdm + tqdm_obj = tqdm(total=len(non_men), desc="Non-hommes") + + # Repartition for non men people first + self.make_repartition(non_men, quotas, tqdm_obj=tqdm_obj) + + quotas = {} + for bus in self.get_buses(): + free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count() + free_seats -= sum(1 for s in non_men if s.information.selected_bus_pk == bus.pk) + # Remove hardcoded people + free_seats -= WEIMembership.objects.filter(bus=bus, registration__first_year=True, + registration__information_json__icontains="hardcoded").count() + quotas[bus] = free_seats + + if display_tqdm: + tqdm_obj.close() + + from tqdm import tqdm + tqdm_obj = tqdm(total=len(men), desc="Hommes") + + self.make_repartition(men, quotas, tqdm_obj=tqdm_obj) + + if display_tqdm: + tqdm_obj.close() + + # Clear cache information after running algorithm + WEISurvey2024.clear_cache() + + def make_repartition(self, surveys, quotas=None, tqdm_obj=None): + free_surveys = surveys.copy() # Remaining surveys + while free_surveys: # Some students are not affected + survey = free_surveys[0] + buses = survey.ordered_buses() # Preferences of the student + for bus, current_score in buses: + if self.get_bus_information(bus).has_free_seats(surveys, quotas): + # Selected bus has free places. Put student in the bus + survey.select_bus(bus) + survey.save() + free_surveys.remove(survey) + break + else: + # Current bus has not enough places. Remove the least preferred student from the bus if existing + least_preferred_survey = None + least_score = -1 + # Find the least student in the bus that has a lower score than the current student + for survey2 in surveys: + if not survey2.information.valid or survey2.information.get_selected_bus() != bus: + continue + score2 = survey2.score(bus) + if current_score <= score2: # Ignore better students + continue + if least_preferred_survey is None or score2 < least_score: + least_preferred_survey = survey2 + least_score = score2 + + if least_preferred_survey is not None: + # Remove the least student from the bus and put the current student in. + # If it does not exist, choose the next bus. + least_preferred_survey.free() + least_preferred_survey.save() + free_surveys.append(least_preferred_survey) + survey.select_bus(bus) + survey.save() + free_surveys.remove(survey) + break + else: + raise ValueError(f"User {survey.registration.user} has no free seat") + + if tqdm_obj is not None: + tqdm_obj.n = len(surveys) - len(free_surveys) + tqdm_obj.refresh() + From d4e85e82158b98f7b20a1296f3204976028a0549 Mon Sep 17 00:00:00 2001 From: korenstin Date: Sun, 4 Aug 2024 17:05:35 +0200 Subject: [PATCH 2/8] test wei 2024, linters --- apps/wei/forms/surveys/wei2024.py | 108 +++++++++----- apps/wei/tests/test_wei_algorithm_2023.py | 43 ------ apps/wei/tests/test_wei_algorithm_2024.py | 172 ++++++++++++++++++++++ apps/wei/tests/test_wei_registration.py | 2 +- 4 files changed, 244 insertions(+), 81 deletions(-) create mode 100644 apps/wei/tests/test_wei_algorithm_2024.py diff --git a/apps/wei/forms/surveys/wei2024.py b/apps/wei/forms/surveys/wei2024.py index 7e8c576e..e37cf621 100644 --- a/apps/wei/forms/surveys/wei2024.py +++ b/apps/wei/forms/surveys/wei2024.py @@ -11,60 +11,95 @@ from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInf from ...models import WEIMembership - - buses_descr = [ -["Magi[Kar]p","#ef5568", -"bus faible en alcool mais fort en connerie avec une partie calme pour les amateurs de sieste et de jeux de société. non discriminant il accepte tout le monde y compris le plus nulle des pokémons (magicarpe !!!!!!). Malgré les accusations mensongères, il n'y a aucun weeb dans le Magi[Kar]p"], -["Va[car]me","#fd7a28", -"descr"], -["[Kar]aïbes","#a5cfdd", -"descr"], -["[Kar]di [Bus]","#e46398", -"descr"], -["Sparta[bus] 🐺🐒🏉","#ebdac2", -"descr"], -["Zanzo[Bus]","#FFFF", -"descr"], -["Bran[Kar]","#6da1ac", -"Si vous ne connaissez pas le Bran[Kar], c’est comme une grande famille qui fait un apéro, qui se bourre un peu la gueule en discutant des heures autour d’une table remplie de bouffe et de super bons cocktails (la plupart des barmen/barwomen du bus sont les barmans de Shakens), sauf qu’on est un bus du Wei (vous comprendrez bien le nom de notre bus en voyant l’état de certain·e·s). Il nous arrive de faire quelques conneries, mais surtout de jouer au Bière-pong en musique !"], -["Techno [kar]ade","#8065a3", -"descr"], -["[Bus]ka-P","#7c4768", -"descr"] + [ + "Magi[Kar]p", "#ef5568", + """bus faible en alcool mais fort en connerie avec une partie calme pour les amateurs de sieste et de jeux de société. + Non discriminant il accepte tout le monde y compris le plus nulle des pokémons (magicarpe !!!!!!). Malgré les + accusations mensongères, il n'y a aucun weeb dans le Magi[Kar]p""", + ], + [ + "Va[car]me", "#fd7a28", + "descr", + ], + [ + "[Kar]aïbes", "#a5cfdd", + "descr", + ], + [ + "[Kar]di [Bus]", "#e46398", + "descr", + ], + [ + "Sparta[bus] 🐺🐒🏉", "#ebdac2", + "descr", + ], + [ + "Zanzo[Bus]", "#FFFF", + "descr", + ], + [ + "Bran[Kar]", "#6da1ac", + """Si vous ne connaissez pas le Bran[Kar], c’est comme une grande famille qui fait un apéro, qui se bourre un peu la + gueule en discutant des heures autour d’une table remplie de bouffe et de super bons cocktails (la plupart des + barmen/barwomen du bus sont les barmans de Shakens), sauf qu’on est un bus du Wei (vous comprendrez bien le nom de notre + bus en voyant l’état de certain·e·s). Il nous arrive de faire quelques conneries, mais surtout de jouer au Bière-pong en + musique !""", + ], + [ + "Techno [kar]ade", "#8065a3", + "descr" + ], + [ + "[Bus]ka-P", "#7c4768", + "descr", + ], ] + def get_survey_info(id): s = {"recap": { - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 0 + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, }} - s_ = {f"bus{id}" : {f"{i}" : 0 for i in range(1,5+1)} for id in range(len(buses_descr))} + s_ = {f"bus{id}": {f"{i}": 0 for i in range(1, 5 + 1)} for id in range(len(buses_descr))} s.update(s_) - s.update({f"bus{id}" : {f"{i}" : i for i in range(1,5+1)}}) - return {"scores" : s} + s.update({f"bus{id}": {f"{i}": i for i in range(1, 5 + 1)}}) + return {"scores": s} def print_bus(id): return buses_descr[id][0] + "\n\n" + buses_descr[id][2] -def print_all_buses(): - l = [print_bus(id) for id in range(len(buses_descr))] - return "\n\n---------\n\n".join(l) -WORDS = {"recap": ["Cher(e) 1A, te voilà arrivé(e) devant un choix fatidique, le choix de ton bus....... \n (Musique effrayante) \n Peite blagounette évidemment, chacun des bus te permettra de passer un excellent WEI ! Mais quitte à avoir le choix, voici la liste de tous les bus ainsi qu'une description détaillée de ces derniers ! Prends ton temps, observe les bien et quand tu te sens prêt(e), appuye sur le bouton 'Noter les bus' pour continuer (pas besoin d'apprendre par coeur les bus, la descirption du bus te sera rappeler avant de le noter !) \n\n\n" + print_all_buses(), { - 1: "Noter les bus :" -}]} +def print_all_buses(): + liste = [print_bus(id) for id in range(len(buses_descr))] + return "

---------

".join(liste) + + +WORDS = { + "recap": + [ + """Chèr⋅e 1A, te voilà arrivé⋅e devant un choix fatidique, le choix de ton bus.......
+ (Musique effrayante)
+ Petite blagounette évidemment, chacun des bus te permettra de passer un excellent WEI ! + Mais quitte à avoir le choix, voici la liste de tous les bus ainsi qu'une description détaillée de ces derniers ! + Prends ton temps, observe les bien et quand tu te sens prêt⋅e, appuye sur le bouton 'Noter les bus' pour continuer + (pas besoin d'apprendre par cœur les bus, la description du bus te sera rappeler avant de le noter !)


""" + print_all_buses(), + { + "1": "Noter les bus :", + } + ] +} WORDS.update({ - f"bus{id}" : [print_bus(id),{i : f"Noter {i}/5" for i in range(1,5+1)}] for id in range(len(buses_descr)) + f"bus{id}": [print_bus(id), {i: f"Noter {i}/5" for i in range(1, 5 + 1)}] for id in range(len(buses_descr)) }) - class WEISurveyForm2024(forms.Form): """ Survey form for the year 2024. @@ -300,4 +335,3 @@ class WEISurveyAlgorithm2024(WEISurveyAlgorithm): if tqdm_obj is not None: tqdm_obj.n = len(surveys) - len(free_surveys) tqdm_obj.refresh() - diff --git a/apps/wei/tests/test_wei_algorithm_2023.py b/apps/wei/tests/test_wei_algorithm_2023.py index c94cbb0c..55c4a6d7 100644 --- a/apps/wei/tests/test_wei_algorithm_2023.py +++ b/apps/wei/tests/test_wei_algorithm_2023.py @@ -6,8 +6,6 @@ from datetime import date, timedelta from django.contrib.auth.models import User from django.test import TestCase -from django.urls import reverse -from note.models import NoteUser from ..forms.surveys.wei2023 import WEIBusInformation2023, WEISurvey2023, WORDS, WEISurveyInformation2023 from ..models import Bus, WEIClub, WEIRegistration @@ -127,44 +125,3 @@ class TestWEIAlgorithm(TestCase): self.assertLessEqual(max_score - score, 25) # Always less than 25 % of tolerance self.assertLessEqual(penalty / 100, 25) # Tolerance of 5 % - - def test_register_1a(self): - """ - Test register a first year member to the WEI and complete the survey - """ - response = self.client.get(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk))) - self.assertEqual(response.status_code, 200) - - user = User.objects.create(username="toto", email="toto@example.com") - NoteUser.objects.create(user=user) - response = self.client.post(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)), dict( - user=user.id, - soge_credit=True, - birth_date=date(2000, 1, 1), - gender='nonbinary', - clothing_cut='female', - clothing_size='XS', - health_issues='I am a bot', - emergency_contact_name='NoteKfet2020', - emergency_contact_phone='+33123456789', - )) - qs = WEIRegistration.objects.filter(user_id=user.id) - self.assertTrue(qs.exists()) - registration = qs.get() - self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, 200) - for question in WORDS: - # Fill 1A Survey, 20 pages - # be careful if questionnary form change (number of page, type of answer...) - response = self.client.post(reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), { - question: "1" - }) - registration.refresh_from_db() - survey = WEISurvey2023(registration) - self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, - 302 if survey.is_complete() else 200) - self.assertIsNotNone(getattr(survey.information, question), "Survey page " + question + " failed") - survey = WEISurvey2023(registration) - self.assertTrue(survey.is_complete()) - survey.select_bus(self.buses[0]) - survey.save() - self.assertIsNotNone(survey.information.get_selected_bus()) diff --git a/apps/wei/tests/test_wei_algorithm_2024.py b/apps/wei/tests/test_wei_algorithm_2024.py new file mode 100644 index 00000000..dddf8417 --- /dev/null +++ b/apps/wei/tests/test_wei_algorithm_2024.py @@ -0,0 +1,172 @@ +# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +import random +from datetime import date, timedelta + +from django.contrib.auth.models import User +from django.test import TestCase +from django.urls import reverse +from note.models import NoteUser + +from ..forms.surveys.wei2024 import WEIBusInformation2024, WEISurvey2024, WORDS, WEISurveyInformation2024 +from ..models import Bus, WEIClub, WEIRegistration + + +class TestWEIAlgorithm(TestCase): + """ + Run some tests to ensure that the WEI algorithm is working well. + """ + fixtures = ('initial',) + + def setUp(self): + """ + Create some test data, with one WEI and 10 buses with random score attributions. + """ + self.user = User.objects.create_superuser( + username="weiadmin", + password="admin", + email="admin@example.com", + ) + self.user.save() + self.client.force_login(self.user) + sess = self.client.session + sess["permission_mask"] = 42 + sess.save() + + self.wei = WEIClub.objects.create( + name="WEI 2024", + email="wei2024@example.com", + parent_club_id=2, + membership_fee_paid=12500, + membership_fee_unpaid=5500, + membership_start='2024-01-01', + membership_end='2024-12-31', + date_start=date.today() + timedelta(days=2), + date_end='2024-12-31', + year=2024, + ) + + self.buses = [] + for i in range(10): + bus = Bus.objects.create(wei=self.wei, name=f"Bus {i}", size=10) + self.buses.append(bus) + information = WEIBusInformation2024(bus) + for question in WORDS: + information.scores[question] = {answer: random.randint(1, 5) for answer in WORDS[question][1]} + information.save() + bus.save() + + def test_survey_algorithm_small(self): + """ + There are only a few people in each bus, ensure that each person has its best bus + """ + # Add a few users + for i in range(10): + user = User.objects.create(username=f"user{i}") + registration = WEIRegistration.objects.create( + user=user, + wei=self.wei, + first_year=True, + birth_date='2000-01-01', + ) + information = WEISurveyInformation2024(registration) + for question in WORDS: + options = list(WORDS[question][1].keys()) + setattr(information, question, random.choice(options)) + information.step = 20 + information.save(registration) + registration.save() + + # Run algorithm + WEISurvey2024.get_algorithm_class()().run_algorithm() + + # Ensure that everyone has its first choice + for r in WEIRegistration.objects.filter(wei=self.wei).all(): + survey = WEISurvey2024(r) + preferred_bus = survey.ordered_buses()[0][0] + chosen_bus = survey.information.get_selected_bus() + self.assertEqual(preferred_bus, chosen_bus) + + def test_survey_algorithm_full(self): + """ + Buses are full of first year people, ensure that they are happy + """ + # Add a lot of users + for i in range(95): + user = User.objects.create(username=f"user{i}") + registration = WEIRegistration.objects.create( + user=user, + wei=self.wei, + first_year=True, + birth_date='2000-01-01', + ) + information = WEISurveyInformation2024(registration) + for question in WORDS: + options = list(WORDS[question][1].keys()) + setattr(information, question, random.choice(options)) + information.step = 20 + information.save(registration) + registration.save() + + # Run algorithm + WEISurvey2024.get_algorithm_class()().run_algorithm() + + penalty = 0 + # Ensure that everyone seems to be happy + # We attribute a penalty for each user that didn't have its first choice + # The penalty is the square of the distance between the score of the preferred bus + # and the score of the attributed bus + # We consider it acceptable if the mean of this distance is lower than 5 % + for r in WEIRegistration.objects.filter(wei=self.wei).all(): + survey = WEISurvey2024(r) + chosen_bus = survey.information.get_selected_bus() + buses = survey.ordered_buses() + score = min(v for bus, v in buses if bus == chosen_bus) + max_score = buses[0][1] + penalty += (max_score - score) ** 2 + + self.assertLessEqual(max_score - score, 25) # Always less than 25 % of tolerance + + self.assertLessEqual(penalty / 100, 25) # Tolerance of 5 % + + def test_register_1a(self): + """ + Test register a first year member to the WEI and complete the survey + """ + response = self.client.get(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk))) + self.assertEqual(response.status_code, 200) + + user = User.objects.create(username="toto", email="toto@example.com") + NoteUser.objects.create(user=user) + response = self.client.post(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)), dict( + user=user.id, + soge_credit=True, + birth_date=date(2000, 1, 1), + gender='nonbinary', + clothing_cut='female', + clothing_size='XS', + health_issues='I am a bot', + emergency_contact_name='NoteKfet2020', + emergency_contact_phone='+33123456789', + )) + qs = WEIRegistration.objects.filter(user_id=user.id) + self.assertTrue(qs.exists()) + registration = qs.get() + self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, 200) + for question in WORDS: + # Fill 1A Survey, 20 pages + # be careful if questionnary form change (number of page, type of answer...) + response = self.client.post(reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), { + question: "1" + }) + registration.refresh_from_db() + survey = WEISurvey2024(registration) + self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, + 302 if survey.is_complete() else 200) + self.assertIsNotNone(getattr(survey.information, question), "Survey page " + question + " failed") + survey = WEISurvey2024(registration) + self.assertTrue(survey.is_complete()) + survey.select_bus(self.buses[0]) + survey.save() + self.assertIsNotNone(survey.information.get_selected_bus()) diff --git a/apps/wei/tests/test_wei_registration.py b/apps/wei/tests/test_wei_registration.py index 2470b48d..21da07f8 100644 --- a/apps/wei/tests/test_wei_registration.py +++ b/apps/wei/tests/test_wei_registration.py @@ -767,7 +767,7 @@ class TestDefaultWEISurvey(TestCase): WEISurvey.update_form(None, None) self.assertEqual(CurrentSurvey.get_algorithm_class().get_survey_class(), CurrentSurvey) - self.assertEqual(CurrentSurvey.get_year(), 2023) + self.assertEqual(CurrentSurvey.get_year(), 2024) class TestWeiAPI(TestAPI): From a21b9275ea1f172049b679b505f9ae1456ecd277 Mon Sep 17 00:00:00 2001 From: korenstin Date: Mon, 5 Aug 2024 11:52:47 +0200 Subject: [PATCH 3/8] Add caution_check in the validation form, #96 --- apps/wei/forms/registration.py | 6 ++++++ apps/wei/views.py | 6 ++++++ locale/fr/LC_MESSAGES/django.po | 1 + 3 files changed, 13 insertions(+) diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index c851248d..cf0579aa 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -81,6 +81,11 @@ class WEIChooseBusForm(forms.Form): class WEIMembershipForm(forms.ModelForm): + caution_check = forms.BooleanField( + required=False, + label=_("Caution check given"), + ) + roles = forms.ModelMultipleChoiceField( queryset=WEIRole.objects, label=_("WEI Roles"), @@ -149,6 +154,7 @@ class WEIMembership1AForm(WEIMembershipForm): """ Used to confirm registrations of first year members without choosing a bus now. """ + caution_check = None roles = None def clean(self): diff --git a/apps/wei/views.py b/apps/wei/views.py index 7a0e32fe..76943198 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -900,6 +900,9 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): form.fields["last_name"].initial = registration.user.last_name form.fields["first_name"].initial = registration.user.first_name + if "caution_check" in form.fields: + form.fields["caution_check"].initial = registration.caution_check + if registration.soge_credit: form.fields["credit_type"].disabled = True form.fields["credit_type"].initial = NoteSpecial.objects.get(special_type="Virement bancaire") @@ -941,6 +944,9 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): club = registration.wei user = registration.user + if "caution_check" in form.data: + registration.caution_check = form.data["caution_check"] == "on" + registration.save() membership = form.instance membership.user = user membership.club = club diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index e3edfeea..47f49d42 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -3001,6 +3001,7 @@ msgid "Credit from Société générale" msgstr "Crédit de la Société générale" #: apps/wei/models.py:188 +#: apps/wei/forms/registration.py:84 msgid "Caution check given" msgstr "Chèque de caution donné" From a201d8376a698005a0a284aed1c480b5c0da28db Mon Sep 17 00:00:00 2001 From: mcngnt Date: Mon, 26 Aug 2024 17:09:05 +0200 Subject: [PATCH 4/8] updated survey --- ...lter_opener_options_alter_opener_opener.py | 24 ++++ .../0005_alter_food_polymorphic_ctype.py | 20 +++ ...7_alter_note_polymorphic_ctype_and_more.py | 25 ++++ .../0009_alter_sogecredit_transactions.py | 19 +++ apps/wei/forms/surveys/wei2024.py | 126 ++++++++++++------ 5 files changed, 175 insertions(+), 39 deletions(-) create mode 100644 apps/activity/migrations/0005_alter_opener_options_alter_opener_opener.py create mode 100644 apps/food/migrations/0005_alter_food_polymorphic_ctype.py create mode 100644 apps/note/migrations/0007_alter_note_polymorphic_ctype_and_more.py create mode 100644 apps/treasury/migrations/0009_alter_sogecredit_transactions.py diff --git a/apps/activity/migrations/0005_alter_opener_options_alter_opener_opener.py b/apps/activity/migrations/0005_alter_opener_options_alter_opener_opener.py new file mode 100644 index 00000000..c09500e1 --- /dev/null +++ b/apps/activity/migrations/0005_alter_opener_options_alter_opener_opener.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.15 on 2024-08-28 08:00 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('note', '0006_trust'), + ('activity', '0004_opener'), + ] + + operations = [ + migrations.AlterModelOptions( + name='opener', + options={'verbose_name': 'Opener', 'verbose_name_plural': 'Openers'}, + ), + migrations.AlterField( + model_name='opener', + name='opener', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activity_responsible', to='note.note', verbose_name='Opener'), + ), + ] diff --git a/apps/food/migrations/0005_alter_food_polymorphic_ctype.py b/apps/food/migrations/0005_alter_food_polymorphic_ctype.py new file mode 100644 index 00000000..5473bffc --- /dev/null +++ b/apps/food/migrations/0005_alter_food_polymorphic_ctype.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.15 on 2024-08-28 08:00 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('food', '0004_auto_20240813_2358'), + ] + + operations = [ + migrations.AlterField( + model_name='food', + name='polymorphic_ctype', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'), + ), + ] diff --git a/apps/note/migrations/0007_alter_note_polymorphic_ctype_and_more.py b/apps/note/migrations/0007_alter_note_polymorphic_ctype_and_more.py new file mode 100644 index 00000000..8d0e8a19 --- /dev/null +++ b/apps/note/migrations/0007_alter_note_polymorphic_ctype_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.15 on 2024-08-28 08:00 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('note', '0006_trust'), + ] + + operations = [ + migrations.AlterField( + model_name='note', + name='polymorphic_ctype', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'), + ), + migrations.AlterField( + model_name='transaction', + name='polymorphic_ctype', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'), + ), + ] diff --git a/apps/treasury/migrations/0009_alter_sogecredit_transactions.py b/apps/treasury/migrations/0009_alter_sogecredit_transactions.py new file mode 100644 index 00000000..e3a46576 --- /dev/null +++ b/apps/treasury/migrations/0009_alter_sogecredit_transactions.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.15 on 2024-08-28 08:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('note', '0007_alter_note_polymorphic_ctype_and_more'), + ('treasury', '0008_auto_20240322_0045'), + ] + + operations = [ + migrations.AlterField( + model_name='sogecredit', + name='transactions', + field=models.ManyToManyField(blank=True, related_name='+', to='note.membershiptransaction', verbose_name='membership transactions'), + ), + ] diff --git a/apps/wei/forms/surveys/wei2024.py b/apps/wei/forms/surveys/wei2024.py index e37cf621..21bd8407 100644 --- a/apps/wei/forms/surveys/wei2024.py +++ b/apps/wei/forms/surveys/wei2024.py @@ -8,38 +8,50 @@ from django.db import transaction from django.db.models import Q from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation -from ...models import WEIMembership +from ...models import Bus, WEIMembership buses_descr = [ [ - "Magi[Kar]p", "#ef5568", - """bus faible en alcool mais fort en connerie avec une partie calme pour les amateurs de sieste et de jeux de société. - Non discriminant il accepte tout le monde y compris le plus nulle des pokémons (magicarpe !!!!!!). Malgré les - accusations mensongères, il n'y a aucun weeb dans le Magi[Kar]p""", + "Magi[Kar]p", "#ef5568", 0, + """Vous l'aurez compris au nom du bus, l'ambiance est aux jeux et à la culture geek ! Ici, vous trouverez une ambiance + calme avec une bonne dose d'autodérision et de second degré. Que vous ayez besoin de beaucoup dormir pour tenir la soirée + du lendemain, ou que vous souhaitiez faire nuit blanche pour jouer toute la nuit, vous pouvez nous rejoindre. Votre voix + n'y survivra peut-être pas à force de chanter. PS : les meilleurs cocktails du WEI sont chez nous, à déguster, pas à + siphoner !""", ], [ - "Va[car]me", "#fd7a28", - "descr", + "Va[car]me", "#fd7a28", 0, + """Ici c'est le bus du bruit. Que ce soit les groupes de musique du Bureau des Arts ou la fanfare, on sera là pour vous + ambiancer ! On fera en sorte que vous vous souveniez de votre WEI et de son Vacarme !""", ], [ - "[Kar]aïbes", "#a5cfdd", - "descr", + "[Kar]aïbes", "#a5cfdd", 0, + """Bus dans le thème des Caraïbes … 🐬🏴‍☠️🐬""", ], [ - "[Kar]di [Bus]", "#e46398", - "descr", + "[Kar]di [Bus]", "#e46398", 0, + """Bienvenue à bord du Kardi Bus, la seul, l’unique, l’inimitable pépite de ce weekend d’intégration ! Inspiré par les + icônes suprêmes de la pop culture telles les Bratz, les Winx et autres Mean Girls, notre bus est un sanctuaire de style, + d’audace et de pur plaisir. A nos cotés attends toi à siroter tes meilleurs Cosmo, sex on the Beach et autres cocktails + de maxi pétasse tout en papotant entre copains copines ! Si tu rejoins le Kardi Bus, tu entres dans un monde où tu + pourras te déhancher sur du Beyoncé, Britney, Aya et autres reines de la pop ! À très vite, les futures stars du Kardi + Bus !""", ], [ - "Sparta[bus] 🐺🐒🏉", "#ebdac2", - "descr", + "Sparta[bus] 🏉", "#ebdac2", 0, + """Dans notre bus, on vous donne un avant goût des plus grandes assos de l'ENS : les Kyottes et l'Aspique (clubs de rugby + féminin et masculin, mais pas que). Bien entendu, qui dit rugby dit les copaings, le pastaga et la Pena Bayona, mais vous + verrez par vous même qu'on est ouvert à toutes propositions quand il s'agit de faire la fête. Pour les casse-cou comme + pour les plus calmes, vous trouverez au bus Aspique-Kyottes les 2A+ qui vous feront kiffer votre WEI.""", ], [ - "Zanzo[Bus]", "#FFFF", - "descr", + "Zanzo[Bus]", "#FFFF", 0, + """Dans un entre-trois bien senti entre zinzinerie, enfance et vieillerie, le Zanzo[BUS] est un concentré de fun mêlé à + de la dinguerie à gogo. N'hésitez plus et rejoignez-nous pour un WEI toujours plus déjanté !""", ], [ - "Bran[Kar]", "#6da1ac", + "Bran[Kar] 🥳", "#6da1ac", 3.5, """Si vous ne connaissez pas le Bran[Kar], c’est comme une grande famille qui fait un apéro, qui se bourre un peu la gueule en discutant des heures autour d’une table remplie de bouffe et de super bons cocktails (la plupart des barmen/barwomen du bus sont les barmans de Shakens), sauf qu’on est un bus du Wei (vous comprendrez bien le nom de notre @@ -47,56 +59,92 @@ buses_descr = [ musique !""", ], [ - "Techno [kar]ade", "#8065a3", - "descr" + "Techno [kar]ade", "#8065a3", 0, + """Avis à tous·tes les gauchos, amoureux·ses de la fête et des manifs : le Techno [kar]ade vous ouvre grand ses bras pour + finir en beauté votre première inté. Préparez-vous à vous abreuver de cocktails (savamment élaborés) à la vibration d’un + système son fabriqué pour l’occasion. Des sets technos à « Mon père était tellement de gauche » en passant par « Female + Body », le car accueillant les meilleures DJs du plateau saura animer le trajet aussi bien que les soirées. Si alcool et + musique seront au rendez-vous, les maîtres mots sont sécurité et inclusivité. Qui que vous soyez et quelle que soit votre + manière de vous amuser, notre objectif est que vous vous sentiez à l’aise pour rencontrer au mieux les 1A, les 2A et les + (nombreux) 3A+ qui auront répondu à l’appel. Bref, rejoignez-nous, on est super cools :)""" ], [ - "[Bus]ka-P", "#7c4768", - "descr", + "[Bus]ka-P", "#7c4768", 0, + """Booska-p, c’est le « site N°1 du Rap français ». Le [Bus]ka-p ? Le bus N°1 sur l’ambiance au WEI. Les nuits vont être + courtes, les cocktails vont couler à flots : tout sera réuni pour vivre un week-end dont tu te souviendras toute ta vie. + Au programme pas un seul temps mort et un maximum de rencontres pour bien commencer ta première année à l’ENS. Et bien + entendu, le tout accompagné des meilleurs sons, de Jul à Aya, en passant par ABBA et Sexion d’Assaut. Bref, si tu veux + vivre un WEI d’anthologie et faire la fête, de jour comme de nuit, nous t’accueillons avec plaisir !""", ], ] -def get_survey_info(id): +def get_survey_info(i): s = {"recap": { "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 0, }} - s_ = {f"bus{id}": {f"{i}": 0 for i in range(1, 5 + 1)} for id in range(len(buses_descr))} + s_ = {f"bus{i}": {f"{i}": 0 for i in range(1, 5 + 1)} for i in range(len(buses_descr))} s.update(s_) - s.update({f"bus{id}": {f"{i}": i for i in range(1, 5 + 1)}}) + s.update({f"bus{i}": {f"{join}": join for join in range(1, 5 + 1)}}) return {"scores": s} -def print_bus(id): - return buses_descr[id][0] + "\n\n" + buses_descr[id][2] +def print_bus(i): + return f"""

{buses_descr[i][0]}


+ Alcoolomètre : {buses_descr[i][2]} / 5 🍻

{buses_descr[i][3]}
""" def print_all_buses(): - liste = [print_bus(id) for id in range(len(buses_descr))] - return "

---------

".join(liste) + liste = [print_bus(i) for i in range(len(buses_descr))] + return "



".join(liste) + + +def populate_buses(self): + self.buses = [] + for i in range(len(buses_descr)): + bus = Bus.objects.create(wei=self.wei, name=f"{buses_descr[i][0]}", size=50) + self.buses.append(bus) + information = WEIBusInformation2024(bus) + information.scores = get_survey_info(i) + information.save() + bus.save() + + +def get_number_comment(i): + match i: + case 1: + return "Même pas en rêve" + case 2: + return "Pas envie" + case 3: + return "Mouais..." + case 4: + return "Pourquoi pas !" + case 5: + return "Ce bus ou rien !!!" + case _: + return "" WORDS = { "recap": [ - """Chèr⋅e 1A, te voilà arrivé⋅e devant un choix fatidique, le choix de ton bus.......
- (Musique effrayante)
- Petite blagounette évidemment, chacun des bus te permettra de passer un excellent WEI ! - Mais quitte à avoir le choix, voici la liste de tous les bus ainsi qu'une description détaillée de ces derniers ! - Prends ton temps, observe les bien et quand tu te sens prêt⋅e, appuye sur le bouton 'Noter les bus' pour continuer - (pas besoin d'apprendre par cœur les bus, la description du bus te sera rappeler avant de le noter !)


""" + print_all_buses(), + """Chèr⋅e 1A, te voilà arrivé⋅e au moment fatidique du choix de ton bus !


+ Ton bus est constitué des gens avec qui tu passeras la majorité de ton temps : que ce soit le voyage d'aller et de + retour et les différentes activité qu'ils pourront te proposer tout au long du WEI donc choisis le bien ! +

Tu trouveras ci-dessous la liste de tous les bus ainsi qu'une description détaillée de ces derniers. + Prends ton temps pour étudier chacun d'eux et quand tu te sens prêt⋅e, appuye sur le bouton 'J'ai pris conaissance + des bus' pour continuer +
(pas besoin d'apprendre par cœur chaque bus, la description de chaque bus te sera rappeler avant de lui attribuer + une note !)



""" + print_all_buses(), { - "1": "Noter les bus :", + "1": "J'ai pris conaissance des différents bus et me sent fin prêt à choisir celui qui me convient le mieux !", } ] } WORDS.update({ - f"bus{id}": [print_bus(id), {i: f"Noter {i}/5" for i in range(1, 5 + 1)}] for id in range(len(buses_descr)) + f"bus{id}": [print_bus(id), {i: f"{get_number_comment(i)} ({i}/5)" for i in range(1, 5 + 1)}] for id in range(len(buses_descr)) }) From 946674f59b3fe2005ba32acdb5eb50ca7eb3708a Mon Sep 17 00:00:00 2001 From: korenstin Date: Wed, 28 Aug 2024 11:11:32 +0200 Subject: [PATCH 5/8] inclusif, avoids python3.10 syntax --- apps/wei/forms/surveys/wei2024.py | 64 ++++++++--------------- apps/wei/tests/test_wei_algorithm_2024.py | 2 +- 2 files changed, 22 insertions(+), 44 deletions(-) diff --git a/apps/wei/forms/surveys/wei2024.py b/apps/wei/forms/surveys/wei2024.py index 21bd8407..891eb7ff 100644 --- a/apps/wei/forms/surveys/wei2024.py +++ b/apps/wei/forms/surveys/wei2024.py @@ -8,7 +8,7 @@ from django.db import transaction from django.db.models import Q from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation -from ...models import Bus, WEIMembership +from ...models import WEIMembership buses_descr = [ @@ -18,7 +18,7 @@ buses_descr = [ calme avec une bonne dose d'autodérision et de second degré. Que vous ayez besoin de beaucoup dormir pour tenir la soirée du lendemain, ou que vous souhaitiez faire nuit blanche pour jouer toute la nuit, vous pouvez nous rejoindre. Votre voix n'y survivra peut-être pas à force de chanter. PS : les meilleurs cocktails du WEI sont chez nous, à déguster, pas à - siphoner !""", + siphonner !""", ], [ "Va[car]me", "#fd7a28", 0, @@ -42,7 +42,7 @@ buses_descr = [ "Sparta[bus] 🏉", "#ebdac2", 0, """Dans notre bus, on vous donne un avant goût des plus grandes assos de l'ENS : les Kyottes et l'Aspique (clubs de rugby féminin et masculin, mais pas que). Bien entendu, qui dit rugby dit les copaings, le pastaga et la Pena Bayona, mais vous - verrez par vous même qu'on est ouvert à toutes propositions quand il s'agit de faire la fête. Pour les casse-cou comme + verrez par vous même qu'on est ouvert⋅e à toutes propositions quand il s'agit de faire la fête. Pour les casse-cous comme pour les plus calmes, vous trouverez au bus Aspique-Kyottes les 2A+ qui vous feront kiffer votre WEI.""", ], [ @@ -66,7 +66,7 @@ buses_descr = [ Body », le car accueillant les meilleures DJs du plateau saura animer le trajet aussi bien que les soirées. Si alcool et musique seront au rendez-vous, les maîtres mots sont sécurité et inclusivité. Qui que vous soyez et quelle que soit votre manière de vous amuser, notre objectif est que vous vous sentiez à l’aise pour rencontrer au mieux les 1A, les 2A et les - (nombreux) 3A+ qui auront répondu à l’appel. Bref, rejoignez-nous, on est super cools :)""" + (nombreux⋅ses) 3A+ qui auront répondu à l’appel. Bref, rejoignez-nous, on est super cools :)""" ], [ "[Bus]ka-P", "#7c4768", 0, @@ -79,16 +79,6 @@ buses_descr = [ ] -def get_survey_info(i): - s = {"recap": { - "1": 0, - }} - s_ = {f"bus{i}": {f"{i}": 0 for i in range(1, 5 + 1)} for i in range(len(buses_descr))} - s.update(s_) - s.update({f"bus{i}": {f"{join}": join for join in range(1, 5 + 1)}}) - return {"scores": s} - - def print_bus(i): return f"""

{buses_descr[i][0]}


Alcoolomètre : {buses_descr[i][2]} / 5 🍻

{buses_descr[i][3]}
""" @@ -99,31 +89,19 @@ def print_all_buses(): return "



".join(liste) -def populate_buses(self): - self.buses = [] - for i in range(len(buses_descr)): - bus = Bus.objects.create(wei=self.wei, name=f"{buses_descr[i][0]}", size=50) - self.buses.append(bus) - information = WEIBusInformation2024(bus) - information.scores = get_survey_info(i) - information.save() - bus.save() - - def get_number_comment(i): - match i: - case 1: - return "Même pas en rêve" - case 2: - return "Pas envie" - case 3: - return "Mouais..." - case 4: - return "Pourquoi pas !" - case 5: - return "Ce bus ou rien !!!" - case _: - return "" + if i == 1: + return "Même pas en rêve" + elif i == 2: + return "Pas envie" + elif i == 3: + return "Mouais..." + elif i == 4: + return "Pourquoi pas !" + elif i == 5: + return "Ce bus ou rien !!!" + else: + return "" WORDS = { @@ -133,12 +111,12 @@ WORDS = { Ton bus est constitué des gens avec qui tu passeras la majorité de ton temps : que ce soit le voyage d'aller et de retour et les différentes activité qu'ils pourront te proposer tout au long du WEI donc choisis le bien !

Tu trouveras ci-dessous la liste de tous les bus ainsi qu'une description détaillée de ces derniers. - Prends ton temps pour étudier chacun d'eux et quand tu te sens prêt⋅e, appuye sur le bouton 'J'ai pris conaissance - des bus' pour continuer + Prends ton temps pour étudier chacun d'eux et quand tu te sens prêt⋅e, appuie sur le bouton « J'ai pris connaissance + des bus » pour continuer
(pas besoin d'apprendre par cœur chaque bus, la description de chaque bus te sera rappeler avant de lui attribuer - une note !)


""" + print_all_buses(), + une note !)


""" + print_all_buses(), { - "1": "J'ai pris conaissance des différents bus et me sent fin prêt à choisir celui qui me convient le mieux !", + "1": "J'ai pris connaissance des différents bus et me sent fin prêt à choisir celui qui me convient le mieux !", } ] } @@ -151,7 +129,7 @@ WORDS.update({ class WEISurveyForm2024(forms.Form): """ Survey form for the year 2024. - Members answer 20 questions, from which we calculate the best associated bus. + Members answer 10 questions, from which we calculate the best associated bus. """ def set_registration(self, registration): """ diff --git a/apps/wei/tests/test_wei_algorithm_2024.py b/apps/wei/tests/test_wei_algorithm_2024.py index dddf8417..a22b0a3a 100644 --- a/apps/wei/tests/test_wei_algorithm_2024.py +++ b/apps/wei/tests/test_wei_algorithm_2024.py @@ -155,7 +155,7 @@ class TestWEIAlgorithm(TestCase): registration = qs.get() self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, 200) for question in WORDS: - # Fill 1A Survey, 20 pages + # Fill 1A Survey, 10 pages # be careful if questionnary form change (number of page, type of answer...) response = self.client.post(reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), { question: "1" From 96539d262f262aae4cfa92717b92134412ae25cd Mon Sep 17 00:00:00 2001 From: mcngnt Date: Thu, 29 Aug 2024 00:05:44 +0200 Subject: [PATCH 6/8] working html for survey + fixed json error + added specific diet text field --- apps/wei/forms/surveys/wei2024.py | 38 +++++++++++++++---- .../0009_weiregistration_specific_diet.py | 18 +++++++++ apps/wei/models.py | 6 +++ apps/wei/templates/wei/1A_list.html | 2 +- apps/wei/templates/wei/attribute_bus_1A.html | 3 ++ .../wei/templates/wei/weimembership_form.html | 3 ++ 6 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 apps/wei/migrations/0009_weiregistration_specific_diet.py diff --git a/apps/wei/forms/surveys/wei2024.py b/apps/wei/forms/surveys/wei2024.py index 891eb7ff..5e2d2b60 100644 --- a/apps/wei/forms/surveys/wei2024.py +++ b/apps/wei/forms/surveys/wei2024.py @@ -1,9 +1,12 @@ # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +import json + from functools import lru_cache from django import forms +from django.utils.safestring import mark_safe from django.db import transaction from django.db.models import Q @@ -13,7 +16,7 @@ from ...models import WEIMembership buses_descr = [ [ - "Magi[Kar]p", "#ef5568", 0, + "Magi[Kar]p 🐙🎮🎲", "#ef5568", 1, """Vous l'aurez compris au nom du bus, l'ambiance est aux jeux et à la culture geek ! Ici, vous trouverez une ambiance calme avec une bonne dose d'autodérision et de second degré. Que vous ayez besoin de beaucoup dormir pour tenir la soirée du lendemain, ou que vous souhaitiez faire nuit blanche pour jouer toute la nuit, vous pouvez nous rejoindre. Votre voix @@ -21,13 +24,13 @@ buses_descr = [ siphonner !""", ], [ - "Va[car]me", "#fd7a28", 0, + "Va[car]me 🎷🍎🔊", "#fd7a28", 3, """Ici c'est le bus du bruit. Que ce soit les groupes de musique du Bureau des Arts ou la fanfare, on sera là pour vous ambiancer ! On fera en sorte que vous vous souveniez de votre WEI et de son Vacarme !""", ], [ "[Kar]aïbes", "#a5cfdd", 0, - """Bus dans le thème des Caraïbes … 🐬🏴‍☠️🐬""", + """Ahoy, explorateurs du WEI ! Le bus Karaibes t’invite à une traversée sous les tropiques, où l’ambiance est toujours au beau fixe ! ☀️🍹 Ici, c’est soleil, rhum, et bonne humeur assurée : une atmosphère de vacances où l’on se laisse porter par la chaleur humaine et la fête. Que tu sois un pirate en quête de sensations fortes ou un amateur de chill avec un cocktail à la main, tu seras à ta place dans notre bus. Les soirées seront marquées par des rythmes tropicaux qui te feront vibrer jusqu’à l’aube. Prêt à embarquer pour une aventure inoubliable avec les meilleurs matelots du WEI ? On t’attend sur le pont du Karaibes pour lever l’ancre ensemble !""", ], [ "[Kar]di [Bus]", "#e46398", 0, @@ -39,19 +42,19 @@ buses_descr = [ Bus !""", ], [ - "Sparta[bus] 🏉", "#ebdac2", 0, + "Sparta[bus] 🐺🐒🏉", "#ebdac2", 0, """Dans notre bus, on vous donne un avant goût des plus grandes assos de l'ENS : les Kyottes et l'Aspique (clubs de rugby féminin et masculin, mais pas que). Bien entendu, qui dit rugby dit les copaings, le pastaga et la Pena Bayona, mais vous verrez par vous même qu'on est ouvert⋅e à toutes propositions quand il s'agit de faire la fête. Pour les casse-cous comme pour les plus calmes, vous trouverez au bus Aspique-Kyottes les 2A+ qui vous feront kiffer votre WEI.""", ], [ - "Zanzo[Bus]", "#FFFF", 0, + "Zanzo[Bus] 🤩👽🐔", "#FFFF", 3, """Dans un entre-trois bien senti entre zinzinerie, enfance et vieillerie, le Zanzo[BUS] est un concentré de fun mêlé à de la dinguerie à gogo. N'hésitez plus et rejoignez-nous pour un WEI toujours plus déjanté !""", ], [ - "Bran[Kar] 🥳", "#6da1ac", 3.5, + "Bran[Kar] 🍹🥳", "#6da1ac", 4, """Si vous ne connaissez pas le Bran[Kar], c’est comme une grande famille qui fait un apéro, qui se bourre un peu la gueule en discutant des heures autour d’une table remplie de bouffe et de super bons cocktails (la plupart des barmen/barwomen du bus sont les barmans de Shakens), sauf qu’on est un bus du Wei (vous comprendrez bien le nom de notre @@ -79,12 +82,30 @@ buses_descr = [ ] +def print_survey_info(i): + s = {"recap": { + "1": 0, + }} + s_ = {f"bus{i}": {f"{i}": 0 for i in range(1, 5 + 1)} for i in range(len(buses_descr))} + s.update(s_) + s.update({f"bus{i}": {f"{join}": join for join in range(1, 5 + 1)}}) + s = {"scores": s} + json_str = json.dumps(s) + print(json_str) + + + + def print_bus(i): return f"""

{buses_descr[i][0]}


Alcoolomètre : {buses_descr[i][2]} / 5 🍻

{buses_descr[i][3]}
""" + + def print_all_buses(): + # for i in range(len(buses_descr)): + # print_survey_info(i) liste = [print_bus(i) for i in range(len(buses_descr))] return "



".join(liste) @@ -129,7 +150,7 @@ WORDS.update({ class WEISurveyForm2024(forms.Form): """ Survey form for the year 2024. - Members answer 10 questions, from which we calculate the best associated bus. + Members score the different buses, from which we calculate the best associated bus. """ def set_registration(self, registration): """ @@ -139,7 +160,7 @@ class WEISurveyForm2024(forms.Form): question = information.questions[information.step] self.fields[question] = forms.ChoiceField( - label=WORDS[question][0], + label=mark_safe(WORDS[question][0]), widget=forms.RadioSelect(), ) answers = [(answer, WORDS[question][1][answer]) for answer in WORDS[question][1]] @@ -224,6 +245,7 @@ class WEISurvey2024(WEISurvey): raise ValueError("Survey is not ended, can't calculate score") bus_info = self.get_algorithm_class().get_bus_information(bus) + print(bus_info) # Score is the given score by the bus subtracted to the mid-score of the buses. s = 0 for question in WORDS: diff --git a/apps/wei/migrations/0009_weiregistration_specific_diet.py b/apps/wei/migrations/0009_weiregistration_specific_diet.py new file mode 100644 index 00000000..8fa0f82a --- /dev/null +++ b/apps/wei/migrations/0009_weiregistration_specific_diet.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.15 on 2024-08-28 20:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wei', '0008_auto_20240111_1545'), + ] + + operations = [ + migrations.AddField( + model_name='weiregistration', + name='specific_diet', + field=models.TextField(blank=True, default='', verbose_name='specific diet'), + ), + ] diff --git a/apps/wei/models.py b/apps/wei/models.py index 76fd465d..80584268 100644 --- a/apps/wei/models.py +++ b/apps/wei/models.py @@ -232,6 +232,12 @@ class WEIRegistration(models.Model): verbose_name=_("health issues"), ) + specific_diet = models.TextField( + blank=True, + default="", + verbose_name=_("specific diet"), + ) + emergency_contact_name = models.CharField( max_length=255, verbose_name=_("emergency contact name"), diff --git a/apps/wei/templates/wei/1A_list.html b/apps/wei/templates/wei/1A_list.html index d9b82937..9229fa22 100644 --- a/apps/wei/templates/wei/1A_list.html +++ b/apps/wei/templates/wei/1A_list.html @@ -12,7 +12,7 @@
{% render_table bus_repartition_table %}
- {% trans "Start attribution!" %} + {% trans "Start attribution !" %}
{% render_table table %}
diff --git a/apps/wei/templates/wei/attribute_bus_1A.html b/apps/wei/templates/wei/attribute_bus_1A.html index 3305981b..89dc692b 100644 --- a/apps/wei/templates/wei/attribute_bus_1A.html +++ b/apps/wei/templates/wei/attribute_bus_1A.html @@ -28,6 +28,9 @@
{% trans 'health issues'|capfirst %}
{{ object.health_issues|default:"—" }}
+
{% trans 'specific diet'|capfirst %}
+
{{ object.specific_diet|default:"—" }}
+
{% trans 'suggested bus'|capfirst %}
{{ survey.information.selected_bus_name }}
diff --git a/apps/wei/templates/wei/weimembership_form.html b/apps/wei/templates/wei/weimembership_form.html index 017b0dd7..ec6ebed4 100644 --- a/apps/wei/templates/wei/weimembership_form.html +++ b/apps/wei/templates/wei/weimembership_form.html @@ -67,6 +67,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% trans 'health issues'|capfirst %}
{{ registration.health_issues }}
+
{% trans 'specific diet'|capfirst %}
+
{{ registration.specific_diet }}
+
{% trans 'emergency contact name'|capfirst %}
{{ registration.emergency_contact_name }}
From 73ff35c232ef248618c294d33f3a5e0fcf8f120d Mon Sep 17 00:00:00 2001 From: mcngnt Date: Thu, 29 Aug 2024 12:42:26 +0200 Subject: [PATCH 7/8] updated bus descr --- apps/wei/forms/surveys/wei2024.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/wei/forms/surveys/wei2024.py b/apps/wei/forms/surveys/wei2024.py index 5e2d2b60..cf0659d2 100644 --- a/apps/wei/forms/surveys/wei2024.py +++ b/apps/wei/forms/surveys/wei2024.py @@ -25,15 +25,14 @@ buses_descr = [ ], [ "Va[car]me 🎷🍎🔊", "#fd7a28", 3, - """Ici c'est le bus du bruit. Que ce soit les groupes de musique du Bureau des Arts ou la fanfare, on sera là pour vous - ambiancer ! On fera en sorte que vous vous souveniez de votre WEI et de son Vacarme !""", + """Ici, c'est le bus du bruit. Si vous voulez réveiller les autres bus en musique, apprendre de merveilleuses mélodies au kazoo tout le week-end, ou simplement profiter d'une bonne ambiance musicale, le BDA et la F[ENS]foire sont là pour vous. Vous pourrez également goûter au célèbre cocktail de la fanfare, concocté pour l'occasion par les tout nouveaux "meilleurs artisans v*********** de France" ! Alors que vous soyez artiste dans l'âme ou que vous souhaitiez juste faire le plus grand Vacarme, rejoignez-nous !""", ], [ - "[Kar]aïbes", "#a5cfdd", 0, + "[Kar]aïbes 🏝️🏴‍☠️🥥", "#a5cfdd", 3, """Ahoy, explorateurs du WEI ! Le bus Karaibes t’invite à une traversée sous les tropiques, où l’ambiance est toujours au beau fixe ! ☀️🍹 Ici, c’est soleil, rhum, et bonne humeur assurée : une atmosphère de vacances où l’on se laisse porter par la chaleur humaine et la fête. Que tu sois un pirate en quête de sensations fortes ou un amateur de chill avec un cocktail à la main, tu seras à ta place dans notre bus. Les soirées seront marquées par des rythmes tropicaux qui te feront vibrer jusqu’à l’aube. Prêt à embarquer pour une aventure inoubliable avec les meilleurs matelots du WEI ? On t’attend sur le pont du Karaibes pour lever l’ancre ensemble !""", ], [ - "[Kar]di [Bus]", "#e46398", 0, + "[Kar]di [Bus] 🎙️💅", "#e46398", 2.5, """Bienvenue à bord du Kardi Bus, la seul, l’unique, l’inimitable pépite de ce weekend d’intégration ! Inspiré par les icônes suprêmes de la pop culture telles les Bratz, les Winx et autres Mean Girls, notre bus est un sanctuaire de style, d’audace et de pur plaisir. A nos cotés attends toi à siroter tes meilleurs Cosmo, sex on the Beach et autres cocktails @@ -42,7 +41,7 @@ buses_descr = [ Bus !""", ], [ - "Sparta[bus] 🐺🐒🏉", "#ebdac2", 0, + "Sparta[bus] 🐺🐒🏉", "#ebdac2", 5, """Dans notre bus, on vous donne un avant goût des plus grandes assos de l'ENS : les Kyottes et l'Aspique (clubs de rugby féminin et masculin, mais pas que). Bien entendu, qui dit rugby dit les copaings, le pastaga et la Pena Bayona, mais vous verrez par vous même qu'on est ouvert⋅e à toutes propositions quand il s'agit de faire la fête. Pour les casse-cous comme @@ -62,7 +61,7 @@ buses_descr = [ musique !""", ], [ - "Techno [kar]ade", "#8065a3", 0, + "Techno [kar]ade 🔊🚩", "#8065a3", 3, """Avis à tous·tes les gauchos, amoureux·ses de la fête et des manifs : le Techno [kar]ade vous ouvre grand ses bras pour finir en beauté votre première inté. Préparez-vous à vous abreuver de cocktails (savamment élaborés) à la vibration d’un système son fabriqué pour l’occasion. Des sets technos à « Mon père était tellement de gauche » en passant par « Female @@ -72,7 +71,7 @@ buses_descr = [ (nombreux⋅ses) 3A+ qui auront répondu à l’appel. Bref, rejoignez-nous, on est super cools :)""" ], [ - "[Bus]ka-P", "#7c4768", 0, + "[Bus]ka-P 🥇🍻🎤", "#7c4768", 4.5, """Booska-p, c’est le « site N°1 du Rap français ». Le [Bus]ka-p ? Le bus N°1 sur l’ambiance au WEI. Les nuits vont être courtes, les cocktails vont couler à flots : tout sera réuni pour vivre un week-end dont tu te souviendras toute ta vie. Au programme pas un seul temps mort et un maximum de rencontres pour bien commencer ta première année à l’ENS. Et bien @@ -245,7 +244,6 @@ class WEISurvey2024(WEISurvey): raise ValueError("Survey is not ended, can't calculate score") bus_info = self.get_algorithm_class().get_bus_information(bus) - print(bus_info) # Score is the given score by the bus subtracted to the mid-score of the buses. s = 0 for question in WORDS: From f50849b4f8040a437f94d49054371d8d4ee8cb18 Mon Sep 17 00:00:00 2001 From: mcngnt Date: Thu, 29 Aug 2024 14:01:55 +0200 Subject: [PATCH 8/8] delete print --- apps/wei/forms/surveys/wei2024.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/apps/wei/forms/surveys/wei2024.py b/apps/wei/forms/surveys/wei2024.py index cf0659d2..ef3f37f9 100644 --- a/apps/wei/forms/surveys/wei2024.py +++ b/apps/wei/forms/surveys/wei2024.py @@ -81,30 +81,13 @@ buses_descr = [ ] -def print_survey_info(i): - s = {"recap": { - "1": 0, - }} - s_ = {f"bus{i}": {f"{i}": 0 for i in range(1, 5 + 1)} for i in range(len(buses_descr))} - s.update(s_) - s.update({f"bus{i}": {f"{join}": join for join in range(1, 5 + 1)}}) - s = {"scores": s} - json_str = json.dumps(s) - print(json_str) - - - def print_bus(i): return f"""

{buses_descr[i][0]}


Alcoolomètre : {buses_descr[i][2]} / 5 🍻

{buses_descr[i][3]}
""" - - def print_all_buses(): - # for i in range(len(buses_descr)): - # print_survey_info(i) liste = [print_bus(i) for i in range(len(buses_descr))] return "



".join(liste)