Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
from coldfront.core.allocation.models import AllocationRenewalRequestStatusChoice
from coldfront.core.project.models import Project
from coldfront.core.project.models import ProjectUser
from coldfront.core.project.utils_.computing_allowance_eligibility_manager import ComputingAllowanceEligibilityManager
from coldfront.core.project.utils_.renewal_utils import AllocationRenewalProcessingRunner
from coldfront.core.project.utils_.renewal_utils import has_non_denied_renewal_request
from coldfront.core.resource.utils_.allowance_utils.computing_allowance import ComputingAllowance
from coldfront.core.resource.utils_.allowance_utils.interface import ComputingAllowanceInterface
from coldfront.core.utils.common import add_argparse_dry_run_argument
from coldfront.core.utils.common import utc_now_offset_aware
Expand Down Expand Up @@ -110,6 +111,8 @@ def parse_input_file(self, file_path, allocation_period):

seen_pi_emails = set()

interface = ComputingAllowanceInterface()

for input_dict in input_dicts:
for key in input_dict:
input_dict[key] = input_dict[key].strip()
Expand All @@ -128,13 +131,21 @@ def parse_input_file(self, file_path, allocation_period):
continue
seen_pi_emails.add(pi_email)

# Validate that the PI does not already have a non-'Denied'
# Validate that the PI is eligible to make a
# AllocationRenewalRequest for this period.
pi = self._get_user_with_email(pi_email)
if (isinstance(pi, User) and
has_non_denied_renewal_request(pi, allocation_period)):
already_renewed.append(pi)
continue
if isinstance(pi, User):
computing_allowance = ComputingAllowance(
interface.allowance_from_code(post_project_name[:3]))
computing_allowance_eligibility_manager = \
ComputingAllowanceEligibilityManager(
computing_allowance,
allocation_period=allocation_period)
is_user_eligible = \
computing_allowance_eligibility_manager.is_user_eligible(pi)
if not is_user_eligible:
already_renewed.append(pi)
continue

# Validate that the pre-Project exists, if given. The PI may have
# previously not had a Project.
Expand Down
87 changes: 85 additions & 2 deletions coldfront/core/project/forms.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import datetime

from django import forms
from django.core.validators import MinLengthValidator
from django.shortcuts import get_object_or_404
from django.db.models import Q

from coldfront.core.project.models import (Project, ProjectReview,
ProjectUserRoleChoice)
from coldfront.core.project.utils_.computing_allowance_eligibility_manager import ComputingAllowanceEligibilityManager
from coldfront.core.resource.utils_.allowance_utils.computing_allowance import ComputingAllowance
from django.contrib.auth.models import User
from coldfront.core.user.utils_.host_user_utils import eligible_host_project_users
from coldfront.core.utils.common import import_from_settings
from coldfront.core.resource.utils import get_compute_resource_names

import datetime


EMAIL_DIRECTOR_PENDING_PROJECT_REVIEW_EMAIL = import_from_settings(
'EMAIL_DIRECTOR_PENDING_PROJECT_REVIEW_EMAIL')
Expand Down Expand Up @@ -243,6 +246,86 @@ def clean(self):
return cleaned_data


# note: from coldfront\core\project\forms_\new_project_forms\request_forms.py
class PIChoiceField(forms.ModelChoiceField):

def label_from_instance(self, obj):
return f'{obj.first_name} {obj.last_name} ({obj.email})'


class ReviewEligibilityForm(ReviewStatusForm):

PI = PIChoiceField(
label='Principal Investigator',
queryset=User.objects.none(),
required=False,
widget=DisabledChoicesSelectWidget(),
help_text= 'Please confirm the PI for this project. If the PI is ' \
'listed, select them from the dropdown and do not fill ' \
'out the PI information fields. If the PI is not ' \
'listed, empty the field and provide the PI\'s ' \
'information below.')
first_name = forms.CharField(max_length=30, required=False)
middle_name = forms.CharField(max_length=30, required=False)
last_name = forms.CharField(max_length=150, required=False)
email = forms.EmailField(max_length=100, required=False)

field_order=['PI', 'first_name', 'middle_name', 'last_name', 'email',
'status', 'justification']

def __init__(self, *args, **kwargs):
self.computing_allowance = kwargs.pop('computing_allowance', None)
self.allocation_period = kwargs.pop('allocation_period', None)
super().__init__(*args, **kwargs)
if self.computing_allowance is not None:
self.computing_allowance = ComputingAllowance(
self.computing_allowance)
self.disable_pi_choices()
self.exclude_pi_choices()

def clean(self):
cleaned_data = super().clean()
pi = self.cleaned_data['PI']
if pi is not None and pi not in self.fields['PI'].queryset:
raise forms.ValidationError(f'Invalid selection {pi.username}.')

# TODO: This doesn't include the clean_email method from
# SavioProjectNewPIForm.

return cleaned_data

def disable_pi_choices(self):
"""Prevent certain Users, who should be displayed, from being
selected as PIs."""
computing_allowance_eligibility_manager = \
ComputingAllowanceEligibilityManager(
self.computing_allowance,
allocation_period=self.allocation_period)

disable_user_pks = \
computing_allowance_eligibility_manager.get_ineligible_users(
pks_only=True)

self.fields['PI'].widget.disabled_choices = disable_user_pks

def exclude_pi_choices(self):
"""Exclude certain Users from being displayed as PI options."""
# Exclude any user that does not have an email address or is inactive.
self.fields['PI'].queryset = User.objects.exclude(
Q(email__isnull=True) | Q(email__exact='') | Q(is_active=False))

def clean(self):
cleaned_data = super().clean()
status = cleaned_data.get('status', 'Pending')
# Require justification for denials.
if status == 'Denied':
justification = cleaned_data.get('justification', '')
if not justification.strip():
raise forms.ValidationError(
'Please provide a justification for your decision.')
return cleaned_data


class JoinRequestSearchForm(forms.Form):
project_name = forms.CharField(label='Project Name',
max_length=100, required=False)
Expand Down
57 changes: 10 additions & 47 deletions coldfront/core/project/forms_/new_project_forms/request_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,12 @@
from coldfront.core.allocation.models import AllocationPeriod
from coldfront.core.project.forms import DisabledChoicesSelectWidget
from coldfront.core.project.models import Project
from coldfront.core.project.utils_.new_project_utils import non_denied_new_project_request_statuses
from coldfront.core.project.utils_.new_project_utils import pis_with_new_project_requests_pks
from coldfront.core.project.utils_.new_project_utils import project_pi_pks
from coldfront.core.project.utils_.renewal_utils import non_denied_renewal_request_statuses
from coldfront.core.project.utils_.renewal_utils import pis_with_renewal_requests_pks
from coldfront.core.project.utils_.computing_allowance_eligibility_manager import ComputingAllowanceEligibilityManager
from coldfront.core.resource.models import Resource
from coldfront.core.resource.utils_.allowance_utils.computing_allowance import ComputingAllowance
from coldfront.core.resource.utils_.allowance_utils.constants import BRCAllowances
from coldfront.core.resource.utils_.allowance_utils.constants import LRCAllowances
from coldfront.core.resource.utils_.allowance_utils.interface import ComputingAllowanceInterface
from coldfront.core.user.utils_.host_user_utils import is_lbl_employee
from coldfront.core.utils.common import utc_now_offset_aware

from django import forms
Expand Down Expand Up @@ -151,7 +146,6 @@ class PIChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return f'{obj.first_name} {obj.last_name} ({obj.email})'


class SavioProjectExistingPIForm(forms.Form):

PI = PIChoiceField(
Expand Down Expand Up @@ -180,45 +174,14 @@ def clean(self):
def disable_pi_choices(self):
"""Prevent certain Users, who should be displayed, from being
selected as PIs."""
disable_user_pks = set()

if self.computing_allowance.is_one_per_pi() and self.allocation_period:
# Disable any PI who has:
# (a) an existing Project with the allowance*,
# (b) a new project request for a Project with the allowance
# during the AllocationPeriod*, or
# (c) an allowance renewal request for a Project with the
# allowance during the AllocationPeriod*.
# * Projects/requests must have ineligible statuses.
resource = self.computing_allowance.get_resource()
project_status_names = ['New', 'Active', 'Inactive']
disable_user_pks.update(
project_pi_pks(
computing_allowance=resource,
project_status_names=project_status_names))
new_project_request_status_names = list(
non_denied_new_project_request_statuses().values_list(
'name', flat=True))
disable_user_pks.update(
pis_with_new_project_requests_pks(
self.allocation_period,
computing_allowance=resource,
request_status_names=new_project_request_status_names))
renewal_request_status_names = list(
non_denied_renewal_request_statuses().values_list(
'name', flat=True))
disable_user_pks.update(
pis_with_renewal_requests_pks(
self.allocation_period,
computing_allowance=resource,
request_status_names=renewal_request_status_names))
computing_allowance_eligibility_manager = \
ComputingAllowanceEligibilityManager(
self.computing_allowance,
allocation_period=self.allocation_period)

if flag_enabled('LRC_ONLY'):
# On LRC, PIs must be LBL employees.
non_lbl_employees = set(
[user.pk for user in User.objects.all()
if not is_lbl_employee(user)])
disable_user_pks.update(non_lbl_employees)
disable_user_pks = \
computing_allowance_eligibility_manager.get_ineligible_users(
pks_only=True)

self.fields['PI'].widget.disabled_choices = disable_user_pks

Expand All @@ -239,8 +202,8 @@ class SavioProjectNewPIForm(forms.Form):
def clean_email(self):
cleaned_data = super().clean()
email = cleaned_data['email'].lower()
if (User.objects.filter(username=email).exists() or
User.objects.filter(email=email).exists()):
if User.objects.filter(username=email).exists() or \
User.objects.filter(email=email).exists():
raise forms.ValidationError(
'A user with that email address already exists.')

Expand Down
46 changes: 14 additions & 32 deletions coldfront/core/project/forms_/renewal_forms/request_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,14 @@
from coldfront.core.project.models import ProjectUser
from coldfront.core.project.models import ProjectUserRoleChoice
from coldfront.core.project.models import ProjectUserStatusChoice
from coldfront.core.project.utils_.new_project_utils import non_denied_new_project_request_statuses
from coldfront.core.project.utils_.new_project_utils import pis_with_new_project_requests_pks
from coldfront.core.project.utils_.renewal_utils import non_denied_renewal_request_statuses
from coldfront.core.project.utils_.renewal_utils import pis_with_renewal_requests_pks
from coldfront.core.project.utils_.computing_allowance_eligibility_manager import ComputingAllowanceEligibilityManager
from coldfront.core.resource.utils_.allowance_utils.computing_allowance import ComputingAllowance
from coldfront.core.resource.utils_.allowance_utils.interface import ComputingAllowanceInterface
from coldfront.core.project.utils_.renewal_survey import is_renewal_survey_completed

from flags.state import flag_enabled
from django import forms
from django.utils.safestring import mark_safe
from django.core.validators import MinLengthValidator
from django.core.exceptions import ValidationError


Expand Down Expand Up @@ -68,34 +64,20 @@ def __init__(self, *args, **kwargs):
def disable_pi_choices(self, pi_project_users):
"""Prevent certain of the given ProjectUsers, who should be
displayed, from being selected for renewal."""
computing_allowance_eligibility_manager = \
ComputingAllowanceEligibilityManager(
self.computing_allowance,
allocation_period=self.allocation_period)

disable_user_pks = \
computing_allowance_eligibility_manager.get_ineligible_users(
is_renewal=True, pks_only=True)

disable_project_user_pks = set()
if self.computing_allowance.is_one_per_pi():
# Disable any PI who has:
# (a) a new project request for a Project during the
# AllocationPeriod*, or
# (b) an AllocationRenewalRequest during the AllocationPeriod*.
# * Requests must have ineligible statuses.
resource = self.computing_allowance.get_resource()
disable_user_pks = set()
new_project_request_status_names = list(
non_denied_new_project_request_statuses().values_list(
'name', flat=True))
disable_user_pks.update(
pis_with_new_project_requests_pks(
self.allocation_period,
computing_allowance=resource,
request_status_names=new_project_request_status_names))
renewal_request_status_names = list(
non_denied_renewal_request_statuses().values_list(
'name', flat=True))
disable_user_pks.update(
pis_with_renewal_requests_pks(
self.allocation_period,
computing_allowance=resource,
request_status_names=renewal_request_status_names))
for project_user in pi_project_users:
if project_user.user.pk in disable_user_pks:
disable_project_user_pks.add(project_user.pk)
for project_user in pi_project_users:
if project_user.user.pk in disable_user_pks:
disable_project_user_pks.add(project_user.pk)

self.fields['PI'].widget.disabled_choices = disable_project_user_pks


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@


{% block content %}
<script type="text/javascript" src="{% static 'common/js/leave_form_alert.js' %}"></script>
<script type='text/javascript' src="{% static 'selectize/selectize.min.js' %}"></script>
<link rel='stylesheet' type='text/css' href="{% static 'selectize/selectize.bootstrap3.css' %}"/>
<h1>Review Eligibility</h1>
<hr>

Expand Down Expand Up @@ -39,11 +42,17 @@ <h4>
<a href="{% url 'new-project-request-detail' savio_request.pk %}" class="btn btn-secondary">
Cancel
</a>
<script>
$('#id_PI').selectize({
create: false,
sortField: 'text',
placeholder: 'Type to search or fill out the PI information below.'
})
</script>
</form>
</div>
</div>

{% include 'project/project_request/savio/project_request_extra_fields_modal.html' with extra_fields_form=extra_fields_form %}
{% include 'project/project_request/savio/project_request_survey_modal.html' with survey_form=survey_form %}

{% endblock %}
Loading
Loading