From 9c6e0a6e3a8a60dd1724e46928e4c18fb8d88c9b Mon Sep 17 00:00:00 2001 From: Claire Peters Date: Thu, 26 Oct 2023 13:01:34 -0700 Subject: [PATCH 01/22] update tasks to use send_email_template --- coldfront/core/allocation/tasks.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/coldfront/core/allocation/tasks.py b/coldfront/core/allocation/tasks.py index ac9face92..b83f49c2b 100644 --- a/coldfront/core/allocation/tasks.py +++ b/coldfront/core/allocation/tasks.py @@ -31,6 +31,7 @@ EMAIL_ADMINS_ON_ALLOCATION_EXPIRE = import_from_settings('EMAIL_ADMINS_ON_ALLOCATION_EXPIRE') EMAIL_ADMIN_LIST = import_from_settings('EMAIL_ADMIN_LIST') +ADMIN_REMINDER_EMAIL = import_from_settings('ADMIN_REMINDER_EMAIL') def update_statuses(): @@ -69,12 +70,14 @@ def send_request_reminder_emails(): 'signature': EMAIL_SIGNATURE, 'url_base': f'{CENTER_BASE_URL.strip("/")}/allocation/change-request/' } - send_admin_email_template( - 'Pending Allocation Changes', - 'email/pending_allocation_changes.txt', - allocation_change_template_context, - ) + send_email_template( + subject='Pending Allocation Changes', + template_name='email/pending_allocation_changes.txt', + template_context=allocation_change_template_context, + sender=EMAIL_SENDER, + receiver_list=[ADMIN_REMINDER_EMAIL,], + ) # Allocation Requests are allocations marked as "new" pending_allocations = Allocation.objects.filter( status__name = 'New', created__lte=req_alert_date @@ -87,11 +90,15 @@ def send_request_reminder_emails(): 'signature': EMAIL_SIGNATURE, 'url_base': f'{CENTER_BASE_URL.strip("/")}/allocation/' } - send_admin_email_template( - 'Pending Allocations', - 'email/pending_allocations.txt', - new_allocation_template_context, + + send_email_template( + subject='Pending Allocations', + template_name='email/pending_allocations.txt', + template_context=new_allocation_template_context, + sender=EMAIL_SENDER, + receiver_list=[ADMIN_REMINDER_EMAIL,], ) + # return statement for testing return (pending_changerequests, pending_allocations) From d133693e98a807c3e19ce2ec1c1c203a30e8fcec Mon Sep 17 00:00:00 2001 From: Claire Peters Date: Fri, 27 Oct 2023 21:55:44 -0700 Subject: [PATCH 02/22] add handling for Tier3 quantity divisibility --- .../allocation/templates/allocation/allocation_detail.html | 4 ++-- coldfront/core/allocation/views.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/coldfront/core/allocation/templates/allocation/allocation_detail.html b/coldfront/core/allocation/templates/allocation/allocation_detail.html index de95b96c6..179984b36 100644 --- a/coldfront/core/allocation/templates/allocation/allocation_detail.html +++ b/coldfront/core/allocation/templates/allocation/allocation_detail.html @@ -115,11 +115,11 @@

Allocation Information

Service Period: 1 Month - {% if offer_letter_code %} + {% if expense_code %} Requested Expense Code: - {% for code in offer_letter_code %} + {% for code in expense_code %} {{ code.value }}
{% endfor %} diff --git a/coldfront/core/allocation/views.py b/coldfront/core/allocation/views.py index c6ffc986f..49775cbe1 100644 --- a/coldfront/core/allocation/views.py +++ b/coldfront/core/allocation/views.py @@ -608,6 +608,10 @@ def form_valid(self, form): quantity = form_data.get('quantity', 1) allocation_account = form_data.get('allocation_account', None) + if resource_obj.name == "Tier 3" and quantity % 20 != 0: + form.add_error("quantity", format_html("Tier 3 quantity must be a multiple of 20.")) + return self.form_invalid(form) + # A resource is selected that requires an account name selection but user has no account names if ( ALLOCATION_ACCOUNT_ENABLED From 2d3569d7992061df44aa3b905a1b43dbcdc1e8ad Mon Sep 17 00:00:00 2001 From: Claire Peters Date: Mon, 30 Oct 2023 15:44:53 -0700 Subject: [PATCH 03/22] hotfix: require nese shares to be divisible by 20 --- coldfront/core/allocation/views.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/coldfront/core/allocation/views.py b/coldfront/core/allocation/views.py index 49775cbe1..f5e2acdd6 100644 --- a/coldfront/core/allocation/views.py +++ b/coldfront/core/allocation/views.py @@ -1864,7 +1864,6 @@ def post(self, request, *args, **kwargs): if new_value != attribute_change.new_value: attribute_change.new_value = new_value attribute_change.save() - if action == 'update': message = 'Allocation change request updated!' if action == 'approve': @@ -2055,6 +2054,13 @@ def post(self, request, *args, **kwargs): formset_data = entry.cleaned_data new_value = formset_data.get('new_value') + # require nese shares to be divisible by 20 + tbs = int(new_value) if formset_data['name'] == 'Storage Quota (TB)' else False + nese = bool(allocation_obj.resources.filter(name__contains="nesetape")) + if nese and tbs and tbs % 20 != 0: + messages.error(request, "Tier 3 quantity must be a multiple of 20.") + return HttpResponseRedirect(reverse('allocation-change', kwargs={'pk': pk})) + if new_value != '': change_requested = True allocation_attribute = AllocationAttribute.objects.get( From dce6997f29051d5b55c196ff460a7bb16237f6b1 Mon Sep 17 00:00:00 2001 From: Claire Peters Date: Mon, 30 Oct 2023 17:18:45 -0700 Subject: [PATCH 04/22] add admin_reminder_email to settings --- coldfront/config/email.py | 1 + 1 file changed, 1 insertion(+) diff --git a/coldfront/config/email.py b/coldfront/config/email.py index aee4d5f0d..6d9db2978 100644 --- a/coldfront/config/email.py +++ b/coldfront/config/email.py @@ -22,3 +22,4 @@ EMAIL_ALLOCATION_EXPIRING_NOTIFICATION_DAYS = ENV.list('EMAIL_ALLOCATION_EXPIRING_NOTIFICATION_DAYS', cast=int, default=[7, 14, 30]) EMAIL_SIGNATURE = ENV.str('EMAIL_SIGNATURE', default='', multiline=True) EMAIL_ADMINS_ON_ALLOCATION_EXPIRE = ENV.bool('EMAIL_ADMINS_ON_ALLOCATION_EXPIRE', default=False) +ADMIN_REMINDER_EMAIL = ENV.str('ADMIN_REMINDER_EMAIL', default='') From a20f84b7b4ea0f24daa3f77637c37fa083487b02 Mon Sep 17 00:00:00 2001 From: Claire Peters Date: Tue, 31 Oct 2023 13:21:07 -0700 Subject: [PATCH 05/22] improve nese ticket generation --- coldfront/core/allocation/views.py | 13 +++++++++---- coldfront/core/utils/mail.py | 5 +++-- coldfront/templates/email/allocation_renewed.txt | 3 ++- .../email/new_allocation_change_request.txt | 10 +++++----- .../templates/email/new_allocation_request.txt | 8 ++++---- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/coldfront/core/allocation/views.py b/coldfront/core/allocation/views.py index f5e2acdd6..ce59ba295 100644 --- a/coldfront/core/allocation/views.py +++ b/coldfront/core/allocation/views.py @@ -2049,6 +2049,10 @@ def post(self, request, *args, **kwargs): if form_data.get('end_date_extension') != 0: change_requested = True + + # if requested resource is on NESE, add to vars + nese = bool(allocation_obj.resources.filter(name__contains="nesetape")) + if attrs_to_change: for entry in formset: formset_data = entry.cleaned_data @@ -2056,7 +2060,6 @@ def post(self, request, *args, **kwargs): new_value = formset_data.get('new_value') # require nese shares to be divisible by 20 tbs = int(new_value) if formset_data['name'] == 'Storage Quota (TB)' else False - nese = bool(allocation_obj.resources.filter(name__contains="nesetape")) if nese and tbs and tbs % 20 != 0: messages.error(request, "Tier 3 quantity must be a multiple of 20.") return HttpResponseRedirect(reverse('allocation-change', kwargs={'pk': pk})) @@ -2097,17 +2100,19 @@ def post(self, request, *args, **kwargs): for a in attribute_changes_to_make if a[0].allocation_attribute_type.name == 'Storage Quota (TB)' ] - # if requested resource is on NESE, add to vars - nese = bool(allocation_obj.resources.filter(name__contains="nesetape")) email_vars = {'justification': justification} if quantity: quantity_num = int(float(quantity[0][1])) difference = quantity_num - int(float(allocation_obj.size)) used_percentage = allocation_obj.get_parent_resource.used_percentage + current_size = allocation_obj.size + if nese: + current_size = round(current_size, -1) + difference = round(difference, -1) email_vars['quantity'] = quantity_num email_vars['nese'] = nese - email_vars['current_size'] = allocation_obj.size + email_vars['current_size'] = current_size email_vars['difference'] = difference email_vars['used_percentage'] = used_percentage diff --git a/coldfront/core/utils/mail.py b/coldfront/core/utils/mail.py index 5a653f7d9..977ac188b 100644 --- a/coldfront/core/utils/mail.py +++ b/coldfront/core/utils/mail.py @@ -106,11 +106,12 @@ def send_allocation_admin_email( url_path = reverse('allocation-request-list') url = build_link(url_path, domain_url=domain_url) - pi_name = f'{allocation_obj.project.pi.first_name} {allocation_obj.project.pi.last_name} ({allocation_obj.project.pi.username})' + pi_name = f'{allocation_obj.project.pi.first_name} {allocation_obj.project.pi.last_name}' resource_name = allocation_obj.get_parent_resource ctx = email_template_context() - ctx['pi'] = pi_name + ctx['pi_name'] = pi_name + ctx['pi_username'] = f'{allocation_obj.project.pi.username}' ctx['resource'] = resource_name ctx['url'] = url if other_vars: diff --git a/coldfront/templates/email/allocation_renewed.txt b/coldfront/templates/email/allocation_renewed.txt index c08a1cf0c..4671c63fa 100644 --- a/coldfront/templates/email/allocation_renewed.txt +++ b/coldfront/templates/email/allocation_renewed.txt @@ -1,2 +1,3 @@ -A allocation has been renewed for {{pi}} - {{resource}}. Please activate the allocation: +A allocation has been renewed for {{pi_name}} ({{pi_username}}) - {{resource}}. +Please activate the allocation: {{url}} diff --git a/coldfront/templates/email/new_allocation_change_request.txt b/coldfront/templates/email/new_allocation_change_request.txt index 2a2fc1003..2feed9f94 100644 --- a/coldfront/templates/email/new_allocation_change_request.txt +++ b/coldfront/templates/email/new_allocation_change_request.txt @@ -1,5 +1,5 @@ {% load mathfilters %} -An allocation change request for has been made for {{pi}} - {{resource}}. +An allocation change request for has been made for {{pi_name}} ({{pi_username}}) - {{resource}}. {% if quantity %} Requested total size: {{quantity}} TB. @@ -11,14 +11,14 @@ This will require adding {{difference|floatformat}} TB to the current size of {{ {% endif %} {% if nese %} -Here is a draft ticket to send to NESE: +Here is a draft ticket to send to NESE. Please carefully check the values to ensure their accuracy. To: help@nese.mghpcc.org Subject: TB Globus tape setup for HU -Service (access type) = S3 vs Globus: -If Globus, short description for Globus Share: -Tennant name (8 char max - Ex. acohen): {{pi.username}} +Service (access type) = S3 vs Globus: Globus +If Globus, short description for Globus Share: {{pi_name}} +Tennant name (11 char max - Ex. acohen): {{pi_username}} Size in 20TB allotments: {{quantity|div:19.48|floatformat:"0"}} (This is a difference of {{difference|div:19.48|floatformat:"0"}} 20TB units from the current size of {{current_size|div:19.48|floatformat:"0"}} 20TB units) Any additional details: Any additional administrators (if applicable): diff --git a/coldfront/templates/email/new_allocation_request.txt b/coldfront/templates/email/new_allocation_request.txt index 1b280e49f..8c09761d3 100644 --- a/coldfront/templates/email/new_allocation_request.txt +++ b/coldfront/templates/email/new_allocation_request.txt @@ -1,5 +1,5 @@ {% load mathfilters %} -A new allocation has been requested for {{pi}} - {{resource}}. +A new allocation has been requested for {{pi_name}} ({{pi_username}}) - {{resource}}. Requested size: {{quantity}} TB. Justification: {{justification}} @@ -14,9 +14,9 @@ Here is a draft ticket to send to NESE: To: help@nese.mghpcc.org Subject: TB Globus tape setup for HU -Service (access type) = S3 vs Globus: -If Globus, short description for Globus Share: -Tennant name (8 char max - Ex. acohen): {{pi.username}} +Service (access type) = S3 vs Globus: Globus +If Globus, short description for Globus Share: {{pi_name}} +Tennant name (8 char max - Ex. acohen): {{pi_username}} Size in 20TB allotments: {{quantity|div:19.48|floatformat:"0"}} Any additional details: Any additional administrators (if applicable): From bcd10af7d075e1d6465ff0d6f4aff608ab37c0b0 Mon Sep 17 00:00:00 2001 From: Claire Peters Date: Wed, 1 Nov 2023 13:49:23 -0700 Subject: [PATCH 06/22] add new tier2 resources --- .../management/commands/add_resource_defaults.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/coldfront/core/resource/management/commands/add_resource_defaults.py b/coldfront/core/resource/management/commands/add_resource_defaults.py index be85ddb1b..f1250fe9f 100644 --- a/coldfront/core/resource/management/commands/add_resource_defaults.py +++ b/coldfront/core/resource/management/commands/add_resource_defaults.py @@ -75,6 +75,7 @@ def handle(self, *args, **options): for name, desc, is_public, rtype, parent_name, default_value in ( ('Tier 0', 'Bulk - Lustre', True, storage_tier, None, 1), ('Tier 1', 'Enterprise - Isilon', True, storage_tier, None, 1), + ('Tier 2', 'CEPH storage', True, storage_tier, None, 1), ('Tier 3', 'Attic Storage - Tape', True, storage_tier, None, 20), ('holylfs04/tier0', 'Holyoke data center lustre storage', True, storage, 'Tier 0', 1), ('holylfs05/tier0', 'Holyoke data center lustre storage', True, storage, 'Tier 0', 1), @@ -82,6 +83,18 @@ def handle(self, *args, **options): ('holy-isilon/tier1', 'Tier1 storage with snapshots and disaster recovery copy', True, storage, 'Tier 1', 1), ('bos-isilon/tier1', 'Tier1 storage for on-campus storage mounting', True, storage, 'Tier 1', 1), ('holystore01/tier0', 'Luster storage under Tier0', True, storage, 'Tier 0', 1), + ('b-nfs02-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1), + ('b-nfs03-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1), + ('b-nfs04-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1), + ('b-nfs05-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1), + ('b-nfs06-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1), + ('b-nfs07-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1), + ('b-nfs08-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1), + ('b-nfs09-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1), + ('h-nfs16-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1), + ('h-nfs17-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1), + ('h-nfs18-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1), + ('h-nfs19-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1), ): resource_defaults = { From 3b34e1f74c92d648947ce71cffceba0f21460d3b Mon Sep 17 00:00:00 2001 From: Aaron Kitzmiller Date: Thu, 2 Nov 2023 10:55:10 -0400 Subject: [PATCH 07/22] Fix product creation Force collectstatic --- coldfront/plugins/ifx/models.py | 5 +++-- container_startup.sh | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/coldfront/plugins/ifx/models.py b/coldfront/plugins/ifx/models.py index 6da48207a..1c0fd6d82 100644 --- a/coldfront/plugins/ifx/models.py +++ b/coldfront/plugins/ifx/models.py @@ -136,12 +136,13 @@ def resource_post_save(sender, instance, **kwargs): except ProductResource.DoesNotExist: # Need to create a Product and ProductResource for this Resource products = FiineAPI.listProducts(product_name=instance.name) + facility = Facility.objects.get(name='Research Computing Storage') if not products: - facility = Facility.objects.get(name='Research Computing Storage') product = create_new_product(product_name=instance.name, product_description=instance.name, facility=facility) else: fiine_product = products[0].to_dict() - fiine_product.pop('facility') + fiine_product.pop('object_code_category') + fiine_product['facility'] = facility fiine_product['billing_calculator'] = 'coldfront.plugins.ifx.calculator.NewColdfrontBillingCalculator' (product, created) = Product.objects.get_or_create(**fiine_product) product_resource = ProductResource.objects.create(product=product, resource=instance) diff --git a/container_startup.sh b/container_startup.sh index 6e2f289de..78668d236 100644 --- a/container_startup.sh +++ b/container_startup.sh @@ -9,7 +9,7 @@ service redis-server start python ./manage.py qcluster & python ./manage.py add_scheduled_tasks -python ./manage.py collectstatic +python ./manage.py collectstatic --noinput # initial_setup does not appear to work as requested. python ./manage.py initial_setup & From bec34a453abea238876e2f771234c1bc8f8ac153 Mon Sep 17 00:00:00 2001 From: Claire Peters Date: Tue, 21 Nov 2023 12:40:07 -0800 Subject: [PATCH 08/22] add unmatched expense codes to allocation request ticket --- coldfront/core/allocation/forms.py | 13 +------------ coldfront/core/allocation/views.py | 13 +++++++++++++ .../templates/email/new_allocation_request.txt | 6 ++++++ 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/coldfront/core/allocation/forms.py b/coldfront/core/allocation/forms.py index dd0d1875f..90b1ef48b 100644 --- a/coldfront/core/allocation/forms.py +++ b/coldfront/core/allocation/forms.py @@ -150,18 +150,7 @@ def clean(self): [d[:3], d[3:8], d[8:12], d[12:18], d[18:24], d[24:28], d[28:33]] ) cleaned_expensecode = insert_dashes(replace_productcode(digits_only(expense_code))) - if 'ifxbilling' in settings.INSTALLED_APPS: - try: - matched_fiineaccts = FiineAPI.listAccounts(code=cleaned_expensecode) - if not matched_fiineaccts: - self.add_error( - "expense_code", - "expense code not found in system - please check the code or get in touch with a system administrator." - ) - except Exception: - #Not authorized to use accounts_list - pass - cleaned_data['expense_code'] = cleaned_expensecode + cleaned_data['expense_code'] = cleaned_expensecode elif existing_expense_codes and existing_expense_codes != '------': cleaned_data['expense_code'] = existing_expense_codes return cleaned_data diff --git a/coldfront/core/allocation/views.py b/coldfront/core/allocation/views.py index ce59ba295..7ca822eb8 100644 --- a/coldfront/core/allocation/views.py +++ b/coldfront/core/allocation/views.py @@ -68,6 +68,7 @@ if 'ifxbilling' in settings.INSTALLED_APPS: + from fiine.client import API as FiineAPI from ifxbilling.models import Account, UserProductAccount if 'django_q' in settings.INSTALLED_APPS: from django_q.tasks import Task @@ -711,7 +712,19 @@ def form_valid(self, form): 'quantity':quantity, 'nese': nese, 'used_percentage': used_percentage, + 'expense_code': expense_code, + 'unmatched_code': False, } + + if 'ifxbilling' in settings.INSTALLED_APPS: + try: + matched_fiineaccts = FiineAPI.listAccounts(code=expense_code) + if not matched_fiineaccts: + other_vars['unmatched_code'] = True + except Exception: + #Not authorized to use accounts_list + pass + send_allocation_admin_email( allocation_obj, 'New Allocation Request', diff --git a/coldfront/templates/email/new_allocation_request.txt b/coldfront/templates/email/new_allocation_request.txt index 8c09761d3..4697a00fe 100644 --- a/coldfront/templates/email/new_allocation_request.txt +++ b/coldfront/templates/email/new_allocation_request.txt @@ -8,6 +8,12 @@ Justification: {{resource}} was last recorded as {{used_percentage}}% allocated. {% endif %} +{% if unmatched_code %} +The expense code entered does not match any in the FIINE database. +Please check it for accuracy and ensure that it is added to FIINE: +{{expense_code}} +{% endif %} + {% if nese %} Here is a draft ticket to send to NESE: From 94e39982d47f92b7d08d9064c67640a6336b7794 Mon Sep 17 00:00:00 2001 From: Claire Peters Date: Tue, 21 Nov 2023 16:51:29 -0800 Subject: [PATCH 09/22] fix EOF error from addition of alphanumeric characters to numeric characters --- coldfront/core/allocation/models.py | 23 +++++--- coldfront/core/allocation/test_models.py | 58 +++++++++++++++++++ .../core/allocation/tests/test_models.py | 36 ------------ 3 files changed, 74 insertions(+), 43 deletions(-) create mode 100644 coldfront/core/allocation/test_models.py delete mode 100644 coldfront/core/allocation/tests/test_models.py diff --git a/coldfront/core/allocation/models.py b/coldfront/core/allocation/models.py index a6b37d81d..0a7a431b3 100644 --- a/coldfront/core/allocation/models.py +++ b/coldfront/core/allocation/models.py @@ -532,20 +532,29 @@ def clean(self): expected_value_type = self.allocation_attribute_type.attribute_type.name.strip() error = None - if expected_value_type == 'Float' and not isinstance(literal_eval(self.value), (float,int)): - error = 'Value must be a float.' - elif expected_value_type == 'Int' and not isinstance(literal_eval(self.value), int): - error = 'Value must be an integer.' - elif expected_value_type == 'Yes/No' and self.value not in ['Yes', 'No']: + if expected_value_type in ['Float', 'Int']: + try: + literal_val = literal_eval(self.value) + except SyntaxError as exc: + error = 'Value must be entirely numeric. Please remove any non-numeric characters.' + raise ValidationError( + f'Invalid Value "{self.value}" for "{self.allocation_attribute_type.name}". {error}' + ) from exc + if expected_value_type == 'Float' and not isinstance(literal_val, (float,int)): + error = 'Value must be a float.' + elif expected_value_type == 'Int' and not isinstance(literal_val, int): + error = 'Value must be an integer.' + elif expected_value_type == "Yes/No" and self.value not in ["Yes", "No"]: error = 'Allowed inputs are "Yes" or "No".' - elif expected_value_type == 'Date': + elif expected_value_type == "Date": try: datetime.datetime.strptime(self.value.strip(), '%Y-%m-%d') except ValueError: error = 'Date must be in format YYYY-MM-DD' if error: raise ValidationError( - 'Invalid Value "%s" for "%s". %s' % (self.value, self.allocation_attribute_type.name, error)) + f'Invalid Value "{self.value}" for "{self.allocation_attribute_type.name}". {error}' + ) def __str__(self): return str(self.allocation_attribute_type.name) diff --git a/coldfront/core/allocation/test_models.py b/coldfront/core/allocation/test_models.py new file mode 100644 index 000000000..de3fd02a7 --- /dev/null +++ b/coldfront/core/allocation/test_models.py @@ -0,0 +1,58 @@ +"""Unit tests for the allocation models""" + +from django.test import TestCase +from django.core.exceptions import ValidationError + +from coldfront.core.test_helpers.factories import ( + AllocationFactory, + ResourceFactory, + AllocationAttributeTypeFactory, + AllocationAttributeFactory, +) + + +class AllocationModelTests(TestCase): + """tests for Allocation model""" + + @classmethod + def setUpTestData(cls): + """Set up allocation to test model properties and methods""" + cls.allocation = AllocationFactory() + cls.allocation.resources.add(ResourceFactory(name='holylfs07/tier1')) + + def test_allocation_str(self): + """test that allocation str method returns correct string""" + allocation_str = '%s (%s)' % ( + self.allocation.get_parent_resource.name, + self.allocation.project.pi + ) + self.assertEqual(str(self.allocation), allocation_str) + + +class AllocationAttributeModelTests(TestCase): + """Tests for allocationattribute models""" + + @classmethod + def setUpTestData(cls): + """Set up allocationattribute to test model properties and methods""" + cls.allocation = AllocationFactory() + cls.allocation.resources.add(ResourceFactory(name='holylfs07/tier1')) + cls.allocationattribute = AllocationAttributeFactory( + allocation=cls.allocation, + value = 100, + allocation_attribute_type=AllocationAttributeTypeFactory( + name='Storage Quota (TB)' + ), + ) + + def test_allocationattribute_clean_no_error(self): + """cleaning a numeric value for an int or float AllocationAttributeType produces no error""" + self.allocationattribute.value = "1000" + self.allocationattribute.clean() + + def test_allocationattribute_clean_nonnumeric_error(self): + """cleaning a non-numeric value for int or float AllocationAttributeTypes returns an informative error message""" + + self.allocationattribute.value = "1000TB" + with self.assertRaisesMessage(ValidationError, 'Value must be entirely numeric. Please remove any non-numeric characters.'): + self.allocationattribute.clean() diff --git a/coldfront/core/allocation/tests/test_models.py b/coldfront/core/allocation/tests/test_models.py deleted file mode 100644 index 940e2cb2b..000000000 --- a/coldfront/core/allocation/tests/test_models.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Unit tests for the allocation models""" - -from django.test import TestCase - -from coldfront.core.test_helpers.factories import setup_models, AllocationFactory - -UTIL_FIXTURES = [ - "coldfront/core/test_helpers/test_data/test_fixtures/ifx.json", -] - -class AllocationModelTests(TestCase): - """tests for Allocation model""" - fixtures = UTIL_FIXTURES - - @classmethod - def setUpTestData(cls): - """Set up project to test model properties and methods""" - setup_models(cls) - - def test_allocation_str(self): - """test that allocation str method returns correct string""" - allocation_str = '%s (%s)' % ( - self.proj_allocation.get_parent_resource.name, - self.proj_allocation.project.pi - ) - - self.assertEqual(str(self.proj_allocation), allocation_str) - - def test_allocation_usage_property(self): - """Test that allocation usage property displays correctly""" - self.assertEqual(self.proj_allocation.usage, 10) - - def test_allocation_usage_property_na(self): - """Create allocation with no usage. Usage property should return None""" - allocation = AllocationFactory() - self.assertIsNone(allocation.usage) From 42a628fed77e3f29572151d2b9fc385143725d19 Mon Sep 17 00:00:00 2001 From: Claire Peters Date: Tue, 21 Nov 2023 17:48:24 -0800 Subject: [PATCH 10/22] move test_models back --- coldfront/core/allocation/{ => tests}/test_models.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename coldfront/core/allocation/{ => tests}/test_models.py (100%) diff --git a/coldfront/core/allocation/test_models.py b/coldfront/core/allocation/tests/test_models.py similarity index 100% rename from coldfront/core/allocation/test_models.py rename to coldfront/core/allocation/tests/test_models.py From 6af3860e335cdbf2c659ade8aaf6a6bdcd402645 Mon Sep 17 00:00:00 2001 From: Claire Peters Date: Tue, 21 Nov 2023 17:59:34 -0800 Subject: [PATCH 11/22] add back fasrc-specific changes to allocation.test_models --- .../core/allocation/tests/test_models.py | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/coldfront/core/allocation/tests/test_models.py b/coldfront/core/allocation/tests/test_models.py index de3fd02a7..ff7cf2ded 100644 --- a/coldfront/core/allocation/tests/test_models.py +++ b/coldfront/core/allocation/tests/test_models.py @@ -3,46 +3,50 @@ from django.test import TestCase from django.core.exceptions import ValidationError -from coldfront.core.test_helpers.factories import ( - AllocationFactory, - ResourceFactory, - AllocationAttributeTypeFactory, - AllocationAttributeFactory, -) +from coldfront.core.test_helpers.factories import setup_models, AllocationFactory + +UTIL_FIXTURES = [ + "coldfront/core/test_helpers/test_data/test_fixtures/ifx.json", +] class AllocationModelTests(TestCase): """tests for Allocation model""" + fixtures = UTIL_FIXTURES @classmethod def setUpTestData(cls): """Set up allocation to test model properties and methods""" - cls.allocation = AllocationFactory() - cls.allocation.resources.add(ResourceFactory(name='holylfs07/tier1')) + setup_models(cls) def test_allocation_str(self): """test that allocation str method returns correct string""" allocation_str = '%s (%s)' % ( - self.allocation.get_parent_resource.name, - self.allocation.project.pi + self.proj_allocation.get_parent_resource.name, + self.proj_allocation.project.pi ) - self.assertEqual(str(self.allocation), allocation_str) + self.assertEqual(str(self.proj_allocation), allocation_str) + + + def test_allocation_usage_property(self): + """Test that allocation usage property displays correctly""" + self.assertEqual(self.proj_allocation.usage, 10) + def test_allocation_usage_property_na(self): + """Create allocation with no usage. Usage property should return None""" + allocation = AllocationFactory() + self.assertIsNone(allocation.usage) class AllocationAttributeModelTests(TestCase): """Tests for allocationattribute models""" + fixtures = UTIL_FIXTURES @classmethod def setUpTestData(cls): """Set up allocationattribute to test model properties and methods""" - cls.allocation = AllocationFactory() - cls.allocation.resources.add(ResourceFactory(name='holylfs07/tier1')) - cls.allocationattribute = AllocationAttributeFactory( - allocation=cls.allocation, - value = 100, - allocation_attribute_type=AllocationAttributeTypeFactory( - name='Storage Quota (TB)' - ), + setup_models(cls) + cls.allocationattribute = cls.proj_allocation.allocationattribute_set.get( + allocation_attribute_type__name='Storage Quota (TB)' ) def test_allocationattribute_clean_no_error(self): From 8a35f5696ec923c960ba207bbd2fdba4854aec95 Mon Sep 17 00:00:00 2001 From: Claire Peters Date: Wed, 22 Nov 2023 15:29:47 -0800 Subject: [PATCH 12/22] active users only on projectuser table --- .../templates/project/project_detail.html | 35 +++---------------- coldfront/core/project/views.py | 3 +- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/coldfront/core/project/templates/project/project_detail.html b/coldfront/core/project/templates/project/project_detail.html index 1da913b84..86f4f3585 100644 --- a/coldfront/core/project/templates/project/project_detail.html +++ b/coldfront/core/project/templates/project/project_detail.html @@ -97,8 +97,8 @@