udata 10.2.1.dev34761__py2.py3-none-any.whl → 10.2.1.dev34775__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/api/__init__.py +41 -8
- udata/api_fields.py +1 -5
- udata/core/contact_point/api.py +2 -6
- udata/core/dataservices/api.py +3 -10
- udata/core/dataset/api.py +2 -6
- udata/core/dataset/apiv2.py +1 -4
- udata/core/dataset/forms.py +1 -1
- udata/core/organization/apiv2.py +1 -5
- udata/core/reports/api.py +1 -6
- udata/core/reuse/api.py +1 -4
- udata/core/spatial/tests/test_api.py +56 -0
- udata/harvest/actions.py +3 -14
- udata/harvest/api.py +13 -9
- udata/harvest/models.py +5 -0
- udata/harvest/tests/test_actions.py +1 -54
- udata/harvest/tests/test_api.py +33 -1
- udata/mongo/errors.py +9 -2
- udata/static/chunks/{11.b6f741fcc366abfad9c4.js → 11.8a2f7828175824bcd74b.js} +3 -3
- udata/static/chunks/{11.b6f741fcc366abfad9c4.js.map → 11.8a2f7828175824bcd74b.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.df16abde17a42033a7f8.js} +3 -3
- udata/static/chunks/{19.f03a102365af4315f9db.js.map → 19.df16abde17a42033a7f8.js.map} +1 -1
- udata/static/chunks/{5.0fa1408dae4e76b87b2e.js → 5.5660483641193b7f8295.js} +3 -3
- udata/static/chunks/{5.0fa1408dae4e76b87b2e.js.map → 5.5660483641193b7f8295.js.map} +1 -1
- udata/static/chunks/{6.d663709d877baa44a71e.js → 6.30dce49d17db07600b06.js} +3 -3
- udata/static/chunks/{6.d663709d877baa44a71e.js.map → 6.30dce49d17db07600b06.js.map} +1 -1
- udata/static/chunks/{8.778091d55cd8ea39af6b.js → 8.54e44b102164ae5e7a67.js} +2 -2
- udata/static/chunks/{8.778091d55cd8ea39af6b.js.map → 8.54e44b102164ae5e7a67.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/tests/api/test_dataservices_api.py +5 -8
- udata/tests/test_api_fields.py +2 -2
- {udata-10.2.1.dev34761.dist-info → udata-10.2.1.dev34775.dist-info}/METADATA +3 -1
- {udata-10.2.1.dev34761.dist-info → udata-10.2.1.dev34775.dist-info}/RECORD +41 -41
- {udata-10.2.1.dev34761.dist-info → udata-10.2.1.dev34775.dist-info}/LICENSE +0 -0
- {udata-10.2.1.dev34761.dist-info → udata-10.2.1.dev34775.dist-info}/WHEEL +0 -0
- {udata-10.2.1.dev34761.dist-info → udata-10.2.1.dev34775.dist-info}/entry_points.txt +0 -0
- {udata-10.2.1.dev34761.dist-info → udata-10.2.1.dev34775.dist-info}/top_level.txt +0 -0
udata/api/__init__.py
CHANGED
|
@@ -4,6 +4,7 @@ import urllib.parse
|
|
|
4
4
|
from functools import wraps
|
|
5
5
|
from importlib import import_module
|
|
6
6
|
|
|
7
|
+
import mongoengine
|
|
7
8
|
from flask import (
|
|
8
9
|
Blueprint,
|
|
9
10
|
current_app,
|
|
@@ -22,7 +23,6 @@ from udata import entrypoints, tracking
|
|
|
22
23
|
from udata.app import csrf
|
|
23
24
|
from udata.auth import Permission, PermissionDenied, RoleNeed, current_user, login_user
|
|
24
25
|
from udata.i18n import get_locale
|
|
25
|
-
from udata.mongo.errors import FieldValidationError
|
|
26
26
|
from udata.utils import safe_unicode
|
|
27
27
|
|
|
28
28
|
from . import fields
|
|
@@ -258,17 +258,50 @@ def handle_unauthorized_file_type(error):
|
|
|
258
258
|
return {"message": msg}, 400
|
|
259
259
|
|
|
260
260
|
|
|
261
|
-
validation_error_fields = api.model(
|
|
261
|
+
validation_error_fields = api.model(
|
|
262
|
+
"ValidationError",
|
|
263
|
+
{"errors": fields.Raw, "message": fields.String},
|
|
264
|
+
)
|
|
262
265
|
|
|
266
|
+
validation_error_fields_v2 = apiv2.inherit("ValidationError", validation_error_fields)
|
|
263
267
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def handle_validation_error(error: FieldValidationError):
|
|
267
|
-
"""A validation error"""
|
|
268
|
+
|
|
269
|
+
def convert_object_of_exceptions_to_object_of_strings(exceptions: dict):
|
|
268
270
|
errors = {}
|
|
269
|
-
|
|
271
|
+
for key, exception in exceptions.items():
|
|
272
|
+
if isinstance(exception, Exception):
|
|
273
|
+
errors[key] = str(exception)
|
|
274
|
+
elif isinstance(exception, dict):
|
|
275
|
+
errors[key] = convert_object_of_exceptions_to_object_of_strings(exception)
|
|
276
|
+
elif isinstance(exception, str):
|
|
277
|
+
errors[key] = exception
|
|
278
|
+
else:
|
|
279
|
+
log.warning(
|
|
280
|
+
f"Unknown type in `convert_object_of_exceptions_to_object_of_strings`: {exception}"
|
|
281
|
+
)
|
|
282
|
+
errors[key] = str(exception)
|
|
283
|
+
|
|
284
|
+
return errors
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@api.errorhandler(mongoengine.errors.ValidationError)
|
|
288
|
+
@api.marshal_with(validation_error_fields, code=400)
|
|
289
|
+
def handle_validation_error(error: mongoengine.errors.ValidationError):
|
|
290
|
+
"""Error returned when validation failed."""
|
|
291
|
+
return (
|
|
292
|
+
{
|
|
293
|
+
"errors": convert_object_of_exceptions_to_object_of_strings(error.errors),
|
|
294
|
+
"message": str(error),
|
|
295
|
+
},
|
|
296
|
+
400,
|
|
297
|
+
)
|
|
298
|
+
|
|
270
299
|
|
|
271
|
-
|
|
300
|
+
@apiv2.errorhandler(mongoengine.errors.ValidationError)
|
|
301
|
+
@apiv2.marshal_with(validation_error_fields_v2, code=400)
|
|
302
|
+
def handle_validation_error_v2(error: mongoengine.errors.ValidationError):
|
|
303
|
+
"""Error returned when validation failed."""
|
|
304
|
+
return handle_validation_error(error)
|
|
272
305
|
|
|
273
306
|
|
|
274
307
|
class API(Resource): # Avoid name collision as resource is a core model
|
udata/api_fields.py
CHANGED
|
@@ -567,11 +567,7 @@ def is_value_modified(old_value, new_value) -> bool:
|
|
|
567
567
|
|
|
568
568
|
def patch_and_save(obj, request) -> type:
|
|
569
569
|
obj = patch(obj, request)
|
|
570
|
-
|
|
571
|
-
try:
|
|
572
|
-
obj.save()
|
|
573
|
-
except mongoengine.errors.ValidationError as e:
|
|
574
|
-
api.abort(400, e.message)
|
|
570
|
+
obj.save()
|
|
575
571
|
|
|
576
572
|
return obj
|
|
577
573
|
|
udata/core/contact_point/api.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import mongoengine
|
|
2
|
-
|
|
3
1
|
from udata.api import API, api
|
|
4
2
|
from udata.api.parsers import ModelApiParser
|
|
5
3
|
from udata.core.dataset.permissions import OwnablePermission
|
|
@@ -33,10 +31,8 @@ class ContactPointsListAPI(API):
|
|
|
33
31
|
def post(self):
|
|
34
32
|
"""Creates a contact point"""
|
|
35
33
|
form = api.validate(ContactPointForm)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
except mongoengine.errors.ValidationError as e:
|
|
39
|
-
api.abort(400, e.message)
|
|
34
|
+
contact_point = form.save()
|
|
35
|
+
|
|
40
36
|
return contact_point, 201
|
|
41
37
|
|
|
42
38
|
|
udata/core/dataservices/api.py
CHANGED
|
@@ -45,11 +45,7 @@ class DataservicesAPI(API):
|
|
|
45
45
|
if not dataservice.owner and not dataservice.organization:
|
|
46
46
|
dataservice.owner = current_user._get_current_object()
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
dataservice.save()
|
|
50
|
-
except mongoengine.errors.ValidationError as e:
|
|
51
|
-
api.abort(400, e.message)
|
|
52
|
-
|
|
48
|
+
dataservice.save()
|
|
53
49
|
return dataservice, 201
|
|
54
50
|
|
|
55
51
|
|
|
@@ -78,11 +74,8 @@ class DataserviceAPI(API):
|
|
|
78
74
|
patch(dataservice, request)
|
|
79
75
|
dataservice.metadata_modified_at = datetime.utcnow()
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return dataservice
|
|
84
|
-
except mongoengine.errors.ValidationError as e:
|
|
85
|
-
api.abort(400, e.message)
|
|
77
|
+
dataservice.save()
|
|
78
|
+
return dataservice
|
|
86
79
|
|
|
87
80
|
@api.secure
|
|
88
81
|
@api.doc("delete_dataservice")
|
udata/core/dataset/api.py
CHANGED
|
@@ -264,12 +264,8 @@ class DatasetAPI(API):
|
|
|
264
264
|
DatasetEditPermission(dataset).test()
|
|
265
265
|
dataset.last_modified_internal = datetime.utcnow()
|
|
266
266
|
form = api.validate(DatasetForm, dataset)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
try:
|
|
270
|
-
return form.save()
|
|
271
|
-
except mongoengine.errors.ValidationError as e:
|
|
272
|
-
api.abort(400, e.message)
|
|
267
|
+
|
|
268
|
+
return form.save()
|
|
273
269
|
|
|
274
270
|
@api.secure
|
|
275
271
|
@api.doc("delete_dataset")
|
udata/core/dataset/apiv2.py
CHANGED
|
@@ -340,10 +340,7 @@ class DatasetExtrasAPI(API):
|
|
|
340
340
|
data.pop(key)
|
|
341
341
|
# then update the extras with the remaining payload
|
|
342
342
|
dataset.extras.update(data)
|
|
343
|
-
|
|
344
|
-
dataset.save(signal_kwargs={"ignores": ["post_save"]})
|
|
345
|
-
except mongoengine.errors.ValidationError as e:
|
|
346
|
-
apiv2.abort(400, e.message)
|
|
343
|
+
dataset.save(signal_kwargs={"ignores": ["post_save"]})
|
|
347
344
|
return dataset.extras
|
|
348
345
|
|
|
349
346
|
@apiv2.secure
|
udata/core/dataset/forms.py
CHANGED
udata/core/organization/apiv2.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import mongoengine
|
|
2
1
|
from flask import request
|
|
3
2
|
|
|
4
3
|
from udata import search
|
|
@@ -65,10 +64,7 @@ class OrganizationExtrasAPI(API):
|
|
|
65
64
|
|
|
66
65
|
# then update the extras with the remaining payload
|
|
67
66
|
org.extras.update(data)
|
|
68
|
-
|
|
69
|
-
org.save()
|
|
70
|
-
except mongoengine.errors.ValidationError as e:
|
|
71
|
-
apiv2.abort(400, e.message)
|
|
67
|
+
org.save()
|
|
72
68
|
return org.extras
|
|
73
69
|
|
|
74
70
|
@apiv2.secure
|
udata/core/reports/api.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import mongoengine
|
|
2
1
|
from flask import request
|
|
3
2
|
from flask_login import current_user
|
|
4
3
|
|
|
@@ -31,11 +30,7 @@ class ReportsAPI(API):
|
|
|
31
30
|
if current_user.is_authenticated:
|
|
32
31
|
report.by = current_user._get_current_object()
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
report.save()
|
|
36
|
-
except mongoengine.errors.ValidationError as e:
|
|
37
|
-
api.abort(400, e.message)
|
|
38
|
-
|
|
33
|
+
report.save()
|
|
39
34
|
return report, 201
|
|
40
35
|
|
|
41
36
|
|
udata/core/reuse/api.py
CHANGED
|
@@ -125,10 +125,7 @@ class ReuseListAPI(API):
|
|
|
125
125
|
if not reuse.owner and not reuse.organization:
|
|
126
126
|
reuse.owner = current_user._get_current_object()
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
reuse.save()
|
|
130
|
-
except mongoengine.errors.ValidationError as e:
|
|
131
|
-
api.abort(400, e.message)
|
|
128
|
+
reuse.save()
|
|
132
129
|
|
|
133
130
|
return patch_and_save(reuse, request), 201
|
|
134
131
|
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
from flask import url_for
|
|
2
2
|
|
|
3
3
|
from udata.core.dataset.factories import DatasetFactory
|
|
4
|
+
from udata.core.dataset.models import Dataset
|
|
4
5
|
from udata.core.organization.factories import OrganizationFactory
|
|
5
6
|
from udata.core.spatial.factories import (
|
|
6
7
|
GeoLevelFactory,
|
|
7
8
|
GeoZoneFactory,
|
|
8
9
|
SpatialCoverageFactory,
|
|
9
10
|
)
|
|
11
|
+
from udata.core.spatial.models import spatial_granularities
|
|
10
12
|
from udata.core.spatial.tasks import compute_geozones_metrics
|
|
11
13
|
from udata.tests.api import APITestCase
|
|
14
|
+
from udata.tests.api.test_datasets_api import SAMPLE_GEOM
|
|
12
15
|
from udata.tests.features.territories import (
|
|
13
16
|
TerritoriesSettings,
|
|
14
17
|
create_geozones_fixtures,
|
|
@@ -286,3 +289,56 @@ class SpatialTerritoriesApiTest(APITestCase):
|
|
|
286
289
|
self.assert200(response)
|
|
287
290
|
# No dynamic datasets given that they are added by udata-front extension.
|
|
288
291
|
self.assertEqual(len(response.json), 2)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class DatasetsSpatialAPITest(APITestCase):
|
|
295
|
+
modules = []
|
|
296
|
+
|
|
297
|
+
def test_create_spatial_zones(self):
|
|
298
|
+
paca, _, _ = create_geozones_fixtures()
|
|
299
|
+
granularity = spatial_granularities[0][0]
|
|
300
|
+
data = DatasetFactory.as_dict()
|
|
301
|
+
data["spatial"] = {
|
|
302
|
+
"zones": [paca.id],
|
|
303
|
+
"granularity": granularity,
|
|
304
|
+
}
|
|
305
|
+
self.login()
|
|
306
|
+
response = self.post(url_for("api.datasets"), data)
|
|
307
|
+
self.assert201(response)
|
|
308
|
+
self.assertEqual(Dataset.objects.count(), 1)
|
|
309
|
+
dataset = Dataset.objects.first()
|
|
310
|
+
self.assertEqual([str(z) for z in dataset.spatial.zones], [paca.id])
|
|
311
|
+
self.assertEqual(dataset.spatial.geom, None)
|
|
312
|
+
self.assertEqual(dataset.spatial.granularity, granularity)
|
|
313
|
+
|
|
314
|
+
def test_create_spatial_geom(self):
|
|
315
|
+
granularity = spatial_granularities[0][0]
|
|
316
|
+
data = DatasetFactory.as_dict()
|
|
317
|
+
data["spatial"] = {
|
|
318
|
+
"geom": SAMPLE_GEOM,
|
|
319
|
+
"granularity": granularity,
|
|
320
|
+
}
|
|
321
|
+
self.login()
|
|
322
|
+
response = self.post(url_for("api.datasets"), data)
|
|
323
|
+
self.assert201(response)
|
|
324
|
+
self.assertEqual(Dataset.objects.count(), 1)
|
|
325
|
+
dataset = Dataset.objects.first()
|
|
326
|
+
self.assertEqual(dataset.spatial.zones, [])
|
|
327
|
+
self.assertEqual(dataset.spatial.geom, SAMPLE_GEOM)
|
|
328
|
+
self.assertEqual(dataset.spatial.granularity, granularity)
|
|
329
|
+
|
|
330
|
+
def test_cannot_create_both_geom_and_zones(self):
|
|
331
|
+
paca, _, _ = create_geozones_fixtures()
|
|
332
|
+
|
|
333
|
+
granularity = spatial_granularities[0][0]
|
|
334
|
+
data = DatasetFactory.as_dict()
|
|
335
|
+
data["spatial"] = {
|
|
336
|
+
"zones": [paca.id],
|
|
337
|
+
"geom": SAMPLE_GEOM,
|
|
338
|
+
"granularity": granularity,
|
|
339
|
+
}
|
|
340
|
+
self.login()
|
|
341
|
+
|
|
342
|
+
response = self.post(url_for("api.datasets"), data)
|
|
343
|
+
self.assert400(response)
|
|
344
|
+
self.assertEqual(Dataset.objects.count(), 0)
|
udata/harvest/actions.py
CHANGED
|
@@ -34,25 +34,14 @@ def list_backends():
|
|
|
34
34
|
return backends.get_all(current_app).values()
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def
|
|
37
|
+
def list_sources(owner=None, deleted=False):
|
|
38
|
+
"""List all harvest sources"""
|
|
38
39
|
sources = HarvestSource.objects
|
|
39
40
|
if not deleted:
|
|
40
41
|
sources = sources.visible()
|
|
41
42
|
if owner:
|
|
42
43
|
sources = sources.owned_by(owner)
|
|
43
|
-
return sources
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def list_sources(owner=None, deleted=False):
|
|
47
|
-
"""List all harvest sources"""
|
|
48
|
-
return list(_sources_queryset(owner=owner, deleted=deleted))
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def paginate_sources(owner=None, page=1, page_size=DEFAULT_PAGE_SIZE, deleted=False):
|
|
52
|
-
"""Paginate harvest sources"""
|
|
53
|
-
sources = _sources_queryset(owner=owner, deleted=deleted)
|
|
54
|
-
page = max(page or 1, 1)
|
|
55
|
-
return sources.paginate(page, page_size)
|
|
44
|
+
return list(sources)
|
|
56
45
|
|
|
57
46
|
|
|
58
47
|
def get_source(ident):
|
udata/harvest/api.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from bson import ObjectId
|
|
2
1
|
from flask import current_app, request
|
|
3
2
|
from werkzeug.exceptions import BadRequest
|
|
4
3
|
|
|
@@ -253,6 +252,7 @@ source_parser.add_argument(
|
|
|
253
252
|
source_parser.add_argument(
|
|
254
253
|
"deleted", type=bool, location="args", default=False, help="Include sources flaggued as deleted"
|
|
255
254
|
)
|
|
255
|
+
source_parser.add_argument("q", type=str, location="args", help="The search query")
|
|
256
256
|
|
|
257
257
|
|
|
258
258
|
@ns.route("/sources/", endpoint="harvest_sources")
|
|
@@ -264,15 +264,19 @@ class SourcesAPI(API):
|
|
|
264
264
|
"""List all harvest sources"""
|
|
265
265
|
args = source_parser.parse_args()
|
|
266
266
|
|
|
267
|
-
|
|
268
|
-
api.abort(400, "`owner` arg must be an identifier")
|
|
267
|
+
sources = HarvestSource.objects()
|
|
269
268
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
269
|
+
if not args["deleted"]:
|
|
270
|
+
sources = sources.visible()
|
|
271
|
+
|
|
272
|
+
if args["owner"]:
|
|
273
|
+
sources = sources.owned_by(args["owner"])
|
|
274
|
+
|
|
275
|
+
if args["q"]:
|
|
276
|
+
phrase_query = " ".join([f'"{elem}"' for elem in args["q"].split(" ")])
|
|
277
|
+
sources = sources.search_text(phrase_query)
|
|
278
|
+
|
|
279
|
+
return sources.paginate(args["page"], args["page_size"])
|
|
276
280
|
|
|
277
281
|
@api.secure
|
|
278
282
|
@api.doc("create_harvest_source")
|
udata/harvest/models.py
CHANGED
|
@@ -155,6 +155,11 @@ class HarvestSource(Owned, db.Document):
|
|
|
155
155
|
|
|
156
156
|
meta = {
|
|
157
157
|
"indexes": [
|
|
158
|
+
{
|
|
159
|
+
"fields": ["$name", "$url"],
|
|
160
|
+
"default_language": "french",
|
|
161
|
+
"weights": {"name": 10, "url": 5},
|
|
162
|
+
},
|
|
158
163
|
"-created_at",
|
|
159
164
|
"slug",
|
|
160
165
|
("deleted", "-created_at"),
|
|
@@ -14,7 +14,7 @@ from udata.core.organization.factories import OrganizationFactory
|
|
|
14
14
|
from udata.core.user.factories import UserFactory
|
|
15
15
|
from udata.models import Dataset, PeriodicTask
|
|
16
16
|
from udata.tests.helpers import assert_emit, assert_equal_dates
|
|
17
|
-
from udata.utils import
|
|
17
|
+
from udata.utils import faker
|
|
18
18
|
|
|
19
19
|
from .. import actions, signals
|
|
20
20
|
from ..backends import BaseBackend
|
|
@@ -113,59 +113,6 @@ class HarvestActionsTest:
|
|
|
113
113
|
for source in sources:
|
|
114
114
|
assert source in result
|
|
115
115
|
|
|
116
|
-
def test_paginate_sources(self):
|
|
117
|
-
result = actions.paginate_sources()
|
|
118
|
-
assert isinstance(result, Paginable)
|
|
119
|
-
assert result.page == 1
|
|
120
|
-
assert result.page_size == actions.DEFAULT_PAGE_SIZE
|
|
121
|
-
assert result.total == 0
|
|
122
|
-
assert len(result.objects) == 0
|
|
123
|
-
|
|
124
|
-
HarvestSourceFactory.create_batch(3)
|
|
125
|
-
|
|
126
|
-
result = actions.paginate_sources(page_size=2)
|
|
127
|
-
assert isinstance(result, Paginable)
|
|
128
|
-
assert result.page == 1
|
|
129
|
-
assert result.page_size == 2
|
|
130
|
-
assert result.total == 3
|
|
131
|
-
assert len(result.objects) == 2
|
|
132
|
-
|
|
133
|
-
result = actions.paginate_sources(page=2, page_size=2)
|
|
134
|
-
assert isinstance(result, Paginable)
|
|
135
|
-
assert result.page == 2
|
|
136
|
-
assert result.page_size == 2
|
|
137
|
-
assert result.total == 3
|
|
138
|
-
assert len(result.objects) == 1
|
|
139
|
-
|
|
140
|
-
def test_paginate_sources_exclude_deleted(self):
|
|
141
|
-
HarvestSourceFactory.create_batch(2)
|
|
142
|
-
HarvestSourceFactory(deleted=datetime.utcnow())
|
|
143
|
-
|
|
144
|
-
result = actions.paginate_sources(page_size=2)
|
|
145
|
-
assert isinstance(result, Paginable)
|
|
146
|
-
assert result.page == 1
|
|
147
|
-
assert result.page_size == 2
|
|
148
|
-
assert result.total == 2
|
|
149
|
-
assert len(result.objects) == 2
|
|
150
|
-
|
|
151
|
-
def test_paginate_sources_include_deleted(self):
|
|
152
|
-
HarvestSourceFactory.create_batch(2)
|
|
153
|
-
HarvestSourceFactory(deleted=datetime.utcnow())
|
|
154
|
-
|
|
155
|
-
result = actions.paginate_sources(page_size=2, deleted=True)
|
|
156
|
-
assert isinstance(result, Paginable)
|
|
157
|
-
assert result.page == 1
|
|
158
|
-
assert result.page_size == 2
|
|
159
|
-
assert result.total == 3
|
|
160
|
-
assert len(result.objects) == 2
|
|
161
|
-
|
|
162
|
-
result = actions.paginate_sources(page=2, page_size=2, deleted=True)
|
|
163
|
-
assert isinstance(result, Paginable)
|
|
164
|
-
assert result.page == 2
|
|
165
|
-
assert result.page_size == 2
|
|
166
|
-
assert result.total == 3
|
|
167
|
-
assert len(result.objects) == 1
|
|
168
|
-
|
|
169
116
|
def test_create_source(self):
|
|
170
117
|
source_url = faker.url()
|
|
171
118
|
|
udata/harvest/tests/test_api.py
CHANGED
|
@@ -8,7 +8,7 @@ from pytest_mock import MockerFixture
|
|
|
8
8
|
from udata.core.organization.factories import OrganizationFactory
|
|
9
9
|
from udata.core.user.factories import AdminFactory, UserFactory
|
|
10
10
|
from udata.models import Member, PeriodicTask
|
|
11
|
-
from udata.tests.helpers import assert200, assert201, assert204, assert400, assert403
|
|
11
|
+
from udata.tests.helpers import assert200, assert201, assert204, assert400, assert403, assert404
|
|
12
12
|
from udata.tests.plugin import ApiClient
|
|
13
13
|
from udata.utils import faker
|
|
14
14
|
|
|
@@ -85,6 +85,38 @@ class HarvestAPITest(MockBackendsMixin):
|
|
|
85
85
|
|
|
86
86
|
assert len(response.json["data"]) == len(sources)
|
|
87
87
|
|
|
88
|
+
def test_list_sources_search(self, api):
|
|
89
|
+
HarvestSourceFactory.create_batch(3)
|
|
90
|
+
source = HarvestSourceFactory(name="Moissonneur GeoNetwork de la ville de Rennes")
|
|
91
|
+
|
|
92
|
+
url = url_for("api.harvest_sources", q="geonetwork rennes")
|
|
93
|
+
response = api.get(url)
|
|
94
|
+
assert200(response)
|
|
95
|
+
|
|
96
|
+
assert len(response.json["data"]) == 1
|
|
97
|
+
assert response.json["data"][0]["id"] == str(source.id)
|
|
98
|
+
|
|
99
|
+
def test_list_sources_paginate(self, api):
|
|
100
|
+
total = 25
|
|
101
|
+
page_size = 20
|
|
102
|
+
HarvestSourceFactory.create_batch(total)
|
|
103
|
+
|
|
104
|
+
url = url_for("api.harvest_sources", page=1, page_size=page_size)
|
|
105
|
+
response = api.get(url)
|
|
106
|
+
assert200(response)
|
|
107
|
+
assert len(response.json["data"]) == page_size
|
|
108
|
+
assert response.json["total"] == total
|
|
109
|
+
|
|
110
|
+
url = url_for("api.harvest_sources", page=2, page_size=page_size)
|
|
111
|
+
response = api.get(url)
|
|
112
|
+
assert200(response)
|
|
113
|
+
assert len(response.json["data"]) == total - page_size
|
|
114
|
+
assert response.json["total"] == total
|
|
115
|
+
|
|
116
|
+
url = url_for("api.harvest_sources", page=3, page_size=page_size)
|
|
117
|
+
response = api.get(url)
|
|
118
|
+
assert404(response)
|
|
119
|
+
|
|
88
120
|
def test_create_source_with_owner(self, api):
|
|
89
121
|
"""It should create and attach a new source to an owner"""
|
|
90
122
|
user = api.login()
|
udata/mongo/errors.py
CHANGED
|
@@ -3,7 +3,14 @@ from mongoengine.errors import ValidationError
|
|
|
3
3
|
|
|
4
4
|
class FieldValidationError(ValidationError):
|
|
5
5
|
field: str
|
|
6
|
+
raw_message: str
|
|
6
7
|
|
|
7
|
-
def __init__(self, *args, field: str, **kwargs):
|
|
8
|
-
self.field = field
|
|
8
|
+
def __init__(self, message: str, *args, field: str, **kwargs):
|
|
9
9
|
super().__init__(*args, **kwargs)
|
|
10
|
+
|
|
11
|
+
self.raw_message = message # It's sad but ValidationError do some stuff with the message…
|
|
12
|
+
self.field = field
|
|
13
|
+
self.errors[self.field] = message
|
|
14
|
+
|
|
15
|
+
def __str__(self):
|
|
16
|
+
return str(self.raw_message)
|