mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-10-30 23:39:54 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			245 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			245 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
 | |
| # SPDX-License-Identifier: GPL-3.0-or-later
 | |
| 
 | |
| from typing import Optional, List
 | |
| 
 | |
| from django.db.models import QuerySet
 | |
| from django.forms import Form
 | |
| 
 | |
| from ...models import WEIClub, WEIRegistration, Bus, WEIMembership
 | |
| 
 | |
| 
 | |
| class WEISurveyInformation:
 | |
|     """
 | |
|     Abstract data of the survey.
 | |
|     """
 | |
|     valid = False
 | |
|     selected_bus_pk = None
 | |
|     selected_bus_name = None
 | |
| 
 | |
|     def __init__(self, registration):
 | |
|         self.__dict__.update(registration.information)
 | |
| 
 | |
|     def get_selected_bus(self) -> Optional[Bus]:
 | |
|         """
 | |
|         If the algorithm ran, return the prefered bus according to the survey.
 | |
|         In the other case, return None.
 | |
|         """
 | |
|         return Bus.objects.get(pk=self.selected_bus_pk) if self.valid else None
 | |
| 
 | |
|     def save(self, registration) -> None:
 | |
|         """
 | |
|         Store the data of the survey into the database, with the information of the registration.
 | |
|         """
 | |
|         registration.information = self.__dict__
 | |
| 
 | |
| 
 | |
| class WEIBusInformation:
 | |
|     """
 | |
|     Abstract data of the bus.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, bus: Bus):
 | |
|         self.__dict__.update(bus.information)
 | |
|         self.bus = bus
 | |
|         self.save()
 | |
| 
 | |
|     def save(self):
 | |
|         d = self.__dict__.copy()
 | |
|         d.pop("bus")
 | |
|         self.bus.information = d
 | |
|         self.bus.save()
 | |
| 
 | |
|     def free_seats(self, surveys: List["WEISurvey"] = None, quotas=None):
 | |
|         if not quotas:
 | |
|             size = self.bus.size
 | |
|             already_occupied = WEIMembership.objects.filter(bus=self.bus).count()
 | |
|             quotas = {self.bus: size - already_occupied}
 | |
| 
 | |
|         quota = quotas[self.bus]
 | |
|         valid_surveys = sum(1 for survey in surveys if survey.information.valid
 | |
|                             and survey.information.get_selected_bus() == self.bus) if surveys else 0
 | |
|         return quota - valid_surveys
 | |
| 
 | |
|     def has_free_seats(self, surveys=None, quotas=None):
 | |
|         return self.free_seats(surveys, quotas) > 0
 | |
| 
 | |
| 
 | |
| class WEISurveyAlgorithm:
 | |
|     """
 | |
|     Abstract algorithm that attributes a bus to each new member.
 | |
|     """
 | |
| 
 | |
|     @classmethod
 | |
|     def get_survey_class(cls):
 | |
|         """
 | |
|         The class of the survey associated with this algorithm.
 | |
|         """
 | |
|         raise NotImplementedError
 | |
| 
 | |
|     @classmethod
 | |
|     def get_bus_information_class(cls):
 | |
|         """
 | |
|         The class of the information associated to a bus extending WEIBusInformation.
 | |
|         Default: WEIBusInformation (contains nothing)
 | |
|         """
 | |
|         return WEIBusInformation
 | |
| 
 | |
|     @classmethod
 | |
|     def get_registrations(cls) -> QuerySet:
 | |
|         """
 | |
|         Queryset of all first year registrations
 | |
|         """
 | |
|         if not hasattr(cls, '_registrations'):
 | |
|             cls._registrations = WEIRegistration.objects.filter(wei__year=cls.get_survey_class().get_year(),
 | |
|                                                                 first_year=True).all()
 | |
| 
 | |
|         return cls._registrations
 | |
| 
 | |
|     @classmethod
 | |
|     def get_buses(cls) -> QuerySet:
 | |
|         """
 | |
|         Queryset of all buses of the associated wei.
 | |
|         """
 | |
|         if not hasattr(cls, '_buses'):
 | |
|             cls._buses = Bus.objects.filter(wei__year=cls.get_survey_class().get_year(), size__gt=0).all()
 | |
|         return cls._buses
 | |
| 
 | |
|     @classmethod
 | |
|     def get_bus_information(cls, bus):
 | |
|         """
 | |
|         Return the WEIBusInformation object containing the data stored in a given bus.
 | |
|         """
 | |
|         return cls.get_bus_information_class()(bus)
 | |
| 
 | |
|     def run_algorithm(self) -> None:
 | |
|         """
 | |
|         Once this method implemented, run the algorithm that attributes a bus to each first year member.
 | |
|         This method can be run in command line through ``python manage.py wei_algorithm``
 | |
|         See ``wei.management.commmands.wei_algorithm``
 | |
|         This method must call Survey.select_bus for each survey.
 | |
|         """
 | |
|         raise NotImplementedError
 | |
| 
 | |
|     @classmethod
 | |
|     def get_bus_information_form(cls):
 | |
|         """
 | |
|         The class of the form to update the bus information.
 | |
|         """
 | |
|         raise NotImplementedError
 | |
| 
 | |
| 
 | |
| class WEISurvey:
 | |
|     """
 | |
|     Survey associated to a first year WEI registration.
 | |
|     The data is stored into WEISurveyInformation, this class acts as a manager.
 | |
|     This is an abstract class: this has to be extended each year to implement custom methods.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, registration: WEIRegistration):
 | |
|         self.registration = registration
 | |
|         self.information = self.get_survey_information_class()(registration)
 | |
| 
 | |
|     @classmethod
 | |
|     def get_year(cls) -> int:
 | |
|         """
 | |
|         Get year of the wei concerned by the type of the survey.
 | |
|         """
 | |
|         raise NotImplementedError
 | |
| 
 | |
|     @classmethod
 | |
|     def get_wei(cls) -> WEIClub:
 | |
|         """
 | |
|         The WEI associated to this kind of survey.
 | |
|         """
 | |
|         if not hasattr(cls, '_wei'):
 | |
|             cls._wei = WEIClub.objects.get(year=cls.get_year())
 | |
| 
 | |
|         return cls._wei
 | |
| 
 | |
|     @classmethod
 | |
|     def get_survey_information_class(cls):
 | |
|         """
 | |
|         The class of the data (extending WEISurveyInformation).
 | |
|         """
 | |
|         raise NotImplementedError
 | |
| 
 | |
|     def get_form_class(self) -> Form:
 | |
|         """
 | |
|         The form class of the survey.
 | |
|         This is proper to the status of the survey: the form class can evolve according to the progress of the survey.
 | |
|         """
 | |
|         raise NotImplementedError
 | |
| 
 | |
|     def update_form(self, form) -> None:
 | |
|         """
 | |
|         Once the form is instanciated, the information can be updated with the information of the registration
 | |
|         and the information of the survey.
 | |
|         This method is called once the form is created.
 | |
|         """
 | |
|         pass
 | |
| 
 | |
|     def form_valid(self, form) -> None:
 | |
|         """
 | |
|         Called when the information of the form are validated.
 | |
|         This method should update the information of the survey.
 | |
|         """
 | |
|         raise NotImplementedError
 | |
| 
 | |
|     def is_complete(self) -> bool:
 | |
|         """
 | |
|         Return True if the survey is complete.
 | |
|         If the survey is complete, then the button "Next" will display some text for the end of the survey.
 | |
|         If not, the survey is reloaded and continues.
 | |
|         """
 | |
|         raise NotImplementedError
 | |
| 
 | |
|     def save(self) -> None:
 | |
|         """
 | |
|         Store the information of the survey into the database.
 | |
|         """
 | |
|         self.information.save(self.registration)
 | |
|         # The information is forced-saved.
 | |
|         # We don't want that anyone can update manually the information, so since most users don't have the
 | |
|         # right to save the information of a registration, we force save.
 | |
|         self.registration._force_save = True
 | |
|         self.registration.save()
 | |
| 
 | |
|     @classmethod
 | |
|     def get_algorithm_class(cls):
 | |
|         """
 | |
|         Algorithm class associated to the survey.
 | |
|         The algorithm, extending WEISurveyAlgorithm, should associate a bus to each first year member.
 | |
|         The association is not permanent: that's only a suggestion.
 | |
|         """
 | |
|         raise NotImplementedError
 | |
| 
 | |
|     def select_bus(self, bus) -> None:
 | |
|         """
 | |
|         Set the suggestion into the data of the membership.
 | |
|         :param bus: The bus suggested.
 | |
|         """
 | |
|         self.information.selected_bus_pk = bus.pk
 | |
|         self.information.selected_bus_name = bus.name
 | |
|         self.information.valid = True
 | |
| 
 | |
|     def free(self) -> None:
 | |
|         """
 | |
|         Unselect the select bus.
 | |
|         """
 | |
|         self.information.selected_bus_pk = None
 | |
|         self.information.selected_bus_name = None
 | |
|         self.information.valid = False
 | |
| 
 | |
|     @classmethod
 | |
|     def clear_cache(cls):
 | |
|         """
 | |
|         Clear stored information.
 | |
|         """
 | |
|         if hasattr(cls, '_wei'):
 | |
|             del cls._wei
 | |
|         if hasattr(cls.get_algorithm_class(), '_registrations'):
 | |
|             del cls.get_algorithm_class()._registrations
 | |
|         if hasattr(cls.get_algorithm_class(), '_buses'):
 | |
|             del cls.get_algorithm_class()._buses
 |