Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
142 changes: 142 additions & 0 deletions coldfront/core/project/forms_/new_project_forms/request_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -859,3 +859,145 @@ def clean_name(self):
raise forms.ValidationError(
f'A project with name {name} already exists.')
return name

# =============================================================================
# STANDALONE CLUSTERS
# =============================================================================

class StandaloneClusterDetailsForm(forms.Form):
name = forms.CharField(
help_text=(
'The unique name of the standalone cluster, which must contain only '
'lowercase letters and numbers.'),
label='Name',
max_length=12,
required=True,
validators=[
MinLengthValidator(4),
RegexValidator(
r'^[0-9a-z]+$',
message=(
'Name must contain only lowercase letters and numbers.'))
])
description = forms.CharField(
help_text='A few sentences describing your standalone cluster.',
label='Description',
validators=[MinLengthValidator(10)],
widget=forms.Textarea(attrs={'rows': 3}))

def clean_name(self):
cleaned_data = super().clean()
name = cleaned_data['name'].lower()
if Project.objects.filter(name=name):
raise forms.ValidationError(
f'A project with name {name} already exists.')
return name

class StandaloneClusterExistingPIForm(forms.Form):
PI = PIChoiceField(
label='Principal Investigator',
queryset=User.objects.none(),
required=False,
widget=DisabledChoicesSelectWidget())

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
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 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))

class StandaloneClusterNewPIForm(forms.Form):
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)

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()):
raise forms.ValidationError(
'A user with that email address already exists.')

if flag_enabled('LRC_ONLY'):
if not email.endswith('@lbl.gov'):
raise forms.ValidationError(
'New PI must be an LBL employee with an LBL email.')

return email

class StandaloneClusterExistingManagerForm(forms.Form):
manager = PIChoiceField(
label='Manager',
queryset=User.objects.none(),
required=False,
widget=DisabledChoicesSelectWidget())

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.exclude_manager_choices()

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

def exclude_manager_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['manager'].queryset = User.objects.exclude(
Q(email__isnull=True) | Q(email__exact='') | Q(is_active=False))

class StandaloneClusterNewManagerForm(forms.Form):
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)

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()):
raise forms.ValidationError(
'A user with that email address already exists.')

return email

class StandaloneClusterReviewAndSubmitForm(forms.Form):

confirmation = forms.BooleanField(
label=(
'I have reviewed my selections and understand the changes '
'described above. Submit my request.'),
required=True)

36 changes: 5 additions & 31 deletions coldfront/core/project/management/commands/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,16 @@

from flags.state import flag_enabled

from coldfront.core.allocation.models import Allocation
from coldfront.core.allocation.models import AllocationPeriod
from coldfront.core.allocation.models import AllocationAttribute
from coldfront.core.allocation.models import AllocationAttributeType
from coldfront.core.allocation.models import AllocationStatusChoice
from coldfront.core.allocation.models import AllocationRenewalRequest
from coldfront.core.allocation.models import AllocationRenewalRequestStatusChoice
from coldfront.core.allocation.utils import calculate_service_units_to_allocate
from coldfront.core.resource.utils_.allowance_utils.interface import ComputingAllowanceInterface
from coldfront.core.project.models import Project
from coldfront.core.project.models import ProjectStatusChoice
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 import is_primary_cluster_project
from coldfront.core.project.utils_.new_project_utils import create_project_with_compute_allocation
from coldfront.core.project.utils_.new_project_user_utils import NewProjectUserRunnerFactory
from coldfront.core.project.utils_.new_project_user_utils import NewProjectUserSource
from coldfront.core.project.utils_.renewal_utils import AllocationRenewalApprovalRunner
Expand All @@ -29,9 +24,8 @@
from coldfront.core.resource.models import Resource
from coldfront.core.resource.utils import get_primary_compute_resource_name
from coldfront.core.resource.utils_.allowance_utils.computing_allowance import ComputingAllowance
from coldfront.core.statistics.models import ProjectTransaction
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 display_time_zone_current_date
from coldfront.core.utils.common import utc_now_offset_aware
from coldfront.core.utils.email.email_strategy import DropEmailStrategy

Expand Down Expand Up @@ -141,10 +135,9 @@ def _create_project_with_compute_allocation_and_pis(project_name,
specified.
"""
with transaction.atomic():
project = Project.objects.create(
name=project_name,
title=project_name,
status=ProjectStatusChoice.objects.get(name='Active'))
project = create_project_with_compute_allocation(project_name,
compute_resource,
settings.ALLOCATION_MAX)

project_users = []
for pi_user in pi_users:
Expand All @@ -156,25 +149,6 @@ def _create_project_with_compute_allocation_and_pis(project_name,
status=ProjectUserStatusChoice.objects.get(name='Active'))
project_users.append(project_user)

allocation = Allocation.objects.create(
project=project,
status=AllocationStatusChoice.objects.get(name='Active'),
start_date=display_time_zone_current_date(),
end_date=None)
allocation.resources.add(compute_resource)

num_service_units = settings.ALLOCATION_MAX
AllocationAttribute.objects.create(
allocation_attribute_type=AllocationAttributeType.objects.get(
name='Service Units'),
allocation=allocation,
value=str(num_service_units))

ProjectTransaction.objects.create(
project=project,
date_time=utc_now_offset_aware(),
allocation=num_service_units)

runner_factory = NewProjectUserRunnerFactory()
for project_user in project_users:
runner = runner_factory.get_runner(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{% extends "common/base.html" %}
{% load crispy_forms_tags %}
{% load static %}


{% block title %}
New Standalone Cluster - Project Details
{% endblock %}


{% block head %}
{{ wizard.form.media }}
{% endblock %}


{% block content %}
<script type="text/javascript" src="{% static 'common/js/leave_form_alert.js' %}"></script>

<h1>New Standalone Cluster Details</h1><hr>

<p>You are creating a new standalone cluster. Please provide the following details:</p>

<form action="" method="post">
{% csrf_token %}
<table>
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{ form|crispy }}
{% endfor %}
{% else %}
{{ wizard.form|crispy }}
{% endif %}
</table>
{% if wizard.steps.prev %}
<button
class="btn btn-secondary"
formnovalidate="formnovalidate"
name="wizard_goto_step"
type="submit"
value="{{ wizard.steps.first }}">
First Step
</button>
<button
class="btn btn-secondary"
formnovalidate="formnovalidate"
name="wizard_goto_step"
type="submit"
value="{{ wizard.steps.prev }}">
Previous Step
</button>
{% endif %}
<input class="btn btn-primary" type="submit" value="Next Step"/>
</form>
<br>

<p>Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</p>

{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{% extends "common/base.html" %}
{% load feature_flags %}
{% load crispy_forms_tags %}
{% load static %}


{% block title %}
New Standalone Cluster - Existing Manager
{% endblock %}


{% block head %}
{{ wizard.form.media }}
{% endblock %}


{% 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>New Standalone Cluster: Manager</h1><hr>

<p>Select an existing user to be a Manager of the project. You may search for the user in the selection field. If the desired user is not listed, you may skip this step and specify information for a new Manager in the next step.</p>

<form action="" method="post">
{% csrf_token %}
<table>
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{ form|crispy }}
{% endfor %}
{% else %}
{{ wizard.form|crispy }}
{% endif %}
</table>
{% if wizard.steps.prev %}
<button
class="btn btn-secondary"
formnovalidate="formnovalidate"
name="wizard_goto_step"
type="submit"
value="{{ wizard.steps.first }}">
First Step
</button>
<button
class="btn btn-secondary"
formnovalidate="formnovalidate"
name="wizard_goto_step"
type="submit"
value="{{ wizard.steps.prev }}">
Previous Step
</button>
{% endif %}
<input class="btn btn-primary" type="submit" value="Next Step"/>
</form>
<br>

<p>Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</p>

<script>
$('select').selectize({
create: false,
sortField: 'text'
})
</script>

{% endblock %}
Loading