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

Development #334

Merged
merged 15 commits into from
Aug 29, 2024
Merged
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
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
Loading