Skip to content

Commit ac9df42

Browse files
authored
Merge pull request #728 from hms-dbmi/development
Development
2 parents 2eeaf68 + fe09443 commit ac9df42

File tree

6 files changed

+401
-287
lines changed

6 files changed

+401
-287
lines changed

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## [1.2.1-rc.1](https://github.com/hms-dbmi/hypatio-app/compare/v1.2.0...v1.2.1-rc.1) (2025-01-07)
2+
3+
4+
### Bug Fixes
5+
6+
* **manage:** Checks for existing pending applications that might be linked to an agreement form from an institutional signer when granting access ([e096de7](https://github.com/hms-dbmi/hypatio-app/commit/e096de7870aff3667d029a899656fcffe1e913d4))
7+
* **projects:** Added a method to SignedAgreementForm to determine whether it's for an institutional signer or not ([ecf5f01](https://github.com/hms-dbmi/hypatio-app/commit/ecf5f017c7d6fcdea5f15229dc0924246e60e1b0))
8+
* **projects:** Set to use method on SignedAgreementForm to determine when to handle SignedAgreementForms from institutional signers ([48b4802](https://github.com/hms-dbmi/hypatio-app/commit/48b4802abb5b04c3834a94ab784ee8dfa93a8067))
9+
* **requirements:** Updated Python requirements ([b04f0ec](https://github.com/hms-dbmi/hypatio-app/commit/b04f0ecd0174558dcba327e473fe0887fcafa18b))
10+
111
# [1.2.0](https://github.com/hms-dbmi/hypatio-app/compare/v1.1.2...v1.2.0) (2024-10-17)
212

313

app/manage/api.py

+69
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from projects.models import TeamComment
4242
from projects.serializers import HostedFileSerializer, HostedFileDownloadSerializer
4343
from projects.models import AGREEMENT_FORM_TYPE_MODEL, AGREEMENT_FORM_TYPE_FILE
44+
from projects.models import InstitutionalOfficial
4445

4546
# Get an instance of a logger
4647
logger = logging.getLogger(__name__)
@@ -1157,6 +1158,54 @@ def grant_view_permission(request, project_key, user_email):
11571158
participant = project.participant_set.get(user__email=user_email)
11581159
participant.permission = 'VIEW'
11591160
participant.save()
1161+
1162+
# Check if this project allows institutional signers
1163+
if project.institutional_signers:
1164+
1165+
# Check if this is a signing official
1166+
try:
1167+
official = InstitutionalOfficial.objects.get(
1168+
project=project,
1169+
user=participant.user,
1170+
)
1171+
1172+
# Iterate linked members
1173+
for member_email in official.member_emails:
1174+
1175+
logger.debug(f"Institutional signer/{participant.user.email}: Checking for existing linked member '{member_email}'")
1176+
1177+
# Check if a participant exists for this email with no VIEW permission
1178+
if Participant.objects.filter(project=project, user__email=member_email).exclude(permission="VIEW").exists():
1179+
1180+
# Fetch them
1181+
member_participant = Participant.objects.get(project=project, user__email=member_email)
1182+
1183+
# Approve signed agreement forms
1184+
for signed_agreement_form in SignedAgreementForm.objects.filter(project=project, user=member_participant.user):
1185+
1186+
# If allows institutional signers, auto-approve
1187+
if signed_agreement_form.agreement_form.institutional_signers:
1188+
1189+
signed_agreement_form.status = "A"
1190+
signed_agreement_form.save()
1191+
1192+
# Grant this user access immediately if all agreement forms are accepted
1193+
for agreement_form in project.agreement_forms.all():
1194+
if not SignedAgreementForm.objects.filter(
1195+
agreement_form=agreement_form,
1196+
project=project,
1197+
user=member_participant.user,
1198+
status="A"
1199+
):
1200+
break
1201+
else:
1202+
1203+
# Call this method to process the access
1204+
grant_view_permission(request, project_key, member_email)
1205+
1206+
except ObjectDoesNotExist:
1207+
pass
1208+
11601209
except Exception as e:
11611210
logger.exception(
11621211
'[HYPATIO][DEBUG][grant_view_permission] User {user} could not have permission added to project {project_key}: {e}'.format(
@@ -1226,6 +1275,26 @@ def remove_view_permission(request, project_key, user_email):
12261275
participant = project.participant_set.get(user__email=user_email)
12271276
participant.permission = None
12281277
participant.save()
1278+
1279+
# Check if this project allows institutional signers
1280+
if project.institutional_signers:
1281+
1282+
# Check if this is a signing official
1283+
try:
1284+
official = InstitutionalOfficial.objects.get(
1285+
project=project,
1286+
user=participant.user,
1287+
)
1288+
1289+
# Iterate linked members
1290+
for member_email in official.member_emails:
1291+
1292+
# Remove their access
1293+
remove_view_permission(request, project_key, member_email)
1294+
1295+
except ObjectDoesNotExist:
1296+
pass
1297+
12291298
except Exception as e:
12301299
logger.exception(
12311300
'[HYPATIO][ERROR][grant_view_permission] User {user} could not have permission remove from project {project_key}: {e}'.format(

app/projects/models.py

+32
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import re
33
import importlib
44
from datetime import datetime
5+
from typing import Optional, Tuple
56

67
import boto3
78
from botocore.exceptions import ClientError
@@ -489,6 +490,37 @@ class Meta:
489490
verbose_name_plural = 'Signed Agreement Forms'
490491

491492

493+
def get_institutional_signer_details(self) -> Optional[Tuple[str, str, list[str]]]:
494+
"""
495+
Checks the SignedAgreementForm to see if it has been signed by an institutional official.
496+
If so, it returns the name of the institution and email of the institutional official along with a list of
497+
emails for the members they are representing.
498+
499+
:returns: A tuple containing the institution name, official email, and a list of member emails if this is an
500+
institutional signer; otherwise tuple of None, None, None
501+
:rtype: Optional[Tuple[str, str, list[str]]], defaults to Tuple[None, None, None]
502+
"""
503+
if self.agreement_form.institutional_signers:
504+
505+
# Fields should contain whether this is an institutional official or not
506+
if self.fields and self.fields.get("registrant_is", "").lower() == "official":
507+
508+
# Ensure member emails is a list
509+
member_emails = self.fields.get("member_emails", [])
510+
if isinstance(member_emails, str):
511+
member_emails = [member_emails]
512+
elif not isinstance(member_emails, list):
513+
raise ValidationError(f"Unhandled state of 'member_emails' field: {type(member_emails)}/{member_emails}")
514+
515+
# Cleanup emails to remove whitespace, if any
516+
member_emails = [email.strip() for email in member_emails]
517+
518+
# Return values
519+
return self.fields["institute_name"], self.user.email, member_emails
520+
521+
return None, None, None
522+
523+
492524
class DataUseReportRequest(models.Model):
493525
"""
494526
This model describes a request for a participant to report on data use.

app/projects/signals.py

+19-13
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,22 @@ def signed_agreement_form_pre_save_handler(sender, **kwargs):
133133
logger.debug(f"Pre-save: {instance}")
134134

135135
# Check for specific types of forms that require additional handling
136-
if instance.status == "A" and instance.agreement_form.short_name == "4ce-dua" \
137-
and instance.fields.get("registrant-is") == "official":
138-
logger.debug(f"Pre-save institutional official: {instance}")
139-
140-
# Create official and member objects
141-
official = InstitutionalOfficial.objects.create(
142-
user=instance.user,
143-
institution=instance.fields["institute-name"],
144-
project=instance.project,
145-
signed_agreement_form=instance,
146-
member_emails=instance.fields["member-emails"],
147-
)
148-
official.save()
136+
institute_name, official_email, member_emails = instance.get_institutional_signer_details()
137+
if instance.status == "A" and institute_name and official_email and member_emails:
138+
logger.debug(f"Pre-save institutional official AgreementForm: {instance}")
139+
140+
# Ensure they don't already exist
141+
if InstitutionalOfficial.objects.filter(user=instance.user, project=instance.project).exists():
142+
logger.debug(f"InstitutionalOfficial already exists for {instance.user}/{instance.project}")
143+
144+
else:
145+
146+
# Create official and member objects
147+
official = InstitutionalOfficial.objects.create(
148+
user=instance.user,
149+
institution=institute_name,
150+
project=instance.project,
151+
signed_agreement_form=instance,
152+
member_emails=member_emails,
153+
)
154+
official.save()

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "dbmi-data-portal"
3-
version = "1.2.0"
3+
version = "1.2.1-rc.1"
44
description = "A portal for hosting and managing access to DBMI-provided datasets"
55
readme = "README.md"
66
requires-python = ">=3.9"

0 commit comments

Comments
 (0)