diff --git a/src/datadoc/frontend/callbacks/dataset.py b/src/datadoc/frontend/callbacks/dataset.py index 7fae71cf..bdb656f7 100644 --- a/src/datadoc/frontend/callbacks/dataset.py +++ b/src/datadoc/frontend/callbacks/dataset.py @@ -2,10 +2,12 @@ from __future__ import annotations +import datetime import logging import re from typing import TYPE_CHECKING +import arrow from dash import no_update from pydantic import ValidationError @@ -15,6 +17,7 @@ from datadoc.backend.dapla_dataset_path_info import DaplaDatasetPathInfo from datadoc.constants import CHECK_OBLIGATORY_METADATA_DATASET_MESSAGE from datadoc.constants import MISSING_METADATA_WARNING +from datadoc.frontend.callbacks.utils import VALIDATION_ERROR from datadoc.frontend.callbacks.utils import MetadataInputTypes from datadoc.frontend.callbacks.utils import find_existing_language_string from datadoc.frontend.callbacks.utils import get_dataset_path @@ -40,6 +43,7 @@ from datadoc.frontend.fields.display_dataset import ( OBLIGATORY_DATASET_METADATA_IDENTIFIERS_AND_DISPLAY_NAME, ) +from datadoc.frontend.fields.display_dataset import TIMEZONE_AWARE_METADATA_IDENTIFIERS from datadoc.frontend.fields.display_dataset import DatasetIdentifiers from datadoc.frontend.text import INVALID_DATE_ORDER from datadoc.frontend.text import INVALID_VALUE @@ -168,6 +172,14 @@ def process_special_cases( ) elif metadata_identifier in DROPDOWN_DATASET_METADATA_IDENTIFIERS and value == "": updated_value = None + elif metadata_identifier in TIMEZONE_AWARE_METADATA_IDENTIFIERS and isinstance( + value, + (str, datetime.date, datetime.datetime), + ): + try: + updated_value = arrow.get(value).astimezone(tz=datetime.UTC) + except arrow.parser.ParserError as e: + raise ValueError(VALIDATION_ERROR + str(e)) from e else: updated_value = value diff --git a/src/datadoc/frontend/fields/display_base.py b/src/datadoc/frontend/fields/display_base.py index 48f7b5b5..9c290196 100644 --- a/src/datadoc/frontend/fields/display_base.py +++ b/src/datadoc/frontend/fields/display_base.py @@ -200,6 +200,29 @@ def render( ) +@dataclass +class MetadataDateField(DisplayMetadata): + """Controls how fields which define a single date are displayed.""" + + def render( + self, + component_id: dict, + metadata: BaseModel, + ) -> ssb.Input: + """Build Input date component.""" + return ssb.Input( + label=self.display_name, + id=component_id, + debounce=False, + type="date", + disabled=not self.editable, + showDescription=True, + description=self.description, + value=get_metadata_and_stringify(metadata, self.identifier), + className="input-component", + ) + + @dataclass class MetadataPeriodField(DisplayMetadata): """Controls how fields which define a time period are displayed. @@ -342,6 +365,7 @@ def render( FieldTypes = ( MetadataInputField | MetadataDropdownField + | MetadataDateField | MetadataCheckboxField | MetadataPeriodField | MetadataMultiLanguageField diff --git a/src/datadoc/frontend/fields/display_dataset.py b/src/datadoc/frontend/fields/display_dataset.py index dd70c475..7acf4705 100644 --- a/src/datadoc/frontend/fields/display_dataset.py +++ b/src/datadoc/frontend/fields/display_dataset.py @@ -14,6 +14,7 @@ from datadoc.frontend.fields.display_base import DROPDOWN_DESELECT_OPTION from datadoc.frontend.fields.display_base import FieldTypes from datadoc.frontend.fields.display_base import MetadataCheckboxField +from datadoc.frontend.fields.display_base import MetadataDateField from datadoc.frontend.fields.display_base import MetadataDropdownField from datadoc.frontend.fields.display_base import MetadataInputField from datadoc.frontend.fields.display_base import MetadataMultiLanguageField @@ -83,6 +84,9 @@ class DatasetIdentifiers(str, Enum): SUBJECT_FIELD = "subject_field" KEYWORD = "keyword" SPATIAL_COVERAGE_DESCRIPTION = "spatial_coverage_description" + CONTAINS_PERSONAL_DATA = "contains_personal_data" + USE_RESTRICTION = "use_restriction" + USE_RESTRICTION_DATE = "use_restriction_date" ID = "id" OWNER = "owner" FILE_PATH = "file_path" @@ -92,9 +96,6 @@ class DatasetIdentifiers(str, Enum): METADATA_LAST_UPDATED_BY = "metadata_last_updated_by" CONTAINS_DATA_FROM = "contains_data_from" CONTAINS_DATA_UNTIL = "contains_data_until" - USE_RESTRICTION = "use_restriction" - USE_RESTRICTION_DATE = "use_restriction_date" - CONTAINS_PERSONAL_DATA = "contains_personal_data" DISPLAY_DATASET: dict[ @@ -292,7 +293,7 @@ class DatasetIdentifiers(str, Enum): enums.UseRestriction, ), ), - DatasetIdentifiers.USE_RESTRICTION_DATE: MetadataPeriodField( + DatasetIdentifiers.USE_RESTRICTION_DATE: MetadataDateField( identifier=DatasetIdentifiers.USE_RESTRICTION_DATE.value, display_name="Bruksrestriksjonsdato", description='Oppgi ev. "tiltaksdato" for bruksrestriksjoner, f.eks. frist for sletting/anonymisering. Noen bruksrestriksjoner vil ikke ha en slik dato, f.eks. vil en behandlingsbegrensning normalt være permanent/tidsuavhengig.', @@ -336,6 +337,10 @@ class DatasetIdentifiers(str, Enum): m.identifier for m in DROPDOWN_DATASET_METADATA ] +TIMEZONE_AWARE_METADATA_IDENTIFIERS = [ + m.identifier for m in DISPLAYED_DATASET_METADATA if isinstance(m, MetadataDateField) +] + OBLIGATORY_DATASET_METADATA_IDENTIFIERS: list[str] = [ m.identifier for m in DISPLAY_DATASET.values() if m.obligatory and m.editable ] diff --git a/tests/frontend/callbacks/test_dataset_callbacks.py b/tests/frontend/callbacks/test_dataset_callbacks.py index c6ac8c62..6999d0b3 100644 --- a/tests/frontend/callbacks/test_dataset_callbacks.py +++ b/tests/frontend/callbacks/test_dataset_callbacks.py @@ -100,7 +100,7 @@ def file_path_without_dates(): "Census", ), ( - DatasetIdentifiers.DESCRIPTION, + DatasetIdentifiers.POPULATION_DESCRIPTION, "Population description", enums.LanguageStringType( [ @@ -124,6 +124,11 @@ def file_path_without_dates(): ], ), ), + ( + DatasetIdentifiers.UNIT_TYPE, + "17", + "17", + ), ( DatasetIdentifiers.TEMPORALITY_TYPE, enums.TemporalityTypeType.ACCUMULATED, @@ -159,6 +164,25 @@ def file_path_without_dates(): ], ), ), + (DatasetIdentifiers.CONTAINS_PERSONAL_DATA, True, True), + ( + DatasetIdentifiers.USE_RESTRICTION, + enums.UseRestriction.PROCESS_LIMITATIONS, + "PROCESS_LIMITATIONS", + ), + ( + DatasetIdentifiers.USE_RESTRICTION_DATE, + "2024-12-31T23:59:59Z", + datetime.datetime( + 2024, + 12, + 31, + 23, + 59, + 59, + tzinfo=datetime.UTC, + ), + ), ( DatasetIdentifiers.ID, "2f72477a-f051-43ee-bf8b-0d8f47b5e0a7", diff --git a/tests/frontend/components/test_build_dataset_edit_section.py b/tests/frontend/components/test_build_dataset_edit_section.py index 7ce4cd47..69d2fe0b 100644 --- a/tests/frontend/components/test_build_dataset_edit_section.py +++ b/tests/frontend/components/test_build_dataset_edit_section.py @@ -248,7 +248,7 @@ def test_build_dataset_edit_section_dropdown_component_props( if isinstance(m, MetadataInputField) and m.type == "url" ] -DATASET_DATE_FIELD_LIST: list[FieldTypes] = [ +DATASET_PERIOD_FIELD_LIST: list[FieldTypes] = [ m for m in DISPLAY_DATASET.values() if isinstance(m, MetadataPeriodField) ] @@ -256,9 +256,12 @@ def test_build_dataset_edit_section_dropdown_component_props( m for m in DISPLAY_DATASET.values() if isinstance(m, MetadataDropdownField) - and m.identifier != DatasetIdentifiers.UNIT_TYPE.value - and m.identifier != DatasetIdentifiers.SUBJECT_FIELD.value - and m.identifier != DatasetIdentifiers.OWNER.value + and m.identifier + not in ( + DatasetIdentifiers.UNIT_TYPE.value, + DatasetIdentifiers.SUBJECT_FIELD.value, + DatasetIdentifiers.OWNER.value, + ) ] @@ -279,11 +282,11 @@ def test_build_dataset_edit_section_dropdown_component_props( ( ( "title", - DATASET_DATE_FIELD_LIST, + DATASET_PERIOD_FIELD_LIST, model.Dataset(short_name="date_dataset"), {"type": "dataset-edit-section", "id": "title-nb"}, ), - 3, + 2, ssb.Input, ), (