From f8f0ac6c21ffdd82b726c932773a7d65c3354d9b Mon Sep 17 00:00:00 2001 From: Hamza Kundi Date: Tue, 16 Apr 2024 09:53:21 -0700 Subject: [PATCH 1/7] added initial field to pi changing form --- coldfront/core/project/forms.py | 15 +++++++++++++++ .../views_/new_project_views/approval_views.py | 6 ++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/coldfront/core/project/forms.py b/coldfront/core/project/forms.py index 0d4afb0f5..f4a3ef27e 100644 --- a/coldfront/core/project/forms.py +++ b/coldfront/core/project/forms.py @@ -7,6 +7,8 @@ from coldfront.core.project.models import (Project, ProjectReview, ProjectUserRoleChoice) +from coldfront.core.user.models import UserProfile +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 @@ -211,6 +213,11 @@ class ReviewDenyForm(forms.Form): class ReviewStatusForm(forms.Form): + pi = forms.ModelChoiceField( + queryset=User.objects.filter(userprofile__is_pi=True).order_by('first_name', 'last_name'), + label='Principal Investigator', + required=False) + status = forms.ChoiceField( choices=( ('', 'Select one.'), @@ -231,6 +238,14 @@ class ReviewStatusForm(forms.Form): required=False, widget=forms.Textarea(attrs={'rows': 3})) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['pi'].label_from_instance = self.label_from_instance + + @staticmethod + def label_from_instance(obj): + return f'{obj.first_name} {obj.last_name} ({obj.email})' + def clean(self): cleaned_data = super().clean() status = cleaned_data.get('status', 'Pending') diff --git a/coldfront/core/project/views_/new_project_views/approval_views.py b/coldfront/core/project/views_/new_project_views/approval_views.py index d4dcb0899..967869cb2 100644 --- a/coldfront/core/project/views_/new_project_views/approval_views.py +++ b/coldfront/core/project/views_/new_project_views/approval_views.py @@ -615,7 +615,7 @@ def dispatch(self, request, *args, **kwargs): if redirect is not None: return redirect return super().dispatch(request, *args, **kwargs) - + def form_valid(self, form): form_data = form.cleaned_data status = form_data['status'] @@ -631,7 +631,8 @@ def form_valid(self, form): if status == 'Denied': runner = ProjectDenialRunner(self.request_obj) runner.run() - + + self.request_obj.pi = form_data['pi'] self.request_obj.save() message = ( @@ -651,6 +652,7 @@ def get_context_data(self, **kwargs): def get_initial(self): initial = super().get_initial() eligibility = self.request_obj.state['eligibility'] + initial['pi'] = self.request_obj.pi initial['status'] = eligibility['status'] initial['justification'] = eligibility['justification'] return initial From b6f32ca277116e2624175c581faf85411ec31c41 Mon Sep 17 00:00:00 2001 From: Hamza Kundi Date: Tue, 23 Apr 2024 10:37:20 -0700 Subject: [PATCH 2/7] added optional new pi functionality to approval view --- coldfront/core/project/forms.py | 24 ++++++++++---- .../forms_/new_project_forms/request_forms.py | 22 +++++++++++-- .../new_project_views/approval_views.py | 32 ++++++++++++++++--- 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/coldfront/core/project/forms.py b/coldfront/core/project/forms.py index f4a3ef27e..59eadbb2a 100644 --- a/coldfront/core/project/forms.py +++ b/coldfront/core/project/forms.py @@ -5,6 +5,7 @@ from django.shortcuts import get_object_or_404 from django.db.models import Q +import coldfront.core.project.forms_.new_project_forms.request_forms as request_forms from coldfront.core.project.models import (Project, ProjectReview, ProjectUserRoleChoice) from coldfront.core.user.models import UserProfile @@ -211,13 +212,15 @@ class ReviewDenyForm(forms.Form): widget=forms.Textarea(attrs={'rows': 3})) -class ReviewStatusForm(forms.Form): - - pi = forms.ModelChoiceField( - queryset=User.objects.filter(userprofile__is_pi=True).order_by('first_name', 'last_name'), - label='Principal Investigator', - required=False) +class ReviewStatusForm(request_forms.SavioProjectNewPIForm, + request_forms.SavioProjectExistingPIForm): + # PI = SavioProjectExistingPIForm.PI + # first_name = forms.CharField(max_length=30, required=True) + # middle_name = forms.CharField(max_length=30, required=False) + # last_name = forms.CharField(max_length=150, required=True) + # email = forms.EmailField(max_length=100, required=True) + status = forms.ChoiceField( choices=( ('', 'Select one.'), @@ -240,7 +243,14 @@ class ReviewStatusForm(forms.Form): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['pi'].label_from_instance = self.label_from_instance + self.fields['PI'].label_from_instance = self.label_from_instance + self.fields['PI'].empty_label = 'New PI' + self.fields['PI'].help_text = ( + 'Please confirm the PI for this project. If the PI is not listed, ' + 'select "New PI" and provide the PI\'s information below.') + self.fields['first_name'].required = False + self.fields['last_name'].required = False + self.fields['email'].required = False @staticmethod def label_from_instance(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 7029299dd..42a33ecb2 100644 --- a/coldfront/core/project/forms_/new_project_forms/request_forms.py +++ b/coldfront/core/project/forms_/new_project_forms/request_forms.py @@ -1,6 +1,5 @@ from coldfront.core.allocation.forms import AllocationPeriodChoiceField 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 @@ -144,6 +143,24 @@ def label_from_instance(self, obj): return f'{obj.first_name} {obj.last_name} ({obj.email})' +class DisabledChoicesSelectWidget(forms.Select): + + def __init__(self, *args, **kwargs): + self.disabled_choices = kwargs.pop('disabled_choices', set()) + super().__init__(*args, **kwargs) + + def create_option(self, name, value, label, selected, index, subindex=None, + attrs=None): + option = super().create_option( + name, value, label, selected, index, subindex=subindex, + attrs=attrs) + try: + if int(str(value)) in self.disabled_choices: + option['attrs']['disabled'] = True + except Exception: + pass + return option + class SavioProjectExistingPIForm(forms.Form): PI = PIChoiceField( @@ -231,7 +248,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 + # "email and" for project.forms.ReviewStatusForm + if email and (User.objects.filter(username=email).exists() or User.objects.filter(email=email).exists()): raise forms.ValidationError( 'A user with that email address already exists.') diff --git a/coldfront/core/project/views_/new_project_views/approval_views.py b/coldfront/core/project/views_/new_project_views/approval_views.py index 967869cb2..74f9726ad 100644 --- a/coldfront/core/project/views_/new_project_views/approval_views.py +++ b/coldfront/core/project/views_/new_project_views/approval_views.py @@ -19,6 +19,7 @@ from coldfront.core.project.utils_.new_project_utils import send_project_request_pooling_email from coldfront.core.project.utils_.new_project_utils import VectorProjectProcessingRunner from coldfront.core.project.utils_.new_project_utils import vector_request_state_status +from django.contrib.auth.models import User 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 display_time_zone_current_date @@ -33,7 +34,7 @@ from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import UserPassesTestMixin -from django.db import transaction +from django.db import IntegrityError, transaction from django.db.models import Q from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 @@ -600,6 +601,8 @@ class SavioProjectReviewEligibilityView(LoginRequiredMixin, template_name = ( 'project/project_request/savio/project_review_eligibility.html') + logger = logging.getLogger(__name__) + def test_func(self): """UserPassesTestMixin tests.""" if self.request.user.is_superuser: @@ -632,8 +635,29 @@ def form_valid(self, form): runner = ProjectDenialRunner(self.request_obj) runner.run() - self.request_obj.pi = form_data['pi'] - self.request_obj.save() + if form_data['PI'] != self.request_obj.pi: + if form_data['PI'] is not None: + self.request_obj.pi = form_data['PI'] + self.request_obj.save() + elif all([form_data['first_name'], + form_data['last_name'], + form_data['email']]): + try: + self.request_obj.pi = User.objects.create( + username=form_data['email'], + first_name=form_data['first_name'], + last_name=form_data['last_name'], + email=form_data['email'], + is_active=True) + self.request_obj.pi.save() + self.request_obj.save() + except IntegrityError as e: + self.logger.error(f'User {form_data["email"]} unexpectedly exists.') + raise e + else: + message = 'PI information is incomplete.' + messages.error(self.request, message) + return self.form_invalid(form) message = ( f'Eligibility status for request {self.request_obj.pk} has been ' @@ -652,7 +676,7 @@ def get_context_data(self, **kwargs): def get_initial(self): initial = super().get_initial() eligibility = self.request_obj.state['eligibility'] - initial['pi'] = self.request_obj.pi + initial['PI'] = self.request_obj.pi initial['status'] = eligibility['status'] initial['justification'] = eligibility['justification'] return initial From 8d4bb2bfb9521249b4f93fef8eabd5f9d6343075 Mon Sep 17 00:00:00 2001 From: Hamza Kundi Date: Tue, 30 Apr 2024 10:12:23 -0700 Subject: [PATCH 3/7] fixed testing --- coldfront/core/utils/tests/test_mou_notify_upload_download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coldfront/core/utils/tests/test_mou_notify_upload_download.py b/coldfront/core/utils/tests/test_mou_notify_upload_download.py index 73ed9656a..1ce6b6afd 100644 --- a/coldfront/core/utils/tests/test_mou_notify_upload_download.py +++ b/coldfront/core/utils/tests/test_mou_notify_upload_download.py @@ -171,7 +171,7 @@ def edit_extra_fields_url(pk): def test_new_project(self): """Test that the MOU notification task, MOU upload, and MOU download features work as expected.""" - eligibility = { 'status': 'Approved' } + eligibility = { 'PI': self.request.pi, 'status': 'Approved' } readiness = { 'status': 'Approved' } extra_fields = { 'course_name': 'TEST 101', From 68cb5e6585a06184175386b2d9e7b4380f1f119b Mon Sep 17 00:00:00 2001 From: Hamza Kundi Date: Mon, 7 Oct 2024 12:58:25 -0700 Subject: [PATCH 4/7] fixed request_obj saving --- .../core/project/views_/new_project_views/approval_views.py | 1 + coldfront/core/utils/tests/test_mou_notify_upload_download.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/coldfront/core/project/views_/new_project_views/approval_views.py b/coldfront/core/project/views_/new_project_views/approval_views.py index 914e9c382..a730f65ae 100644 --- a/coldfront/core/project/views_/new_project_views/approval_views.py +++ b/coldfront/core/project/views_/new_project_views/approval_views.py @@ -650,6 +650,7 @@ def form_valid(self, form): messages.error(self.request, message) return self.form_invalid(form) + self.request_obj.save() message = ( f'Eligibility status for request {self.request_obj.pk} has been ' f'set to {status}.') diff --git a/coldfront/core/utils/tests/test_mou_notify_upload_download.py b/coldfront/core/utils/tests/test_mou_notify_upload_download.py index 1ce6b6afd..5a26caca3 100644 --- a/coldfront/core/utils/tests/test_mou_notify_upload_download.py +++ b/coldfront/core/utils/tests/test_mou_notify_upload_download.py @@ -171,7 +171,7 @@ def edit_extra_fields_url(pk): def test_new_project(self): """Test that the MOU notification task, MOU upload, and MOU download features work as expected.""" - eligibility = { 'PI': self.request.pi, 'status': 'Approved' } + eligibility = { 'PI': self.request.pi.pk, 'status': 'Approved' } readiness = { 'status': 'Approved' } extra_fields = { 'course_name': 'TEST 101', From 11665b40a46dbc9972ae0d0fe48e965344a4b024 Mon Sep 17 00:00:00 2001 From: Hamza Kundi Date: Mon, 28 Oct 2024 10:40:01 -0700 Subject: [PATCH 5/7] refactored forms and help text --- coldfront/core/project/forms.py | 141 +++++++++++++++--- .../forms_/new_project_forms/request_forms.py | 25 +--- .../savio/project_existing_pi.html | 12 +- .../savio/project_review_eligibility.html | 14 ++ .../new_project_views/approval_views.py | 27 +++- 5 files changed, 164 insertions(+), 55 deletions(-) diff --git a/coldfront/core/project/forms.py b/coldfront/core/project/forms.py index 59eadbb2a..f657e2ee6 100644 --- a/coldfront/core/project/forms.py +++ b/coldfront/core/project/forms.py @@ -1,19 +1,22 @@ -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 -import coldfront.core.project.forms_.new_project_forms.request_forms as request_forms from coldfront.core.project.models import (Project, ProjectReview, ProjectUserRoleChoice) +from coldfront.core.project.utils_.new_project_utils import non_denied_new_project_request_statuses, pis_with_new_project_requests_pks, project_pi_pks +from coldfront.core.project.utils_.renewal_utils import non_denied_renewal_request_statuses, pis_with_renewal_requests_pks +from coldfront.core.resource.utils_.allowance_utils.computing_allowance import ComputingAllowance from coldfront.core.user.models import UserProfile 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 +from flags.state import flag_enabled + EMAIL_DIRECTOR_PENDING_PROJECT_REVIEW_EMAIL = import_from_settings( 'EMAIL_DIRECTOR_PENDING_PROJECT_REVIEW_EMAIL') @@ -211,16 +214,20 @@ class ReviewDenyForm(forms.Form): required=True, widget=forms.Textarea(attrs={'rows': 3})) +class ReviewDenyForm(forms.Form): -class ReviewStatusForm(request_forms.SavioProjectNewPIForm, - request_forms.SavioProjectExistingPIForm): + justification = forms.CharField( + help_text=( + 'Provide reasoning for your decision. It will be included in the ' + 'notification email.'), + label='Justification', + validators=[MinLengthValidator(10)], + required=True, + widget=forms.Textarea(attrs={'rows': 3})) + + +class ReviewStatusForm(forms.Form): - # PI = SavioProjectExistingPIForm.PI - # first_name = forms.CharField(max_length=30, required=True) - # middle_name = forms.CharField(max_length=30, required=False) - # last_name = forms.CharField(max_length=150, required=True) - # email = forms.EmailField(max_length=100, required=True) - status = forms.ChoiceField( choices=( ('', 'Select one.'), @@ -241,16 +248,112 @@ class ReviewStatusForm(request_forms.SavioProjectNewPIForm, required=False, widget=forms.Textarea(attrs={'rows': 3})) + 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 + + +# 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(), + empty_label='New PI', + 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, select "New PI" 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) - self.fields['PI'].label_from_instance = self.label_from_instance - self.fields['PI'].empty_label = 'New PI' - self.fields['PI'].help_text = ( - 'Please confirm the PI for this project. If the PI is not listed, ' - 'select "New PI" and provide the PI\'s information below.') - self.fields['first_name'].required = False - self.fields['last_name'].required = False - self.fields['email'].required = False + 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}.') + return cleaned_data + + 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)) + + 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) + + 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)) @staticmethod def label_from_instance(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 816978136..53e9253d7 100644 --- a/coldfront/core/project/forms_/new_project_forms/request_forms.py +++ b/coldfront/core/project/forms_/new_project_forms/request_forms.py @@ -1,5 +1,6 @@ from coldfront.core.allocation.forms import AllocationPeriodChoiceField 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 @@ -145,25 +146,6 @@ class PIChoiceField(forms.ModelChoiceField): def label_from_instance(self, obj): return f'{obj.first_name} {obj.last_name} ({obj.email})' - -class DisabledChoicesSelectWidget(forms.Select): - - def __init__(self, *args, **kwargs): - self.disabled_choices = kwargs.pop('disabled_choices', set()) - super().__init__(*args, **kwargs) - - def create_option(self, name, value, label, selected, index, subindex=None, - attrs=None): - option = super().create_option( - name, value, label, selected, index, subindex=subindex, - attrs=attrs) - try: - if int(str(value)) in self.disabled_choices: - option['attrs']['disabled'] = True - except Exception: - pass - return option - class SavioProjectExistingPIForm(forms.Form): PI = PIChoiceField( @@ -251,9 +233,8 @@ class SavioProjectNewPIForm(forms.Form): def clean_email(self): cleaned_data = super().clean() email = cleaned_data['email'].lower() - # "email and" for project.forms.ReviewStatusForm - if email and (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.') diff --git a/coldfront/core/project/templates/project/project_request/savio/project_existing_pi.html b/coldfront/core/project/templates/project/project_request/savio/project_existing_pi.html index 2c68eb445..88aa9050f 100644 --- a/coldfront/core/project/templates/project/project_request/savio/project_existing_pi.html +++ b/coldfront/core/project/templates/project/project_request/savio/project_existing_pi.html @@ -64,11 +64,12 @@

{{ PRIMARY_CLUSTER_NAME }}: Principal Investigator


{% endif %} - @@ -96,11 +97,4 @@

{{ PRIMARY_CLUSTER_NAME }}: Principal Investigator


Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}

- - {% endblock %} diff --git a/coldfront/core/project/templates/project/project_request/savio/project_review_eligibility.html b/coldfront/core/project/templates/project/project_request/savio/project_review_eligibility.html index d76e7e615..ab1dff2fd 100644 --- a/coldfront/core/project/templates/project/project_request/savio/project_review_eligibility.html +++ b/coldfront/core/project/templates/project/project_request/savio/project_review_eligibility.html @@ -39,6 +39,13 @@

Cancel + @@ -46,4 +53,11 @@

{% 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 %} diff --git a/coldfront/core/project/views_/new_project_views/approval_views.py b/coldfront/core/project/views_/new_project_views/approval_views.py index a730f65ae..f4df2918a 100644 --- a/coldfront/core/project/views_/new_project_views/approval_views.py +++ b/coldfront/core/project/views_/new_project_views/approval_views.py @@ -3,7 +3,7 @@ from coldfront.core.allocation.utils import calculate_service_units_to_allocate from coldfront.core.project.forms import MemorandumSignedForm from coldfront.core.project.forms import ReviewDenyForm -from coldfront.core.project.forms import ReviewStatusForm +from coldfront.core.project.forms import ReviewStatusForm, ReviewEligibilityForm from coldfront.core.project.forms_.new_project_forms.request_forms import NewProjectExtraFieldsFormFactory from coldfront.core.project.forms_.new_project_forms.request_forms import SavioProjectSurveyForm from coldfront.core.project.forms_.new_project_forms.approval_forms import SavioProjectReviewSetupForm @@ -21,6 +21,7 @@ from django.contrib.auth.models import User from coldfront.core.resource.utils_.allowance_utils.computing_allowance import ComputingAllowance from coldfront.core.resource.utils_.allowance_utils.interface import ComputingAllowanceInterface +from coldfront.core.user.models import UserProfile from coldfront.core.utils.common import display_time_zone_current_date from coldfront.core.utils.common import format_date_month_name_day_year from coldfront.core.utils.common import utc_now_offset_aware @@ -588,7 +589,7 @@ def is_checklist_complete(self): class SavioProjectReviewEligibilityView(LoginRequiredMixin, UserPassesTestMixin, SavioProjectRequestMixin, FormView): - form_class = ReviewStatusForm + form_class = ReviewEligibilityForm template_name = ( 'project/project_request/savio/project_review_eligibility.html') @@ -643,10 +644,26 @@ def form_valid(self, form): self.request_obj.pi.save() self.request_obj.save() except IntegrityError as e: - self.logger.error(f'User {form_data["email"]} unexpectedly exists.') + self.logger.error(f'User {form_data["email"]} ' + 'unexpectedly exists.') raise e + try: + pi_profile = self.request_obj.pi.userprofile + except UserProfile.DoesNotExist as e: + self.logger.error( + f'User {form_data["email"]} unexpectedly has no ' + 'UserProfile.') + raise e + pi_profile.middle_name = form_data['middle_name'] or None + pi_profile.upgrade_request = utc_now_offset_aware() + pi_profile.save() else: - message = 'PI information is incomplete.' + incomplete_fields = [field for field in ['email', 'first_name', + 'last_name'] if not form_data[field]] + message = \ + f'Incomplete field(s): {", ".join(incomplete_fields)}. ' \ + 'Please specify a PI or provide all required fields ' \ + 'for a new PI.' messages.error(self.request, message) return self.form_invalid(form) @@ -1247,7 +1264,7 @@ def is_checklist_complete(self): class VectorProjectReviewEligibilityView(LoginRequiredMixin, UserPassesTestMixin, VectorProjectRequestMixin, FormView): - form_class = ReviewStatusForm + form_class = ReviewEligibilityForm template_name = ( 'project/project_request/vector/project_review_eligibility.html') From 79942c2d736d5ef1bd1571c2e91eb61b0fdf5116 Mon Sep 17 00:00:00 2001 From: Hamza Kundi Date: Mon, 28 Oct 2024 10:53:52 -0700 Subject: [PATCH 6/7] fixed selectize --- .../project_request/savio/project_existing_pi.html | 10 ++++++++-- .../savio/project_review_eligibility.html | 14 ++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/coldfront/core/project/templates/project/project_request/savio/project_existing_pi.html b/coldfront/core/project/templates/project/project_request/savio/project_existing_pi.html index 88aa9050f..66f16762b 100644 --- a/coldfront/core/project/templates/project/project_request/savio/project_existing_pi.html +++ b/coldfront/core/project/templates/project/project_request/savio/project_existing_pi.html @@ -65,11 +65,10 @@

{{ PRIMARY_CLUSTER_NAME }}: Principal Investigator


@@ -97,4 +96,11 @@

{{ PRIMARY_CLUSTER_NAME }}: Principal Investigator


Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}

+ + {% endblock %} diff --git a/coldfront/core/project/templates/project/project_request/savio/project_review_eligibility.html b/coldfront/core/project/templates/project/project_request/savio/project_review_eligibility.html index ab1dff2fd..7bb387a80 100644 --- a/coldfront/core/project/templates/project/project_request/savio/project_review_eligibility.html +++ b/coldfront/core/project/templates/project/project_request/savio/project_review_eligibility.html @@ -9,6 +9,8 @@ {% block content %} + +

Review Eligibility


@@ -40,10 +42,10 @@

Cancel @@ -52,12 +54,4 @@

{% 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 %} From 2ac3640db8ef3c5aac43d50808277a60f180544d Mon Sep 17 00:00:00 2001 From: Hamza Kundi Date: Mon, 28 Oct 2024 10:58:05 -0700 Subject: [PATCH 7/7] fixed selectize wording --- coldfront/core/project/forms.py | 3 +-- .../project_request/savio/project_review_eligibility.html | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coldfront/core/project/forms.py b/coldfront/core/project/forms.py index f657e2ee6..3fee69d24 100644 --- a/coldfront/core/project/forms.py +++ b/coldfront/core/project/forms.py @@ -273,11 +273,10 @@ class ReviewEligibilityForm(ReviewStatusForm): queryset=User.objects.none(), required=False, widget=DisabledChoicesSelectWidget(), - empty_label='New PI', 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, select "New PI" and provide the PI\'s ' \ + '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) diff --git a/coldfront/core/project/templates/project/project_request/savio/project_review_eligibility.html b/coldfront/core/project/templates/project/project_request/savio/project_review_eligibility.html index 7bb387a80..0bae8bbaf 100644 --- a/coldfront/core/project/templates/project/project_request/savio/project_review_eligibility.html +++ b/coldfront/core/project/templates/project/project_request/savio/project_review_eligibility.html @@ -11,6 +11,7 @@ {% block content %} +

Review Eligibility


@@ -45,7 +46,7 @@

$('#id_PI').selectize({ create: false, sortField: 'text', - placeholder: 'Type to search.' + placeholder: 'Type to search or fill out the PI information below.' })