You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

310 lines
15 KiB
Python

from django.utils.translation import gettext_lazy as _
# from rest_framework import viewsets
from rest_framework import status
from rest_framework.decorators import action, api_view
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from drf_yasg.utils import swagger_auto_schema
from .models import Study, StudyRole, StudyRoleNames
from .serializers import ContributorSerializer, ContributorUpdateSerializer, InviteSerializer, StudyRoleSerializer, StudySerializer, StudyDetailSerializer
from apps.researcher.models import Researcher
from apps.university.serializers import StudyFieldSerializer
class Studies(ModelViewSet):
"""
API endpoint for creating/reading/updating/deleting studies.
"""
queryset = Study.objects.all().order_by('name')
serializer_class = StudyDetailSerializer
http_method_names = ['get', 'post', 'put', 'delete']
serializer_class_by_action = {
'create': StudySerializer,
'update': StudySerializer,
}
def get_serializer_class(self):
if hasattr(self, 'serializer_class_by_action'):
return self.serializer_class_by_action.get(self.action, self.serializer_class)
return super().get_serializer_class()
def get_queryset(self):
if getattr(self, 'swagger_fake_view', False):
return self.queryset
return Study.objects.filter(contributors__in=[self.request.user.researcher]).order_by('name')
def _valid_study_field_check(self, serializer):
# Check if the study field is correct based on the researcher that is creating this study
if serializer.validated_data.get('field').faculty != self.request.user.researcher.faculty:
raise ValidationError(_('Study field %(study_field_name)s is not valid for faculty %(faculty_name)s') % {'study_field_name': serializer.validated_data['field'], 'faculty_name': self.request.user.researcher.faculty})
def _valid_contributors_check(self, serializer):
contributors = serializer.validated_data.get('contributors', [])
if len(contributors) == 0:
# There are zero contributors, so add the current logged in user as the first one with admin rights
contributors.append({'researcher': self.request.user.researcher, 'role': StudyRoleNames.ADMIN, 'active': True})
else:
# Check if the current logged in user is already part of this study
creator_in_contributors = any([contributor['researcher'] == self.request.user.researcher for contributor in contributors])
if not creator_in_contributors:
# Add the current logged in user as an admin to the study
contributors.append({'researcher': self.request.user.researcher, 'role': StudyRoleNames.ADMIN, 'active': True})
# Get a list of the researchers working on the same faculty
valid_researchers = list(Researcher.objects.filter(faculty=self.request.user.researcher.faculty).values_list('pk', flat=True))
# Filter the list based on the logged in researcher his faculty
contributors = [contributor for contributor in contributors if contributor['researcher'].id in valid_researchers]
return contributors
def perform_create(self, serializer):
# Check if there are already extra contributors added
contributors = self._valid_contributors_check(serializer)
# Check if the study field is correct based on the researcher that is creating this study
self._valid_study_field_check(serializer)
serializer.save(owner=self.request.user.researcher, contributors=contributors)
def perform_update(self, serializer):
# Check if there are already extra contributors added
# TODO: Is this needed? As with an update, we cannot change the contributors...
# self._valid_contributors_check(serializer)
# Check if the study field is correct based on the researcher that is creating this study
self._valid_study_field_check(serializer)
serializer.save()
@swagger_auto_schema(request_body=StudySerializer(many=False), responses={200: StudyDetailSerializer(many=False)})
def create(self, request, *args, **kwargs):
study_response = super().create(request, *args, **kwargs)
# Get the created study and reload it for different serializer
study = get_object_or_404(Study, pk=study_response.data['id'], contributors__in=[self.request.user.researcher])
return Response(StudyDetailSerializer(study).data, status=study_response.status_code)
@swagger_auto_schema(request_body=StudySerializer(many=False), responses={200: StudyDetailSerializer(many=False)})
def update(self, request, *args, **kwargs):
study_response = super().update(request, *args, **kwargs)
# Get the created study and reload it for different serializer
study = get_object_or_404(Study, pk=study_response.data['id'], contributors__in=[self.request.user.researcher])
return Response(StudyDetailSerializer(study).data, status=study_response.status_code)
# TODO: Figure out how to add the pagination to the schema output...
@swagger_auto_schema(responses={200: StudyRoleSerializer(many=True)})
@action(detail=False, methods=['get'])
def roles(self, request):
"""Get all the availabe researcher roles for a study.
Args:
request ([type]): The incoming web request
Returns:
StudyRoleSerializer: A list of zero or more study roles
"""
study_roles = []
for role in StudyRoleNames.choices:
study_roles.append({'id': role[0], 'name': role[1]})
page = self.paginate_queryset(study_roles)
if page is not None:
serializer = StudyRoleSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = StudyRoleSerializer(study_roles, many=True)
return Response(serializer.data)
# TODO: Figure out how to add the pagination to the schema output...
@swagger_auto_schema(responses={200: StudyFieldSerializer(many=True)})
@action(detail=False, methods=['get'])
def fields(self, request):
"""Get the logged in researcher his study fields based on the faculty where he belongs to. In other words, this is the list of study fields where the logged in user can do research on.
Args:
request ([type]): The incoming web request
Returns:
StudyFieldSerializer: A list with zero or more study fields for the logged in researcher.
"""
study_fields = request.user.researcher.faculty.studyfield_set.all().order_by('name')
page = self.paginate_queryset(study_fields)
if page is not None:
serializer = StudyFieldSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = StudyFieldSerializer(study_fields, many=True)
return Response(serializer.data)
@swagger_auto_schema(responses={200: StudyDetailSerializer(many=True)})
@action(detail=False, methods=['get'])
def invited(self, request):
"""Show a list of studies where the researcher is invited to.
Args:
request ([type]): The incoming web request
Returns:
StudyFieldSerializer: A list with zero or more study fields for the logged in researcher which he is invited to.
"""
studies = Study.objects.filter(contributors__in=[request.user.researcher], studyrole__active=False, studyrole__invited_at__isnull=False)
request.user.researcher.faculty.studyfield_set.all().order_by('name')
page = self.paginate_queryset(studies)
if page is not None:
serializer = StudyDetailSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = StudyDetailSerializer(studies, many=True)
return Response(serializer.data)
class Contributors(ModelViewSet):
http_method_names = ['get', 'put', 'post', 'delete']
queryset = StudyRole.objects.all()
serializer_class = ContributorSerializer
@swagger_auto_schema(responses={200: ContributorSerializer(many=True)})
def list(self, request, *args, **kwargs):
"""
Get all the contributors that are assigned to a study of which you are also a member of.
"""
study = get_object_or_404(Study, pk=kwargs['study_id'], contributors__in=[self.request.user.researcher])
contributors = StudyRole.objects.filter(study=study).order_by('researcher__last_name')
# Add manual pagination.
page = self.paginate_queryset(contributors)
if page is not None:
serializer = ContributorSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = ContributorSerializer(contributors, many=True)
return Response(serializer.data)
@swagger_auto_schema(responses={200: ContributorSerializer(many=False)})
def get(self, request, *args, **kwargs):
"""
Get a single contributors that is assigned to a study for a study of which you are also a member of.
"""
contributor = get_object_or_404(StudyRole, pk=kwargs['contributor_id'], study=kwargs['study_id'], study__contributors__in=[self.request.user.researcher])
serializer = ContributorSerializer(contributor, many=False)
return Response(serializer.data)
@swagger_auto_schema(request_body=ContributorUpdateSerializer(many=False), responses={200: ContributorSerializer(many=False)})
def update(self, request, *args, **kwargs):
"""Update an existing contributor role. This can only be done by study admins.
Raises:
Http404 : When the combination contributor and study does not exists, or you are not a member of this study
PermissionDenied: If you are not an admin of this study.
ValidationError: If you change the owner rights or when you delete all admins
Returns:
ContributorSerializer: Serialized data of the new contributor role
"""
# Check if combination of study and contributor does exists and the logged in user is part of the study
contributor = get_object_or_404(StudyRole, pk=kwargs['contributor_id'], study=kwargs['study_id'], study__contributors__in=[self.request.user.researcher])
# Check if logged in user is an admin for this study
if not (contributor.study.owner == request.user.researcher or contributor.study.is_an_admin(request.user.researcher)):
raise PermissionDenied(_('You are not allowed to update this contributor.'))
# We cannot change the study rights for an owner
if contributor.researcher == contributor.study.owner:
raise ValidationError(_('The rights of an owner of the study cannot be changed or deleted.'))
# Load serializer
serializer = self.get_serializer(contributor, data=request.data, partial=False)
# Check if data is valid
serializer.is_valid(raise_exception=True)
if contributor.role != serializer.validated_data['role'] and contributor.role == StudyRoleNames.ADMIN:
# Make sure at least 1 admin is left
if contributor.study.contributors.filter(studyrole__role=StudyRoleNames.ADMIN).exclude(studyrole__researcher=contributor.researcher).count() == 0:
raise ValidationError(_('At least 1 administrator needs to remain for this study.'))
# Update the role
contributor.role = serializer.validated_data['role']
# Save to database
contributor.save()
serializer = ContributorSerializer(contributor, many=False)
return Response(serializer.data)
def delete(self, request, *args, **kwargs):
# Check if combination of study and contributor does exists and the logged in user is part of the study
contributor = get_object_or_404(StudyRole, pk=kwargs['contributor_id'], study=kwargs['study_id'], study__contributors__in=[self.request.user.researcher])
# Check if logged in user is an admin for this study
if not (contributor.study.owner == request.user.researcher or contributor.study.is_an_admin(request.user.researcher)):
raise PermissionDenied(_('You are not allowed to delete this contributor'))
# We cannot change the study rights for an owner
if contributor.researcher == contributor.study.owner:
raise ValidationError(_('The rights of an owner of the study cannot be removed'))
if contributor.role == StudyRoleNames.ADMIN:
# Make sure at least 1 admin is left
if contributor.study.contributors.filter(studyrole__role=StudyRoleNames.ADMIN).exclude(studyrole__researcher=contributor.researcher).count() == 0:
raise ValidationError(_('At least 1 administrator needs to remain for this study'))
# Send an email to the researcher that is removed from the study
contributor.send_remove_notification_email(sender=self.request.user.researcher)
self.perform_destroy(contributor)
return Response(status=status.HTTP_204_NO_CONTENT)
@swagger_auto_schema(methods=['post'], request_body=InviteSerializer(many=False))
@api_view(['POST'])
def process_study_invite(request, *args, **kwargs):
# TODO: How is allowed to make an invitation? Owner or any Admin? For now, an owner and any admin can send invitations
# Check if we are a contributor of the project and does project exists
study = get_object_or_404(Study, pk=kwargs['study_id'], contributors__in=[request.user.researcher], studyrole__active=True)
# Check if logged in user is an admin for this study
if not (study.owner == request.user.researcher or study.is_an_admin(request.user.researcher)):
raise PermissionDenied(_('You are not allowed to invite a new researcher'))
invite = InviteSerializer(data=request.data)
invite.is_valid(raise_exception=True)
contributor = invite.save(study=study)
contributor.sent_invitation_email(sender=request.user.researcher)
return Response({'message': _('Invitation to %(researcher_name)s for study %(study_name)s is sent.') % {'researcher_name': contributor.researcher.display_name, 'study_name': study.name}})
@swagger_auto_schema(method='get', auto_schema=None)
@api_view(['GET'])
def validate_study_invite(request, *args, **kwargs):
# Check if we are a contributor of the project and does project exists. And make sure the user is not yet activated
study = get_object_or_404(Study, pk=kwargs['pk'], contributors__in=[request.user.researcher], studyrole__active=False)
# Check if we are invited for this study
invitation = get_object_or_404(StudyRole, study=study, researcher=request.user.researcher, active=False)
if not invitation.check_invitation_link(request.user.researcher, kwargs['jwt_token']):
raise ValidationError(_('Could not validate the invitation to study %(study_name)s') % {'study_name': study.name})
invitation.active = True
invitation.save()
return Response({'message': _('Invitation for %(researcher_name)s for study %(study_name)s is accepted.') % {'researcher_name': invitation.researcher.display_name, 'study_name': study.name}})