2021-06-14 19:45:36 +00:00
|
|
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
2020-04-19 18:35:49 +00:00
|
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
2021-08-27 08:33:41 +00:00
|
|
|
from typing import Optional, List
|
2020-04-19 18:35:49 +00:00
|
|
|
|
2020-04-19 23:26:53 +00:00
|
|
|
from django.db.models import QuerySet
|
|
|
|
from django.forms import Form
|
2020-04-19 18:35:49 +00:00
|
|
|
|
2021-08-27 08:33:41 +00:00
|
|
|
from ...models import WEIClub, WEIRegistration, Bus, WEIMembership
|
2020-04-19 18:35:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
class WEISurveyInformation:
|
2020-04-19 23:26:53 +00:00
|
|
|
"""
|
|
|
|
Abstract data of the survey.
|
|
|
|
"""
|
2020-04-19 18:35:49 +00:00
|
|
|
valid = False
|
|
|
|
selected_bus_pk = None
|
2020-04-19 20:16:57 +00:00
|
|
|
selected_bus_name = None
|
2020-04-19 18:35:49 +00:00
|
|
|
|
|
|
|
def __init__(self, registration):
|
|
|
|
self.__dict__.update(registration.information)
|
|
|
|
|
2020-04-19 23:26:53 +00:00
|
|
|
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.
|
|
|
|
"""
|
2020-08-11 11:30:44 +00:00
|
|
|
return Bus.objects.get(pk=self.selected_bus_pk) if self.valid else None
|
2020-04-19 20:16:57 +00:00
|
|
|
|
2020-04-19 23:26:53 +00:00
|
|
|
def save(self, registration) -> None:
|
|
|
|
"""
|
|
|
|
Store the data of the survey into the database, with the information of the registration.
|
|
|
|
"""
|
2020-04-19 18:35:49 +00:00
|
|
|
registration.information = self.__dict__
|
|
|
|
|
|
|
|
|
2020-04-24 17:34:20 +00:00
|
|
|
class WEIBusInformation:
|
|
|
|
"""
|
|
|
|
Abstract data of the bus.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, bus: Bus):
|
|
|
|
self.__dict__.update(bus.information)
|
|
|
|
self.bus = bus
|
2020-08-07 18:11:28 +00:00
|
|
|
self.save()
|
|
|
|
|
|
|
|
def save(self):
|
|
|
|
d = self.__dict__.copy()
|
|
|
|
d.pop("bus")
|
|
|
|
self.bus.information = d
|
|
|
|
self.bus.save()
|
2020-04-24 17:34:20 +00:00
|
|
|
|
2021-09-16 18:05:20 +00:00
|
|
|
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]
|
2021-09-01 23:36:37 +00:00
|
|
|
valid_surveys = sum(1 for survey in surveys if survey.information.valid
|
|
|
|
and survey.information.get_selected_bus() == self.bus) if surveys else 0
|
2021-09-16 18:05:20 +00:00
|
|
|
return quota - valid_surveys
|
2021-08-27 08:33:41 +00:00
|
|
|
|
2021-09-16 18:05:20 +00:00
|
|
|
def has_free_seats(self, surveys=None, quotas=None):
|
|
|
|
return self.free_seats(surveys, quotas) > 0
|
2021-08-27 08:33:41 +00:00
|
|
|
|
2020-04-24 17:34:20 +00:00
|
|
|
|
2020-04-19 18:35:49 +00:00
|
|
|
class WEISurveyAlgorithm:
|
2020-04-19 23:26:53 +00:00
|
|
|
"""
|
|
|
|
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.
|
|
|
|
"""
|
2020-04-19 18:35:49 +00:00
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-04-24 17:34:20 +00:00
|
|
|
@classmethod
|
|
|
|
def get_bus_information_class(cls):
|
|
|
|
"""
|
|
|
|
The class of the information associated to a bus extending WEIBusInformation.
|
|
|
|
Default: WEIBusInformation (contains nothing)
|
|
|
|
"""
|
|
|
|
return WEIBusInformation
|
|
|
|
|
2020-04-19 23:26:53 +00:00
|
|
|
@classmethod
|
|
|
|
def get_registrations(cls) -> QuerySet:
|
|
|
|
"""
|
|
|
|
Queryset of all first year registrations
|
|
|
|
"""
|
2021-09-16 18:05:20 +00:00
|
|
|
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
|
2020-04-19 23:26:53 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_buses(cls) -> QuerySet:
|
|
|
|
"""
|
|
|
|
Queryset of all buses of the associated wei.
|
|
|
|
"""
|
2021-09-16 18:05:20 +00:00
|
|
|
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
|
2020-04-19 23:26:53 +00:00
|
|
|
|
2020-04-24 17:34:20 +00:00
|
|
|
@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)
|
|
|
|
|
2020-04-19 23:26:53 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
"""
|
2021-09-16 18:05:20 +00:00
|
|
|
if not hasattr(cls, '_wei'):
|
|
|
|
cls._wei = WEIClub.objects.get(year=cls.get_year())
|
|
|
|
|
|
|
|
return cls._wei
|
2020-04-19 23:26:53 +00:00
|
|
|
|
|
|
|
@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
|
2020-04-19 18:35:49 +00:00
|
|
|
|
2020-04-19 23:26:53 +00:00
|
|
|
def save(self) -> None:
|
|
|
|
"""
|
|
|
|
Store the information of the survey into the database.
|
|
|
|
"""
|
|
|
|
self.information.save(self.registration)
|
2020-04-22 11:28:52 +00:00
|
|
|
# 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
|
2020-04-19 23:26:53 +00:00
|
|
|
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.
|
|
|
|
"""
|
2020-04-19 18:35:49 +00:00
|
|
|
raise NotImplementedError
|
2020-04-19 23:26:53 +00:00
|
|
|
|
|
|
|
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
|
2021-08-27 08:33:41 +00:00
|
|
|
|
|
|
|
def free(self) -> None:
|
|
|
|
"""
|
|
|
|
Unselect the select bus.
|
|
|
|
"""
|
|
|
|
self.information.selected_bus_pk = None
|
|
|
|
self.information.selected_bus_name = None
|
|
|
|
self.information.valid = False
|
2021-09-27 11:56:10 +00:00
|
|
|
|
|
|
|
@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
|