Skip to content

Commit

Permalink
Adding cohost permissions to view/respond to participation requests (M…
Browse files Browse the repository at this point in the history
…IT-LCP#2243)

MIT-LCP#2192 Implements the ability for an event host to add cohosts to the
event. Currently cohosts don't have permissions to do anything on the
platform.

This PR implements the change in the events functionality for the cohost
to be able to view the participant lists, edit the event, view/respond
to participation requests.

A scenario: Considering the event is held across the nation with
multiple hubs, with each hub having an admin/lead. The admin for each
hub is a cohost that can individually verify and approve the participant
requests for the hub.
  • Loading branch information
tompollard authored Jul 3, 2024
2 parents fc284c7 + 107f502 commit c29fc7b
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
<ul class="list-group list-group-flush">
<li class="list-group-item">
<h5>Add a dataset to the event</h5>
<p>By adding a dataset, all participants (approved by {{ event.host.username }} or their cohosts) will be allowed to access this
dataset for the duration of the event (from {{ event.start_date }} to {{ event.end_date }}). There are currently {{ event.participants.count }} approved
participants in this event. Note that once a dataset is added to the event, the dates of the event cannot be changed.</p>
<form action="" method="post" class="form-event-dataset">
{% csrf_token %}
{% include 'form_snippet.html' with form=event_dataset_form %}
Expand Down
5 changes: 3 additions & 2 deletions physionet-django/console/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3134,7 +3134,8 @@ def event_archive(request):
})


@console_permission_required('user.view_all_events')
@console_permission_required('event.add_event_dataset')
@console_permission_required('event.view_all_events')
def event_management(request, event_slug):
"""
Admin page for managing an individual Event.
Expand Down Expand Up @@ -3172,7 +3173,7 @@ def event_management(request, event_slug):

return redirect("event_management", event_slug=event_slug)
else:
event_dataset_form = EventDatasetForm()
event_dataset_form = EventDatasetForm(user=request.user)

participants = selected_event.participants.all()
pending_applications = selected_event.applications.filter(
Expand Down
14 changes: 13 additions & 1 deletion physionet-django/events/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from events.widgets import DatePickerInput
from events.models import Event, EventApplication, EventAgreement, EventDataset, CohostInvitation
from project.models import PublishedProject
from user.models import CredentialApplication

INVITATION_CHOICES = (
(1, 'Accept'),
Expand Down Expand Up @@ -72,7 +73,18 @@ class EventDatasetForm(forms.ModelForm):
"""
A form for adding datasets to an event.
"""
dataset = forms.ModelChoiceField(queryset=PublishedProject.objects.all(),

def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(EventDatasetForm, self).__init__(*args, **kwargs)

# Get the projects that the user is credentialed to access
projects = PublishedProject.objects.accessible_by(user, include_event_datasets=False)

# Update the queryset of the 'dataset' field
self.fields['dataset'].queryset = projects

dataset = forms.ModelChoiceField(queryset=PublishedProject.objects.none(),
widget=forms.Select(attrs={'class': 'form-control'}))

class Meta:
Expand Down
23 changes: 23 additions & 0 deletions physionet-django/events/migrations/0010_alter_event_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.11 on 2024-05-29 16:05

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("events", "0009_cohostinvitation"),
]

operations = [
migrations.AlterModelOptions(
name="event",
options={
"permissions": [
("view_all_events", "Can view all events in the console"),
("view_event_menu", "Can view event menu in the navbar"),
("add_event_dataset", "Can add a dataset to an event"),
]
},
),
]
6 changes: 4 additions & 2 deletions physionet-django/events/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class Event(models.Model):
class Meta:
unique_together = ('title', 'host')
permissions = [('view_all_events', 'Can view all events in the console'),
('view_event_menu', 'Can view event menu in the navbar')]
('view_event_menu', 'Can view event menu in the navbar'),
('add_event_dataset', 'Can add a dataset to an event')]

def save(self, *args, **kwargs):
if not self.slug:
Expand Down Expand Up @@ -218,7 +219,8 @@ def is_accessible(self):
if not self.is_active:
return False

if timezone.now().date() > self.event.end_date:
if ((timezone.now().date() > self.event.end_date)
or (timezone.now().date() < self.event.start_date)):
return False
return True

Expand Down
6 changes: 4 additions & 2 deletions physionet-django/events/templates/events/event_home.html
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,15 @@ <h3>{{ event.title }}</h3>
<small>Registration status: On waiting list</small><br>
{% endif %}

{% if event.host == user %}
{% if user|can_view_participants:event %}
<small>Share the class code: {{ url_prefix }}{% url 'event_detail' event.slug %} </small><br>
</p>
<button class="btn btn-sm btn-primary" data-toggle ="modal" data-target="#participants-modal-{{ event.id }}">View participants</button>
<button class="btn btn-sm btn-primary" data-toggle ="modal" data-target="#pending_applications-modal-{{ event.id }}">Pending Applications</button>
<button class="btn btn-sm btn-primary" data-toggle ="modal" data-target="#rejected_applications-modal-{{ event.id }}">Rejected Applications</button>
<button class="btn btn-sm btn-primary" data-toggle ="modal" data-target="#withdrawn_applications-modal-{{ event.id }}">Withdrawn Applications</button>
<button class="btn btn-sm btn-primary" data-toggle ="modal" data-target="#withdrawn_applications-modal-{{ event.id }}">Withdrawn Applications</button>
{% endif %}
{% if user == event.host %}
<a class="btn btn-sm btn-secondary" href="{% url 'update_event' event.slug %}" data-form-url="{% url 'update_event' event.slug %}" >Edit Event</a>
{% endif %}

Expand Down
20 changes: 20 additions & 0 deletions physionet-django/events/templates/events/event_notifications.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,26 @@ <h5 class="modal-title">Respond to participation request</h5>
<div class="modal-body author_participation-response_form">
<p>{{ participation_response_form.instance.user.get_full_name }} is requesting
to join Event {{ participation_response_form.instance.event.title }}.</p>

{% if participation_response_form.instance.event.datasets.exists %}

<p>If a participant is approved to join this event, the participant will be
allowed to access the following datasets for the duration of the
event ({{ participation_response_form.instance.event.start_date }}
to {{ participation_response_form.instance.event.end_date }}):</p>

<ul>

{% for dataset in participation_response_form.instance.event.datasets.all %}
<li>{{ dataset.dataset.title }}</li>
{% endfor %}

</ul>

{% endif %}

<br>

{{ participation_response_form }}
</div>

Expand Down
8 changes: 8 additions & 0 deletions physionet-django/events/templatetags/participation_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,11 @@ def has_access_to_event_dataset(user, dataset):
@register.filter(name="get_applicant_info")
def get_applicant_info(event_details, event_id):
return event_details[event_id]


@register.filter(name="can_view_participants")
def can_view_participants(user, event):
# check if the user is a host or an event participant with is_cohost flag set to True
return event.host == user or event.participants.filter(
user=user, is_cohost=True
).exists()
30 changes: 25 additions & 5 deletions physionet-django/events/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,16 @@

@login_required
def update_event(request, event_slug, **kwargs):

user = request.user
can_change_event = user.has_perm('events.add_event')
event = Event.objects.get(slug=event_slug)

# if the event has dataset added to it, it cannot be edited
if event.datasets.exists():
messages.error(request, "Event with datasets cannot be edited")
return redirect(reverse('event_detail', args=[event_slug]))

if request.method == 'POST':
event_form = AddEventForm(user=user, data=request.POST, instance=event)
if event_form.is_valid():
Expand Down Expand Up @@ -109,6 +116,9 @@ def event_home(request):

form_error = False

cohost_ids = EventParticipant.objects.filter(
user=user, is_cohost=True).values_list("event__id", flat=True)

# handle notifications to join an event
if request.method == "POST" and "participation_response" in request.POST.keys():
formset = EventApplicationResponseFormSet(request.POST)
Expand All @@ -123,7 +133,13 @@ def event_home(request):

event_application = form.save(commit=False)
event = event_application.event
if event.host != user:
# if user is not a host or a participant with cohort status, they don't have permission to accept/reject
if not (
event.host == user
or EventParticipant.objects.filter(
event=event, user=user, is_cohost=True
).exists()
):
messages.error(
request,
"You don't have permission to accept/reject this application",
Expand Down Expand Up @@ -226,16 +242,20 @@ def event_home(request):
},
]

# get all participation requests for Active events where the current user is the host and the participants are
# waiting for a response
# making the query to get all the participation requests for the events
# where the user is the host or an event participant with cohort status
participation_requests = EventApplication.objects.filter(
status=EventApplication.EventApplicationStatus.WAITLISTED
).filter(event__host=user, event__end_date__gte=datetime.now())
Q(event__host=user) | Q(event__id__in=cohost_ids),
status=EventApplication.EventApplicationStatus.WAITLISTED,
event__end_date__gte=datetime.now(),
)

participation_response_formset = EventApplicationResponseFormSet(
queryset=participation_requests
)
invitation_response_formset = InvitationResponseFormSet(
queryset=CohostInvitation.get_user_invitations(user))

return render(
request,
"events/event_home.html",
Expand Down
21 changes: 11 additions & 10 deletions physionet-django/project/managers/publishedproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


class PublishedProjectManager(Manager):
def accessible_by(self, user):
def accessible_by(self, user, include_event_datasets=True):
"""
Return all published projects accessible by a specified user
Part of the `hdn-research-environment` app contract
Expand Down Expand Up @@ -52,14 +52,15 @@ def accessible_by(self, user):
contributor_review_with_access | credentialed_with_dua_signed
)

# add projects that are accessible through events
events_all = Event.objects.filter(Q(host=user) | Q(participants__user=user))
active_events = set(events_all.filter(end_date__gte=datetime.now()))
accessible_datasets = EventDataset.objects.filter(event__in=active_events, is_active=True)
accessible_projects_ids = []
for event_dataset in accessible_datasets:
if has_access_to_event_dataset(user, event_dataset):
accessible_projects_ids.append(event_dataset.dataset.id)
query |= Q(id__in=accessible_projects_ids)
if include_event_datasets:
# add projects that are accessible through events
events_all = Event.objects.filter(Q(host=user) | Q(participants__user=user))
active_events = set(events_all.filter(end_date__gte=datetime.now()))
accessible_datasets = EventDataset.objects.filter(event__in=active_events, is_active=True)
accessible_projects_ids = []
for event_dataset in accessible_datasets:
if has_access_to_event_dataset(user, event_dataset):
accessible_projects_ids.append(event_dataset.dataset.id)
query |= Q(id__in=accessible_projects_ids)

return self.filter(query).distinct()

0 comments on commit c29fc7b

Please sign in to comment.