Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement event agreement on event app #1904

Draft
wants to merge 11 commits into
base: dev
Choose a base branch
from
26 changes: 26 additions & 0 deletions physionet-django/events/fixtures/demo-events.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,31 @@
"slug": "PsenZXNhPluI",
"allowed_domains": null
}
},
{
"model": "events.eventagreement",
"pk": 1,
"fields": {
"name": "PhysioNet Restricted Health Data Use Agreement",
"slug": "sdadadsadsaa",
"version": "1.1",
"is_active": true,
"html_content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Tincidunt lobortis feugiat vivamus at augue eget.</p>",
"access_template": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Tincidunt lobortis feugiat vivamus at augue eget.</p>",
"creator": 1
}
},
{
"model": "events.eventagreement",
"pk": 2,
"fields": {
"name": "PhysioNet Restricted Health Data Use Agreement",
"slug": "vafgtyathsgh",
"version": "1.2",
"is_active": true,
"html_content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Tincidunt lobortis feugiat vivamus at augue eget.</p>",
"access_template": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Tincidunt lobortis feugiat vivamus at augue eget.</p>",
"creator": 1
}
}
]
3 changes: 2 additions & 1 deletion physionet-django/events/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class AddEventForm(forms.ModelForm):
class Meta:
model = Event
fields = ('title', 'description', 'start_date', 'end_date',
'category', 'allowed_domains')
'category', 'event_agreement', 'allowed_domains')
labels = {'title': 'Event Name', 'description': 'Description',
'start_date': 'Start Date', 'end_date': 'End Date',
'category': 'Category', 'allowed_domains': 'Allowed domains'}
Expand All @@ -23,6 +23,7 @@ class Meta:
def __init__(self, user, *args, **kwargs):
self.host = user
super(AddEventForm, self).__init__(*args, **kwargs)
self.fields['event_agreement'].queryset = EventAgreement.objects.filter(is_active=True)

def clean(self):
cleaned_data = super(AddEventForm, self).clean()
Expand Down
20 changes: 20 additions & 0 deletions physionet-django/events/migrations/0009_event_event_agreement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 3.2.16 on 2023-03-15 17:03

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('events', '0008_alter_event_description'),
]

operations = [
migrations.AddField(
model_name='event',
name='event_agreement',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL,
to='events.eventagreement'),
),
]
11 changes: 8 additions & 3 deletions physionet-django/events/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Event(models.Model):
slug = models.SlugField(unique=True)
allowed_domains = models.CharField(blank=True, null=True, validators=[
validators.validate_domain_list], max_length=100)
event_agreement = models.ForeignKey('events.EventAgreement', null=True, blank=True, on_delete=models.SET_NULL)

class Meta:
unique_together = ('title', 'host')
Expand Down Expand Up @@ -171,7 +172,7 @@ class Meta:
unique_together = (('name', 'version'),)

def __str__(self):
return self.name
return self.name + ' ' + self.version


class EventAgreementSignature(models.Model):
Expand Down Expand Up @@ -230,13 +231,17 @@ def has_access(self, user):
if not self.is_accessible():
return False

if self.event.host == user:
return True
# check if the user is a participant of the event or the host of the event
# In addition to participants, host should also have access to the dataset of their own event
# we dont need to worry about cohosts here as they are already participants
if not self.event.participants.filter(user=user).exists() and not self.event.host == user:
if not self.event.participants.filter(user=user).exists():
return False

# TODO once Event Agreement/DUA is merged, check if the user has accepted the agreement
if self.event.event_agreement and \
not EventAgreementSignature.objects.filter(event=self.event, user=user).exists():
return False

return True

Expand Down
10 changes: 10 additions & 0 deletions physionet-django/events/templates/events/event_agreement.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% if is_participant and event.event_agreement %}
<div class="container">
<h5>Event Agreement</h5>
{% if not has_signed_event_agreement %}
<p><a href="{% url 'sign_event_agreement' event.slug %}">Please sign the Event Agreement</a> to access the Datasets</p>
{% else %}
<p>You have signed the Event Agreement. Please check the Datasets table below to access data.</p>
{% endif %}
</div>
{% endif %}
6 changes: 4 additions & 2 deletions physionet-django/events/templates/events/event_datasets.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ <h5>Datasets</h5>
<td>{{ dataset.get_access_type_display }}</td>
<td>
{% if not dataset.is_accessible %}
<a class="btn btn-success" href="#">Access Archieved</a>
{% elif is_waitlisted%}
<a class="btn btn-success" href="#">Access Archived</a>
{% elif is_waitlisted %}
<a class="btn btn-secondary" href="#">On waiting list</a>
{% elif not has_signed_event_agreement and is_participant %}
<a class="btn btn-secondary" href="{% url 'sign_event_agreement' event.slug %}">Sign the event agreement</a>
{% elif user|has_access_to_event_dataset:dataset %}
<a class="btn btn-success" href="/environments/">Access here</a>
{% else %}
Expand Down
4 changes: 3 additions & 1 deletion physionet-django/events/templates/events/event_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ <h4>Event Details</h4>

{% include "events/event_participation.html" %}
<hr>
{% include "events/event_agreement.html" %}
<hr>
{% if event_datasets|length != 0 %}
{% include "events/event_datasets.html" %}
{% endif %}

</div>
{% endif %}
{% endblock %}
29 changes: 29 additions & 0 deletions physionet-django/events/templates/events/sign_event_agreement.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{% extends "base.html" %}

{% load static %}

{% block title %}
Sign Event Agreement for {{ event }}
{% endblock %}

{% block content %}
<div class="container">
<h1>Sign Event Agreement - {{ event }}</h1>
<hr>
<p>Sign the following Event agreement to access the datasets in <a href="{% url 'event_detail' event.slug %}">{{ event }}</a>.</p>
<hr>
<h2>{{ event.event_agreement.name }}</h2>

{{ event.event_agreement.html_content|safe }}
<hr>
{{ event.event_agreement.access_template|safe }}
<hr>
<form method="POST">
{% csrf_token %}
<p class="text-center">
<button class="btn btn-lg btn-success" name="agree" type="submit">I agree</button> <a class="btn btn-lg btn-danger" href="{% url 'event_detail' event.slug %}">I do not agree</a>
</p>
</form>

</div>
{% endblock %}
57 changes: 56 additions & 1 deletion physionet-django/events/tests_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from django.urls import reverse

from events.models import Event, EventApplication
from events.models import Event, EventApplication, EventAgreementSignature
from user.test_views import TestMixin


Expand Down Expand Up @@ -35,6 +35,7 @@ def test_add_event_valid(self):
'start_date': self.new_event_start_date_str,
'end_date': self.new_event_end_date_str,
'category': 'Course',
'event_agreement': ['1'],
'allowed_domains': '',
'add-event': ''
})
Expand All @@ -57,6 +58,7 @@ def test_add_event_invalid(self):
'start_date': self.new_event_start_date_str,
'end_date': self.new_event_end_date_str,
'category': 'Course',
'event_agreement': ['1'],
'allowed_domains': '',
'add-event': ''
})
Expand All @@ -81,6 +83,7 @@ def test_edit_event_valid(self):
'start_date': self.new_event_start_date_str,
'end_date': self.new_event_end_date_str,
'category': 'Course',
'event_agreement': ['1'],
'allowed_domains': ''
})

Expand All @@ -105,6 +108,7 @@ def test_edit_event_invalid(self):
'start_date': self.new_event_start_date_str,
'end_date': self.new_event_end_date_str,
'category': 'Course',
'event_agreement': ['1'],
'allowed_domains': '',
'add-event': ''
})
Expand All @@ -122,6 +126,7 @@ def test_edit_event_invalid(self):
'start_date': self.new_event_start_date_str,
'end_date': self.new_event_end_date_str,
'category': 'Workshop',
'event_agreement': ['1'],
'allowed_domains': ''
})

Expand Down Expand Up @@ -259,3 +264,53 @@ def test_event_participation_rejected(self):
# check the status on the event application
event_application.refresh_from_db()
self.assertEqual(event_application.status, EventApplication.EventApplicationStatus.NOT_APPROVED)

def test_event_edit_change_event_agreement(self):
"""tests the view that edits an event and changes the event agreement"""

# create an event
self.test_add_event_valid()

event = Event.objects.get(title=self.new_event_name)
slug = event.slug

# login as the host and edit the event
self.client.login(username='admin', password='Tester11!')

response = self.client.post(
reverse('update_event', kwargs={'event_slug': slug}),
data={
'title': event.title,
'description': event.description,
'start_date': event.start_date,
'end_date': event.end_date,
'category': event.category,
'event_agreement': ['2'],
'allowed_domains': '',
})
self.assertEqual(response.status_code, 302)
event = Event.objects.get(slug=slug)
self.assertEqual(event.event_agreement.pk, 2)

def test_sign_event_agreement(self):
"""tests the view that signs an event agreement"""

# create an event, login as participant, join the event, and login as host and approve the user
self.test_event_participation_approved()

event = Event.objects.get(title=self.new_event_name)

# login as the participant
self.client.login(username='amitupreti', password='Tester11!')

# sign the event agreement
response = self.client.post(
reverse('sign_event_agreement', kwargs={'event_slug': event.slug}),
data={
'agree': ''
})
self.assertEqual(response.status_code, 302)

# check if the user signed the event agreement
EventAgreementSignature.objects.filter(event=event, user__username='amitupreti',
event_agreement=event.event_agreement).exists()
1 change: 1 addition & 0 deletions physionet-django/events/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
path('<slug:event_slug>/', views.event_detail, name='event_detail'),
path('<slug:event_slug>/edit_event/', views.update_event, name='update_event'),
path('<slug:event_slug>/details/', views.get_event_details, name='get_event_details'),
path('sign-event-agreement/<slug:event_slug>', views.sign_event_agreement, name='sign_event_agreement'),
]

# Parameters for testing URLs (see physionet/test_urls.py)
Expand Down
41 changes: 40 additions & 1 deletion physionet-django/events/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import notification.utility as notification
from events.forms import AddEventForm, EventApplicationResponseForm
from events.models import Event, EventApplication, EventParticipant
from events.models import Event, EventApplication, EventParticipant, EventAgreementSignature
from events.utility import notify_host_cohosts_new_registration


Expand Down Expand Up @@ -171,6 +171,13 @@ def event_detail(request, event_slug):

registration_allowed = True
is_waitlisted = False
is_participant = False
if event.event_agreement:
has_signed_event_agreement = EventAgreementSignature.objects.filter(
event=event, user=user, event_agreement=event.event_agreement).exists()
else:
has_signed_event_agreement = True

registration_error_message = ''

# if the event has ended, registration is not allowed, so we can skip the rest of the checks
Expand All @@ -182,6 +189,7 @@ def event_detail(request, event_slug):
registration_error_message = 'You are the host of this event'
elif event.participants.filter(user=user).exists():
registration_allowed = False
is_participant = True
registration_error_message = 'You are registered for this event'
else: # we don't need to check for waitlisted / other stuff if the user is already registered
event_participation_request = EventApplication.objects.filter(
Expand Down Expand Up @@ -233,5 +241,36 @@ def event_detail(request, event_slug):
'registration_allowed': registration_allowed,
'registration_error_message': registration_error_message,
'is_waitlisted': is_waitlisted,
'is_participant': is_participant,
'has_signed_event_agreement': has_signed_event_agreement,
'event_datasets': event_datasets,
})


@login_required
def sign_event_agreement(request, event_slug):
"""
Page to sign the event agreement for participants of an event
"""
user = request.user

event = get_object_or_404(Event, slug=event_slug)

if not event.event_agreement:
messages.error(request, "Event does not have an event agreement.")
return redirect('event_detail', event_slug=event.slug)

if not event.participants.filter(user=user).exists():
messages.error(request, "You are not a participant of this event.")
return redirect('event_detail', event_slug=event.slug)

if EventAgreementSignature.objects.filter(event=event, user=user, event_agreement=event.event_agreement).exists():
messages.error(request, "You have already signed the event agreement.")
return redirect('event_detail', event_slug=event.slug)

if request.method == 'POST' and 'agree' in request.POST:
EventAgreementSignature.objects.create(event=event, user=user, event_agreement=event.event_agreement)
messages.success(request, "You have successfully signed the event agreement.")
return redirect('event_detail', event_slug=event.slug)

return render(request, 'events/sign_event_agreement.html', {'event': event})