From 63d043effdeffad8393653d20da649632892e57e Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Mon, 16 Mar 2020 11:06:51 +0100 Subject: [PATCH 1/4] rename SubjectUpdateView to AdditionalInfoUpdateView --- .../templates/recruitment/contact.html | 12 ++++++------ castellum/recruitment/urls.py | 8 ++++---- castellum/recruitment/views.py | 4 ++-- castellum/subjects/mixins.py | 2 +- .../templates/subjects/subject_base.html | 18 +++++++++--------- castellum/subjects/urls.py | 4 ++-- castellum/subjects/views.py | 8 ++++---- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/castellum/recruitment/templates/recruitment/contact.html b/castellum/recruitment/templates/recruitment/contact.html index 398e66f12..71872c658 100644 --- a/castellum/recruitment/templates/recruitment/contact.html +++ b/castellum/recruitment/templates/recruitment/contact.html @@ -72,16 +72,16 @@ {% if can_change_contact %} {% trans 'Edit contact data'%} {% endif %} - {% has_perm 'subjects.change_subject' user study as can_change_subject %} - {% if can_change_subject %} - - {% trans 'Edit subject' %} - - {% endif %} {% has_perm 'recruitment.change_attributeset' user study as can_change_attributeset %} {% if can_change_attributeset %} {% trans 'Edit attributes'%} {% endif %} + {% has_perm 'subjects.change_subject' user study as can_change_subject %} + {% if can_change_subject %} + + {% trans 'Edit additional info' %} + + {% endif %}
diff --git a/castellum/recruitment/urls.py b/castellum/recruitment/urls.py index a32baf4b9..cddedb471 100644 --- a/castellum/recruitment/urls.py +++ b/castellum/recruitment/urls.py @@ -23,6 +23,7 @@ from django.urls import path from .feeds import FollowUpFeedForStudy from .feeds import FollowUpFeedForUser +from .views import AdditionalInfoUpdateView from .views import AttributeSetUpdateView from .views import ContactUpdateView from .views import ContactView @@ -32,7 +33,6 @@ from .views import RecruitmentViewOpen from .views import RecruitmentViewUnsuitable from .views import SearchView from .views import StudyListView -from .views import SubjectUpdateView app_name = 'recruitment' urlpatterns = [ @@ -60,8 +60,8 @@ urlpatterns = [ name='attributeset-update', ), path( - '//update-subject/', - SubjectUpdateView.as_view(), - name='subject-update', + '//update-additional-info/', + AdditionalInfoUpdateView.as_view(), + name='additional-info-update', ), ] diff --git a/castellum/recruitment/views.py b/castellum/recruitment/views.py index b1700e795..c64f1a9c6 100644 --- a/castellum/recruitment/views.py +++ b/castellum/recruitment/views.py @@ -51,7 +51,7 @@ from castellum.recruitment.mixins import BaseAttributeSetUpdateView from castellum.recruitment.models.attributesets import get_description_by_statistics_rank from castellum.studies.mixins import StudyMixin from castellum.studies.models import Study -from castellum.subjects.mixins import BaseSubjectUpdateView +from castellum.subjects.mixins import BaseAdditionalInfoUpdateView from castellum.subjects.mixins import SubjectMixin from castellum.utils.views import GetFormView @@ -506,7 +506,7 @@ class AttributeSetUpdateView(StudyMixin, BaseAttributeSetUpdateView): return self.get_success_url() -class SubjectUpdateView(StudyMixin, BaseSubjectUpdateView): +class AdditionalInfoUpdateView(StudyMixin, BaseAdditionalInfoUpdateView): study_status = [Study.EXECUTION] def get_object(self): diff --git a/castellum/subjects/mixins.py b/castellum/subjects/mixins.py index 275a07e19..41c0e581f 100644 --- a/castellum/subjects/mixins.py +++ b/castellum/subjects/mixins.py @@ -61,7 +61,7 @@ class SubjectMixin: return context -class BaseSubjectUpdateView(SubjectMixin, PermissionRequiredMixin, UpdateView): +class BaseAdditionalInfoUpdateView(SubjectMixin, PermissionRequiredMixin, UpdateView): model = Subject form_class = SubjectForm permission_required = 'subjects.change_subject' diff --git a/castellum/subjects/templates/subjects/subject_base.html b/castellum/subjects/templates/subjects/subject_base.html index 4ea9bc96b..ba089c255 100644 --- a/castellum/subjects/templates/subjects/subject_base.html +++ b/castellum/subjects/templates/subjects/subject_base.html @@ -24,25 +24,25 @@ {% endif %} + {% has_perm 'recruitment.change_attributeset' user as can_change_attributeset %} + {% if can_change_attributeset %} + + {% endif %} + {% has_perm 'subjects.change_subject' user as can_change_subject %} {% if can_change_subject %} {% endif %} - {% has_perm 'recruitment.change_attributeset' user as can_change_attributeset %} - {% if can_change_attributeset %} - - {% endif %} - diff --git a/castellum/subjects/urls.py b/castellum/subjects/urls.py index a395f3c03..2136c589a 100644 --- a/castellum/subjects/urls.py +++ b/castellum/subjects/urls.py @@ -21,6 +21,7 @@ from django.urls import path +from .views import AdditionalInfoUpdateView from .views import AddToStudyView from .views import AttributeSetUpdateView from .views import ContactUpdateView @@ -35,7 +36,6 @@ from .views import SubjectDeleteView from .views import SubjectDetailView from .views import SubjectExportView from .views import SubjectSearchView -from .views import SubjectUpdateView app_name = 'subjects' urlpatterns = [ @@ -43,9 +43,9 @@ urlpatterns = [ path('/', SubjectDetailView.as_view(), name='detail'), path('/delete/', SubjectDeleteView.as_view(), name='delete'), path('/export/', SubjectExportView.as_view(), name='export'), - path('/subject/', SubjectUpdateView.as_view(), name='subject'), path('/contact/', ContactUpdateView.as_view(), name='contact'), path('/attributes/', AttributeSetUpdateView.as_view(), name='attributeset'), + path('/additional-info/', AdditionalInfoUpdateView.as_view(), name='additional-info'), path('/coverletter.docx', CoverletterView.as_view(), name='coverletter'), path( '/participations/', diff --git a/castellum/subjects/views.py b/castellum/subjects/views.py index 85d5ea821..1d2bdc88e 100644 --- a/castellum/subjects/views.py +++ b/castellum/subjects/views.py @@ -57,7 +57,7 @@ from castellum.studies.models import Study from castellum.utils.views import GetFormView from .forms import SubjectPrivacyLevelForm -from .mixins import BaseSubjectUpdateView +from .mixins import BaseAdditionalInfoUpdateView from .mixins import SubjectMixin from .models import Consent from .models import ExportAnswer @@ -240,11 +240,11 @@ class SubjectExportView(SubjectMixin, PermissionRequiredMixin, DetailView): return redirect('subjects:export', self.object.pk) -class SubjectUpdateView(BaseSubjectUpdateView): - tab = 'subject' +class AdditionalInfoUpdateView(BaseAdditionalInfoUpdateView): + tab = 'additional-info' def get_success_url(self): - return reverse('subjects:subject', args=[self.object.pk]) + return reverse('subjects:additional-info', args=[self.object.pk]) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) -- GitLab From 31791ed50ee30018b1d7ae2d4e15859ed59958ad Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Mon, 16 Mar 2020 11:34:52 +0100 Subject: [PATCH 2/4] split data protection view from additional info view --- .../templates/recruitment/contact.html | 3 + castellum/recruitment/urls.py | 6 + castellum/recruitment/views.py | 22 +++ castellum/subjects/forms.py | 4 +- castellum/subjects/mixins.py | 93 +++++++----- .../subject_additional_info_form.html | 96 ++++++++++++ .../templates/subjects/subject_base.html | 7 +- .../subject_data_protection_form.html | 68 +++++++++ .../templates/subjects/subject_form.html | 139 ------------------ castellum/subjects/urls.py | 2 + castellum/subjects/views.py | 17 +++ 11 files changed, 279 insertions(+), 178 deletions(-) create mode 100644 castellum/subjects/templates/subjects/subject_additional_info_form.html create mode 100644 castellum/subjects/templates/subjects/subject_data_protection_form.html delete mode 100644 castellum/subjects/templates/subjects/subject_form.html diff --git a/castellum/recruitment/templates/recruitment/contact.html b/castellum/recruitment/templates/recruitment/contact.html index 71872c658..fc06518f6 100644 --- a/castellum/recruitment/templates/recruitment/contact.html +++ b/castellum/recruitment/templates/recruitment/contact.html @@ -78,6 +78,9 @@ {% endif %} {% has_perm 'subjects.change_subject' user study as can_change_subject %} {% if can_change_subject %} + + {% trans 'Edit data protection' %} + {% trans 'Edit additional info' %} diff --git a/castellum/recruitment/urls.py b/castellum/recruitment/urls.py index cddedb471..1e4b3991e 100644 --- a/castellum/recruitment/urls.py +++ b/castellum/recruitment/urls.py @@ -27,6 +27,7 @@ from .views import AdditionalInfoUpdateView from .views import AttributeSetUpdateView from .views import ContactUpdateView from .views import ContactView +from .views import DataProtectionUpdateView from .views import MailRecruitmentView from .views import RecruitmentViewInvited from .views import RecruitmentViewOpen @@ -59,6 +60,11 @@ urlpatterns = [ AttributeSetUpdateView.as_view(), name='attributeset-update', ), + path( + '//update-data-protection/', + DataProtectionUpdateView.as_view(), + name='data-protection-update', + ), path( '//update-additional-info/', AdditionalInfoUpdateView.as_view(), diff --git a/castellum/recruitment/views.py b/castellum/recruitment/views.py index c64f1a9c6..0bd21c3a9 100644 --- a/castellum/recruitment/views.py +++ b/castellum/recruitment/views.py @@ -52,6 +52,7 @@ from castellum.recruitment.models.attributesets import get_description_by_statis 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.utils.views import GetFormView @@ -506,6 +507,27 @@ class AttributeSetUpdateView(StudyMixin, BaseAttributeSetUpdateView): return self.get_success_url() +class DataProtectionUpdateView(StudyMixin, BaseDataProtectionUpdateView): + study_status = [Study.EXECUTION] + + def get_object(self): + participation_request = get_object_or_404( + ParticipationRequest, study=self.study, pk=self.kwargs['pk'] + ) + return participation_request.subject + + def get_success_url(self): + return reverse('recruitment:contact', args=[self.kwargs['study_pk'], self.kwargs['pk']]) + + def get_back_url(self): + return self.get_success_url() + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['base_template'] = "recruitment/base.html" + return context + + class AdditionalInfoUpdateView(StudyMixin, BaseAdditionalInfoUpdateView): study_status = [Study.EXECUTION] diff --git a/castellum/subjects/forms.py b/castellum/subjects/forms.py index 9347c3d29..a0116f515 100644 --- a/castellum/subjects/forms.py +++ b/castellum/subjects/forms.py @@ -32,11 +32,11 @@ from .models import ConsentDocument from .models import Subject -class SubjectForm(forms.ModelForm): +class DataProtectionForm(forms.ModelForm): class Meta: model = Subject - exclude = ('to_be_deleted', 'to_be_deleted_notified', 'export_requested') + fields = ('privacy_level', 'no_recruitment') widgets = { 'privacy_level': forms.RadioSelect, 'source': DatalistWidget(datalist=( diff --git a/castellum/subjects/mixins.py b/castellum/subjects/mixins.py index 41c0e581f..c25484173 100644 --- a/castellum/subjects/mixins.py +++ b/castellum/subjects/mixins.py @@ -29,7 +29,7 @@ from django.views.generic import UpdateView from castellum.castellum_auth.mixins import PermissionRequiredMixin -from .forms import SubjectForm +from .forms import DataProtectionForm from .models import Subject from .models import SubjectNote @@ -61,16 +61,70 @@ class SubjectMixin: return context -class BaseAdditionalInfoUpdateView(SubjectMixin, PermissionRequiredMixin, UpdateView): +class BaseDataProtectionUpdateView(SubjectMixin, PermissionRequiredMixin, UpdateView): model = Subject - form_class = SubjectForm + form_class = DataProtectionForm permission_required = 'subjects.change_subject' + template_name = 'subjects/subject_data_protection_form.html' def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs + def form_valid(self, form): + if settings.CASTELLUM_DELETE_NOTIFICATION_TO: + old = Subject.objects.get(pk=self.object.pk) + if self.object.export_requested and not old.export_requested: + rel = reverse('subjects:detail', args=[self.object.pk]) + url = self.request.build_absolute_uri(rel) + + self.object.to_be_deleted_notified = send_mail( + settings.CASTELLUM_EXPORT_SUBJECT_NOTIFICATION_SUBJECT, + settings.CASTELLUM_EXPORT_SUBJECT_NOTIFICATION_BODY.format(url=url), + settings.DEFAULT_FROM_EMAIL, + [settings.CASTELLUM_DELETE_NOTIFICATION_TO], + ) + + if self.object.to_be_deleted and not self.object.to_be_deleted_notified: + rel = reverse('subjects:detail', args=[self.object.pk]) + url = self.request.build_absolute_uri(rel) + + self.object.to_be_deleted_notified = send_mail( + settings.CASTELLUM_DELETE_SUBJECT_NOTIFICATION_SUBJECT, + settings.CASTELLUM_DELETE_SUBJECT_NOTIFICATION_BODY.format(url=url), + settings.DEFAULT_FROM_EMAIL, + [settings.CASTELLUM_DELETE_NOTIFICATION_TO], + ) + + # If a user explicitly removes the ``to_be_deleted`` they + # expect that this task is done. If there are still reasons + # for deletion there should be a new notificaition. + if not self.object.to_be_deleted and self.object.to_be_deleted_notified: + old = Subject.objects.get(pk=self.object.pk) + if old.to_be_deleted: + self.object.to_be_deleted_notified = False + + return super().form_valid(form) + + +class BaseAdditionalInfoUpdateView(SubjectMixin, PermissionRequiredMixin, UpdateView): + model = Subject + fields = ( + 'note_hard_of_hearing', + 'note_difficult_to_understand', + 'note_abusive_language', + 'availability_monday', + 'availability_tuesday', + 'availability_wednesday', + 'availability_thursday', + 'availability_friday', + 'not_available_until', + 'source', + ) + permission_required = 'subjects.change_subject' + template_name = 'subjects/subject_additional_info_form.html' + def get_notes_form(self): form_class = forms.modelform_factory(SubjectNote, fields=('content',)) return form_class(prefix='{prefix}') @@ -80,7 +134,6 @@ class BaseAdditionalInfoUpdateView(SubjectMixin, PermissionRequiredMixin, Update SubjectNote, fields=('content',), extra=0, can_delete=True ) kwargs = self.get_form_kwargs() - kwargs.pop('user') kwargs.pop('instance') kwargs.pop('initial') return formset_class(queryset=SubjectNote.objects.filter(subject=self.object), **kwargs) @@ -108,38 +161,6 @@ class BaseAdditionalInfoUpdateView(SubjectMixin, PermissionRequiredMixin, Update ) def form_valid(self, form, notes_formset): - if settings.CASTELLUM_DELETE_NOTIFICATION_TO: - old = Subject.objects.get(pk=self.object.pk) - if self.object.export_requested and not old.export_requested: - rel = reverse('subjects:detail', args=[self.object.pk]) - url = self.request.build_absolute_uri(rel) - - self.object.to_be_deleted_notified = send_mail( - settings.CASTELLUM_EXPORT_SUBJECT_NOTIFICATION_SUBJECT, - settings.CASTELLUM_EXPORT_SUBJECT_NOTIFICATION_BODY.format(url=url), - settings.DEFAULT_FROM_EMAIL, - [settings.CASTELLUM_DELETE_NOTIFICATION_TO], - ) - - if self.object.to_be_deleted and not self.object.to_be_deleted_notified: - rel = reverse('subjects:detail', args=[self.object.pk]) - url = self.request.build_absolute_uri(rel) - - self.object.to_be_deleted_notified = send_mail( - settings.CASTELLUM_DELETE_SUBJECT_NOTIFICATION_SUBJECT, - settings.CASTELLUM_DELETE_SUBJECT_NOTIFICATION_BODY.format(url=url), - settings.DEFAULT_FROM_EMAIL, - [settings.CASTELLUM_DELETE_NOTIFICATION_TO], - ) - - # If a user explicitly removes the ``to_be_deleted`` they - # expect that this task is done. If there are still reasons - # for deletion there should be a new notificaition. - if not self.object.to_be_deleted and self.object.to_be_deleted_notified: - old = Subject.objects.get(pk=self.object.pk) - if old.to_be_deleted: - self.object.to_be_deleted_notified = False - response = super().form_valid(form) for subform in notes_formset: diff --git a/castellum/subjects/templates/subjects/subject_additional_info_form.html b/castellum/subjects/templates/subjects/subject_additional_info_form.html new file mode 100644 index 000000000..ac1153a3b --- /dev/null +++ b/castellum/subjects/templates/subjects/subject_additional_info_form.html @@ -0,0 +1,96 @@ +{% extends base_template|default:"subjects/base.html" %} +{% load static i18n bootstrap4 subjects utils %} + +{% block title %} + {% trans "Edit subject" %} · {{ block.super }} +{% endblock %} + +{% block content %} + + +
+ {% for error in form.non_field_errors %} + + {% endfor %} + + {% csrf_token %} + +
+ {% trans "Availability" %} + +
+
+ {% bootstrap_field form.availability_monday %} +
+
+ {% bootstrap_field form.availability_tuesday %} +
+
+ {% bootstrap_field form.availability_wednesday %} +
+
+ {% bootstrap_field form.availability_thursday %} +
+
+ {% bootstrap_field form.availability_friday %} +
+
+
+ + {% bootstrap_field form.not_available_until %} + +
+ {% trans "Notes" %} + + {% bootstrap_field form.note_hard_of_hearing %} + {% bootstrap_field form.note_difficult_to_understand %} + {% bootstrap_field form.note_abusive_language %} + + {{ notes_formset.management_form }} +
+ {% for subform in notes_formset %} +
+
+ {{ subform.id.as_hidden }} + {{ subform.DELETE.as_hidden }} + + +
+
+ {% endfor %} +
+ +
+ +
+
+ + {% bootstrap_field form.source %} + +
+ {% if view.get_back_url %} + {% trans "Back" %} + {% endif %} + +
+
+{% endblock %} + +{% block extra_scripts %} + + +{% endblock %} diff --git a/castellum/subjects/templates/subjects/subject_base.html b/castellum/subjects/templates/subjects/subject_base.html index ba089c255..fe88c72a2 100644 --- a/castellum/subjects/templates/subjects/subject_base.html +++ b/castellum/subjects/templates/subjects/subject_base.html @@ -34,10 +34,15 @@ {% has_perm 'subjects.change_subject' user as can_change_subject %} {% if can_change_subject %} + diff --git a/castellum/subjects/templates/subjects/subject_data_protection_form.html b/castellum/subjects/templates/subjects/subject_data_protection_form.html new file mode 100644 index 000000000..bb7e36003 --- /dev/null +++ b/castellum/subjects/templates/subjects/subject_data_protection_form.html @@ -0,0 +1,68 @@ +{% extends base_template|default:"subjects/base.html" %} +{% load static i18n bootstrap4 subjects utils %} + +{% block title %} + {% trans "Edit subject" %} · {{ block.super }} +{% endblock %} + +{% block content %} +
+ {% for error in form.non_field_errors %} + + {% endfor %} + + {% csrf_token %} + +
+ {% trans 'Privacy level' %} + + {% bootstrap_field form.privacy_level show_label=False %} +
+ + {% get_db_consent_uploaded as db_consent_uploaded %} + {% if db_consent_uploaded %} +
+ {% trans 'Recruitment consent' %} + + {% with consent=object.confirmed_consent %} +
+ {% if consent %} + {{ consent.document }} {% if not consent.document.is_current %}({% trans 'outdated' %}){% endif %} +
+ {{ consent|display:'type' }}, + {% elif object.no_recruitment %} + {% trans 'No consent needed' %} + {% else %} + {% trans 'Missing consent' %} + {% endif %} +
+ {% endwith %} + + {% with consent=object.consent %} + {% if consent.status == consent.WAITING or not consent.document.is_current %} + {% bootstrap_field form.consent_type %} + {% bootstrap_field form.consent_status %} + {% endif %} + {% endwith %} +
+ {% endif %} + +
+ {% bootstrap_field form.export_requested %} + {% bootstrap_field form.to_be_deleted %} + {% bootstrap_field form.no_recruitment %} + +
+ +
+ {% if view.get_back_url %} + {% trans "Back" %} + {% endif %} + +
+
+{% endblock %} + +{% block extra_scripts %} + +{% endblock %} diff --git a/castellum/subjects/templates/subjects/subject_form.html b/castellum/subjects/templates/subjects/subject_form.html deleted file mode 100644 index 26cc68781..000000000 --- a/castellum/subjects/templates/subjects/subject_form.html +++ /dev/null @@ -1,139 +0,0 @@ -{% extends base_template|default:"subjects/base.html" %} -{% load static i18n bootstrap4 subjects utils %} - -{% block title %} - {% trans "Edit subject" %} · {{ block.super }} -{% endblock %} - -{% block content %} - - -
- {% for error in form.non_field_errors %} - - {% endfor %} - - {% csrf_token %} - -
- {% trans 'Privacy level' %} - - {% bootstrap_field form.privacy_level show_label=False %} -
- - {% get_db_consent_uploaded as db_consent_uploaded %} - {% if db_consent_uploaded %} -
- {% trans 'Recruitment consent' %} - - {% with consent=object.confirmed_consent %} -
- {% if consent %} - {{ consent.document }} {% if not consent.document.is_current %}({% trans 'outdated' %}){% endif %} -
- {{ consent|display:'type' }}, - {% elif object.no_recruitment %} - {% trans 'No consent needed' %} - {% else %} - {% trans 'Missing consent' %} - {% endif %} -
- {% endwith %} - - {% with consent=object.consent %} - {% if consent.status == consent.WAITING or not consent.document.is_current %} - {% bootstrap_field form.consent_type %} - {% bootstrap_field form.consent_status %} - {% endif %} - {% endwith %} -
- {% endif %} - -
- {% trans 'Operational hints' %} - - {% bootstrap_field form.export_requested %} - {% bootstrap_field form.to_be_deleted %} - {% bootstrap_field form.no_recruitment %} - - -
- {% trans "Availability" %} - -
-
- {% bootstrap_field form.availability_monday %} -
-
- {% bootstrap_field form.availability_tuesday %} -
-
- {% bootstrap_field form.availability_wednesday %} -
-
- {% bootstrap_field form.availability_thursday %} -
-
- {% bootstrap_field form.availability_friday %} -
-
-
- - {% bootstrap_field form.not_available_until %} - -
- {% trans "Notes" %} - - {% bootstrap_field form.note_hard_of_hearing %} - {% bootstrap_field form.note_difficult_to_understand %} - {% bootstrap_field form.note_abusive_language %} - - {{ notes_formset.management_form }} -
- {% for subform in notes_formset %} -
-
- {{ subform.id.as_hidden }} - {{ subform.DELETE.as_hidden }} - - -
-
- {% endfor %} -
- -
- -
-
- - {% bootstrap_field form.source %} -
- -
- {% if view.get_back_url %} - {% trans "Back" %} - {% endif %} - -
-
-{% endblock %} - -{% block extra_scripts %} - - -{% endblock %} diff --git a/castellum/subjects/urls.py b/castellum/subjects/urls.py index 2136c589a..711274b40 100644 --- a/castellum/subjects/urls.py +++ b/castellum/subjects/urls.py @@ -26,6 +26,7 @@ from .views import AddToStudyView from .views import AttributeSetUpdateView from .views import ContactUpdateView from .views import CoverletterView +from .views import DataProtectionUpdateView from .views import GuardianSearchView from .views import MaintenanceAttributesView from .views import MaintenanceConsentView @@ -45,6 +46,7 @@ urlpatterns = [ path('/export/', SubjectExportView.as_view(), name='export'), path('/contact/', ContactUpdateView.as_view(), name='contact'), path('/attributes/', AttributeSetUpdateView.as_view(), name='attributeset'), + path('/data-protection/', DataProtectionUpdateView.as_view(), name='data-protection'), path('/additional-info/', AdditionalInfoUpdateView.as_view(), name='additional-info'), path('/coverletter.docx', CoverletterView.as_view(), name='coverletter'), path( diff --git a/castellum/subjects/views.py b/castellum/subjects/views.py index 1d2bdc88e..c416d0295 100644 --- a/castellum/subjects/views.py +++ b/castellum/subjects/views.py @@ -58,6 +58,7 @@ from castellum.utils.views import GetFormView from .forms import SubjectPrivacyLevelForm from .mixins import BaseAdditionalInfoUpdateView +from .mixins import BaseDataProtectionUpdateView from .mixins import SubjectMixin from .models import Consent from .models import ExportAnswer @@ -240,6 +241,22 @@ class SubjectExportView(SubjectMixin, PermissionRequiredMixin, DetailView): return redirect('subjects:export', self.object.pk) +class DataProtectionUpdateView(BaseDataProtectionUpdateView): + tab = 'data-protection' + + def get_success_url(self): + return reverse('subjects:data-protection', args=[self.object.pk]) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['base_template'] = 'subjects/subject_base.html' + return context + + def form_valid(self, form, *args): + messages.success(self.request, _('Data has been saved.')) + return super().form_valid(form, *args) + + class AdditionalInfoUpdateView(BaseAdditionalInfoUpdateView): tab = 'additional-info' -- GitLab From 7e19f996e0880ff2de9e1710daa850abba8a7dca Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Mon, 16 Mar 2020 11:42:04 +0100 Subject: [PATCH 3/4] reduce code duplication --- castellum/recruitment/views.py | 62 +++++++--------------------------- castellum/subjects/views.py | 47 ++++++-------------------- 2 files changed, 24 insertions(+), 85 deletions(-) diff --git a/castellum/recruitment/views.py b/castellum/recruitment/views.py index 0bd21c3a9..62628a161 100644 --- a/castellum/recruitment/views.py +++ b/castellum/recruitment/views.py @@ -469,16 +469,14 @@ class ContactView(StudyMixin, SubjectMixin, PermissionRequiredMixin, UpdateView) return reverse('recruitment:recruitment-open', args=[self.object.study.pk]) -class ContactUpdateView(StudyMixin, BaseContactUpdateView): +class RecruitmentUpdateMixin(StudyMixin): study_status = [Study.EXECUTION] def get_object(self): - participation_request = get_object_or_404( + return get_object_or_404( ParticipationRequest, study=self.study, pk=self.kwargs['pk'] ) - return participation_request.subject.contact - def get_success_url(self): return reverse('recruitment:contact', args=[self.kwargs['study_pk'], self.kwargs['pk']]) @@ -491,63 +489,29 @@ class ContactUpdateView(StudyMixin, BaseContactUpdateView): return context -class AttributeSetUpdateView(StudyMixin, BaseAttributeSetUpdateView): - study_status = [Study.EXECUTION] - +class ContactUpdateView(RecruitmentUpdateMixin, BaseContactUpdateView): def get_object(self): - participation_request = get_object_or_404( - ParticipationRequest, study=self.study, pk=self.kwargs['pk'] - ) - return participation_request.subject.attributeset - - def get_success_url(self): - return reverse('recruitment:contact', args=[self.kwargs['study_pk'], self.kwargs['pk']]) + participation_request = super().get_object() + return participation_request.subject.contact - def get_back_url(self): - return self.get_success_url() +class AttributeSetUpdateView(RecruitmentUpdateMixin, BaseAttributeSetUpdateView): + def get_object(self): + participation_request = super().get_object() + return participation_request.subject.attributeset -class DataProtectionUpdateView(StudyMixin, BaseDataProtectionUpdateView): - study_status = [Study.EXECUTION] +class DataProtectionUpdateView(RecruitmentUpdateMixin, BaseDataProtectionUpdateView): def get_object(self): - participation_request = get_object_or_404( - ParticipationRequest, study=self.study, pk=self.kwargs['pk'] - ) + participation_request = super().get_object() return participation_request.subject - def get_success_url(self): - return reverse('recruitment:contact', args=[self.kwargs['study_pk'], self.kwargs['pk']]) - - def get_back_url(self): - return self.get_success_url() - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context['base_template'] = "recruitment/base.html" - return context - - -class AdditionalInfoUpdateView(StudyMixin, BaseAdditionalInfoUpdateView): - study_status = [Study.EXECUTION] +class AdditionalInfoUpdateView(RecruitmentUpdateMixin, BaseAdditionalInfoUpdateView): def get_object(self): - participation_request = get_object_or_404( - ParticipationRequest, study=self.study, pk=self.kwargs['pk'] - ) + participation_request = super().get_object() return participation_request.subject - def get_success_url(self): - return reverse('recruitment:contact', args=[self.kwargs['study_pk'], self.kwargs['pk']]) - - def get_back_url(self): - return self.get_success_url() - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context['base_template'] = "recruitment/base.html" - return context - class SearchView(LoginRequiredMixin, GetFormView): template_name = 'recruitment/search.html' diff --git a/castellum/subjects/views.py b/castellum/subjects/views.py index c416d0295..b6a0002e3 100644 --- a/castellum/subjects/views.py +++ b/castellum/subjects/views.py @@ -241,12 +241,7 @@ class SubjectExportView(SubjectMixin, PermissionRequiredMixin, DetailView): return redirect('subjects:export', self.object.pk) -class DataProtectionUpdateView(BaseDataProtectionUpdateView): - tab = 'data-protection' - - def get_success_url(self): - return reverse('subjects:data-protection', args=[self.object.pk]) - +class SubjectUpdateMixin: def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['base_template'] = 'subjects/subject_base.html' @@ -257,23 +252,21 @@ class DataProtectionUpdateView(BaseDataProtectionUpdateView): return super().form_valid(form, *args) -class AdditionalInfoUpdateView(BaseAdditionalInfoUpdateView): - tab = 'additional-info' +class DataProtectionUpdateView(SubjectUpdateMixin, BaseDataProtectionUpdateView): + tab = 'data-protection' def get_success_url(self): - return reverse('subjects:additional-info', args=[self.object.pk]) + return reverse('subjects:data-protection', args=[self.object.pk]) - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context['base_template'] = 'subjects/subject_base.html' - return context - def form_valid(self, form, *args): - messages.success(self.request, _('Data has been saved.')) - return super().form_valid(form, *args) +class AdditionalInfoUpdateView(SubjectUpdateMixin, BaseAdditionalInfoUpdateView): + tab = 'additional-info' + def get_success_url(self): + return reverse('subjects:additional-info', args=[self.object.pk]) -class ContactUpdateView(BaseContactUpdateView): + +class ContactUpdateView(SubjectUpdateMixin, BaseContactUpdateView): tab = 'contact' def get_object(self): @@ -283,17 +276,8 @@ class ContactUpdateView(BaseContactUpdateView): def get_success_url(self): return reverse('subjects:contact', args=[self.object.subject.pk]) - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context['base_template'] = 'subjects/subject_base.html' - return context - - def form_valid(self, form): - messages.success(self.request, _('Data has been saved.')) - return super().form_valid(form) - -class AttributeSetUpdateView(BaseAttributeSetUpdateView): +class AttributeSetUpdateView(SubjectUpdateMixin, BaseAttributeSetUpdateView): tab = 'attributeset' def get_object(self): @@ -306,15 +290,6 @@ class AttributeSetUpdateView(BaseAttributeSetUpdateView): def get_success_url(self): return reverse('subjects:attributeset', args=[self.object.subject.pk]) - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context['base_template'] = "subjects/subject_base.html" - return context - - def form_valid(self, form): - messages.success(self.request, _('Data has been saved.')) - return super().form_valid(form) - class ParticipationListView(SubjectMixin, PermissionRequiredMixin, ListView): tab = 'participations' -- GitLab From 8d0b7f3b8c28d5a6498cc1d3e60d74f1bb769fe8 Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Mon, 16 Mar 2020 12:00:13 +0100 Subject: [PATCH 4/4] adapt tests --- .../views/test_subject_update_view.py | 16 ++++++++++-- tests/subjects/views/test_views.py | 26 ++++++++++++++----- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/tests/recruitment/views/test_subject_update_view.py b/tests/recruitment/views/test_subject_update_view.py index 4b7068e88..75569d8f7 100644 --- a/tests/recruitment/views/test_subject_update_view.py +++ b/tests/recruitment/views/test_subject_update_view.py @@ -4,12 +4,24 @@ from castellum.recruitment.models import ParticipationRequest @pytest.mark.smoketest -def test_200(client, member, participation_request): +def test_data_protection_200(client, member, participation_request): participation_request.status = ParticipationRequest.NOT_CONTACTED participation_request.save() client.force_login(member) - response = client.get('/recruitment/{}/{}/update-subject/'.format( + response = client.get('/recruitment/{}/{}/update-data-protection/'.format( + participation_request.study.pk, participation_request.pk + )) + assert response.status_code == 200 + + +@pytest.mark.smoketest +def test_additional_info_200(client, member, participation_request): + participation_request.status = ParticipationRequest.NOT_CONTACTED + participation_request.save() + + client.force_login(member) + response = client.get('/recruitment/{}/{}/update-additional-info/'.format( participation_request.study.pk, participation_request.pk )) assert response.status_code == 200 diff --git a/tests/subjects/views/test_views.py b/tests/subjects/views/test_views.py index d564646b9..772364482 100644 --- a/tests/subjects/views/test_views.py +++ b/tests/subjects/views/test_views.py @@ -51,19 +51,19 @@ def test_detail_with_attributeset_200(client, user, contact, attributeset): 'subject_manager', pytest.param('data_protection_coordinator', marks=pytest.mark.xfail(strict=True)), ]) -def test_subject_200(request, client, user_fixture, contact): +def test_data_protection_200(request, client, user_fixture, contact): user = request.getfixturevalue(user_fixture) client.force_login(user) - url = '/subjects/{}/subject/'.format(contact.subject.pk) + url = '/subjects/{}/data-protection/'.format(contact.subject.pk) response = client.get(url) assert response.status_code == 200 -def test_subject_post_valid(client, user, contact): +def test_data_protection_post_valid(client, user, contact): client.force_login(user) assert contact.subject.privacy_level == 0 - url = '/subjects/{}/subject/'.format(contact.subject.pk) + url = '/subjects/{}/data-protection/'.format(contact.subject.pk) response = client.post(url, { 'privacy_level': 1, 'form-TOTAL_FORMS': 0, @@ -75,10 +75,10 @@ def test_subject_post_valid(client, user, contact): assert contact.subject.privacy_level == 1 -def test_subject_post_invalid_privacy_level(client, user, contact): +def test_data_protection_post_invalid_privacy_level(client, user, contact): client.force_login(user) - url = '/subjects/{}/subject/'.format(contact.subject.pk) + url = '/subjects/{}/data-protection/'.format(contact.subject.pk) response = client.post(url, { 'privacy_level': 2, 'form-TOTAL_FORMS': 0, @@ -88,6 +88,20 @@ def test_subject_post_invalid_privacy_level(client, user, contact): assert b'Please choose lower privacy level for the subject.' in response.content +@pytest.mark.parametrize('user_fixture', [ + pytest.param('study_coordinator', marks=pytest.mark.xfail(strict=True)), + pytest.param('recruiter', marks=pytest.mark.xfail(strict=True)), + 'subject_manager', + pytest.param('data_protection_coordinator', marks=pytest.mark.xfail(strict=True)), +]) +def test_additional_info_200(request, client, user_fixture, contact): + user = request.getfixturevalue(user_fixture) + client.force_login(user) + url = '/subjects/{}/additional-info/'.format(contact.subject.pk) + response = client.get(url) + assert response.status_code == 200 + + @pytest.mark.smoketest @pytest.mark.parametrize('user_fixture', [ pytest.param('study_coordinator', marks=pytest.mark.xfail(strict=True)), -- GitLab