mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-11-04 01:12:08 +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
 |