diff --git a/allocations/admin.py b/allocations/admin.py index 6d2a7b19..b32759be 100644 --- a/allocations/admin.py +++ b/allocations/admin.py @@ -25,32 +25,32 @@ def pi_institution(self, obj): keycloak_client = KeycloakClient() uname = obj.project.pi.username user = keycloak_client.get_user_by_username(uname) - return user['attributes'].get('affiliationInstitution', '') + return user["attributes"].get("affiliationInstitution", "") list_display = ( - 'project_title', - 'project', - 'status', - 'date_requested', - 'date_reviewed', - 'reviewer', + "project_title", + "project", + "status", + "date_requested", + "date_reviewed", + "reviewer", ) fields = ( - 'pi_name', - 'pi_email', - 'pi_institution', - 'project', - 'project_title', - 'project_description', - 'justification', - 'status', - 'requestor', - 'decision_summary', - 'reviewer', - 'date_requested', - 'date_reviewed', - 'start_date', - 'expiration_date', + "pi_name", + "pi_email", + "pi_institution", + "project", + "project_title", + "project_description", + "justification", + "status", + "requestor", + "decision_summary", + "reviewer", + "date_requested", + "date_reviewed", + "start_date", + "expiration_date", ) ordering = ["-date_requested"] diff --git a/allocations/models.py b/allocations/models.py index b8caa9fb..b1323766 100644 --- a/allocations/models.py +++ b/allocations/models.py @@ -110,10 +110,11 @@ class ChargeBudget(models.Model): related_name="projectbudgets", on_delete=models.CASCADE, ) - project = models.ForeignKey( - Project, on_delete=models.CASCADE - ) + project = models.ForeignKey(Project, on_delete=models.CASCADE) su_budget = models.IntegerField(default=0, validators=[MinValueValidator(0)]) class Meta: - unique_together = ('user', 'project',) + unique_together = ( + "user", + "project", + ) diff --git a/allocations/tests.py b/allocations/tests.py index 476f0673..e9cdc6d4 100644 --- a/allocations/tests.py +++ b/allocations/tests.py @@ -16,6 +16,7 @@ LOG = logging.getLogger(__name__) + class StatusTests(TestCase): def setUp(self): User = get_user_model() @@ -37,7 +38,7 @@ def setUp(self): pi=self.test_requestor, title="Test Project", nickname="test_project", - charge_code="TEST123" + charge_code="TEST123", ) self.test_project.save() @@ -47,10 +48,14 @@ def tearDown(self): self.test_reviewer.delete() self.test_project.delete() - @mock.patch.object(KeycloakClient, '_lookup_group', return_value={"attributes": {"has_active_allocation": False}}) - @mock.patch.object(KeycloakClient, 'update_project', return_value=None) + @mock.patch.object( + KeycloakClient, + "_lookup_group", + return_value={"attributes": {"has_active_allocation": False}}, + ) + @mock.patch.object(KeycloakClient, "update_project", return_value=None) def test_deactivate_active_allocation(self, update_project_mock, lookup_group_mock): - """ Tests whether deactivate_active_allocation() + """Tests whether deactivate_active_allocation() updates an active allocation's status to inactive """ test_active_allocation = Allocation( @@ -70,7 +75,7 @@ def test_deactivate_active_allocation(self, update_project_mock, lookup_group_mo balance_service_version=2, ) sample_dict = {"has_active_allocation": False} - sample_dict.get('has_active_allocation') + sample_dict.get("has_active_allocation") tasks._deactivate_allocation(test_active_allocation) update_project_mock.assert_called_once_with( @@ -82,9 +87,15 @@ def test_deactivate_active_allocation(self, update_project_mock, lookup_group_mo self.assertEqual(test_active_allocation.status, "inactive") - @mock.patch.object(KeycloakClient, '_lookup_group', return_value={"attributes": {"has_active_allocation": False}}) - @mock.patch.object(KeycloakClient, 'update_project', return_value=None) - def test_deactivate_inactive_allocation(self, update_project_mock, lookup_group_mock): + @mock.patch.object( + KeycloakClient, + "_lookup_group", + return_value={"attributes": {"has_active_allocation": False}}, + ) + @mock.patch.object(KeycloakClient, "update_project", return_value=None) + def test_deactivate_inactive_allocation( + self, update_project_mock, lookup_group_mock + ): """Tests whether deactivate_active_allocation() does not update an inactive allocation's status """ @@ -105,7 +116,7 @@ def test_deactivate_inactive_allocation(self, update_project_mock, lookup_group_ balance_service_version=2, ) sample_dict = {"has_active_allocation": False} - sample_dict.get('has_active_allocation') + sample_dict.get("has_active_allocation") tasks._deactivate_allocation(test_inactive_allocation) update_project_mock.assert_called_once_with( @@ -128,7 +139,7 @@ def test_deactivate_multiple_allocations_of_projects(self): pi=self.test_requestor, # Replace my_user with an actual User instance title="Multiple Alloc Project", nickname="multiple_alloc_project", - charge_code="multiple_alloc" # You can set a unique charge code + charge_code="multiple_alloc", # You can set a unique charge code ) test_project_multiple_alloc.save() @@ -176,11 +187,11 @@ def test_deactivate_multiple_allocations_of_projects(self): passed_assertions = 0 for alloc in inactive_allocs: - if (alloc.justification == "earlier alloc"): + if alloc.justification == "earlier alloc": self.assertEqual(alloc.status, "inactive") passed_assertions += 1 alloc.delete() - if (alloc.justification == "later alloc"): + if alloc.justification == "later alloc": self.assertEqual(alloc.status, "active") passed_assertions += 1 alloc.delete() @@ -189,8 +200,12 @@ def test_deactivate_multiple_allocations_of_projects(self): self.assertEqual(passed_assertions, 2) - @mock.patch.object(KeycloakClient, '_lookup_group', return_value={"attributes": {"has_active_allocation": True}}) - @mock.patch.object(KeycloakClient, 'update_project', return_value=None) + @mock.patch.object( + KeycloakClient, + "_lookup_group", + return_value={"attributes": {"has_active_allocation": True}}, + ) + @mock.patch.object(KeycloakClient, "update_project", return_value=None) def test_active_approved_allocations(self, update_project_mock, lookup_group_mock): """Tests that active_approved_allocations() changes the status of approved allocations to active @@ -215,8 +230,9 @@ def test_active_approved_allocations(self, update_project_mock, lookup_group_moc test_approved_allocation.save() tasks.active_approved_allocations() - test_allocations = Allocation.objects.filter(project=self.test_project, - justification="approved alloc") + test_allocations = Allocation.objects.filter( + project=self.test_project, justification="approved alloc" + ) update_project_mock.assert_called_once_with( test_approved_allocation.project.charge_code, has_active_allocation="true" @@ -231,9 +247,15 @@ def test_active_approved_allocations(self, update_project_mock, lookup_group_moc test_approved_allocation.delete() - @mock.patch.object(KeycloakClient, '_lookup_group', return_value={"attributes": {"has_active_allocation": False}}) - @mock.patch.object(KeycloakClient, 'update_project', return_value=None) - def test_nonapproved_active_approved_allocation(self, update_project_mock, lookup_group_mock): + @mock.patch.object( + KeycloakClient, + "_lookup_group", + return_value={"attributes": {"has_active_allocation": False}}, + ) + @mock.patch.object(KeycloakClient, "update_project", return_value=None) + def test_nonapproved_active_approved_allocation( + self, update_project_mock, lookup_group_mock + ): """Tests that active_approved_allocations() does not change the status of non-approved allocations """ @@ -257,8 +279,9 @@ def test_nonapproved_active_approved_allocation(self, update_project_mock, looku test_nonapproved_allocation.save() tasks.active_approved_allocations() - test_allocations = Allocation.objects.filter(project=self.test_project, - justification="pending alloc") + test_allocations = Allocation.objects.filter( + project=self.test_project, justification="pending alloc" + ) for alloc in test_allocations: self.assertEqual(alloc.status, "pending") @@ -291,7 +314,7 @@ def setUp(self): pi=self.test_requestor, # Replace my_user with an actual User instance title="Test Project", nickname="test_project", - charge_code="TEST123" # You can set a unique charge code + charge_code="TEST123", # You can set a unique charge code ) self.test_project.save() @@ -301,9 +324,15 @@ def tearDown(self): self.test_reviewer.delete() self.test_project.delete() - @mock.patch.object(KeycloakClient, '_lookup_group', return_value={"attributes": {"has_active_allocation": False}}) - @mock.patch.object(KeycloakClient, 'update_project', return_value=None) - def test_expire_expired_alloc_allocations(self, update_project_mock, lookup_group_mock): + @mock.patch.object( + KeycloakClient, + "_lookup_group", + return_value={"attributes": {"has_active_allocation": False}}, + ) + @mock.patch.object(KeycloakClient, "update_project", return_value=None) + def test_expire_expired_alloc_allocations( + self, update_project_mock, lookup_group_mock + ): """Tests that expore_allocations() changes the status of all expired allocations to inactive """ @@ -338,16 +367,22 @@ def test_expire_expired_alloc_allocations(self, update_project_mock, lookup_grou expired_allocations = Allocation.objects.filter( project=self.test_project, justification="expired allocation", - expiration_date__lte=timezone.now() + expiration_date__lte=timezone.now(), ) for alloc in expired_allocations: self.assertTrue(alloc.status, "inactive") alloc.delete() - @mock.patch.object(KeycloakClient, '_lookup_group', return_value={"attributes": {"has_active_allocation": False}}) - @mock.patch.object(KeycloakClient, 'update_project', return_value=None) - def test_expire_non_expired_alloc_allocations(self, update_project_mock, lookup_group_mock): + @mock.patch.object( + KeycloakClient, + "_lookup_group", + return_value={"attributes": {"has_active_allocation": False}}, + ) + @mock.patch.object(KeycloakClient, "update_project", return_value=None) + def test_expire_non_expired_alloc_allocations( + self, update_project_mock, lookup_group_mock + ): """Tests that expore_allocations() does not change the status of unexpired allocations """ diff --git a/allocations/views.py b/allocations/views.py index fcf6272d..cb2f0971 100644 --- a/allocations/views.py +++ b/allocations/views.py @@ -154,17 +154,17 @@ def approval(request): "Waiting", "waiting", ]: - errors[ - "status" - ] = 'Status must be "Pending", "pending", "Approved", "approved", "Rejected", "rejected"' + errors["status"] = ( + 'Status must be "Pending", "pending", "Approved", "approved", "Rejected", "rejected"' + ) else: if data["start"]: try: validate_datestring(data["start"]) except ValidationError: - errors[ - "start" - ] = 'Start date must be a valid date string e.g. "2015-05-20" .' + errors["start"] = ( + 'Start date must be a valid date string e.g. "2015-05-20" .' + ) elif data["status"].lower() == "approved": errors["start"] = "Start date is required." @@ -172,9 +172,9 @@ def approval(request): try: validate_datestring(data["end"]) except ValidationError: - errors[ - "end" - ] = 'Start date must be a valid date string e.g. "2015-05-20" .' + errors["end"] = ( + 'Start date must be a valid date string e.g. "2015-05-20" .' + ) elif data["status"].lower() == "approved": errors["end"] = "Start date is required." @@ -240,9 +240,9 @@ def approval(request): try: validate_datetimestring(data["dateRequested"]) except ValidationError: - errors[ - "dateRequested" - ] = 'Requested date must be a valid date string e.g. "2015-05-20T05:00:00Z" .' + errors["dateRequested"] = ( + 'Requested date must be a valid date string e.g. "2015-05-20T05:00:00Z" .' + ) # else: # errors['dateRequested'] = 'Requested date is required.' @@ -250,9 +250,9 @@ def approval(request): try: validate_datestring(data["dateReviewed"]) except ValidationError: - errors[ - "dateReviewed" - ] = 'Reviewed date must be a valid date string e.g. "2015-05-20" .' + errors["dateReviewed"] = ( + 'Reviewed date must be a valid date string e.g. "2015-05-20" .' + ) else: errors["dateReviewed"] = "Reviewed date is required." if len(errors) == 0: @@ -265,9 +265,9 @@ def approval(request): except Exception as e: logger.exception("Error processing allocation approval.") status = "error" - errors[ - "message" - ] = "An unexpected error occurred. If this problem persists please create a help ticket." + errors["message"] = ( + "An unexpected error occurred. If this problem persists please create a help ticket." + ) else: logger.info("Request data failed validation. %s", list(errors.values())) @@ -302,9 +302,9 @@ def contact(request): except Exception: logger.exception("Error contacting PI.") status = "error" - errors[ - "message" - ] = "An unexpected error occurred. If this problem persists please create a help ticket." + errors["message"] = ( + "An unexpected error occurred. If this problem persists please create a help ticket." + ) else: logger.info("Request data failed validation. %s", list(errors.values())) diff --git a/appliance_catalog/admin.py b/appliance_catalog/admin.py index 1b3d7eb2..040a8a70 100644 --- a/appliance_catalog/admin.py +++ b/appliance_catalog/admin.py @@ -33,12 +33,8 @@ class ApplianceAdmin(admin.ModelAdmin): "needs_review", "project_supported", ) - ordering = [ - "-created_date" - ] - list_filter = [ - "needs_review" - ] + ordering = ["-created_date"] + list_filter = ["needs_review"] actions = [make_reviewed] form = ApplianceAdminForm diff --git a/chameleon/decorators.py b/chameleon/decorators.py index 894820e9..8885cc63 100644 --- a/chameleon/decorators.py +++ b/chameleon/decorators.py @@ -1,4 +1,5 @@ """View Decorators for termsandconditions module""" + import urllib.parse from functools import wraps from django.conf import settings diff --git a/chameleon/research_impacts.py b/chameleon/research_impacts.py index 9bdc535f..8a37e248 100644 --- a/chameleon/research_impacts.py +++ b/chameleon/research_impacts.py @@ -91,6 +91,7 @@ def similarity_score(str1, str2): "edu_epscor_states": len(edu_epscor_states), } + def _allocation_counts(projects): allocation_counts = [] # For projects that were active at one point @@ -209,13 +210,15 @@ def get_context(): for name, tag in tags.items(): active_projects_per_year_per_tag[name].append( ( - year, + year, Project.objects.filter( allocations__status__in=allocation_statuses, allocations__start_date__lte=year_end, allocations__expiration_date__gte=current_year, tag=tag, - ).distinct().count() + ) + .distinct() + .count(), ) ) @@ -263,6 +266,7 @@ def get_institution_context(): "institutions": institution_report(), } + def get_sus_context(): start_year = 2015 end_year = datetime.now().year + 1 @@ -274,13 +278,12 @@ def get_sus_context(): new_data[k] = dict(v) for n in v.values(): all_sum += n - new_data["Total"] = {all_sum : ""} + new_data["Total"] = {all_sum: ""} return { "su_usage_data": new_data, } - def get_education_users(): computing_education_tag = Tag.objects.get(name="Computing Education") edu_users = set() @@ -301,13 +304,13 @@ def su_information(start_year, end_year): # Calculate SU usage sus = ( Charge.objects.filter(start_time__year=year) - .annotate(duration=F('end_time') - F('start_time')) + .annotate(duration=F("end_time") - F("start_time")) .values("region_name", "duration", "hourly_cost") ) total_su = defaultdict(int) total_su_all = 0 for su in sus: - cost = su["duration"].total_seconds()/3600 * su["hourly_cost"] + cost = su["duration"].total_seconds() / 3600 * su["hourly_cost"] total_su[su["region_name"]] += cost total_su_all += cost total_su["All Sites"] = total_su_all diff --git a/chameleon/settings.py b/chameleon/settings.py index 1194087f..063ccb78 100644 --- a/chameleon/settings.py +++ b/chameleon/settings.py @@ -7,6 +7,7 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.11/ref/settings/ """ + import os import re import sys @@ -245,7 +246,7 @@ }, } -TESTING = 'test' in sys.argv or 'test_coverage' in sys.argv +TESTING = "test" in sys.argv or "test_coverage" in sys.argv # Database # https://docs.djangoproject.com/en/1.7/ref/settings/#databases @@ -261,7 +262,7 @@ "USER": os.environ.get("DB_USER"), "PASSWORD": os.environ.get("DB_PASSWORD"), "TEST": { - 'NAME': os.environ.get("DB_NAME") + "_test", + "NAME": os.environ.get("DB_NAME") + "_test", }, }, } diff --git a/chameleon/urls.py b/chameleon/urls.py index aefd118f..164c5714 100644 --- a/chameleon/urls.py +++ b/chameleon/urls.py @@ -48,7 +48,7 @@ def get(self, request, **kwargs): chameleon_views.admin_research_impacts_institutions, name="research_impacts_institutions", ), - path( + path( "admin/research_impacts/sus/", chameleon_views.admin_research_impacts_sus, name="research_impacts_sus", diff --git a/chameleon/views.py b/chameleon/views.py index f309a61a..52b7032a 100644 --- a/chameleon/views.py +++ b/chameleon/views.py @@ -35,6 +35,7 @@ edge_api = EDGE_HW_API() + @login_required def dashboard(request): context = {} @@ -80,7 +81,7 @@ def __init__(self, *args, **kwargs): def edge_hardware_discovery(request): """Hardware resource discovery page for CHI@Edge.""" - devices = {'devices': edge_api.get_devices()} + devices = {"devices": edge_api.get_devices()} return render(request, "edge-hw-discovery/resources.html", devices) @@ -216,14 +217,24 @@ def admin_or_superuser(user): @login_required @user_passes_test(admin_or_superuser) def admin_research_impacts(request): - return render(request, "admin/research_impacts.html", research_impacts.get_context()) + return render( + request, "admin/research_impacts.html", research_impacts.get_context() + ) + @login_required @user_passes_test(admin_or_superuser) def admin_research_impacts_institutions(request): - return render(request, "admin/research_impacts_institutions.html", research_impacts.get_institution_context()) + return render( + request, + "admin/research_impacts_institutions.html", + research_impacts.get_institution_context(), + ) + @login_required @user_passes_test(admin_or_superuser) def admin_research_impacts_sus(request): - return render(request, "admin/research_impacts_sus.html", research_impacts.get_sus_context()) + return render( + request, "admin/research_impacts_sus.html", research_impacts.get_sus_context() + ) diff --git a/projects/models.py b/projects/models.py index 88782e34..e1bae8f1 100644 --- a/projects/models.py +++ b/projects/models.py @@ -48,7 +48,9 @@ class Project(models.Model): title = models.TextField(blank=False) nickname = models.CharField(max_length=255, blank=False, unique=True) charge_code = models.CharField(max_length=50, blank=False, unique=True) - default_su_budget = models.IntegerField(default=0, validators=[MinValueValidator(0)]) + default_su_budget = models.IntegerField( + default=0, validators=[MinValueValidator(0)] + ) def __str__(self) -> str: return self.charge_code diff --git a/projects/pub_views.py b/projects/pub_views.py index c81e9f47..accc39e0 100644 --- a/projects/pub_views.py +++ b/projects/pub_views.py @@ -96,10 +96,12 @@ def user_publications(request): "author": pub.author, "link": "" if not pub.link else pub.link, "forum": pub.forum, - "month": "" - if not pub.month - else datetime.datetime.strptime(str(pub.month), "%m").strftime( - "%b" + "month": ( + "" + if not pub.month + else datetime.datetime.strptime(str(pub.month), "%m").strftime( + "%b" + ) ), "year": pub.year, "nickname": project.nickname, diff --git a/projects/user_publication/citation.py b/projects/user_publication/citation.py index 5eb4d53d..e4c03a77 100644 --- a/projects/user_publication/citation.py +++ b/projects/user_publication/citation.py @@ -1,6 +1,7 @@ """ Fill in the latest citation numbers for all publication """ + import logging import re import time diff --git a/sharing_portal/trovi.py b/sharing_portal/trovi.py index b1f1c807..f2d1c253 100644 --- a/sharing_portal/trovi.py +++ b/sharing_portal/trovi.py @@ -159,11 +159,13 @@ def portal_artifact_to_trovi(portal_artifact, prompt_input=False): "authors": [ get_author(author, prompt_input) for author in portal_artifact.authors.all() ], - "linked_projects": [] - if not portal_artifact.project - else [ - f"urn:trovi:project:{settings.ARTIFACT_OWNER_PROVIDER}:{portal_artifact.project.charge_code}" - ], + "linked_projects": ( + [] + if not portal_artifact.project + else [ + f"urn:trovi:project:{settings.ARTIFACT_OWNER_PROVIDER}:{portal_artifact.project.charge_code}" + ] + ), "reproducibility": { "enable_requests": portal_artifact.is_reproducible, "access_hours": portal_artifact.reproduce_hours, diff --git a/util/sql_format.py b/util/sql_format.py index 3ddcaf56..b7826ae5 100644 --- a/util/sql_format.py +++ b/util/sql_format.py @@ -1,4 +1,5 @@ """Pretty-Print SQL queries for console output.""" + from logging import Formatter from django.conf import settings