Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Temporal coverage with only start date #3192

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Current (in progress)

- Allow temporal coverage with only a start date [#3192](https://github.com/opendatateam/udata/pull/3192)
- Fix the `parse-url` command [#3225](https://github.com/opendatateam/udata/pull/3225)

## 10.0.5 (2024-12-09)
Expand Down
5 changes: 2 additions & 3 deletions js/components/form/daterange-picker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
<input type="text" class="form-control daterange-picker-end"
v-el:end-input :placeholder="_('End')"
@focus="onFocus" @input="onChange | debounce 500"
:required="required"
:value="endValue|dt dateFormat ''"
:readonly="readonly">
<span class="input-group-btn">
Expand Down Expand Up @@ -131,11 +130,11 @@ export default {
$(this.$els.endHidden).rules('add', {
dateGreaterThan: this.$els.startHidden.id,
required: (el) => {
return (this.startValue && !this.endValue) || (this.endValue && !this.startValue);
return !!(this.endValue && !this.startValue);
},
messages: {
dateGreaterThan: this._('End date should be after start date'),
required: this._('Both dates are required')
required: this._('At least a start date is required')
}
});
},
Expand Down
2 changes: 1 addition & 1 deletion js/locales/udata.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"Are you sure you want to delete this comment?": "Are you sure you want to delete this comment?",
"Are you sure?": "Are you sure?",
"As administrator you can choose any organization to publish": "As administrator you can choose any organization to publish",
"At least a start date is required": "At least a start date is required",
"Attention": "Attention",
"Automatic archiving": "Automatic archiving",
"Availability": "Availability",
Expand All @@ -47,7 +48,6 @@
"Between": "Between",
"Body Type": "Body Type",
"Bold": "Bold",
"Both dates are required": "Both dates are required",
"Business id": "Business id",
"Cancel": "Cancel",
"Center the full picture": "Center the full picture",
Expand Down
2 changes: 1 addition & 1 deletion udata/core/dataset/api_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@
"TemporalCoverage",
{
"start": fields.ISODateTime(description="The temporal coverage start date", required=True),
"end": fields.ISODateTime(description="The temporal coverage end date", required=True),
"end": fields.ISODateTime(description="The temporal coverage end date"),
},
)

Expand Down
15 changes: 10 additions & 5 deletions udata/core/dataset/rdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ def temporal_to_rdf(daterange, graph=None):
pot = graph.resource(BNode())
pot.set(RDF.type, DCT.PeriodOfTime)
pot.set(DCAT.startDate, Literal(daterange.start))
pot.set(DCAT.endDate, Literal(daterange.end))
if daterange.end:
pot.set(DCAT.endDate, Literal(daterange.end))
return pot


Expand Down Expand Up @@ -370,18 +371,22 @@ def temporal_from_resource(resource):
g = Graph().parse(str(resource.identifier))
resource = g.resource(resource.identifier)
if resource.value(SCHEMA.startDate):
end = resource.value(SCHEMA.endDate)
return db.DateRange(
start=resource.value(SCHEMA.startDate).toPython(),
end=resource.value(SCHEMA.endDate).toPython(),
end=end.toPython() if end else None,
)
elif resource.value(DCAT.startDate):
end = resource.value(DCAT.endDate)
return db.DateRange(
start=resource.value(DCAT.startDate).toPython(),
end=resource.value(DCAT.endDate).toPython(),
end=end.toPython() if end else None,
)
elif resource.value(SCV.min):
end = resource.value(SCV.max)
return db.DateRange(
start=resource.value(SCV.min).toPython(), end=resource.value(SCV.max).toPython()
start=resource.value(SCV.min).toPython(),
end=end.toPython() if end else None,
)


Expand Down Expand Up @@ -717,7 +722,7 @@ def dataset_from_rdf(graph: Graph, dataset=None, node=None, remote_url_prefix: s

temporal_coverage = temporal_from_rdf(d.value(DCT.temporal))
if temporal_coverage:
dataset.temporal_coverage = temporal_from_rdf(d.value(DCT.temporal))
dataset.temporal_coverage = temporal_coverage

provenances = provenances_from_rdf(d)
if provenances:
Expand Down
12 changes: 9 additions & 3 deletions udata/forms/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,14 +694,20 @@ def process_formdata(self, valuelist):
value = valuelist[0]
if isinstance(value, str):
start, end = value.split(" - ")
if end is not None:
end = parse(end, yearfirst=True).date()
self.data = db.DateRange(
start=parse(start, yearfirst=True).date(),
end=parse(end, yearfirst=True).date(),
end=end,
)
elif "start" in value and "end" in value:
elif "start" in value:
if value.get("end", None):
end = parse(value["end"], yearfirst=True).date()
else:
end = None
self.data = db.DateRange(
start=parse(value["start"], yearfirst=True).date(),
end=parse(value["end"], yearfirst=True).date(),
end=end,
)
else:
raise validators.ValidationError(_("Unable to parse date range"))
Expand Down
21 changes: 21 additions & 0 deletions udata/tests/api/test_datasets_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,27 @@ def test_dataset_api_update_deleted(self):
self.assertEqual(Dataset.objects.count(), 1)
self.assertEqual(Dataset.objects.first().description, dataset.description)

def test_update_temporal_coverage(self):
user = self.login()
dataset = DatasetFactory(owner=user)
data = dataset.to_dict()
data["temporal_coverage"] = {
"start": "2024-01-01",
"end": "2024-01-31",
}
response = self.put(url_for("api.dataset", dataset=dataset), data)
self.assert200(response)
dataset.reload()
self.assertEqual("2024-01-01", str(dataset.temporal_coverage.start))
self.assertEqual("2024-01-31", str(dataset.temporal_coverage.end))
data = dataset.to_dict()
data["temporal_coverage"] = {"start": "2024-01-01", "end": None}
response = self.put(url_for("api.dataset", dataset=dataset), data)
self.assert200(response)
dataset.reload()
self.assertEqual("2024-01-01", str(dataset.temporal_coverage.start))
self.assertIsNone(dataset.temporal_coverage.end)

def test_dataset_api_update_contact_point(self):
"""It should update a dataset from the API"""
self.login()
Expand Down
27 changes: 27 additions & 0 deletions udata/tests/dataset/test_dataset_rdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,19 @@ def test_temporal_coverage(self):
assert pot.value(DCAT.startDate).toPython() == start
assert pot.value(DCAT.endDate).toPython() == end

def test_temporal_coverage_only_start(self):
start = faker.past_date(start_date="-30d")
temporal_coverage = db.DateRange(start=start)
dataset = DatasetFactory(temporal_coverage=temporal_coverage)

d = dataset_to_rdf(dataset)

pot = d.value(DCT.temporal)

assert pot.value(RDF.type).identifier == DCT.PeriodOfTime
assert pot.value(DCAT.startDate).toPython() == start
assert pot.value(DCAT.endDate) is None

def test_from_external_repository(self):
dataset = DatasetFactory(
harvest=HarvestDatasetMetadata(
Expand Down Expand Up @@ -776,6 +789,20 @@ def test_parse_temporal_as_schema_format(self):
assert daterange.start == start
assert daterange.end == end

def test_parse_temporal_as_schema_format_only_start_date(self):
node = BNode()
g = Graph()
start = faker.past_date(start_date="-30d")

g.set((node, RDF.type, DCT.PeriodOfTime))
g.set((node, SCHEMA.startDate, Literal(start)))

daterange = temporal_from_rdf(g.resource(node))

assert isinstance(daterange, db.DateRange)
assert daterange.start == start
assert daterange.end is None

def test_parse_temporal_as_iso_interval(self):
start = faker.past_date(start_date="-30d")
end = faker.future_date(end_date="+30d")
Expand Down
8 changes: 5 additions & 3 deletions udata/translations/udata.pot
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ msgstr ""
msgid "Creation date"
msgstr ""


#: udata/core/dataservices/models.py:180 udata/core/dataset/models.py:572
#: udata/mongo/datetime_fields.py:66
msgid "Last modification date"
Expand Down Expand Up @@ -573,6 +574,7 @@ msgid ""
"Allowed versions: {values}"
msgstr ""


#: udata/core/dataset/models.py:463 udata/core/dataset/rdf.py:546
#: udata/tests/dataset/test_dataset_rdf.py:584
#: udata/tests/dataset/test_dataset_rdf.py:597
Expand Down Expand Up @@ -947,7 +949,7 @@ msgstr ""
msgid "reuse"
msgstr ""

#: udata/core/reuse/tasks.py:45
#: udata/core/reuse/tasks.py:46
msgid "New reuse"
msgstr ""

Expand Down Expand Up @@ -1158,11 +1160,11 @@ msgstr ""
msgid "Unknown identifiers: {identifiers}"
msgstr ""

#: udata/forms/fields.py:707
#: udata/forms/fields.py:713
msgid "Unable to parse date range"
msgstr ""

#: udata/forms/fields.py:733 udata/forms/fields.py:754
#: udata/forms/fields.py:739 udata/forms/fields.py:760
msgid "You must be authenticated"
msgstr ""

Expand Down