udata 10.1.4.dev34377__py2.py3-none-any.whl → 10.1.4.dev34401__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of udata might be problematic. Click here for more details.
- udata/core/dataset/apiv2.py +26 -4
- udata/core/dataset/models.py +38 -3
- udata/routing.py +13 -2
- udata/settings.py +4 -0
- udata/static/chunks/{11.b6f741fcc366abfad9c4.js → 11.51d706fb9521c16976bc.js} +3 -3
- udata/static/chunks/{11.b6f741fcc366abfad9c4.js.map → 11.51d706fb9521c16976bc.js.map} +1 -1
- udata/static/chunks/{13.2d06442dd9a05d9777b5.js → 13.39e106d56f794ebd06a0.js} +2 -2
- udata/static/chunks/{13.2d06442dd9a05d9777b5.js.map → 13.39e106d56f794ebd06a0.js.map} +1 -1
- udata/static/chunks/{17.e8e4caaad5cb0cc0bacc.js → 17.70cbb4a91b002338007e.js} +2 -2
- udata/static/chunks/{17.e8e4caaad5cb0cc0bacc.js.map → 17.70cbb4a91b002338007e.js.map} +1 -1
- udata/static/chunks/{19.f03a102365af4315f9db.js → 19.a348a5fff8fe2801e52a.js} +3 -3
- udata/static/chunks/{19.f03a102365af4315f9db.js.map → 19.a348a5fff8fe2801e52a.js.map} +1 -1
- udata/static/chunks/{5.0fa1408dae4e76b87b2e.js → 5.343ca020a2d38cec1a14.js} +3 -3
- udata/static/chunks/{5.0fa1408dae4e76b87b2e.js.map → 5.343ca020a2d38cec1a14.js.map} +1 -1
- udata/static/chunks/{6.d663709d877baa44a71e.js → 6.a3b07de9dd2ca2d24e85.js} +3 -3
- udata/static/chunks/{6.d663709d877baa44a71e.js.map → 6.a3b07de9dd2ca2d24e85.js.map} +1 -1
- udata/static/chunks/{8.778091d55cd8ea39af6b.js → 8.462bb3029de008497675.js} +2 -2
- udata/static/chunks/{8.778091d55cd8ea39af6b.js.map → 8.462bb3029de008497675.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/tests/apiv2/test_datasets.py +21 -0
- {udata-10.1.4.dev34377.dist-info → udata-10.1.4.dev34401.dist-info}/METADATA +2 -2
- {udata-10.1.4.dev34377.dist-info → udata-10.1.4.dev34401.dist-info}/RECORD +27 -27
- {udata-10.1.4.dev34377.dist-info → udata-10.1.4.dev34401.dist-info}/LICENSE +0 -0
- {udata-10.1.4.dev34377.dist-info → udata-10.1.4.dev34401.dist-info}/WHEEL +0 -0
- {udata-10.1.4.dev34377.dist-info → udata-10.1.4.dev34401.dist-info}/entry_points.txt +0 -0
- {udata-10.1.4.dev34377.dist-info → udata-10.1.4.dev34401.dist-info}/top_level.txt +0 -0
udata/core/dataset/apiv2.py
CHANGED
|
@@ -2,6 +2,7 @@ import logging
|
|
|
2
2
|
|
|
3
3
|
import mongoengine
|
|
4
4
|
from flask import abort, request, url_for
|
|
5
|
+
from flask_login import current_user
|
|
5
6
|
from flask_restx import marshal
|
|
6
7
|
|
|
7
8
|
from udata import search
|
|
@@ -11,7 +12,7 @@ from udata.core.organization.api_fields import member_user_with_email_fields
|
|
|
11
12
|
from udata.core.spatial.api_fields import geojson
|
|
12
13
|
from udata.utils import get_by
|
|
13
14
|
|
|
14
|
-
from .api import ResourceMixin
|
|
15
|
+
from .api import DEFAULT_SORTING, DatasetApiParser, ResourceMixin
|
|
15
16
|
from .api_fields import (
|
|
16
17
|
badge_fields,
|
|
17
18
|
catalog_schema_fields,
|
|
@@ -128,7 +129,7 @@ dataset_fields = apiv2.model(
|
|
|
128
129
|
_external=True,
|
|
129
130
|
),
|
|
130
131
|
"type": "GET",
|
|
131
|
-
"total":
|
|
132
|
+
"total": o.resources_len, # :ResourcesLengthProperty may call MongoDB to fetch the length if resources were not fetched
|
|
132
133
|
},
|
|
133
134
|
description="Link to the dataset resources",
|
|
134
135
|
),
|
|
@@ -155,7 +156,7 @@ dataset_fields = apiv2.model(
|
|
|
155
156
|
),
|
|
156
157
|
"frequency_date": fields.ISODateTime(
|
|
157
158
|
description=(
|
|
158
|
-
"Next expected update date, you will be notified
|
|
159
|
+
"Next expected update date, you will be notified once that date is reached."
|
|
159
160
|
)
|
|
160
161
|
),
|
|
161
162
|
"harvest": fields.Nested(
|
|
@@ -276,7 +277,28 @@ class DatasetSearchAPI(API):
|
|
|
276
277
|
abort(500, "Internal search service error")
|
|
277
278
|
|
|
278
279
|
|
|
279
|
-
|
|
280
|
+
dataset_parser = DatasetApiParser()
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@ns.route("/", endpoint="datasets")
|
|
284
|
+
class DatasetListAPI(API):
|
|
285
|
+
"""Datasets collection endpoint"""
|
|
286
|
+
|
|
287
|
+
@apiv2.doc("list_datasets")
|
|
288
|
+
@apiv2.expect(dataset_parser.parser)
|
|
289
|
+
@apiv2.marshal_with(dataset_page_fields)
|
|
290
|
+
def get(self):
|
|
291
|
+
"""List or search all datasets"""
|
|
292
|
+
args = dataset_parser.parse()
|
|
293
|
+
datasets = Dataset.objects.exclude("resources").visible_by_user(
|
|
294
|
+
current_user, mongoengine.Q(private__ne=True, archived=None, deleted=None)
|
|
295
|
+
)
|
|
296
|
+
datasets = dataset_parser.parse_filters(datasets, args)
|
|
297
|
+
sort = args["sort"] or ("$text_score" if args["q"] else None) or DEFAULT_SORTING
|
|
298
|
+
return datasets.order_by(sort).paginate(args["page"], args["page_size"])
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@ns.route("/<dataset_without_resources:dataset>/", endpoint="dataset", doc=common_doc)
|
|
280
302
|
@apiv2.response(404, "Dataset not found")
|
|
281
303
|
@apiv2.response(410, "Dataset has been deleted")
|
|
282
304
|
class DatasetAPI(API):
|
udata/core/dataset/models.py
CHANGED
|
@@ -2,6 +2,7 @@ import logging
|
|
|
2
2
|
import re
|
|
3
3
|
from datetime import datetime, timedelta
|
|
4
4
|
from pydoc import locate
|
|
5
|
+
from typing import Self
|
|
5
6
|
from urllib.parse import urlparse
|
|
6
7
|
|
|
7
8
|
import requests
|
|
@@ -11,7 +12,7 @@ from flask import current_app
|
|
|
11
12
|
from mongoengine import DynamicEmbeddedDocument
|
|
12
13
|
from mongoengine import ValidationError as MongoEngineValidationError
|
|
13
14
|
from mongoengine.fields import DateTimeField
|
|
14
|
-
from mongoengine.signals import post_save, pre_save
|
|
15
|
+
from mongoengine.signals import post_save, pre_init, pre_save
|
|
15
16
|
from stringdist import rdlevenshtein
|
|
16
17
|
from werkzeug.utils import cached_property
|
|
17
18
|
|
|
@@ -617,6 +618,31 @@ class Dataset(WithMetrics, DatasetBadgeMixin, Owned, db.Document):
|
|
|
617
618
|
|
|
618
619
|
verbose_name = _("dataset")
|
|
619
620
|
|
|
621
|
+
missing_resources = False
|
|
622
|
+
|
|
623
|
+
@cached_property
|
|
624
|
+
def resources_len(self):
|
|
625
|
+
# :ResourcesLengthProperty
|
|
626
|
+
# If we've excluded the resources from the Mongo request we need
|
|
627
|
+
# to do a new Mongo request to fetch the resources length manually.
|
|
628
|
+
# If the resources are already present, just return the `len()` of the array.
|
|
629
|
+
if not self.missing_resources:
|
|
630
|
+
return len(self.resources)
|
|
631
|
+
|
|
632
|
+
pipeline = [{"$project": {"_id": 1, "resources_len": {"$size": "$resources"}}}]
|
|
633
|
+
data = Dataset.objects(id=self.id).aggregate(pipeline)
|
|
634
|
+
|
|
635
|
+
return next(data)["resources_len"]
|
|
636
|
+
|
|
637
|
+
@classmethod
|
|
638
|
+
def pre_init(cls, sender, document: Self, values, **kwargs):
|
|
639
|
+
# MongoEngine loses the information about raw values during the __init__ function
|
|
640
|
+
# Here we catch the raw values from the database (or from the creation of the object)
|
|
641
|
+
# and we check if resources were returned (sometimes we exclude `resources` from the
|
|
642
|
+
# Mongo request to improve perfs)
|
|
643
|
+
# This is used in :ResourcesLengthProperty
|
|
644
|
+
document.missing_resources = "resources" not in values
|
|
645
|
+
|
|
620
646
|
@classmethod
|
|
621
647
|
def pre_save(cls, sender, document, **kwargs):
|
|
622
648
|
cls.before_save.send(document)
|
|
@@ -941,9 +967,17 @@ class Dataset(WithMetrics, DatasetBadgeMixin, Owned, db.Document):
|
|
|
941
967
|
"url": endpoint_for("datasets.show", "api.dataset", dataset=self, _external=True),
|
|
942
968
|
"name": self.title,
|
|
943
969
|
"keywords": ",".join(self.tags),
|
|
944
|
-
"distribution": [
|
|
970
|
+
"distribution": [
|
|
971
|
+
resource.json_ld
|
|
972
|
+
for resource in self.resources[: current_app.config["MAX_RESOURCES_IN_JSON_LD"]]
|
|
973
|
+
],
|
|
945
974
|
# Theses values are not standard
|
|
946
|
-
"contributedDistribution": [
|
|
975
|
+
"contributedDistribution": [
|
|
976
|
+
resource.json_ld
|
|
977
|
+
for resource in self.community_resources[
|
|
978
|
+
: current_app.config["MAX_RESOURCES_IN_JSON_LD"]
|
|
979
|
+
]
|
|
980
|
+
],
|
|
947
981
|
"extras": [get_json_ld_extra(*item) for item in self.extras.items()],
|
|
948
982
|
}
|
|
949
983
|
|
|
@@ -995,6 +1029,7 @@ class Dataset(WithMetrics, DatasetBadgeMixin, Owned, db.Document):
|
|
|
995
1029
|
self.save()
|
|
996
1030
|
|
|
997
1031
|
|
|
1032
|
+
pre_init.connect(Dataset.pre_init, sender=Dataset)
|
|
998
1033
|
pre_save.connect(Dataset.pre_save, sender=Dataset)
|
|
999
1034
|
post_save.connect(Dataset.post_save, sender=Dataset)
|
|
1000
1035
|
|
udata/routing.py
CHANGED
|
@@ -77,6 +77,9 @@ class ModelConverter(BaseConverter):
|
|
|
77
77
|
def has_redirected_slug(self):
|
|
78
78
|
return self.has_slug and self.model.slug.follow
|
|
79
79
|
|
|
80
|
+
def get_excludes(self):
|
|
81
|
+
return []
|
|
82
|
+
|
|
80
83
|
def quote(self, value):
|
|
81
84
|
if self.has_slug:
|
|
82
85
|
return self.model.slug.slugify(value)
|
|
@@ -85,13 +88,13 @@ class ModelConverter(BaseConverter):
|
|
|
85
88
|
|
|
86
89
|
def to_python(self, value):
|
|
87
90
|
try:
|
|
88
|
-
return self.model.objects.get_or_404(id=value)
|
|
91
|
+
return self.model.objects.exclude(*self.get_excludes()).get_or_404(id=value)
|
|
89
92
|
except (NotFound, ValidationError):
|
|
90
93
|
pass
|
|
91
94
|
try:
|
|
92
95
|
quoted = self.quote(value)
|
|
93
96
|
query = db.Q(slug=value) | db.Q(slug=quoted)
|
|
94
|
-
obj = self.model.objects(query).get()
|
|
97
|
+
obj = self.model.objects(query).exclude(*self.get_excludes()).get()
|
|
95
98
|
except (InvalidQueryError, self.model.DoesNotExist):
|
|
96
99
|
# If the model doesn't have a slug or matching slug doesn't exist.
|
|
97
100
|
if self.has_redirected_slug:
|
|
@@ -121,6 +124,13 @@ class DatasetConverter(ModelConverter):
|
|
|
121
124
|
model = models.Dataset
|
|
122
125
|
|
|
123
126
|
|
|
127
|
+
class DatasetWithoutResourcesConverter(ModelConverter):
|
|
128
|
+
model = models.Dataset
|
|
129
|
+
|
|
130
|
+
def get_excludes(self):
|
|
131
|
+
return ["resources"]
|
|
132
|
+
|
|
133
|
+
|
|
124
134
|
class DataserviceConverter(ModelConverter):
|
|
125
135
|
model = Dataservice
|
|
126
136
|
|
|
@@ -226,6 +236,7 @@ def init_app(app):
|
|
|
226
236
|
app.url_map.converters["pathlist"] = PathListConverter
|
|
227
237
|
app.url_map.converters["uuid"] = UUIDConverter
|
|
228
238
|
app.url_map.converters["dataset"] = DatasetConverter
|
|
239
|
+
app.url_map.converters["dataset_without_resources"] = DatasetWithoutResourcesConverter
|
|
229
240
|
app.url_map.converters["dataservice"] = DataserviceConverter
|
|
230
241
|
app.url_map.converters["crid"] = CommunityResourceConverter
|
|
231
242
|
app.url_map.converters["org"] = OrganizationConverter
|
udata/settings.py
CHANGED
|
@@ -570,6 +570,10 @@ class Defaults(object):
|
|
|
570
570
|
###########################################################################
|
|
571
571
|
TABULAR_API_DATASERVICE_ID = None
|
|
572
572
|
|
|
573
|
+
# JSON-LD settings
|
|
574
|
+
###########################################################################
|
|
575
|
+
MAX_RESOURCES_IN_JSON_LD = 20
|
|
576
|
+
|
|
573
577
|
|
|
574
578
|
class Testing(object):
|
|
575
579
|
"""Sane values for testing. Should be applied as override"""
|