Skip to content

Commit

Permalink
Merge pull request #334 from fasrc/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
claire-peters authored Aug 29, 2024
2 parents a89bdb2 + 921f461 commit 9f319b2
Show file tree
Hide file tree
Showing 27 changed files with 494 additions and 269 deletions.
3 changes: 2 additions & 1 deletion coldfront/core/allocation/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,8 @@ def __init__(self, *args, **kwargs):
else:
if allo_resource.resource_type.name == 'Storage Tier':
self.fields['resource'].queryset = Resource.objects.filter(
parent_resource=allo_resource
parent_resource=allo_resource,
is_allocatable=True
)
else:
self.fields['resource'].required = False
Expand Down
8 changes: 8 additions & 0 deletions coldfront/core/allocation/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ def save(self, *args, **kwargs):
def offer_letter_code(self):
return self.get_attribute('Offer Letter Code')

@property
def requires_payment(self):
requires_payment = self.get_attribute('Requires Payment')
if requires_payment == None:
return self.get_parent_resource.requires_payment

@property
def fairshare(self):
return self.get_attribute('FairShare')
Expand Down Expand Up @@ -270,6 +276,8 @@ def cost(self):
return None
except TypeError:
return None
except ObjectDoesNotExist:
return None
size_attr_name = self._return_size_attr_name()
if not size_attr_name:
return None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ <h3><i class="fas fa-list" aria-hidden="true"></i> Allocation Information</h3>
</td>
</tr>
<tr>
<th scope="row" class="text-nowrap">Resource{{ allocation.resources.all|pluralize }} in allocation:</th>
<th scope="row" class="text-nowrap">Allocated Resource{{ allocation.resources.all|pluralize }}:</th>
<td>
{% if allocation.get_resources_as_list %}
{% for resource in allocation.get_resources_as_list %}
Expand Down Expand Up @@ -129,7 +129,7 @@ <h3><i class="fas fa-list" aria-hidden="true"></i> Allocation Information</h3>
</td>
</tr>
{% endif %}
{% if "Storage" in allocation.get_parent_resource.resource_type.name %}
{% if invoice %}
<tr>
<th scope="row" class="text-nowrap">FIINE Expense Codes:</th>
<td>
Expand All @@ -140,7 +140,7 @@ <h3><i class="fas fa-list" aria-hidden="true"></i> Allocation Information</h3>
</tr>
{% endif %}
<tr>
<th scope="row" class="text-nowrap">Total Users in Your Bill:</th>
<th scope="row" class="text-nowrap">Total Users with Usage:</th>
<td>{{ allocation_users.count }}</td>
</tr>
<tr>
Expand All @@ -164,13 +164,15 @@ <h3><i class="fas fa-list" aria-hidden="true"></i> Allocation Information</h3>
<th scope="row" class="text-nowrap">Quota (TB):</th>
<td id="group_quota">{{ allocation.size|floatformat:2 }}</td>
</tr>
<tr>
<th style="background-color:#D3D3D3" bordercolor="red" scope="row" class="text-nowrap">Total Amount Due: </th>
{% cost_tb allocation.size as cost %}
{% if cost %}
<td style="background-color:#D3D3D3">{{ cost }}</td>
{% endif %}
</tr>
{% if invoice %}
<tr>
<th style="background-color:#D3D3D3" bordercolor="red" scope="row" class="text-nowrap">Total Amount Due: </th>
{% cost_tb allocation.size as cost %}
{% if cost %}
<td style="background-color:#D3D3D3">{{ cost }}</td>
{% endif %}
</tr>
{% endif %}
{% endif %}
<tr>
<th scope="row" class="text-nowrap">Start Date:</th>
Expand Down Expand Up @@ -489,7 +491,9 @@ <h3 class="d-inline"><i class="fas fa-users" aria-hidden="true"></i> Users in Al
{% if "Storage" in allocation.get_parent_resource.resource_type.name %}
<th scope="col">Logical Usage</th>
<th scope="col">Percent Usage</th>
<th scope="col">Cost Per User (TB/month)</th>
{% if invoice %}
<th scope="col">Cost Per User (TB/month)</th>
{% endif %}
{% else %}
<th scope="col">CPU Hours</th>
<th scope="col">Percent Usage</th>
Expand Down Expand Up @@ -546,15 +550,16 @@ <h3 class="d-inline"><i class="fas fa-users" aria-hidden="true"></i> Users in Al
<td>{{userusage|div:allocationusage|mul:100|floatformat:2 }}%</td>
{% endif %}

{% if "Storage" in allocation.get_parent_resource.resource_type.name %}
{% if invoice %}
{% cost_bytes userusage as cost %}
{% if cost %}
<td>{{ cost }}</td>
{% endif %}
{% else %}
<td>{{ user.effectvusage }}</td>
<td>{{ user.normshares }}</td>
<td>{{ user.fairshare }}</td>
{% endif %}
{% if 'Cluster' in allocation.get_parent_resource.resource_type.name %}
<td>{{ user.effectvusage }}</td>
<td>{{ user.normshares }}</td>
<td>{{ user.fairshare }}</td>
{% endif %}
</tr>
{% endfor %}
Expand Down
72 changes: 56 additions & 16 deletions coldfront/core/allocation/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
Allocation,
AllocationUserNote,
AllocationAttribute,
AllocationAttributeType,
AllocationStatusChoice,
AllocationChangeRequest,
)
from coldfront.core.resource.models import Resource
from coldfront.core.test_helpers.factories import (
setup_models,
UserFactory,
Expand Down Expand Up @@ -74,7 +77,7 @@ def setUpTestData(cls):
AllocationFactory() for i in list(range(50))
]
for allocation in cls.additional_allocations:
allocation.resources.add(ResourceFactory(name='holylfs09/tier1', id=2))
allocation.resources.add(Resource.objects.get(name='holylfs09/tier1'))
cls.nonproj_nonallocation_user = UserFactory(username='rdrake')

def test_allocation_list_access_admin(self):
Expand All @@ -92,7 +95,7 @@ def test_allocation_list_access_pi(self):
# confirm that show_all_allocations=on enables admin to view all allocations
self.client.force_login(self.pi_user, backend=BACKEND)
response = self.client.get("/allocation/?show_all_allocations=on")
self.assertEqual(len(response.context['item_list']), 1)
self.assertEqual(len(response.context['item_list']), 2)

def test_allocation_list_access_manager(self):
"""Confirm that AllocationList access control works for managers
Expand All @@ -102,11 +105,11 @@ def test_allocation_list_access_manager(self):
# confirm that show_all_allocations=on enables admin to view all allocations
self.client.force_login(self.proj_datamanager, backend=BACKEND)
response = self.client.get("/allocation/?show_all_allocations=on")
self.assertEqual(len(response.context['item_list']), 1)
self.assertEqual(len(response.context['item_list']), 2)
# confirm that show_all_allocations=on enables admin to view all allocations
self.client.force_login(self.proj_accessmanager, backend=BACKEND)
response = self.client.get("/allocation/?show_all_allocations=on")
self.assertEqual(len(response.context['item_list']), 1)
self.assertEqual(len(response.context['item_list']), 2)

def test_allocation_list_access_user(self):
"""Confirm that AllocationList access control works for non-pi users
Expand All @@ -117,21 +120,21 @@ def test_allocation_list_access_user(self):
# contains only the user's allocations
self.client.force_login(self.proj_allocation_user, backend=BACKEND)
response = self.client.get("/allocation/")
self.assertEqual(len(response.context['item_list']), 1)
self.assertEqual(len(response.context['item_list']), 2)
response = self.client.get("/allocation/?show_all_allocations=on")
self.assertEqual(len(response.context['item_list']), 1)
self.assertEqual(len(response.context['item_list']), 2)

# allocation user not belonging to project can see allocation
self.client.force_login(self.nonproj_allocation_user, backend=BACKEND)
response = self.client.get("/allocation/")
self.assertEqual(len(response.context['item_list']), 1)
self.assertEqual(len(response.context['item_list']), 2)
response = self.client.get("/allocation/?show_all_allocations=on")
self.assertEqual(len(response.context['item_list']), 1)
self.assertEqual(len(response.context['item_list']), 2)

# nonallocation user belonging to project can see allocation
self.client.force_login(self.proj_nonallocation_user, backend=BACKEND)
response = self.client.get("/allocation/?show_all_allocations=on")
self.assertEqual(len(response.context['item_list']), 1)
self.assertEqual(len(response.context['item_list']), 2)

# nonallocation user not belonging to project can't see allocation
self.client.force_login(self.nonproj_nonallocation_user, backend=BACKEND)
Expand Down Expand Up @@ -280,8 +283,10 @@ def setUp(self):

def test_allocation_detail_access(self):
self.allocation_access_tstbase(self.url)
utils.test_user_can_access(self, self.pi_user, self.url) # PI can access
# pi, project nonallocation user, nonproj_allocation_user can access
utils.test_user_can_access(self, self.pi_user, self.url)
utils.test_user_can_access(self, self.proj_nonallocation_user, self.url)
utils.test_user_can_access(self, self.nonproj_allocation_user, self.url)
# check access for allocation user with "Removed" status

def test_allocation_detail_template_value_render(self):
Expand Down Expand Up @@ -328,18 +333,19 @@ def test_allocationattribute_button_visibility(self):
def test_allocationuser_button_visibility(self):
"""Test visibility of "Add/Remove Users" buttons for different user types"""
# we're removing these buttons for everybody, to avoid confusion re: procedure for user addition/removal
# admin can't see add/remove users buttons
utils.page_does_not_contain_for_user(
self, self.admin_user, self.url, 'Add Users'
)
utils.page_does_not_contain_for_user(
self, self.admin_user, self.url, 'Remove Users'
)
# pi
# pi can't see add/remove users buttons
utils.page_does_not_contain_for_user(self, self.pi_user, self.url, 'Add Users')
utils.page_does_not_contain_for_user(
self, self.pi_user, self.url, 'Remove Users'
)
# allocation user
# allocation user can't see add/remove users buttons
utils.page_does_not_contain_for_user(
self, self.proj_allocation_user, self.url, 'Add Users'
)
Expand All @@ -348,6 +354,40 @@ def test_allocationuser_button_visibility(self):
)


class AllocationDetailViewPostTest(AllocationViewBaseTest):
def setUp(self):
self.new_allocation = AllocationFactory(
project=self.project, quantity=20, justification='test new allocation',
status=AllocationStatusChoice.objects.get(name='New')
)
self.new_allocation.resources.add(Resource.objects.get(name='holylfs09/tier1'))
quotatb = AllocationAttributeType.objects.get(name='Storage Quota (TB)')
self.new_allocation.allocationattribute_set.create(
value=20, allocation_attribute_type=quotatb
)
self.url = f'/allocation/{self.new_allocation.pk}/'

def test_allocationdetail_approval_post(self):
"""test approval of new allocation"""
self.client.force_login(self.admin_user)
form_data = {
'status': AllocationStatusChoice.objects.get(name='Active').pk,
#'start_date': self.new_allocation.start_date,
#'end_date': self.new_allocation.end_date,
'description': "description test",
'is_locked': False,
'is_changeable': True,
'resource': self.new_allocation.resources.first().pk,
'action': 'approve',
'auto_create_opts': '1',
}
response = self.client.post(self.url, data=form_data, follow=True)
# confirm that messages in response contains "Allocation Activated"
self.assertContains(response, 'Allocation Activated')
self.new_allocation.refresh_from_db()
self.assertEqual(self.new_allocation.status.name, 'Active')


class AllocationCreateViewTest(AllocationViewBaseTest):
"""Tests for the AllocationCreateView"""

Expand All @@ -373,20 +413,20 @@ def test_allocationcreateview_access(self):

def test_allocationcreateview_post(self):
"""Test POST to the AllocationCreateView"""
self.assertEqual(len(self.project.allocation_set.all()), 1)
self.assertEqual(len(self.project.allocation_set.all()), 2)
response = self.client.post(self.url, data=self.post_data, follow=True)
self.assertContains(response, "Allocation requested.")
self.assertEqual(len(self.project.allocation_set.all()), 2)
self.assertEqual(len(self.project.allocation_set.all()), 3)

def test_allocationcreateview_post_zeroquantity(self):
"""Test POST to the AllocationCreateView with default post_data:
No expense_code, dua, heavy_io, mounted, external_sharing, high_security
"""
self.post_data['quantity'] = '0'
self.assertEqual(len(self.project.allocation_set.all()), 1)
self.assertEqual(len(self.project.allocation_set.all()), 2)
response = self.client.post(self.url, data=self.post_data, follow=True)
self.assertContains(response, "Allocation requested.")
self.assertEqual(len(self.project.allocation_set.all()), 2)
self.assertEqual(len(self.project.allocation_set.all()), 3)

def test_allocationcreateview_post_offerlettercode_valid(self):
"""ensure 33-digit codes go through and get formatted"""
Expand Down
11 changes: 11 additions & 0 deletions coldfront/core/allocation/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ def get_context_data(self, **kwargs):
context['expense_codes'] = expense_codes

offer_letter_code_type = AllocationAttributeType.objects.get(name="Expense Code")
context['invoice'] = (
allocation_obj.requires_payment
and allocation_obj.status.name in ACTIVE_ALLOCATION_STATUSES
and "Storage" in allocation_obj.get_parent_resource.resource_type.name
)
context['expense_code'] = allocation_obj.allocationattribute_set.filter(
allocation_attribute_type=offer_letter_code_type
)
Expand Down Expand Up @@ -269,6 +274,7 @@ def post(self, request, *args, **kwargs):
err = 'You do not have permission to update the allocation'
messages.error(request, err)
return HttpResponseRedirect(reverse('allocation-detail', kwargs={'pk': pk}))

initial_data = {
'status': allocation_obj.status,
'end_date': allocation_obj.end_date,
Expand Down Expand Up @@ -356,6 +362,11 @@ def post(self, request, *args, **kwargs):
allocation_obj.resources.add(resource)

allocation_obj.status = AllocationStatusChoice.objects.get(name='Active')
AllocationAttribute.objects.get_or_create(
allocation=allocation_obj,
allocation_attribute_type=AllocationAttributeType.objects.get(name='RequiresPayment'),
defaults={'value': resource.requires_payment}
)

elif action == 'deny':
allocation_obj.status = AllocationStatusChoice.objects.get(name='Denied')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ <h3 class="d-inline"><i class="fas fa-users" aria-hidden="true"></i>Quarterly Cl
<th scope="col">Project</th>
<th scope="col">Allocation</th>
<th scope="col">Users</th>
<th scope="col">Usage</th>
<th scope="col">Usage (CPU Hours)</th>
<!-- <th scope="col">Quarterly Cost to Date</th> -->
</tr>
</thead>
Expand Down
5 changes: 4 additions & 1 deletion coldfront/core/department/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,10 @@ def get_context_data(self, **kwargs):
status__name='Active',
)
p.storage_allocs = p.allocs.filter(
resources__resource_type__name='Storage')
resources__resource_type__name='Storage',
allocationattribute__allocation_attribute_type__name='RequiresPayment',
allocationattribute__value='True'
)
p.compute_allocs = p.allocs.filter(
resources__resource_type__name='Cluster')
storage_pi_dict[p.pi].extend(list(p.storage_allocs))
Expand Down
Loading

0 comments on commit 9f319b2

Please sign in to comment.