diff --git a/README.md b/README.md index 17a25982..77aeb3d5 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ You are encouraged to try Daisy for yourself using our [DEMO deployment](https:/ 1. Create initial data in the database ```bash + docker-compose exec web bash -c "apt update && apt install -y wget" docker-compose exec web bash -c "cd core/fixtures/ && wget https://git-r3lab.uni.lu/pinar.alper/metadata-tools/raw/master/metadata_tools/resources/edda.json && wget https://git-r3lab.uni.lu/pinar.alper/metadata-tools/raw/master/metadata_tools/resources/hpo.json && wget https://git-r3lab.uni.lu/pinar.alper/metadata-tools/raw/master/metadata_tools/resources/hdo.json && wget https://git-r3lab.uni.lu/pinar.alper/metadata-tools/raw/master/metadata_tools/resources/hgnc.json" docker-compose exec web python manage.py load_initial_data ``` @@ -69,7 +70,7 @@ You are encouraged to try Daisy for yourself using our [DEMO deployment](https:/ ```bash docker-compose exec web python manage.py load_demo_data ``` - This will create mock datasets, projects and create an demo admin account. + This will create mock datasets, projects and create a demo admin account. 1. Optional - import users from an active directory instance @@ -170,7 +171,7 @@ where ${JSON_FILE} is the path to a json file that will be produced. In additio ```bash docker-compose exec web python manage.py rebuild_index -u default - ``` + ``` 1. Reimport the users (optional). If LDAP was used during initial setup to import users, they have to be imported again: diff --git a/core/forms/access.py b/core/forms/access.py index 48f2e8f8..a15d2e31 100644 --- a/core/forms/access.py +++ b/core/forms/access.py @@ -17,16 +17,16 @@ class Meta: "access_notes": Textarea(attrs={"rows": 2, "cols": 40}), } heading = "Access" - heading_help = "Please provide a help text for Access form" + heading_help = "Specify who can access the data, the duration, and any relevant notes. Ensure accuracy for data security." + def __init__(self, *args, **kwargs): dataset = kwargs.pop("dataset", None) super().__init__(*args, **kwargs) # we don't allow editing dataset self.fields.pop("dataset") - if dataset: - self.fields["defined_on_locations"].choices = [ - (d.id, d) for d in dataset.data_locations.all() - ] + self.fields["defined_on_locations"].choices = [ + (d.id, d) for d in dataset.data_locations.all() + ] field_order = [ "contact", diff --git a/core/forms/data_declaration.py b/core/forms/data_declaration.py index 2d4f62e0..bf0b16c2 100644 --- a/core/forms/data_declaration.py +++ b/core/forms/data_declaration.py @@ -1,10 +1,8 @@ from django import forms -from django.forms import ValidationError from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy -from core.forms import SkipFieldValidationMixin -from core.forms import UseRestrictionForm +from core.forms import UseRestrictionForm, SkipFieldValidationMixin from core.models import DataDeclaration, Partner, Contract, GDPRRole from core.models.contract import PartnerRole @@ -12,31 +10,31 @@ class DataDeclarationEditForm(forms.ModelForm): class Meta: - model = DataDeclaration - fields = [ - 'title', - 'cohorts', - 'partner', - 'contract', - 'data_declarations_parents', - 'comments', - 'data_types_generated', - 'data_types_received', - 'deidentification_method', - 'has_special_subjects', - 'subjects_category', - 'consent_status', - 'special_subjects_description', - 'end_of_storage_duration', - 'storage_duration_criteria', - 'embargo_date', - 'data_types_notes' - ] - widgets = { - # Date pickers - 'end_of_storage_duration': forms.DateInput(attrs={'class': 'datepicker'}), - 'embargo_date': forms.DateInput(attrs={'class': 'datepicker'}), - } + model = DataDeclaration + fields = [ + 'title', + 'cohorts', + 'partner', + 'contract', + 'data_declarations_parents', + 'comments', + 'data_types_generated', + 'data_types_received', + 'deidentification_method', + 'has_special_subjects', + 'subjects_category', + 'consent_status', + 'special_subjects_description', + 'end_of_storage_duration', + 'storage_duration_criteria', + 'embargo_date', + 'data_types_notes' + ] + widgets = { + # Date pickers + 'end_of_storage_duration': forms.DateInput(attrs={'class': 'datepicker'}), + 'embargo_date': forms.DateInput(attrs={'class': 'datepicker'}), + } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -46,7 +44,6 @@ def __init__(self, *args, **kwargs): self.fields['data_declarations_parents'].widget.attrs['class'] = 'ontocomplete'+ ' '+ self.fields['data_declarations_parents'].widget.attrs.get('class','') self.fields['data_declarations_parents'].widget.attrs['data-url'] = reverse_lazy('data_dec_paginated_search') - def clean(self): """ Override to check selected Partner and Contract match @@ -175,7 +172,7 @@ class Meta: model = DataDeclaration fields = ['title'] heading = "Data declaration" - heading_help = "Please provide a help text for the Data declaration form" + heading_help = "Detail your data's origin for clarity. Knowing its provenance enhances its value and trust." def __init__(self, *args, **kwargs): self.dataset = kwargs.pop('dataset') diff --git a/core/forms/dataset.py b/core/forms/dataset.py index 4da7256b..08390e35 100644 --- a/core/forms/dataset.py +++ b/core/forms/dataset.py @@ -4,13 +4,29 @@ from core.models import Dataset, User, Project from core.models.contract import Contract +from typing import Any + class SkipFieldValidationMixin: - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: + """ + Initialize the mixin. Adds a hidden Boolean field named `skip_wizard` + to the form which determines if validation should be skipped. + + Args: + *args: Variable arguments to pass to the superclass. + **kwargs: Keyword arguments to pass to the superclass. + """ super().__init__(*args, **kwargs) self.fields['skip_wizard'] = forms.BooleanField(initial=False, required=False, widget=forms.HiddenInput()) - def is_valid(self): + def is_valid(self) -> bool: + """ + Check if the form is valid. Validation is skipped if `skip_wizard` is True. + + Returns: + bool: True if the form is valid or if `skip_wizard` is True, otherwise False. + """ if self.data.get(f'{self.prefix}-skip_wizard') == 'True': return True return super().is_valid() @@ -25,7 +41,7 @@ class Meta: 'comments': forms.Textarea(attrs={'rows': 2, 'cols': 40}), } heading = "Dataset" - heading_help = "Please provide a help text for the dataset form" + heading_help = "Complete the form to enhance dataset quality. Your careful input benefits our community!" def __init__(self, *args, **kwargs): dataset = None diff --git a/core/forms/legal_basis.py b/core/forms/legal_basis.py index f80f4e98..e84538d2 100644 --- a/core/forms/legal_basis.py +++ b/core/forms/legal_basis.py @@ -10,15 +10,14 @@ class Meta: fields = '__all__' exclude = [] heading = "Data Legal Basis" - heading_help = "Please provide a title for this data declaration" + heading_help = "Define the legal grounds for processing. Ensure your data complies with relevant regulations." def __init__(self, *args, **kwargs): dataset = kwargs.pop('dataset', None) super().__init__(*args, **kwargs) # we don't allow editing dataset self.fields.pop('dataset') - if dataset: - self.fields['data_declarations'].choices = [(d.id, d.title) for d in dataset.data_declarations.all()] + self.fields['data_declarations'].choices = [(d.id, d.title) for d in dataset.data_declarations.all()] field_order = [ 'data_declarations', diff --git a/core/forms/storage_location.py b/core/forms/storage_location.py index 8b2e4406..b0cf8d3c 100644 --- a/core/forms/storage_location.py +++ b/core/forms/storage_location.py @@ -10,7 +10,7 @@ class Meta: fields = '__all__' exclude = [] heading = "Add a new storage location" - heading_help = "Please provide a title for this storage location" + heading_help = "Specify your data's home. Clear storage details ensure easy retrieval and management." def __init__(self, *args, **kwargs): dataset = kwargs.pop('dataset', None) diff --git a/docker-compose.yaml b/docker-compose.yaml index 26376c50..01afc1d3 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -20,7 +20,7 @@ services: - solrdata:/solr - .:/code ports: - - "5000:5000" + - "5001:5000" depends_on: - db - node diff --git a/docker/node/Dockerfile b/docker/node/Dockerfile index d76d8eb9..c0b909d2 100644 --- a/docker/node/Dockerfile +++ b/docker/node/Dockerfile @@ -1,4 +1,4 @@ -FROM node:11.10.1-alpine +FROM node:14 COPY web/static/css /static/css COPY web/static/js /static/js COPY web/static/vendor/bootstrap-material-datetimepicker /static/vendor/bootstrap-material-datetimepicker diff --git a/web/static/css/dataset_wizard.css b/web/static/css/dataset_wizard.css index 76cd76f6..d0d78cc1 100644 --- a/web/static/css/dataset_wizard.css +++ b/web/static/css/dataset_wizard.css @@ -105,5 +105,5 @@ } .skipped:after { - background: #e9ecef; + background: #6c757d; } \ No newline at end of file diff --git a/web/static/js/data_declaration.js b/web/static/js/data_declaration.js index 80f249bf..49c61dbd 100644 --- a/web/static/js/data_declaration.js +++ b/web/static/js/data_declaration.js @@ -1,8 +1,6 @@ const skipInput = document.getElementById(skipInputID) const skipButton = document.getElementById('skipButton') -if (!skipInput) { - skipButton.remove(); -} + if (skipButton) { skipButton.addEventListener('click', function () { if (skipInput) { @@ -11,19 +9,32 @@ if (skipButton) { } }); } + +/* + * On page load, this script checks if the current step is `data_declaration`. + * If true, it listens for changes on any radio button input. When a radio button + * input changes, the script dynamically loads and inserts a sub-form based on + * the selected declaration type and the dataset ID. This sub-form is placed + * before the form footer. Additionally, after loading the sub-form, it initializes + * select2 on any select fields and applies Bootstrap Material Design styles. + */ + $(document).ready(function () { - $('input[type=radio]').change(function () { - const submit_button = $("#buttons") - $("#sub-form").remove(); - const sub_form = $("
"); - submit_button.before(sub_form); - const declaration_type = this.value; - const forms_url = dataDeclarationsAddSubFormUrl + '?declaration_type=' + declaration_type + '&dataset_id=' + dataset_id; - sub_form.load(forms_url, function () { - sub_form.find('select').select2(); - sub_form.bootstrapMaterialDesign(); - } - ) - ; - }); + if (stepName === 'data_declaration') { + $('input[type=radio]').change(function () { + const form_footer = $("#form_footer") + $("#sub-form").remove(); + const sub_form = $("
"); + form_footer.before(sub_form); + const declaration_type = this.value; + const forms_url = dataDeclarationsAddSubFormUrl + '?declaration_type=' + declaration_type + '&dataset_id=' + dataset_id; + sub_form.load(forms_url, function () { + sub_form.find('select').select2(); + sub_form.bootstrapMaterialDesign(); + } + ) + ; + }); + } + }); \ No newline at end of file diff --git a/web/templates/_includes/forms.html b/web/templates/_includes/forms.html index a74a10e6..49bcfb46 100644 --- a/web/templates/_includes/forms.html +++ b/web/templates/_includes/forms.html @@ -26,7 +26,7 @@ {% endfor %} {% if not hide_submit %} {% if wizard %} -
+