Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 595: Allow admins to edit a new project request's PI #597

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion coldfront/core/project/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
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
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
Expand Down Expand Up @@ -209,8 +212,15 @@ class ReviewDenyForm(forms.Form):
widget=forms.Textarea(attrs={'rows': 3}))


class ReviewStatusForm(forms.Form):
class ReviewStatusForm(request_forms.SavioProjectNewPIForm,
Hzaakk marked this conversation as resolved.
Show resolved Hide resolved
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.'),
Expand All @@ -231,6 +241,21 @@ 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
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):
return f'{obj.first_name} {obj.last_name} ({obj.email})'

def clean(self):
cleaned_data = super().clean()
status = cleaned_data.get('status', 'Pending')
Expand Down
22 changes: 20 additions & 2 deletions coldfront/core/project/forms_/new_project_forms/request_forms.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -147,6 +146,24 @@ def label_from_instance(self, obj):
return f'{obj.first_name} {obj.last_name} ({obj.email})'


class DisabledChoicesSelectWidget(forms.Select):
Hzaakk marked this conversation as resolved.
Show resolved Hide resolved

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(
Expand Down Expand Up @@ -234,7 +251,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.')
Expand Down
33 changes: 30 additions & 3 deletions coldfront/core/project/views_/new_project_views/approval_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,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
Expand All @@ -32,7 +33,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
Expand Down Expand Up @@ -591,6 +592,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:
Expand All @@ -606,7 +609,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']
Expand All @@ -622,9 +625,32 @@ def form_valid(self, form):
if status == 'Denied':
runner = ProjectDenialRunner(self.request_obj)
runner.run()

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)

self.request_obj.save()

message = (
f'Eligibility status for request {self.request_obj.pk} has been '
f'set to {status}.')
Expand All @@ -642,6 +668,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.pk, 'status': 'Approved' }
readiness = { 'status': 'Approved' }
extra_fields = {
'course_name': 'TEST 101',
Expand Down
Loading