diff --git a/coldfront/config/local_settings.py.sample b/coldfront/config/local_settings.py.sample index a8fdcd8d1a..b7e3aed6da 100644 --- a/coldfront/config/local_settings.py.sample +++ b/coldfront/config/local_settings.py.sample @@ -31,9 +31,9 @@ SESSION_COOKIE_SECURE = False # Sessions should last for one hour. SESSION_COOKIE_AGE = 60 * 60 -# ------------------------------------------------------------------------------ +#------------------------------------------------------------------------------ # myBRC settings -# ------------------------------------------------------------------------------ +#------------------------------------------------------------------------------ # The ID of the Site object to use (probably 1). SITE_ID = 1 diff --git a/coldfront/core/allocation/forms.py b/coldfront/core/allocation/forms.py index f5f4b65d22..9ebabfa8ed 100644 --- a/coldfront/core/allocation/forms.py +++ b/coldfront/core/allocation/forms.py @@ -285,7 +285,11 @@ def __init__(self, *args, **kwargs): not isinstance(self.computing_allowance, ComputingAllowance)): self.computing_allowance = ComputingAllowance( self.computing_allowance) - self.interface = ComputingAllowanceInterface() + self.interface = ( + # Short-circuit to avoid instantiating ComputingAllowanceInterface + # if possible. + kwargs.pop('computing_allowance_interface', None) or + ComputingAllowanceInterface()) super().__init__(*args, **kwargs) def label_from_instance(self, obj): diff --git a/coldfront/core/project/forms_/new_project_forms/request_forms.py b/coldfront/core/project/forms_/new_project_forms/request_forms.py index ddccb697bc..2a3e3e7039 100644 --- a/coldfront/core/project/forms_/new_project_forms/request_forms.py +++ b/coldfront/core/project/forms_/new_project_forms/request_forms.py @@ -50,6 +50,11 @@ class SavioProjectAllocationPeriodForm(forms.Form): def __init__(self, *args, **kwargs): computing_allowance = kwargs.pop('computing_allowance', None) + computing_allowance_interface = ( + # Short-circuit to avoid instantiating ComputingAllowanceInterface + # if possible. + kwargs.pop('computing_allowance_interface', None) or + ComputingAllowanceInterface()) super().__init__(*args, **kwargs) if computing_allowance is not None: computing_allowance = ComputingAllowance(computing_allowance) @@ -60,6 +65,7 @@ def __init__(self, *args, **kwargs): queryset = AllocationPeriod.objects.none() self.fields['allocation_period'] = AllocationPeriodChoiceField( computing_allowance=computing_allowance, + computing_allowance_interface=computing_allowance_interface, label='Allocation Period', queryset=queryset, required=True) @@ -164,6 +170,11 @@ class SavioProjectExistingPIForm(forms.Form): def __init__(self, *args, **kwargs): self.computing_allowance = kwargs.pop('computing_allowance', None) self.allocation_period = kwargs.pop('allocation_period', None) + self.interface = ( + # Short-circuit to avoid instantiating ComputingAllowanceInterface + # if possible. + kwargs.pop('computing_allowance_interface', None) or + ComputingAllowanceInterface()) super().__init__(*args, **kwargs) if self.computing_allowance is not None: self.computing_allowance = ComputingAllowance( @@ -196,7 +207,8 @@ def disable_pi_choices(self): disable_user_pks.update( project_pi_pks( computing_allowance=resource, - project_status_names=project_status_names)) + project_status_names=project_status_names, + computing_allowance_interface=self.interface)) new_project_request_status_names = list( non_denied_new_project_request_statuses().values_list( 'name', flat=True)) @@ -212,7 +224,8 @@ def disable_pi_choices(self): pis_with_renewal_requests_pks( self.allocation_period, computing_allowance=resource, - request_status_names=renewal_request_status_names)) + request_status_names=renewal_request_status_names, + computing_allowance_interface=self.interface)) if flag_enabled('LRC_ONLY'): # On LRC, PIs must be LBL employees. @@ -528,7 +541,11 @@ class SavioProjectPooledProjectSelectionForm(forms.Form): def __init__(self, *args, **kwargs): self.computing_allowance = kwargs.pop('computing_allowance', None) - self.interface = ComputingAllowanceInterface() + self.interface = ( + # Short-circuit to avoid instantiating ComputingAllowanceInterface + # if possible. + kwargs.pop('computing_allowance_interface', None) or + ComputingAllowanceInterface()) super().__init__(*args, **kwargs) f = Q(status__name__in=['Pending - Add', 'New', 'Active']) @@ -587,7 +604,11 @@ class SavioProjectDetailsForm(forms.Form): def __init__(self, *args, **kwargs): self.computing_allowance = kwargs.pop('computing_allowance', None) - self.interface = ComputingAllowanceInterface() + self.interface = ( + # Short-circuit to avoid instantiating ComputingAllowanceInterface + # if possible. + kwargs.pop('computing_allowance_interface', None) or + ComputingAllowanceInterface()) super().__init__(*args, **kwargs) if self.computing_allowance is not None: self.computing_allowance = ComputingAllowance( diff --git a/coldfront/core/project/utils_/new_project_utils.py b/coldfront/core/project/utils_/new_project_utils.py index ffcf8fdd14..565b0b784c 100644 --- a/coldfront/core/project/utils_/new_project_utils.py +++ b/coldfront/core/project/utils_/new_project_utils.py @@ -82,7 +82,8 @@ def pis_with_new_project_requests_pks(allocation_period, f).values_list('pi__pk', flat=True)) -def project_pi_pks(computing_allowance=None, project_status_names=[]): +def project_pi_pks(computing_allowance=None, project_status_names=[], + computing_allowance_interface=None): """Return a list of primary keys of PI Users of Projects that match the given filters. @@ -91,6 +92,9 @@ def project_pi_pks(computing_allowance=None, project_status_names=[]): allowance to filter with - project_status_names (list[str]): A list of names of Project statuses to filter with + - computing_allowance_interface (ComputingAllowanceInterface): + An optional ComputingAllowanceInterface instance to + perform lookups with Returns: - A list of integers representing primary keys of matching PIs. @@ -101,9 +105,13 @@ def project_pi_pks(computing_allowance=None, project_status_names=[]): cannot be retrieved. """ project_prefix = '' + if computing_allowance_interface is not None: + assert isinstance( + computing_allowance_interface, ComputingAllowanceInterface) if computing_allowance is not None: assert isinstance(computing_allowance, Resource) - interface = ComputingAllowanceInterface() + interface = ( + computing_allowance_interface or ComputingAllowanceInterface()) project_prefix = interface.code_from_name(computing_allowance.name) return set( ProjectUser.objects.filter( diff --git a/coldfront/core/project/utils_/renewal_utils.py b/coldfront/core/project/utils_/renewal_utils.py index 2a4a044ef7..20306faea1 100644 --- a/coldfront/core/project/utils_/renewal_utils.py +++ b/coldfront/core/project/utils_/renewal_utils.py @@ -232,7 +232,8 @@ def non_denied_renewal_request_statuses(): def pis_with_renewal_requests_pks(allocation_period, computing_allowance=None, - request_status_names=[]): + request_status_names=[], + computing_allowance_interface=None): """Return a list of primary keys of PIs of allocation renewal requests for the given AllocationPeriod that match the given filters. @@ -243,6 +244,9 @@ def pis_with_renewal_requests_pks(allocation_period, computing_allowance=None, allowance to filter with - request_status_names (list[str]): A list of names of request statuses to filter with + - computing_allowance_interface (ComputingAllowanceInterface): + An optional ComputingAllowanceInterface instance to + perform lookups with Returns: - A list of integers representing primary keys of matching PIs. @@ -254,9 +258,13 @@ def pis_with_renewal_requests_pks(allocation_period, computing_allowance=None, """ assert isinstance(allocation_period, AllocationPeriod) f = Q(allocation_period=allocation_period) + if computing_allowance_interface is not None: + assert isinstance( + computing_allowance_interface, ComputingAllowanceInterface) if computing_allowance is not None: assert isinstance(computing_allowance, Resource) - interface = ComputingAllowanceInterface() + interface = ( + computing_allowance_interface or ComputingAllowanceInterface()) project_prefix = interface.code_from_name(computing_allowance.name) f = f & Q(post_project__name__startswith=project_prefix) if request_status_names: diff --git a/coldfront/core/project/views_/new_project_views/request_views.py b/coldfront/core/project/views_/new_project_views/request_views.py index 2a8d41d12a..439b77bb95 100644 --- a/coldfront/core/project/views_/new_project_views/request_views.py +++ b/coldfront/core/project/views_/new_project_views/request_views.py @@ -29,6 +29,7 @@ from coldfront.core.resource.models import Resource from coldfront.core.resource.utils import get_primary_compute_resource from coldfront.core.resource.utils_.allowance_utils.computing_allowance import ComputingAllowance +from coldfront.core.resource.utils_.allowance_utils.interface import cached_computing_allowance_interface from coldfront.core.resource.utils_.allowance_utils.interface import ComputingAllowanceInterface from coldfront.core.user.models import UserProfile from coldfront.core.user.utils import access_agreement_signed @@ -178,6 +179,8 @@ def __init__(self, *args, **kwargs): # Define a lookup table from form name to step number. self.step_numbers_by_form_name = { name: i for i, (name, _) in enumerate(self.FORMS)} + self.computing_allowance_interface = \ + cached_computing_allowance_interface() def test_func(self): if self.request.user.is_superuser: @@ -223,6 +226,17 @@ def get_form_kwargs(self, step=None): step_name = step_names_by_step_number[step_number] for key in required_keys_by_step_name[step_name]: kwargs[key] = data.get(key, None) + + step_names_requiring_computing_allowance_interface = { + 'allocation_period', + 'existing_pi', + 'pooled_project_selection', + 'details', + } + if step_name in step_names_requiring_computing_allowance_interface: + kwargs['computing_allowance_interface'] = \ + self.computing_allowance_interface + return kwargs def get_template_names(self): diff --git a/coldfront/core/resource/utils_/allowance_utils/interface.py b/coldfront/core/resource/utils_/allowance_utils/interface.py index 92820a55d7..45f77fde49 100644 --- a/coldfront/core/resource/utils_/allowance_utils/interface.py +++ b/coldfront/core/resource/utils_/allowance_utils/interface.py @@ -1,7 +1,14 @@ +from base64 import b64decode +from base64 import b64encode + +from django.core.cache import cache + from coldfront.core.allocation.models import AllocationPeriod from coldfront.core.resource.models import Resource from coldfront.core.resource.models import ResourceType +import pickle + class ComputingAllowanceInterface(object): """A singleton that fetches computing allowances from the database @@ -135,3 +142,20 @@ def service_units_from_name(self, name, is_timed=False, class ComputingAllowanceInterfaceError(Exception): """An exception to be raised by the ComputingAllowanceInterface.""" pass + + +def cached_computing_allowance_interface(): + """Return an instance of ComputingAllowanceInterface. If one is + already in the cache, return it. Otherwise, create one, store it in + the cache, and return it.""" + cache_key = 'computing_allowance_interface' + if cache_key in cache: + cache_value = cache.get(cache_key) + computing_allowance_interface = pickle.loads( + b64decode(cache_value.encode('utf-8'))) + else: + computing_allowance_interface = ComputingAllowanceInterface() + cache_value = b64encode( + pickle.dumps(computing_allowance_interface)).decode('utf-8') + cache.set(cache_key, cache_value) + return computing_allowance_interface