Skip to content

Commit

Permalink
Merge pull request #728 from hms-dbmi/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
b32147 authored Jan 7, 2025
2 parents 2eeaf68 + fe09443 commit ac9df42
Show file tree
Hide file tree
Showing 6 changed files with 401 additions and 287 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## [1.2.1-rc.1](https://github.com/hms-dbmi/hypatio-app/compare/v1.2.0...v1.2.1-rc.1) (2025-01-07)


### Bug Fixes

* **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))
* **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))
* **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))
* **requirements:** Updated Python requirements ([b04f0ec](https://github.com/hms-dbmi/hypatio-app/commit/b04f0ecd0174558dcba327e473fe0887fcafa18b))

# [1.2.0](https://github.com/hms-dbmi/hypatio-app/compare/v1.1.2...v1.2.0) (2024-10-17)


Expand Down
69 changes: 69 additions & 0 deletions app/manage/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from projects.models import TeamComment
from projects.serializers import HostedFileSerializer, HostedFileDownloadSerializer
from projects.models import AGREEMENT_FORM_TYPE_MODEL, AGREEMENT_FORM_TYPE_FILE
from projects.models import InstitutionalOfficial

# Get an instance of a logger
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -1157,6 +1158,54 @@ def grant_view_permission(request, project_key, user_email):
participant = project.participant_set.get(user__email=user_email)
participant.permission = 'VIEW'
participant.save()

# Check if this project allows institutional signers
if project.institutional_signers:

# Check if this is a signing official
try:
official = InstitutionalOfficial.objects.get(
project=project,
user=participant.user,
)

# Iterate linked members
for member_email in official.member_emails:

logger.debug(f"Institutional signer/{participant.user.email}: Checking for existing linked member '{member_email}'")

# Check if a participant exists for this email with no VIEW permission
if Participant.objects.filter(project=project, user__email=member_email).exclude(permission="VIEW").exists():

# Fetch them
member_participant = Participant.objects.get(project=project, user__email=member_email)

# Approve signed agreement forms
for signed_agreement_form in SignedAgreementForm.objects.filter(project=project, user=member_participant.user):

# If allows institutional signers, auto-approve
if signed_agreement_form.agreement_form.institutional_signers:

signed_agreement_form.status = "A"
signed_agreement_form.save()

# Grant this user access immediately if all agreement forms are accepted
for agreement_form in project.agreement_forms.all():
if not SignedAgreementForm.objects.filter(
agreement_form=agreement_form,
project=project,
user=member_participant.user,
status="A"
):
break
else:

# Call this method to process the access
grant_view_permission(request, project_key, member_email)

except ObjectDoesNotExist:
pass

except Exception as e:
logger.exception(
'[HYPATIO][DEBUG][grant_view_permission] User {user} could not have permission added to project {project_key}: {e}'.format(
Expand Down Expand Up @@ -1226,6 +1275,26 @@ def remove_view_permission(request, project_key, user_email):
participant = project.participant_set.get(user__email=user_email)
participant.permission = None
participant.save()

# Check if this project allows institutional signers
if project.institutional_signers:

# Check if this is a signing official
try:
official = InstitutionalOfficial.objects.get(
project=project,
user=participant.user,
)

# Iterate linked members
for member_email in official.member_emails:

# Remove their access
remove_view_permission(request, project_key, member_email)

except ObjectDoesNotExist:
pass

except Exception as e:
logger.exception(
'[HYPATIO][ERROR][grant_view_permission] User {user} could not have permission remove from project {project_key}: {e}'.format(
Expand Down
32 changes: 32 additions & 0 deletions app/projects/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re
import importlib
from datetime import datetime
from typing import Optional, Tuple

import boto3
from botocore.exceptions import ClientError
Expand Down Expand Up @@ -489,6 +490,37 @@ class Meta:
verbose_name_plural = 'Signed Agreement Forms'


def get_institutional_signer_details(self) -> Optional[Tuple[str, str, list[str]]]:
"""
Checks the SignedAgreementForm to see if it has been signed by an institutional official.
If so, it returns the name of the institution and email of the institutional official along with a list of
emails for the members they are representing.
:returns: A tuple containing the institution name, official email, and a list of member emails if this is an
institutional signer; otherwise tuple of None, None, None
:rtype: Optional[Tuple[str, str, list[str]]], defaults to Tuple[None, None, None]
"""
if self.agreement_form.institutional_signers:

# Fields should contain whether this is an institutional official or not
if self.fields and self.fields.get("registrant_is", "").lower() == "official":

# Ensure member emails is a list
member_emails = self.fields.get("member_emails", [])
if isinstance(member_emails, str):
member_emails = [member_emails]
elif not isinstance(member_emails, list):
raise ValidationError(f"Unhandled state of 'member_emails' field: {type(member_emails)}/{member_emails}")

# Cleanup emails to remove whitespace, if any
member_emails = [email.strip() for email in member_emails]

# Return values
return self.fields["institute_name"], self.user.email, member_emails

return None, None, None


class DataUseReportRequest(models.Model):
"""
This model describes a request for a participant to report on data use.
Expand Down
32 changes: 19 additions & 13 deletions app/projects/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,22 @@ def signed_agreement_form_pre_save_handler(sender, **kwargs):
logger.debug(f"Pre-save: {instance}")

# Check for specific types of forms that require additional handling
if instance.status == "A" and instance.agreement_form.short_name == "4ce-dua" \
and instance.fields.get("registrant-is") == "official":
logger.debug(f"Pre-save institutional official: {instance}")

# Create official and member objects
official = InstitutionalOfficial.objects.create(
user=instance.user,
institution=instance.fields["institute-name"],
project=instance.project,
signed_agreement_form=instance,
member_emails=instance.fields["member-emails"],
)
official.save()
institute_name, official_email, member_emails = instance.get_institutional_signer_details()
if instance.status == "A" and institute_name and official_email and member_emails:
logger.debug(f"Pre-save institutional official AgreementForm: {instance}")

# Ensure they don't already exist
if InstitutionalOfficial.objects.filter(user=instance.user, project=instance.project).exists():
logger.debug(f"InstitutionalOfficial already exists for {instance.user}/{instance.project}")

else:

# Create official and member objects
official = InstitutionalOfficial.objects.create(
user=instance.user,
institution=institute_name,
project=instance.project,
signed_agreement_form=instance,
member_emails=member_emails,
)
official.save()
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "dbmi-data-portal"
version = "1.2.0"
version = "1.2.1-rc.1"
description = "A portal for hosting and managing access to DBMI-provided datasets"
readme = "README.md"
requires-python = ">=3.9"
Expand Down
Loading

0 comments on commit ac9df42

Please sign in to comment.