Skip to content

Commit

Permalink
Merge pull request #624 from hms-dbmi/HYP-298
Browse files Browse the repository at this point in the history
HYP-298 - Setup SignedAgreementForms to be blank for instances where …
  • Loading branch information
b32147 authored Sep 13, 2023
2 parents 9a86750 + d1df15b commit 31efbd5
Show file tree
Hide file tree
Showing 13 changed files with 335 additions and 11 deletions.
4 changes: 4 additions & 0 deletions app/manage/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,7 @@ def __init__(self, *args, **kwargs):
# Limit agreement form choices to those related to the passed project
if project_key:
self.fields['agreement_form'].queryset = DataProject.objects.get(project_key=project_key).agreement_forms.all()


class UploadSignedAgreementFormFileForm(forms.Form):
file = forms.FileField(label="Signed Agreement Form PDF", required=True)
2 changes: 2 additions & 0 deletions app/manage/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from manage.views import ProjectPendingParticipants
from manage.views import team_notification
from manage.views import UploadSignedAgreementFormView
from manage.views import UploadSignedAgreementFormFileView

from manage.api import set_dataproject_details
from manage.api import set_dataproject_registration_status
Expand Down Expand Up @@ -62,6 +63,7 @@
re_path(r'^get-project-participants/(?P<project_key>[^/]+)/$', ProjectParticipants.as_view(), name='get-project-participants'),
re_path(r'^get-project-pending-participants/(?P<project_key>[^/]+)/$', ProjectPendingParticipants.as_view(), name='get-project-pending-participants'),
re_path(r'^upload-signed-agreement-form/(?P<project_key>[^/]+)/(?P<user_email>[^/]+)/$', UploadSignedAgreementFormView.as_view(), name='upload-signed-agreement-form'),
re_path(r'^upload-signed-agreement-form-file/(?P<signed_agreement_form_id>[^/]+)/$', UploadSignedAgreementFormFileView.as_view(), name='upload-signed-agreement-form-file'),
re_path(r'^(?P<project_key>[^/]+)/$', DataProjectManageView.as_view(), name='manage-project'),
re_path(r'^(?P<project_key>[^/]+)/(?P<team_leader>[^/]+)/$', manage_team, name='manage-team'),
]
86 changes: 86 additions & 0 deletions app/manage/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from dbmi_client import fileservice
from django.shortcuts import get_object_or_404

from hypatio.sciauthz_services import SciAuthZ
from hypatio.scireg_services import get_user_profile, get_distinct_countries_participating

from manage.forms import NotificationForm
from manage.models import ChallengeTaskSubmissionExport
from manage.forms import UploadSignedAgreementFormForm
from manage.forms import UploadSignedAgreementFormFileForm
from projects.models import AgreementForm, ChallengeTaskSubmission
from projects.models import DataProject
from projects.models import Participant
Expand Down Expand Up @@ -909,3 +911,87 @@ def post(self, request, project_key, user_email, *args, **kwargs):
response['X-IC-Script'] += "$('#page-modal').modal('hide');"

return response


@method_decorator([user_auth_and_jwt], name='dispatch')
class UploadSignedAgreementFormFileView(View):
"""
View to upload signed agreement form files for participants.
* Requires token authentication.
* Only admin users are able to access this view.
"""
def get(self, request, signed_agreement_form_id, *args, **kwargs):
"""
Return the upload form template
"""
user = request.user
user_jwt = request.COOKIES.get("DBMI_JWT", None)

signed_agreement_form = get_object_or_404(SignedAgreementForm, id=signed_agreement_form_id)

sciauthz = SciAuthZ(user_jwt, user.email)
is_manager = sciauthz.user_has_manage_permission(signed_agreement_form.project.project_key)

if not is_manager:
logger.debug('User {email} does not have MANAGE permissions for item {project_key}.'.format(
email=user.email,
project_key=signed_agreement_form.project.project_key
))
return HttpResponse(403)

# Return file upload form
form = UploadSignedAgreementFormFileForm()

# Set context
context = {
"form": form,
"signed_agreement_form_id": signed_agreement_form_id,
}

# Render html
return render(request, "manage/upload-signed-agreement-form-file.html", context)

def post(self, request, signed_agreement_form_id, *args, **kwargs):
"""
Process the form
"""
user = request.user
user_jwt = request.COOKIES.get("DBMI_JWT", None)

signed_agreement_form = get_object_or_404(SignedAgreementForm, id=signed_agreement_form_id)

sciauthz = SciAuthZ(user_jwt, user.email)
is_manager = sciauthz.user_has_manage_permission(signed_agreement_form.project.project_key)

if not is_manager:
logger.debug('User {email} does not have MANAGE permissions for item {project_key}.'.format(
email=user.email,
project_key=signed_agreement_form.project.project_key
))
return HttpResponse(403)

# Assembles the form and run validation.
form = UploadSignedAgreementFormFileForm(data=request.POST, files=request.FILES)
if not form.is_valid():
logger.warning('Form failed: {}'.format(form.errors.as_json()))
return HttpResponse(status=400)

logger.debug(f"[upload_signed_agreement_form_file] Data -> {form.cleaned_data}")

# Set the file and save
signed_agreement_form.upload = form.cleaned_data['file']
signed_agreement_form.save()

# Create the response.
response = HttpResponse(status=201)

# Setup the script run.
response['X-IC-Script'] = "notify('{}', '{}', 'glyphicon glyphicon-{}');".format(
"success", "Signed agreement form file successfully uploaded", "thumbs-up"
)

# Close the modal
response['X-IC-Script'] += "$('#page-modal').modal('hide');"

return response
18 changes: 18 additions & 0 deletions app/projects/migrations/0102_agreementform_skippable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.4 on 2023-09-12 16:20

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('projects', '0101_challengetask_submission_instructions'),
]

operations = [
migrations.AddField(
model_name='agreementform',
name='skippable',
field=models.BooleanField(default=False, help_text='Allow participants to skip this step in instances where they have submitted the agreement form via email or some other means. They will be required to include the name and contact information of the person who they submitted their signed agreement form to.'),
),
]
6 changes: 6 additions & 0 deletions app/projects/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,12 @@ class AgreementForm(models.Model):
order = models.IntegerField(default=50, help_text="Indicate an order (lowest number = first listing) for how the Agreement Forms should be listed during registration workflows.")
content = models.TextField(blank=True, null=True, help_text="If Agreement Form type is set to 'MODEL', the HTML set here will be rendered for the user")
internal = models.BooleanField(default=False, help_text="Internal agreement forms are never presented to participants and are only submitted by administrators on behalf of participants")
skippable = models.BooleanField(
default=False,
help_text="Allow participants to skip this step in instances where they have submitted the agreement form via"
" email or some other means. They will be required to include the name and contact information of"
" the person who they submitted their signed agreement form to."
)

# Meta
created = models.DateTimeField(auto_now_add=True)
Expand Down
12 changes: 12 additions & 0 deletions app/static/js/portal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

/**
* Finds all 'button' elements contained in a form and toggles their 'disabled' proeprty.
* @param {String} formSelector The jQuery selector of the form to disable buttons for.
*/
function toggleFormButtons(formSelector) {

// Toggle disabled state of all buttons in form
$(formSelector).find("button").each(function() {
$(this).prop("disabled", !$(this).prop("disabled"));
});
}
52 changes: 49 additions & 3 deletions app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
<script type='text/javascript' src="{% static 'plugins/bootstrap-notify/bootstrap-notify.min.js' %}"></script>
<script type="text/javascript" src="{% static 'plugins/intercooler/intercooler.min.js' %}"></script>

<!-- Include portal specific Javascript -->
<script type='text/javascript' src="{% static 'js/portal.js' %}"></script>

<title>{% block tab_name %}DBMI Portal{% endblock %}</title>

<script type="application/javascript">
Expand Down Expand Up @@ -226,12 +229,14 @@
<a class="dropdown-toggle" data-toggle="dropdown" href="#" id="download">{{ request.user.email }}<span class="caret"></span></a>
<ul class="dropdown-menu" aria-labelledby="downloadn">
<li><a class="nav-link" href="{% url 'profile:profile' %}">Profile <span class="fas fa-sm fa-user"></span></a></li>

{% is_project_manager request as is_manager %}
{% if is_manager %}
<li><a class="nav-link" href="{% url 'manage:manage-projects' %}">Manage Projects</a></li>
<li><a class="nav-link" href="{% url 'manage:manage-projects' %}">Manage Projects</a></li>
{% endif %}

{% if dbmiuser and dbmiuser.jwt %}
<li><a id="jwt-copy" class="nav-link clipboard-copy" data-clipboard-text="{{ dbmiuser.jwt|default:"<empty>" }}" data-toggle="tooltip" style="cursor: pointer;" title="Copy API Key" data-tooltip-title="Copy API Key">API Key <i class="fa fa-wrench" aria-hidden="true"></i>
{% endif %}
</a></li>
<li><a class="nav-link" href="{% url 'profile:signout' %}">Sign Out</a></li>
</ul>
</li>
Expand All @@ -255,4 +260,45 @@

{# Allow for some javascript to be added per page #}
{% block javascript %}{% endblock %}

<script type="text/javascript" src="{% static 'js/clipboard.min.js' %}"></script>
<script type="application/javascript">
$(document).ready(function(){

// Initialize tooltips
$('[data-toggle="tooltip"]').tooltip();

// Reset tooltips
$('[data-toggle="tooltip"][data-tooltip-title]').on('hidden.bs.tooltip', function(){
$(this).attr('data-original-title', $(this).attr('data-tooltip-title'));
});

// Setup copy button
var clipboards = new ClipboardJS(".clipboard-copy");
clipboards.on('success', function(e) {

// Update tooltip
$(e.trigger).attr('data-original-title', "Copied!")
.tooltip('fixTitle')
.tooltip('setContent')
.tooltip('show');

e.clearSelection();
});

clipboards.on('error', function(e) {

// Update tooltip
$(e.trigger).attr('data-original-title', "Error!")
.tooltip('fixTitle')
.tooltip('setContent')
.tooltip('show');

// Log it
console.log('Copy error:' + e.toString());

e.clearSelection();
});
});
</script>
</html>
23 changes: 23 additions & 0 deletions app/templates/manage/upload-signed-agreement-form-file.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% load bootstrap3 %}

<form id="upload-signed-agreement-form-file-form" class="file-upload-form" method="post" enctype="multipart/form-data"
ic-post-to="{% url 'manage:upload-signed-agreement-form-file' signed_agreement_form_id %}"
ic-on-beforeSend="toggleFormButtons('#upload-signed-agreement-form-file-form')"
ic-on-complete="toggleFormButtons('#upload-signed-agreement-form-file-form')">
<div class="modal-header modal-header-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h3 class="modal-title">Signed Agreement Form File Upload</h3>
</div>
<div class="modal-body">
{% csrf_token %}
{% bootstrap_form form inline=True %}
</div>
<div class="modal-footer">
{% buttons %}
<button id="file-upload-close-button" type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button id="file-upload-submit-button" type="submit" class="btn btn-primary">Submit
<span id="file-upload-form-indicator" style="display: none; margin-left: 5px;" class="ic-indicator fa fa-spinner fa-spin"></span>
</button>
{% endbuttons %}
</div>
</form>
7 changes: 5 additions & 2 deletions app/templates/manage/upload-signed-agreement-form.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{% load bootstrap3 %}

<form id="upload-signed-agreement-form-form" class="file-upload-form" method="post" enctype="multipart/form-data" ic-post-to="{% url 'manage:upload-signed-agreement-form' project_key user_email %}">
<form id="upload-signed-agreement-form-form" class="file-upload-form" method="post" enctype="multipart/form-data"
ic-post-to="{% url 'manage:upload-signed-agreement-form' project_key user_email %}"
ic-on-beforeSend="toggleFormButtons('#upload-signed-agreement-form-form')"
ic-on-complete="toggleFormButtons('#upload-signed-agreement-form-form')">
<div class="modal-header modal-header-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h3 class="modal-title">Signed Agreement Form Upload</h3>
Expand All @@ -13,7 +16,7 @@ <h3 class="modal-title">Signed Agreement Form Upload</h3>
{% buttons %}
<button id="file-upload-close-button" type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button id="file-upload-submit-button" type="submit" class="btn btn-primary">Submit
<span id="file-upload-form-indicator" style="display: none; margin-left: 5px;" class="ic-indicator glyphicon glyphicon-refresh gly-spin"></span>
<span id="file-upload-form-indicator" style="display: none; margin-left: 5px;" class="ic-indicator fa fa-spinner fa-spin"></span>
</button>
{% endbuttons %}
</div>
Expand Down
19 changes: 17 additions & 2 deletions app/templates/projects/participate/view-signed-agreement-form.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@
<div class="col-md-7">
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="heading-description">
<h2 class="panel-title">Signed Form Content</h2>
<h2 class="panel-title">Signed Form Content
<button type="button" class="btn btn-default btn-xs btn-primary pull-right"
ic-get-from="{% url 'manage:upload-signed-agreement-form-file' signed_form.id %}"
data-toggle="modal" data-target="#page-modal" data-backdrop="static" data-keyboard="false"
ic-target="#page-modal-content">
Upload File&nbsp;&nbsp;<span class="glyphicon glyphicon-cloud-upload" aria-hidden="true"></span>
</button>
</h2>
</div>
<div class="panel-body" style="height: 850px;">
{# Check type #}
{% if signed_form.agreement_form.type == "FILE" %}
{% if signed_form.agreement_form.type == "FILE" or signed_form.upload %}

{# Handle instances where a DUA was submitted when it was not required to upload a file #}
{% if not signed_form.upload %}
Expand Down Expand Up @@ -156,4 +163,12 @@ <h2 class="panel-title">Actions</h2>
});
});
</script>

{# Add a placeholder for any modal dialogs #}
<div id='page-modal' class='modal fade' tabindex='-1'>
<div class="modal-dialog" role="document">
<div id='page-modal-content' class="modal-content">
</div>
</div>
</div>
{% endblock %}
Loading

0 comments on commit 31efbd5

Please sign in to comment.