From 2f46c2f6a24445a9424b5add03039827b2b24aa5 Mon Sep 17 00:00:00 2001 From: Edo Storm Date: Tue, 24 Sep 2024 15:35:39 +0200 Subject: [PATCH 01/23] feat: rename TranslatedConsentForm --- proposals/forms.py | 4 ++-- proposals/utils/checkers.py | 2 +- proposals/views/proposal_views.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/proposals/forms.py b/proposals/forms.py index e02e07a6f..eb71e1597 100644 --- a/proposals/forms.py +++ b/proposals/forms.py @@ -851,7 +851,7 @@ def clean(self): ) -class TranslatedConsentForms(SoftValidationMixin, ConditionalModelForm): +class TranslatedConsentForm(SoftValidationMixin, ConditionalModelForm): class Meta: model = Proposal fields = ["translated_forms", "translated_forms_languages"] @@ -862,7 +862,7 @@ class Meta: _soft_validation_fields = ["translated_forms", "translated_forms_languages"] def clean(self): - cleaned_data = super(TranslatedConsentForms, self).clean() + cleaned_data = super(TranslatedConsentForm, self).clean() if cleaned_data["translated_forms"] is None: self.add_error( diff --git a/proposals/utils/checkers.py b/proposals/utils/checkers.py index ecc15ae8e..53612c768 100644 --- a/proposals/utils/checkers.py +++ b/proposals/utils/checkers.py @@ -803,7 +803,7 @@ def make_stepper_item(self): class TranslationChecker( ModelFormChecker, ): - form_class = proposal_forms.TranslatedConsentForms + form_class = proposal_forms.TranslatedConsentForm title = _("Vertalingen") location = "data_management" diff --git a/proposals/views/proposal_views.py b/proposals/views/proposal_views.py index 652b722e9..adcd280c9 100644 --- a/proposals/views/proposal_views.py +++ b/proposals/views/proposal_views.py @@ -45,7 +45,7 @@ FundingForm, ResearchGoalForm, PreApprovedForm, - TranslatedConsentForms, + TranslatedConsentForm, ProposalForm, ) from ..models import Proposal, Wmo @@ -485,7 +485,7 @@ def get_back_url(self): class TranslatedConsentFormsView(ProposalContextMixin, UpdateView): model = Proposal - form_class = TranslatedConsentForms + form_class = TranslatedConsentForm template_name = "proposals/translated_consent_forms.html" def get_next_url(self): From 045e437f9106ae48a20ec7e19260300965de3e90 Mon Sep 17 00:00:00 2001 From: Edo Storm Date: Wed, 25 Sep 2024 15:58:02 +0200 Subject: [PATCH 02/23] feat: add fields to proposal and make migration --- ...54_proposal_knowledge_security_and_more.py | 49 +++++++++++++++++++ proposals/models.py | 37 ++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 proposals/migrations/0054_proposal_knowledge_security_and_more.py diff --git a/proposals/migrations/0054_proposal_knowledge_security_and_more.py b/proposals/migrations/0054_proposal_knowledge_security_and_more.py new file mode 100644 index 000000000..0ccea66e6 --- /dev/null +++ b/proposals/migrations/0054_proposal_knowledge_security_and_more.py @@ -0,0 +1,49 @@ +# Generated by Django 4.2.11 on 2024-09-24 14:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("proposals", "0053_auto_20240201_1557"), + ] + + operations = [ + migrations.AddField( + model_name="proposal", + name="knowledge_security", + field=models.CharField( + blank=True, + choices=[("Y", "ja"), ("N", "nee"), ("?", "twijfel")], + help_text="Kennisveiligheid gaat over het tijdig signaleren en mitigeren van veiligheidsrisico's bij wetenschappelijk onderzoek. Klik hier voor meer informatie.", + max_length=1, + verbose_name="Zijn er kwesties rondom kennisveiligheid?", + ), + ), + migrations.AddField( + model_name="proposal", + name="knowledge_security_details", + field=models.TextField( + blank=True, max_length=200, verbose_name="Licht toe" + ), + ), + migrations.AddField( + model_name="proposal", + name="researcher_risk", + field=models.CharField( + blank=True, + choices=[("Y", "ja"), ("N", "nee"), ("?", "twijfel")], + help_text="Houd hierbij niet alleen rekening met mogelijke psychische of fysieke schade, maar ook met andere mogelijke schade, zoals bijv. hiërarchische machtsverhoudingen in veldwerk, mogelijke negatieve gevolgen voor de zichtbaarheid/vindbaarheid van de onderzoeker in in het publieke domein, juridische vervolging of aansprakelijkheid, e.d.", + max_length=1, + verbose_name="Zijn er kwesties rondom de veiligheid van of risico's voor de onderzoeker(s)?", + ), + ), + migrations.AddField( + model_name="proposal", + name="researcher_risk_details", + field=models.TextField( + blank=True, max_length=200, verbose_name="Licht toe" + ), + ), + ] diff --git a/proposals/models.py b/proposals/models.py index 81ff7709b..25247f68c 100644 --- a/proposals/models.py +++ b/proposals/models.py @@ -497,6 +497,43 @@ class PracticeReasons(models.IntegerChoices): MaxWordsValidator(SELF_ASSESSMENT_MAX_WORDS), ], ) + knowledge_security = models.CharField( + _("Zijn er kwesties rondom kennisveiligheid?"), + help_text=mark_safe_lazy( + _( + "Kennisveiligheid gaat over het tijdig signaleren en mitigeren " + "van veiligheidsrisico's bij wetenschappelijk onderzoek. Klik " + "hier " + "voor meer informatie." + ) + ), + max_length=1, + choices=YesNoDoubt.choices, + blank=True, + ) + knowledge_security_details = models.TextField( + _("Licht toe"), max_length=200, blank=True + ) + researcher_risk = models.CharField( + _( + "Zijn er kwesties rondom de veiligheid van of risico's voor de onderzoeker(s)?" + ), + help_text=_( + "Houd hierbij niet alleen rekening met mogelijke psychische of " + "fysieke schade, maar ook met andere mogelijke schade, zoals bijv. " + "hiërarchische machtsverhoudingen in veldwerk, mogelijke negatieve " + "gevolgen voor de zichtbaarheid/vindbaarheid van de onderzoeker in " + "in het publieke domein, juridische vervolging of " + "aansprakelijkheid, e.d." + ), + max_length=1, + choices=YesNoDoubt.choices, + blank=True, + ) + researcher_risk_details = models.TextField( + _("Licht toe"), max_length=200, blank=True + ) + # References to other models relation = models.ForeignKey( From b3ed94777dd945f77159e4903e33dcf7bce1a63e Mon Sep 17 00:00:00 2001 From: Edo Storm Date: Wed, 25 Sep 2024 15:58:33 +0200 Subject: [PATCH 03/23] feat: modify study model and migration --- .../0029_remove_study_stressful_and_more.py | 62 ++++++++++++ studies/models.py | 99 +++++++------------ 2 files changed, 95 insertions(+), 66 deletions(-) create mode 100644 studies/migrations/0029_remove_study_stressful_and_more.py diff --git a/studies/migrations/0029_remove_study_stressful_and_more.py b/studies/migrations/0029_remove_study_stressful_and_more.py new file mode 100644 index 000000000..93ade15af --- /dev/null +++ b/studies/migrations/0029_remove_study_stressful_and_more.py @@ -0,0 +1,62 @@ +# Generated by Django 4.2.11 on 2024-09-24 14:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("studies", "0028_remove_study_sessions_number"), + ] + + operations = [ + migrations.RemoveField( + model_name="study", + name="stressful", + ), + migrations.RemoveField( + model_name="study", + name="stressful_details", + ), + migrations.AlterField( + model_name="study", + name="deception", + field=models.CharField( + blank=True, + choices=[("Y", "ja"), ("N", "nee"), ("?", "twijfel")], + help_text='Misleiding is het doelbewust verschaffen van inaccurate informatie over het doel en/of belangrijke aspecten van de gang van zaken tijdens het onderzoek. Denk aan zaken als een bewust misleidende "cover story" voor het experiment; het ten onrechte suggereren dat er door de deelnemer met andere deelnemers wordt samengewerkt; het onaangekondigd aanbieden van een cruciale geheugentaak of het geven van gefingeerde feedback. Wellicht ten overvloede: het gaat hierbij niet om fillers in bijv. taalwetenschappelijk onderzoek.', + max_length=1, + verbose_name="Is er binnen bovenstaand onderzoekstraject sprake van misleiding van de deelnemer?", + ), + ), + migrations.AlterField( + model_name="study", + name="negativity", + field=models.CharField( + blank=True, + choices=[("Y", "ja"), ("N", "nee"), ("?", "twijfel")], + max_length=1, + verbose_name="Bevat bovenstaand onderzoekstraject elementen die tijdens de deelname zodanig belastend zijn dat deze vragen, weerstand, of zelfs verontwaardiging zouden kunnen oproepen, bijvoorbeeld bij collega-onderzoekers, bij de deelnemers zelf, of bij ouders of andere vertegenwoordigers?", + ), + ), + migrations.AlterField( + model_name="study", + name="negativity_details", + field=models.TextField( + blank=True, + help_text="Geef concrete voorbeelden van de relevante aspecten van jouw onderzoek (bijv. voorbeelden van mogelijk zeer kwetsende woorden of uitspraken in de taak; zeer confronterende vragen in een vragenlijst; negatieve feedback), zodat de commissie zich een goed beeld kan vormen.", + verbose_name="Licht toe", + ), + ), + migrations.AlterField( + model_name="study", + name="risk", + field=models.CharField( + blank=True, + choices=[("Y", "ja"), ("N", "nee"), ("?", "twijfel")], + help_text="Houd hierbij niet alleen rekening met mogelijke psychische of fysieke schadelijke gevolgen, maar ook met andere mogelijke schade, zoals bijv. stigmatisering, (re-)traumatisering, aantasting van zelfbeeld, verlies van privacy, toevalsbevindingen, juridische vervolging of aansprakelijkheid, e.d.", + max_length=1, + verbose_name="Zijn er kwesties rondom de veiligheid van, of risico's voor de deelnemers tijdens of na deelname aan het onderzoek?", + ), + ), + ] diff --git a/studies/models.py b/studies/models.py index fe6e96a37..d9c8974c3 100644 --- a/studies/models.py +++ b/studies/models.py @@ -274,17 +274,17 @@ class Study(models.Model): # Fields with respect to Sessions deception = models.CharField( _( - "Is er binnen bovenstaand onderzoekstraject sprake van \ -misleiding van de deelnemer?" + "Is er binnen bovenstaand onderzoekstraject sprake van " +"misleiding van de deelnemer?" ), help_text=_( - 'Misleiding is het doelbewust verschaffen van inaccurate \ -informatie over het doel en/of belangrijke aspecten van de gang van zaken \ -tijdens het onderzoek. Denk aan zaken als een bewust misleidende "cover story" \ -voor het experiment; het ten onrechte suggereren dat er met andere \ -deelnemers wordt samengewerkt; het onaangekondigd aanbieden van een cruciale \ -geheugentaak of het geven van gefingeerde feedback. Wellicht ten overvloede: \ -het gaat hierbij niet om fillers.' + 'Misleiding is het doelbewust verschaffen van inaccurate ' +'informatie over het doel en/of belangrijke aspecten van de gang van zaken ' +'tijdens het onderzoek. Denk aan zaken als een bewust misleidende "cover story" ' +'voor het experiment; het ten onrechte suggereren dat er door de deelnemer met andere ' +'deelnemers wordt samengewerkt; het onaangekondigd aanbieden van een cruciale ' +'geheugentaak of het geven van gefingeerde feedback. Wellicht ten overvloede: ' +'het gaat hierbij niet om fillers in bijv. taalwetenschappelijk onderzoek.' ), max_length=1, choices=YesNoDoubt.choices, @@ -292,81 +292,48 @@ class Study(models.Model): ) deception_details = models.TextField( _( - "Geef een toelichting en beschrijf hoe en wanneer de deelnemer \ -zal worden gedebrieft." + "Geef een toelichting en beschrijf hoe en wanneer de deelnemer " +"zal worden gedebrieft." ), blank=True, ) negativity = models.CharField( _( - "Bevat bovenstaand onderzoekstraject elementen die \ -tijdens de deelname niet-triviale negatieve emoties kunnen opwekken? \ -Denk hierbij bijvoorbeeld aan emotioneel indringende vragen, kwetsende \ -uitspraken, negatieve feedback, frustrerende, zware, (heel) lange en/of \ -(heel) saaie taken." + "Bevat bovenstaand onderzoekstraject elementen die tijdens" + " de deelname zodanig belastend zijn dat deze vragen, " + "weerstand, of zelfs verontwaardiging zouden kunnen oproepen, " + "bijvoorbeeld bij collega-onderzoekers, bij de deelnemers zelf, " + "of bij ouders of andere vertegenwoordigers?" ), max_length=1, choices=YesNoDoubt.choices, blank=True, ) - negativity_details = models.TextField(_("Licht je antwoord toe."), blank=True) - stressful = models.CharField( - _( - "Bevat bovenstaand onderzoekstraject elementen die tijdens de \ -deelname zodanig belastend zijn dat deze ondanks de verkregen \ -informed consent vragen zou kunnen oproepen (of zelfs \ -verontwaardiging), bijvoorbeeld bij collega-onderzoekers, bij de deelnemers \ -zelf, of bij ouders of andere vertegenwoordigers?" - ), - help_text=mark_safe_lazy( - _( - "Dit zou bijvoorbeeld het geval kunnen zijn \ -bij een 'onmenselijk' lange en uitputtende taak, een zeer confronterende \ -vragenlijst, of voortdurend vernietigende feedback, maar ook bij een ervaren \ -inbreuk op de privacy, of een ander ervaren gebrek aan respect. \ -Let op, het gaat bij deze vraag om de door de deelnemer ervaren belasting \ -tijdens het onderzoek, niet om de opgelopen psychische of fysieke schade \ -door het onderzoek." - ) - ), - max_length=1, - choices=YesNoDoubt.choices, - blank=True, - ) - stressful_details = models.TextField( - _( - "Licht je antwoord toe. Geef concrete voorbeelden van de relevante \ -aspecten van jouw onderzoek (bijv. representatieve voorbeelden van mogelijk zeer \ -kwetsende woorden of uitspraken in de taak, of van zeer confronterende \ -vragen in een vragenlijst), zodat de commissie zich een goed beeld kan \ -vormen." + negativity_details = models.TextField( + _("Licht toe"), + help_text=_( + "Geef concrete voorbeelden van de relevante aspecten van jouw " + "onderzoek (bijv. voorbeelden van mogelijk zeer kwetsende woorden " + "of uitspraken in de taak; zeer confronterende vragen in een " + "vragenlijst; negatieve feedback), zodat de commissie zich een " + "goed beeld kan vormen." ), blank=True, ) + risk = models.CharField( _( - 'Zijn de risico\'s op psychische, fysieke, of andere (bijv. \ -economische, juridische) schade door deelname aan bovenstaand \ -onderzoekstraject meer dan minimaal? \ -D.w.z. ligt de kans op en/of omvang van mogelijke schade \ -bij de deelnemers duidelijk boven het "achtergrondrisico"?' + "Zijn er kwesties rondom de veiligheid van, of risico's voor de \ + deelnemers tijdens of na deelname aan het onderzoek?" ), help_text=mark_safe_lazy( _( - 'Achtergrondrisico is datgene dat gezonde, \ -gemiddelde burgers in de relevante leeftijdscategorie normaalgesproken \ -in het dagelijks leven ten deel valt. \ -Denk bij schade ook aan de gevolgen die het voor de deelnemer of \ -anderen beschikbaar komen van bepaalde informatie kan hebben, bijv. \ -op het vlak van zelfbeeld, stigmatisering door anderen, economische \ -schade door data-koppeling, et cetera. Het achtergrondrisico voor \ -psychische en fysieke schade omvat bijvoorbeeld ook de risico\'s van \ -"routine"-tests, -onderzoeken of -procedures die in alledaagse didactische, \ -psychologische of medische contexten plaatsvinden (zoals een eindexamen, \ -een rijexamen, een stressbestendigheids-assessment, een \ -intelligentie- of persoonlijkheidstest, of een hartslagmeting na fysieke \ -inspanning; dit alles, waar relevant, onder begeleiding van adequaat \ -geschoolde specialisten).' + "Houd hierbij niet alleen rekening met mogelijke psychische \ + of fysieke schadelijke gevolgen, maar ook met andere \ + mogelijke schade, zoals bijv. stigmatisering, \ + (re-)traumatisering, aantasting van zelfbeeld, verlies van \ + privacy, toevalsbevindingen, juridische vervolging of \ + aansprakelijkheid, e.d." ) ), max_length=1, From 40e7235cb6e5c6fc1497ae182e5354811988a74c Mon Sep 17 00:00:00 2001 From: Edo Storm Date: Wed, 25 Sep 2024 15:59:24 +0200 Subject: [PATCH 04/23] feat: make KnowledgeSecurityForm --- proposals/forms.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/proposals/forms.py b/proposals/forms.py index eb71e1597..58bb0eafc 100644 --- a/proposals/forms.py +++ b/proposals/forms.py @@ -851,6 +851,65 @@ def clean(self): ) +class KnowledgeSecurityForm(SoftValidationMixin, ConditionalModelForm): + + class Meta: + model = Proposal + fields = [ + "knowledge_security", + "knowledge_security_details", + "researcher_risk", + "researcher_risk_details", + ] + widgets = { + "knowledge_security": BootstrapRadioSelect(), + "researcher_risk": BootstrapRadioSelect(), + } + + _soft_validation_fields = [ + "knowledge_security", + "knowledge_security_details", + "researcher_risk", + "researcher_risk_details", + ] + + def __init__(self, *args, **kwargs): + """ + - Set the Study for later reference + - Remove empty label from fields and reset the choices + """ + super(KnowledgeSecurityForm, self).__init__(*args, **kwargs) + + self.base_fields = ( + "knowledge_security", + "researcher_risk", + ) + for field in self.base_fields: + self.fields[field].empty_label = None + self.fields[field].choices = YesNoDoubt.choices + + def clean(self): + """ + Check for conditional requirements: + - If deception is set to yes, make sure deception_details has been filled out + - If negativity is set to yes, make sure negativity_details has been filled out + - If risk is set to yes, make sure risk_details has been filled out + """ + cleaned_data = super(KnowledgeSecurityForm, self).clean() + + self.mark_soft_required( + cleaned_data, "knowledge_security", "researcher_risk" + ) + + for field in self.base_fields: + self.check_dependency_list( + cleaned_data, + f"{field}", + f"{field}_details", + f1_value_list=[YesNoDoubt.YES, YesNoDoubt.DOUBT], + ) + + class TranslatedConsentForm(SoftValidationMixin, ConditionalModelForm): class Meta: model = Proposal From fb6b7d8453aba8221637c41d4384a9be8e651e65 Mon Sep 17 00:00:00 2001 From: Edo Storm Date: Wed, 25 Sep 2024 16:00:15 +0200 Subject: [PATCH 05/23] feat: update StudyEndForm --- studies/forms.py | 59 ++++++++++++++++-------------------------------- 1 file changed, 19 insertions(+), 40 deletions(-) diff --git a/studies/forms.py b/studies/forms.py index 721ab8df1..d484dc904 100644 --- a/studies/forms.py +++ b/studies/forms.py @@ -277,15 +277,12 @@ class Meta: "deception_details", "negativity", "negativity_details", - "stressful", - "stressful_details", "risk", "risk_details", ] widgets = { "deception": BootstrapRadioSelect(), "negativity": BootstrapRadioSelect(), - "stressful": BootstrapRadioSelect(), "risk": BootstrapRadioSelect(), } @@ -303,23 +300,22 @@ class Meta: def __init__(self, *args, **kwargs): """ - Set the Study for later reference - - Remove empty label from deception/negativity/stressful/risk field and reset the choices - - mark_safe the labels of negativity/stressful/risk + - Remove empty label from deception/negativity/risk field and reset the choices + - mark_safe the labels of negativity/risk """ super(StudyEndForm, self).__init__(*args, **kwargs) - self.fields["deception"].empty_label = None - self.fields["deception"].choices = YesNoDoubt.choices - self.fields["negativity"].empty_label = None - self.fields["negativity"].choices = YesNoDoubt.choices - self.fields["stressful"].empty_label = None - self.fields["stressful"].choices = YesNoDoubt.choices - self.fields["risk"].empty_label = None - self.fields["risk"].choices = YesNoDoubt.choices + self.base_fields = ( + "deception", + "negativity", + "risk", + ) + for field in self.base_fields: + self.fields[field].empty_label = None + self.fields[field].choices = YesNoDoubt.choices self.fields["negativity"].label = mark_safe(self.fields["negativity"].label) - self.fields["stressful"].label = mark_safe(self.fields["stressful"].label) self.fields["risk"].label = mark_safe(self.fields["risk"].label) if not self.instance.has_sessions: @@ -337,36 +333,19 @@ def clean(self): cleaned_data = super(StudyEndForm, self).clean() # TODO: find a way to hide this on the first view - self.mark_soft_required(cleaned_data, "negativity", "stressful", "risk") + self.mark_soft_required(cleaned_data, "negativity", "risk") if "deception" in self.fields: self.mark_soft_required(cleaned_data, "deception") - self.check_dependency_list( - cleaned_data, - "deception", - "deception_details", - f1_value_list=[YesNoDoubt.YES, YesNoDoubt.DOUBT], - ) - self.check_dependency_list( - cleaned_data, - "negativity", - "negativity_details", - f1_value_list=[YesNoDoubt.YES, YesNoDoubt.DOUBT], - ) - self.check_dependency_list( - cleaned_data, - "stressful", - "stressful_details", - f1_value_list=[YesNoDoubt.YES, YesNoDoubt.DOUBT], - ) - self.check_dependency_list( - cleaned_data, - "risk", - "risk_details", - f1_value_list=[YesNoDoubt.YES, YesNoDoubt.DOUBT], - ) - + for field in self.base_fields: + self.check_dependency_list( + cleaned_data, + f"{field}", + f"{field}_details", + f1_value_list=[YesNoDoubt.YES, YesNoDoubt.DOUBT], + ) + class StudyUpdateAttachmentsForm(TemplatedModelForm): class Meta: From 1b538c306145df3159776c23996df46e303a77ad Mon Sep 17 00:00:00 2001 From: Edo Storm Date: Wed, 25 Sep 2024 16:01:22 +0200 Subject: [PATCH 06/23] feat: add KnowledgeSecurityView --- .../proposals/knowledge_security_form.html | 23 +++++++++++++++++++ proposals/urls.py | 2 ++ proposals/utils/checkers.py | 22 +++++++++++++++++- proposals/views/proposal_views.py | 11 +++++++++ proposals/views/study_views.py | 2 +- studies/views/study_views.py | 2 +- 6 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 proposals/templates/proposals/knowledge_security_form.html diff --git a/proposals/templates/proposals/knowledge_security_form.html b/proposals/templates/proposals/knowledge_security_form.html new file mode 100644 index 000000000..cfe91e4ef --- /dev/null +++ b/proposals/templates/proposals/knowledge_security_form.html @@ -0,0 +1,23 @@ +{% extends "base/fetc_form_base.html" %} + +{% load static %} +{% load i18n %} + +{% block header_title %} + {% trans "Afronding trajecten" %} - {{ block.super }} +{% endblock %} + + +{% block html_head %} + {{ block.super }} + +{% endblock %} + +{% block pre-form-text %} +

{% trans "Afronding trajecten" %}

+{% endblock %} \ No newline at end of file diff --git a/proposals/urls.py b/proposals/urls.py index 4572c2cb6..0a031e20f 100644 --- a/proposals/urls.py +++ b/proposals/urls.py @@ -44,6 +44,7 @@ ProposalUpdateDataManagement, TranslatedConsentFormsView, ProposalUpdateDateStart, + ProposalKnowledgeSecurity, ) from .views.study_views import StudyStart, StudyConsent @@ -213,6 +214,7 @@ ), path("confirm//", ProposalConfirmation.as_view(), name="confirmation"), path("study_start//", StudyStart.as_view(), name="study_start"), + path("knowledge_security//", ProposalKnowledgeSecurity.as_view(), name="knowledge_security"), path("consent//", StudyConsent.as_view(), name="consent"), path( "translated//", TranslatedConsentFormsView.as_view(), name="translated" diff --git a/proposals/utils/checkers.py b/proposals/utils/checkers.py index 53612c768..2dbf59da0 100644 --- a/proposals/utils/checkers.py +++ b/proposals/utils/checkers.py @@ -323,6 +323,10 @@ def remaining_checkers( self, ): return [ + TrajectoriesEndChecker( + self.stepper, + parent=self.item + ), DocumentsChecker, DataManagementChecker, SubmitChecker, @@ -348,6 +352,22 @@ def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs["proposal"] = self.proposal return kwargs + +class TrajectoriesEndChecker( + ModelFormChecker, +): + form_class = proposal_forms.KnowledgeSecurityForm + title = _("Traject afronding") + + def check(self): + if self.stepper.has_multiple_studies(): + self.title = _("Trajecten afronding") + self.stepper.items.append(self.make_stepper_item()) + return [] + + def get_url(self,): + return reverse("proposals:knowledge_security", + args=[self.proposal.pk]) class StudyChecker( @@ -497,7 +517,7 @@ def get_url( class StudyEndChecker( ModelFormChecker, ): - title = _("Afronding") + title = _("Traject overzicht") form_class = study_forms.StudyEndForm def __init__( diff --git a/proposals/views/proposal_views.py b/proposals/views/proposal_views.py index adcd280c9..949d1ab34 100644 --- a/proposals/views/proposal_views.py +++ b/proposals/views/proposal_views.py @@ -47,6 +47,7 @@ PreApprovedForm, TranslatedConsentForm, ProposalForm, + KnowledgeSecurityForm, ) from ..models import Proposal, Wmo from ..utils import generate_pdf, generate_ref_number @@ -482,6 +483,16 @@ def get_back_url(self): """Return to the Proposal Form page""" return reverse("proposals:research_goal", args=(self.object.pk,)) +class ProposalKnowledgeSecurity(ProposalContextMixin, AllowErrorsOnBackbuttonMixin, UpdateView): + model = Proposal + form_class = KnowledgeSecurityForm + template_name = "proposals/knowledge_security_form.html" + + def get_next_url(self): + return reverse("proposals:consent", args=(self.object.pk,)) + + def get_back_url(self): + return reverse("studies:design_end", args=(self.object.last_study().pk,)) class TranslatedConsentFormsView(ProposalContextMixin, UpdateView): model = Proposal diff --git a/proposals/views/study_views.py b/proposals/views/study_views.py index 1c8843c69..09cb6d044 100644 --- a/proposals/views/study_views.py +++ b/proposals/views/study_views.py @@ -119,4 +119,4 @@ def get_next_url(self): def get_back_url(self): """Return to the Study design view""" proposal = Proposal.objects.get(pk=self.kwargs.get("pk")) - return reverse("studies:design_end", args=(proposal.last_study().pk,)) + return reverse("proposals:knowledge_security", args=(proposal.pk,)) diff --git a/studies/views/study_views.py b/studies/views/study_views.py index 3b54454e4..66062c06f 100644 --- a/studies/views/study_views.py +++ b/studies/views/study_views.py @@ -172,7 +172,7 @@ def get_next_url(self): next_study = Study.objects.get(proposal=proposal, order=next_order) return reverse("studies:update", args=(next_study.pk,)) else: - return reverse("proposals:consent", args=(proposal.pk,)) + return reverse("proposals:knowledge_security", args=(proposal.pk,)) def get_back_url(self): study = self.object From 6446f56215a5bfa654af3013d2e1b09589264e95 Mon Sep 17 00:00:00 2001 From: Edo Storm Date: Wed, 25 Sep 2024 16:02:05 +0200 Subject: [PATCH 07/23] feat: implement pdf_diff_logic for new questions --- proposals/utils/pdf_diff_logic.py | 67 ++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/proposals/utils/pdf_diff_logic.py b/proposals/utils/pdf_diff_logic.py index 289e20e9a..93a38e2e6 100644 --- a/proposals/utils/pdf_diff_logic.py +++ b/proposals/utils/pdf_diff_logic.py @@ -827,8 +827,6 @@ class StudyOverviewSection(BaseSection): "deception_details", "negativity", "negativity_details", - "stressful", - "stressful_details", "risk", "risk_details", ] @@ -842,7 +840,7 @@ def get_row_fields(self): obj = self.obj rows_to_remove = [] - for x in range(0, 7, 2): + for x in range(0, len(self.row_fields), 2): if getattr(obj, rows[x]) == "N": rows_to_remove.append(rows[x + 1]) rows = [row for row in rows if row not in rows_to_remove] @@ -856,6 +854,30 @@ def get_row_fields(self): return rows +class KnowledgeSecuritySection(BaseSection): + """This class receives a Proposal object.""" + + section_title = _("Kennisveiligheid en risico onderzoekers") + row_fields = [ + "knowledge_security", + "knowledge_security_details", + "researcher_risk", + "researcher_risk_details", + ] + + def get_row_fields(self): + rows = copy(self.row_fields) + obj = self.obj + + rows_to_remove = [] + for x in range(0, len(self.row_fields), 2): + if getattr(obj, rows[x]) == "N": + rows_to_remove.append(rows[x + 1]) + rows = [row for row in rows if row not in rows_to_remove] + + return rows + + class InformedConsentFormsSection(BaseSection): """This class receives a Documents object""" @@ -997,24 +1019,24 @@ def get_extra_documents(obj): return extra_documents -def create_context_pdf(context, model): +def create_context_pdf(context, proposal): """A function to create the context for the PDF, which gets called in the ProposalAsPdf view.""" sections = [] - sections.append(GeneralSection(model)) + sections.append(GeneralSection(proposal)) - if hasattr(model, "wmo"): - sections.append(WMOSection(model.wmo)) + if hasattr(proposal, "wmo"): + sections.append(WMOSection(proposal.wmo)) - if not model.is_pre_assessment: - if model.wmo.status != model.wmo.WMOStatuses.NO_WMO: - sections.append(METCSection(model.wmo)) + if not proposal.is_pre_assessment: + if proposal.wmo.status != proposal.wmo.WMOStatuses.NO_WMO: + sections.append(METCSection(proposal.wmo)) - sections.append(TrajectoriesSection(model)) + sections.append(TrajectoriesSection(proposal)) - if model.wmo.status == model.wmo.WMOStatuses.NO_WMO: - for study in model.study_set.all(): + if proposal.wmo.status == proposal.wmo.WMOStatuses.NO_WMO: + for study in proposal.study_set.all(): sections.append(StudySection(study)) if study.has_intervention: sections.append(InterventionSection(study.intervention)) @@ -1029,15 +1051,17 @@ def create_context_pdf(context, model): sections.append(StudyOverviewSection(study)) sections.append(InformedConsentFormsSection(study.documents)) - extra_documents = get_extra_documents(model) + sections.append(KnowledgeSecuritySection(proposal)) + + extra_documents = get_extra_documents(proposal) for num, document in enumerate(extra_documents): sections.append(ExtraDocumentsSection(document, num)) - sections.append(DMPFileSection(model)) + sections.append(DMPFileSection(proposal)) - sections.append(EmbargoSection(model)) - sections.append(CommentsSection(model)) + sections.append(EmbargoSection(proposal)) + sections.append(CommentsSection(proposal)) context["sections"] = sections @@ -1193,6 +1217,13 @@ def create_context_diff(context, old_proposal, new_proposal): ) ) + sections.append( + DiffSection( + KnowledgeSecuritySection(old_proposal), + KnowledgeSecuritySection(new_proposal), + ) + ) + old_extra_docs = get_extra_documents(old_proposal) new_extra_docs = get_extra_documents(new_proposal) @@ -1210,7 +1241,7 @@ def create_context_diff(context, old_proposal, new_proposal): sections.append( DiffSection( - *multi_sections(DMPFileSection, [old_proposal, new_proposal]) + DMPFileSection(old_proposal), DMPFileSection(new_proposal) ) ) From 15154117d7d8b9fe657203f3c9c509a115ffe054 Mon Sep 17 00:00:00 2001 From: Edo Storm Date: Wed, 25 Sep 2024 16:02:29 +0200 Subject: [PATCH 08/23] feat: update auto_review & tests --- reviews/tests.py | 10 ++++++++-- reviews/utils/review_utils.py | 27 ++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/reviews/tests.py b/reviews/tests.py index 6f30888f2..fe0191359 100644 --- a/reviews/tests.py +++ b/reviews/tests.py @@ -258,18 +258,24 @@ def test_auto_review(self): reasons = auto_review(self.proposal) self.assertEqual(len(reasons), 5) - self.study.stressful = YesNoDoubt.YES + self.study.risk = YesNoDoubt.YES self.study.save() reasons = auto_review(self.proposal) self.assertEqual(len(reasons), 6) - self.study.risk = YesNoDoubt.YES + self.study.researcher_risk = YesNoDoubt.YES self.study.save() reasons = auto_review(self.proposal) self.assertEqual(len(reasons), 7) + self.study.negativity = YesNoDoubt.YES + self.study.save() + + reasons = auto_review(self.proposal) + self.assertEqual(len(reasons), 8) + def test_auto_review_age_groups(self): self.study.has_sessions = True self.study.age_groups.set(AgeGroup.objects.filter(pk=2)) # toddlers diff --git a/reviews/utils/review_utils.py b/reviews/utils/review_utils.py index 8eb6bf9ce..5b1ec688e 100644 --- a/reviews/utils/review_utils.py +++ b/reviews/utils/review_utils.py @@ -482,19 +482,20 @@ def auto_review(proposal: Proposal): for task in Task.objects.filter(session__study=study): reasons.extend(auto_review_task(study, task)) - if study.stressful in [YesNoDoubt.YES, YesNoDoubt.DOUBT]: + if study.negativity in [YesNoDoubt.YES, YesNoDoubt.DOUBT]: reasons.append( _( - "De onderzoeker geeft aan dat (of twijfelt erover of) het onderzoek op onderdelen of \ -als geheel zodanig belastend is dat deze ondanks de verkregen informed consent vragen zou kunnen oproepen." + "De onderzoeker geeft aan dat sommige vragen binnen het onderzoek mogelijk " + "dermate belastend kunnen zijn dat ze negatieve reacties bij de deelnemers " + "en/of onderzoekers kunnen veroorzaken." ) ) if study.risk in [YesNoDoubt.YES, YesNoDoubt.DOUBT]: reasons.append( _( - "De onderzoeker geeft aan dat (of twijfelt erover of) de risico's op psychische of \ -fysieke schade bij deelname aan het onderzoek meer dan minimaal zijn." + "De onderzoeker geeft aan dat er mogelijk kwesties zijn rondom de veiligheid " + "van de deelnemers tijdens of na het onderzoek." ) ) @@ -514,6 +515,22 @@ def auto_review(proposal: Proposal): max_d=age_group.max_net_duration, ) ) + if proposal.knowledge_security in [YesNoDoubt.YES, YesNoDoubt.DOUBT]: + reasons.append( + _( + "De onderzoeker geeft aan dat er mogelijk kwesties zijn rondom " + "kennisveiligheid." + ) + ) + + if proposal.researcher_risk in [YesNoDoubt.YES, YesNoDoubt.DOUBT]: + reasons.append( + _( + "De onderzoeker geeft aan dat er mogelijk kwesties zijn " + "rondom de veiligheid van de betrokken onderzoekers." + ) + ) + return reasons From 8c523b5d87e87b6e8dec09257901eff8e4ad5b00 Mon Sep 17 00:00:00 2001 From: Edo Storm Date: Wed, 25 Sep 2024 16:10:36 +0200 Subject: [PATCH 09/23] fix: woops, some ugly string stuff --- .../0029_remove_study_stressful_and_more.py | 6 +++--- studies/models.py | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/studies/migrations/0029_remove_study_stressful_and_more.py b/studies/migrations/0029_remove_study_stressful_and_more.py index 93ade15af..d60bdff50 100644 --- a/studies/migrations/0029_remove_study_stressful_and_more.py +++ b/studies/migrations/0029_remove_study_stressful_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.11 on 2024-09-24 14:18 +# Generated by Django 4.2.11 on 2024-09-25 14:10 from django.db import migrations, models @@ -54,9 +54,9 @@ class Migration(migrations.Migration): field=models.CharField( blank=True, choices=[("Y", "ja"), ("N", "nee"), ("?", "twijfel")], - help_text="Houd hierbij niet alleen rekening met mogelijke psychische of fysieke schadelijke gevolgen, maar ook met andere mogelijke schade, zoals bijv. stigmatisering, (re-)traumatisering, aantasting van zelfbeeld, verlies van privacy, toevalsbevindingen, juridische vervolging of aansprakelijkheid, e.d.", + help_text="Houd hierbij niet alleen rekening met mogelijke psychische of fysieke schadelijke gevolgen, maar ook met andere mogelijke schade, zoals bijv. stigmatisering, (re-)traumatisering, aantasting van zelfbeeld, verlies van privacy, toevalsbevindingen, juridische vervolging of aansprakelijkheid, e.d.", max_length=1, - verbose_name="Zijn er kwesties rondom de veiligheid van, of risico's voor de deelnemers tijdens of na deelname aan het onderzoek?", + verbose_name="Zijn er kwesties rondom de veiligheid van, of risico's voor de deelnemers tijdens of na deelname aan het onderzoek?", ), ), ] diff --git a/studies/models.py b/studies/models.py index d9c8974c3..bab1df7e3 100644 --- a/studies/models.py +++ b/studies/models.py @@ -323,17 +323,17 @@ class Study(models.Model): risk = models.CharField( _( - "Zijn er kwesties rondom de veiligheid van, of risico's voor de \ - deelnemers tijdens of na deelname aan het onderzoek?" + "Zijn er kwesties rondom de veiligheid van, of risico's voor de " + "deelnemers tijdens of na deelname aan het onderzoek?" ), help_text=mark_safe_lazy( _( - "Houd hierbij niet alleen rekening met mogelijke psychische \ - of fysieke schadelijke gevolgen, maar ook met andere \ - mogelijke schade, zoals bijv. stigmatisering, \ - (re-)traumatisering, aantasting van zelfbeeld, verlies van \ - privacy, toevalsbevindingen, juridische vervolging of \ - aansprakelijkheid, e.d." + "Houd hierbij niet alleen rekening met mogelijke psychische " + "of fysieke schadelijke gevolgen, maar ook met andere " + "mogelijke schade, zoals bijv. stigmatisering, " + "(re-)traumatisering, aantasting van zelfbeeld, verlies van " + "privacy, toevalsbevindingen, juridische vervolging of " + "aansprakelijkheid, e.d." ) ), max_length=1, From 33be1ae69aeeb24c37a5b8c251f358ae6ec75b19 Mon Sep 17 00:00:00 2001 From: Edo Storm Date: Wed, 25 Sep 2024 16:47:13 +0200 Subject: [PATCH 10/23] fix: rename KnowledgeSecurityChecker --- proposals/utils/checkers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/utils/checkers.py b/proposals/utils/checkers.py index 2dbf59da0..e7141b816 100644 --- a/proposals/utils/checkers.py +++ b/proposals/utils/checkers.py @@ -323,7 +323,7 @@ def remaining_checkers( self, ): return [ - TrajectoriesEndChecker( + KnowledgeSecurityChecker( self.stepper, parent=self.item ), @@ -353,7 +353,7 @@ def get_form_kwargs(self): kwargs["proposal"] = self.proposal return kwargs -class TrajectoriesEndChecker( +class KnowledgeSecurityChecker( ModelFormChecker, ): form_class = proposal_forms.KnowledgeSecurityForm From 712270f6b0f43efdfdd24d54ad9f47e36474e447 Mon Sep 17 00:00:00 2001 From: Edo Storm Date: Wed, 25 Sep 2024 16:56:28 +0200 Subject: [PATCH 11/23] style: steez --- proposals/forms.py | 6 ++---- proposals/models.py | 7 +++---- .../proposals/knowledge_security_form.html | 3 +-- proposals/urls.py | 6 +++++- proposals/utils/checkers.py | 17 ++++++++--------- proposals/views/proposal_views.py | 8 ++++++-- reviews/tests.py | 2 +- reviews/utils/review_utils.py | 1 - studies/forms.py | 2 +- studies/models.py | 18 +++++++++--------- 10 files changed, 36 insertions(+), 34 deletions(-) diff --git a/proposals/forms.py b/proposals/forms.py index 58bb0eafc..c1753819b 100644 --- a/proposals/forms.py +++ b/proposals/forms.py @@ -887,7 +887,7 @@ def __init__(self, *args, **kwargs): for field in self.base_fields: self.fields[field].empty_label = None self.fields[field].choices = YesNoDoubt.choices - + def clean(self): """ Check for conditional requirements: @@ -897,9 +897,7 @@ def clean(self): """ cleaned_data = super(KnowledgeSecurityForm, self).clean() - self.mark_soft_required( - cleaned_data, "knowledge_security", "researcher_risk" - ) + self.mark_soft_required(cleaned_data, "knowledge_security", "researcher_risk") for field in self.base_fields: self.check_dependency_list( diff --git a/proposals/models.py b/proposals/models.py index 25247f68c..b0a688c54 100644 --- a/proposals/models.py +++ b/proposals/models.py @@ -502,9 +502,9 @@ class PracticeReasons(models.IntegerChoices): help_text=mark_safe_lazy( _( "Kennisveiligheid gaat over het tijdig signaleren en mitigeren " - "van veiligheidsrisico's bij wetenschappelijk onderzoek. Klik " - "hier " - "voor meer informatie." + "van veiligheidsrisico's bij wetenschappelijk onderzoek. Klik " + "hier " + "voor meer informatie." ) ), max_length=1, @@ -534,7 +534,6 @@ class PracticeReasons(models.IntegerChoices): _("Licht toe"), max_length=200, blank=True ) - # References to other models relation = models.ForeignKey( Relation, diff --git a/proposals/templates/proposals/knowledge_security_form.html b/proposals/templates/proposals/knowledge_security_form.html index cfe91e4ef..9ab4eda93 100644 --- a/proposals/templates/proposals/knowledge_security_form.html +++ b/proposals/templates/proposals/knowledge_security_form.html @@ -7,7 +7,6 @@ {% trans "Afronding trajecten" %} - {{ block.super }} {% endblock %} - {% block html_head %} {{ block.super }}