diff --git a/castellum/contacts/templates/contacts/__guardian_item.html b/castellum/contacts/templates/contacts/__guardian_item.html index 82fbd160aa4063f5476f9079bad77e2d5e653d4c..25285b53ef6cbf9168fc54fa661704570d894ef3 100644 --- a/castellum/contacts/templates/contacts/__guardian_item.html +++ b/castellum/contacts/templates/contacts/__guardian_item.html @@ -1,4 +1,4 @@ -{% load i18n auth %} +{% load i18n recruitment %}
@@ -10,9 +10,16 @@ {% endif %}
- {% has_perm 'subjects.view_subject' user as can_view_subject %} - {% if slug and can_view_subject %} - {% translate 'Details' %} + {% if guardian %} + {% if view.context == 'subject_management' %} + {% translate 'Details' %} + {% elif view.context == 'recruitment' %} + {% study_guardian_hash study guardian as hash %} + {% translate 'Details' %} + {% elif view.context == 'execution' %} + {% study_guardian_hash study guardian as hash %} + {% translate 'Details' %} + {% endif %} {% endif %} diff --git a/castellum/contacts/templates/contacts/contact_form.html b/castellum/contacts/templates/contacts/contact_form.html index d0856e847951b54c15a5e44a473a6ab32673fa8a..7daa4d63eda55fda2ce6f7426e9f9306c13597d0 100644 --- a/castellum/contacts/templates/contacts/contact_form.html +++ b/castellum/contacts/templates/contacts/contact_form.html @@ -1,5 +1,5 @@ {% extends view.base_template|default:"subjects/base.html" %} -{% load static i18n auth bootstrap4 %} +{% load static i18n auth recruitment bootstrap4 %} {% block title %} {% if object %} @@ -42,7 +42,7 @@ {% for widget in form.guardians_remove %} - {% include 'contacts/__guardian_item.html' with name=form.guardians_remove.name pk=widget.data.value label=widget.choice_label slug=widget.choice_label.subject.slug removed=widget.data.selected %} + {% include 'contacts/__guardian_item.html' with name=form.guardians_remove.name pk=widget.data.value label=widget.choice_label guardian=widget.choice_label.subject removed=widget.data.selected %} {% endfor %} {% for subject in form.cleaned_data.guardians_add %} @@ -59,8 +59,14 @@
{{ contact }}
- {% if can_view_subject %} + {% if view.context == 'subject_management' %} {% translate 'Details' %} + {% elif view.context == 'recruitment' %} + {% study_guardian_hash study contact.subject as hash %} + {% translate 'Details' %} + {% elif view.context == 'execution' %} + {% study_guardian_hash study contact.subject as hash %} + {% translate 'Details' %} {% endif %} {% endfor %} diff --git a/castellum/execution/urls.py b/castellum/execution/urls.py index 0e34dd7dbeea367383d9ac8d3adc656267fb6a44..dfc453e5e570d71829f7167418f2d1e86a8a0e4d 100644 --- a/castellum/execution/urls.py +++ b/castellum/execution/urls.py @@ -36,6 +36,7 @@ from .views import ContactUpdateView from .views import DataProtectionUpdateView from .views import ExclusionCriteriaView from .views import ExportView +from .views import GuardianContactUpdateView from .views import NewsMailView from .views import ParticipationAppointmentsUpdateView from .views import ParticipationDetailView @@ -116,6 +117,11 @@ urlpatterns = [ AdditionalInfoUpdateView.as_view(), name='additional-info-update', ), + path( + '//guardians//update-contact/', + GuardianContactUpdateView.as_view(), + name='guardian-contact-update', + ), ] if settings.CASTELLUM_PSEUDONYMS_API_ENABLED: diff --git a/castellum/execution/views.py b/castellum/execution/views.py index 44ba07f659ce73854166ef79e1d7e0f467715e14..b694cf28c882aba952af738fb9f0dc76762ac343 100644 --- a/castellum/execution/views.py +++ b/castellum/execution/views.py @@ -27,6 +27,7 @@ from django.contrib import messages from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import PermissionDenied from django.db import models +from django.http import Http404 from django.http import HttpResponse from django.shortcuts import get_object_or_404 from django.shortcuts import redirect @@ -57,10 +58,12 @@ from castellum.recruitment.models import ExecutionTag from castellum.recruitment.models import NewsMailBatch from castellum.recruitment.models import Participation from castellum.recruitment.models import ReliabilityEntry +from castellum.recruitment.templatetags.recruitment import study_guardian_hash from castellum.studies.mixins import StudyMixin from castellum.studies.models import Study from castellum.subjects.mixins import BaseAdditionalInfoUpdateView from castellum.subjects.mixins import BaseDataProtectionUpdateView +from castellum.subjects.mixins import SubjectMixin from castellum.subjects.models import Subject from castellum.utils.mail import MailContext @@ -474,6 +477,7 @@ class ExecutionUpdateMixin(ParticipationMixin): class ContactUpdateView(ExecutionUpdateMixin, BaseContactUpdateView): subtab = 'contact' + context = 'execution' def get_object(self): return self.participation.subject.contact @@ -608,3 +612,36 @@ class TagView(StudyMixin, PermissionRequiredMixin, TemplateView): tag.delete() messages.success(request, _('Tag has been deleted.')) return redirect('execution:execution-tags', self.study.pk) + + +class GuardianContactUpdateView(StudyMixin, SubjectMixin, BaseContactUpdateView): + base_template = 'execution/base.html' + study_status = [Study.EXECUTION] + + def get_success_url(self): + return self.request.path + + def form_valid(self, form, *args): + messages.success(self.request, _('Data has been saved.')) + return super().form_valid(form, *args) + + def get_permission_required(self): + permission_required = {'recruitment.conduct_study'} + permission_required.update(super().get_permission_required()) + return permission_required + + def get_object(self): + participation = get_object_or_404( + Participation, + pk=self.kwargs['participation_pk'], + study=self.study, + status=Participation.INVITED, + ) + if not self.request.user.has_privacy_level(participation.subject.privacy_level): + return self.handle_no_permission() + + for guardian in participation.subject.contact.guardians.all(): + if study_guardian_hash(self.study, guardian.subject) == self.kwargs['hash']: + return guardian + + raise Http404 diff --git a/castellum/recruitment/templatetags/recruitment.py b/castellum/recruitment/templatetags/recruitment.py new file mode 100644 index 0000000000000000000000000000000000000000..404d6b79964fc7b82e267c1db102a922ca1431f0 --- /dev/null +++ b/castellum/recruitment/templatetags/recruitment.py @@ -0,0 +1,34 @@ +# (c) 2018-2021 +# MPIB , +# MPI-CBS , +# MPIP +# +# This file is part of Castellum. +# +# Castellum is free software; you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Castellum is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with Castellum. If not, see +# . + +import hashlib + +from django import template + +register = template.Library() + + +@register.simple_tag +def study_guardian_hash(study, subject): + m = hashlib.sha256() + m.update(str(study.pk).encode('ascii')) + m.update(subject.slug.encode('ascii')) + return m.hexdigest() diff --git a/castellum/recruitment/urls.py b/castellum/recruitment/urls.py index bac076d75048fddf9eff99cb8063a949f858212f..8d454a70c2275b831aed161a5d17fb2bf2773cb8 100644 --- a/castellum/recruitment/urls.py +++ b/castellum/recruitment/urls.py @@ -32,6 +32,7 @@ from .views import CalendarView from .views import ContactUpdateView from .views import ContactView from .views import DataProtectionUpdateView +from .views import GuardianContactUpdateView from .views import MailRecruitmentCleanupView from .views import MailRecruitmentRemindView from .views import MailRecruitmentView @@ -94,4 +95,9 @@ urlpatterns = [ AdditionalInfoUpdateView.as_view(), name='additional-info-update', ), + path( + '//guardians//update-contact/', + GuardianContactUpdateView.as_view(), + name='guardian-contact-update', + ), ] diff --git a/castellum/recruitment/views.py b/castellum/recruitment/views.py index 3307bdea9aa4791513966386feb6981e92106add..a3559f8d82f1e4c22a112145f655a6dca3350e1c 100644 --- a/castellum/recruitment/views.py +++ b/castellum/recruitment/views.py @@ -60,6 +60,7 @@ from castellum.studies.models import StudySession from castellum.studies.models import StudyTypeEventFeed from castellum.subjects.mixins import BaseAdditionalInfoUpdateView from castellum.subjects.mixins import BaseDataProtectionUpdateView +from castellum.subjects.mixins import SubjectMixin from castellum.subjects.models import Subject from castellum.utils import cached_request from castellum.utils import contrast_color @@ -75,6 +76,7 @@ from .mixins import get_recruitable from .models import MailBatch from .models import Participation from .models.attributes import get_description_by_statistics_rank +from .templatetags.recruitment import study_guardian_hash logger = logging.getLogger(__name__) @@ -524,6 +526,7 @@ class RecruitmentUpdateMixin(ParticipationMixin): class ContactUpdateView(RecruitmentUpdateMixin, BaseContactUpdateView): subtab = 'contact' + context = 'recruitment' def get_object(self): return self.participation.subject.contact @@ -550,6 +553,36 @@ class AdditionalInfoUpdateView(RecruitmentUpdateMixin, BaseAdditionalInfoUpdateV return self.participation.subject +class GuardianContactUpdateView(StudyMixin, SubjectMixin, BaseContactUpdateView): + base_template = 'recruitment/base.html' + study_status = [Study.EXECUTION] + + def get_success_url(self): + return self.request.path + + def form_valid(self, form, *args): + messages.success(self.request, _('Data has been saved.')) + return super().form_valid(form, *args) + + def get_permission_required(self): + permission_required = {'recruitment.recruit'} + permission_required.update(super().get_permission_required()) + return permission_required + + def get_object(self): + participation = get_object_or_404( + Participation, pk=self.kwargs['participation_pk'], study=self.study + ) + if not self.request.user.has_privacy_level(participation.subject.privacy_level): + return self.handle_no_permission() + + for guardian in participation.subject.contact.guardians.all(): + if study_guardian_hash(self.study, guardian.subject) == self.kwargs['hash']: + return guardian + + raise Http404 + + class CalendarView(StudyMixin, PermissionRequiredMixin, BaseCalendarView): model = Study permission_required = 'appointments.change_appointment' diff --git a/castellum/subjects/views.py b/castellum/subjects/views.py index 5854bf2904f41e7268b5cadc6ab5bf8478c06130..fec839eadf736a4332d6a81f60efff7a857627e2 100644 --- a/castellum/subjects/views.py +++ b/castellum/subjects/views.py @@ -346,6 +346,7 @@ class AdditionalInfoUpdateView(SubjectUpdateMixin, BaseAdditionalInfoUpdateView) class ContactUpdateView(SubjectUpdateMixin, BaseContactUpdateView): tab = 'contact' + context = 'subject_management' def get_object(self): subject = get_object_or_404(Subject, slug=self.kwargs['slug'])