udata 10.3.3.dev35091__py2.py3-none-any.whl → 10.3.3.dev35125__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/api.py CHANGED
@@ -604,8 +604,11 @@ class ResourceAPI(ResourceMixin, API):
604
604
  @api.marshal_with(resource_fields)
605
605
  def get(self, dataset, rid):
606
606
  """Get a resource given its identifier"""
607
- if dataset.deleted and not DatasetEditPermission(dataset).can():
608
- api.abort(410, "Dataset has been deleted")
607
+ if not DatasetEditPermission(dataset).can():
608
+ if dataset.private:
609
+ api.abort(404)
610
+ elif dataset.deleted:
611
+ api.abort(410, "Dataset has been deleted")
609
612
  resource = self.get_resource_or_404(dataset, rid)
610
613
  return resource
611
614
 
@@ -8,6 +8,7 @@ from flask_restx import marshal
8
8
  from udata import search
9
9
  from udata.api import API, apiv2, fields
10
10
  from udata.core.contact_point.api_fields import contact_point_fields
11
+ from udata.core.dataset.api_fields import license_fields
11
12
  from udata.core.organization.api_fields import member_user_with_email_fields
12
13
  from udata.core.spatial.api_fields import geojson
13
14
  from udata.utils import get_by
@@ -28,7 +29,7 @@ from .api_fields import (
28
29
  temporal_coverage_fields,
29
30
  user_ref_fields,
30
31
  )
31
- from .constants import DEFAULT_FREQUENCY, DEFAULT_LICENSE, UPDATE_FREQUENCIES
32
+ from .constants import DEFAULT_FREQUENCY, DEFAULT_LICENSE, FULL_OBJECTS_HEADER, UPDATE_FREQUENCIES
32
33
  from .models import CommunityResource, Dataset
33
34
  from .permissions import DatasetEditPermission, ResourceEditPermission
34
35
  from .search import DatasetSearch
@@ -149,11 +150,17 @@ dataset_fields = apiv2.model(
149
150
  },
150
151
  description="Link to the dataset community resources",
151
152
  ),
152
- "frequency": fields.String(
153
- description="The update frequency",
154
- required=True,
153
+ "frequency": fields.Raw(
154
+ attribute=lambda d: {
155
+ "id": d.frequency or DEFAULT_FREQUENCY,
156
+ "label": UPDATE_FREQUENCIES.get(d.frequency or DEFAULT_FREQUENCY),
157
+ }
158
+ if request.headers.get(FULL_OBJECTS_HEADER, False, bool)
159
+ else d.frequency,
155
160
  enum=list(UPDATE_FREQUENCIES),
156
161
  default=DEFAULT_FREQUENCY,
162
+ required=True,
163
+ description="The update frequency (full Frequency object if `X-Get-Datasets-Full-Objects` is set, ID of the frequency otherwise)",
157
164
  ),
158
165
  "frequency_date": fields.ISODateTime(
159
166
  description=(
@@ -183,8 +190,12 @@ dataset_fields = apiv2.model(
183
190
  "spatial": fields.Nested(
184
191
  spatial_coverage_fields, allow_null=True, description="The spatial coverage"
185
192
  ),
186
- "license": fields.String(
187
- attribute="license.id", default=DEFAULT_LICENSE["id"], description="The dataset license"
193
+ "license": fields.Raw(
194
+ attribute=lambda d: marshal(d.license, license_fields)
195
+ if request.headers.get(FULL_OBJECTS_HEADER, False, bool)
196
+ else (d.license.id if d.license is not None else None),
197
+ default=DEFAULT_LICENSE["id"],
198
+ description="The dataset license (full License object if `X-Get-Datasets-Full-Objects` is set, ID of the license otherwise)",
188
199
  ),
189
200
  "uri": fields.UrlFor(
190
201
  "api.dataset",
@@ -307,8 +318,11 @@ class DatasetAPI(API):
307
318
  @apiv2.marshal_with(dataset_fields)
308
319
  def get(self, dataset):
309
320
  """Get a dataset given its identifier"""
310
- if dataset.deleted and not DatasetEditPermission(dataset).can():
311
- apiv2.abort(410, "Dataset has been deleted")
321
+ if not DatasetEditPermission(dataset).can():
322
+ if dataset.private:
323
+ apiv2.abort(404)
324
+ elif dataset.deleted:
325
+ apiv2.abort(410, "Dataset has been deleted")
312
326
  return dataset
313
327
 
314
328
 
@@ -321,8 +335,11 @@ class DatasetExtrasAPI(API):
321
335
  @apiv2.doc("get_dataset_extras")
322
336
  def get(self, dataset):
323
337
  """Get a dataset extras given its identifier"""
324
- if dataset.deleted and not DatasetEditPermission(dataset).can():
325
- apiv2.abort(410, "Dataset has been deleted")
338
+ if not DatasetEditPermission(dataset).can():
339
+ if dataset.private:
340
+ apiv2.abort(404)
341
+ elif dataset.deleted:
342
+ apiv2.abort(410, "Dataset has been deleted")
326
343
  return dataset.extras
327
344
 
328
345
  @apiv2.secure
@@ -370,6 +387,11 @@ class ResourcesAPI(API):
370
387
  @apiv2.marshal_with(resource_page_fields)
371
388
  def get(self, dataset):
372
389
  """Get the given dataset resources, paginated."""
390
+ if not DatasetEditPermission(dataset).can():
391
+ if dataset.private:
392
+ apiv2.abort(404)
393
+ elif dataset.deleted:
394
+ apiv2.abort(410, "Dataset has been deleted")
373
395
  args = resources_parser.parse_args()
374
396
  page = args["page"]
375
397
  page_size = args["page_size"]
@@ -410,6 +432,11 @@ class ResourceAPI(API):
410
432
  def get(self, rid):
411
433
  dataset = Dataset.objects(resources__id=rid).first()
412
434
  if dataset:
435
+ if not DatasetEditPermission(dataset).can():
436
+ if dataset.private:
437
+ apiv2.abort(404)
438
+ elif dataset.deleted:
439
+ apiv2.abort(410, "Dataset has been deleted")
413
440
  resource = get_by(dataset.resources, "id", rid)
414
441
  else:
415
442
  resource = CommunityResource.objects(id=rid).first()
@@ -436,8 +463,11 @@ class ResourceExtrasAPI(ResourceMixin, API):
436
463
  @apiv2.doc("get_resource_extras")
437
464
  def get(self, dataset, rid):
438
465
  """Get a resource extras given its identifier"""
439
- if dataset.deleted and not DatasetEditPermission(dataset).can():
440
- apiv2.abort(410, "Dataset has been deleted")
466
+ if not DatasetEditPermission(dataset).can():
467
+ if dataset.private:
468
+ apiv2.abort(404)
469
+ elif dataset.deleted:
470
+ apiv2.abort(410, "Dataset has been deleted")
441
471
  resource = self.get_resource_or_404(dataset, rid)
442
472
  return resource.extras
443
473
 
@@ -89,3 +89,5 @@ SCHEMA_CACHE_DURATION = 60 * 5 # In seconds
89
89
 
90
90
  TITLE_SIZE_LIMIT = 350
91
91
  DESCRIPTION_SIZE_LIMIT = 100000
92
+
93
+ FULL_OBJECTS_HEADER = "X-Get-Datasets-Full-Objects"
@@ -1,4 +1,8 @@
1
+ from flask import request
2
+ from flask_restx import marshal
3
+
1
4
  from udata.api import api, fields
5
+ from udata.core.dataset.constants import FULL_OBJECTS_HEADER
2
6
 
3
7
  GEOM_TYPES = ("Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon")
4
8
 
@@ -66,9 +70,18 @@ spatial_coverage_fields = api.model(
66
70
  "geom": fields.Nested(
67
71
  geojson, allow_null=True, description="A multipolygon for the whole coverage"
68
72
  ),
69
- "zones": fields.List(fields.String, description="The covered zones identifiers"),
70
- "granularity": fields.String(
71
- default="other", description="The spatial/territorial granularity"
73
+ "zones": fields.Raw(
74
+ attribute=lambda s: marshal(s.zones, zone_fields)
75
+ if request.headers.get(FULL_OBJECTS_HEADER, False, bool)
76
+ else [str(z) for z in s.zones],
77
+ description="The covered zones identifiers (full GeoZone objects if `X-Get-Datasets-Full-Objects` is set, IDs of the zones otherwise)",
78
+ ),
79
+ "granularity": fields.Raw(
80
+ attribute=lambda s: {"id": s.granularity or "other", "name": s.granularity_label}
81
+ if request.headers.get(FULL_OBJECTS_HEADER, False, bool)
82
+ else s.granularity,
83
+ default="other",
84
+ description="The spatial/territorial granularity (full Granularity object if `X-Get-Datasets-Full-Objects` is set, ID of the granularity otherwise)",
72
85
  ),
73
86
  },
74
87
  )
@@ -19,6 +19,7 @@ from udata.core.dataset.api_fields import (
19
19
  resource_harvest_fields,
20
20
  )
21
21
  from udata.core.dataset.constants import (
22
+ FULL_OBJECTS_HEADER,
22
23
  LEGACY_FREQUENCIES,
23
24
  RESOURCE_TYPES,
24
25
  UPDATE_FREQUENCIES,
@@ -37,7 +38,7 @@ from udata.core.dataset.models import (
37
38
  ResourceMixin,
38
39
  )
39
40
  from udata.core.organization.factories import OrganizationFactory
40
- from udata.core.spatial.factories import SpatialCoverageFactory
41
+ from udata.core.spatial.factories import GeoLevelFactory, SpatialCoverageFactory
41
42
  from udata.core.topic.factories import TopicFactory
42
43
  from udata.core.user.factories import AdminFactory, UserFactory
43
44
  from udata.i18n import gettext as _
@@ -506,6 +507,41 @@ class DatasetAPITest(APITestCase):
506
507
  response = self.get(url_for("api.dataset", dataset=dataset))
507
508
  self.assert200(response)
508
509
 
510
+ def test_dataset_api_get_with_full_objects(self):
511
+ """It should fetch a private dataset from the API if user is authorized"""
512
+ license = LicenseFactory(id="lov2", title="Licence Ouverte Version 2.0")
513
+ paca, bdr, arles = create_geozones_fixtures()
514
+ country = GeoLevelFactory(id="country", name="Pays", admin_level=10)
515
+
516
+ dataset = DatasetFactory(
517
+ frequency="monthly",
518
+ license=license,
519
+ spatial=SpatialCoverageFactory(zones=[paca.id], granularity=country.id),
520
+ )
521
+
522
+ response = self.get(url_for("apiv2.dataset", dataset=dataset))
523
+ self.assert200(response)
524
+ assert response.json["frequency"] == "monthly"
525
+ assert response.json["license"] == "lov2"
526
+ assert response.json["spatial"]["zones"][0] == paca.id
527
+ assert response.json["spatial"]["granularity"] == "country"
528
+
529
+ response = self.get(
530
+ url_for("apiv2.dataset", dataset=dataset),
531
+ headers={
532
+ FULL_OBJECTS_HEADER: "True",
533
+ },
534
+ )
535
+ self.assert200(response)
536
+ assert response.json["frequency"]["id"] == "monthly"
537
+ assert response.json["frequency"]["label"] == "Mensuelle"
538
+ assert response.json["license"]["id"] == "lov2"
539
+ assert response.json["license"]["title"] == license.title
540
+ assert response.json["spatial"]["zones"][0]["id"] == paca.id
541
+ assert response.json["spatial"]["zones"][0]["name"] == paca.name
542
+ assert response.json["spatial"]["granularity"]["id"] == "country"
543
+ assert response.json["spatial"]["granularity"]["name"] == country.name
544
+
509
545
  def test_dataset_api_create(self):
510
546
  """It should create a dataset from the API"""
511
547
  data = DatasetFactory.as_dict()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: udata
3
- Version: 10.3.3.dev35091
3
+ Version: 10.3.3.dev35125
4
4
  Summary: Open data portal
5
5
  Home-page: https://github.com/opendatateam/udata
6
6
  Author: Opendata Team
@@ -142,7 +142,8 @@ It is collectively taken care of by members of the
142
142
  ## Current (in progress)
143
143
 
144
144
  - Improve reuse api perfs by adding a mask on datasets [#3309](https://github.com/opendatateam/udata/pull/3309)
145
- - Private objects should return 404 by api [#3311](https://github.com/opendatateam/udata/pull/3311)
145
+ - Private objects should return 404 by api [#3311](https://github.com/opendatateam/udata/pull/3311) [#3316](https://github.com/opendatateam/udata/pull/3316)
146
+ - Allow returning full sub-objects (license, frequency, zones and granularity) for datasets APIv2 [#3310](https://github.com/opendatateam/udata/pull/3310)
146
147
  - Add `featured` to dataset default mask [#3313](https://github.com/opendatateam/udata/pull/3313)
147
148
 
148
149
  ## 10.3.2 (2025-05-06)
@@ -94,11 +94,11 @@ udata/core/dataservices/tasks.py,sha256=d2tG1l6u8-eUKUYBOgnCsQLbLmLgJXU-DOzZWhhL
94
94
  udata/core/dataset/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
95
  udata/core/dataset/actions.py,sha256=mX6xox0PiMrbcAPZ3VZsI26rfM-ciYfEXxN6sqqImKA,1222
96
96
  udata/core/dataset/activities.py,sha256=v8k1jwhdx62Z2ARZq8Q-x86OWSsBK99hRloPl74OCgA,1502
97
- udata/core/dataset/api.py,sha256=gMpY3w1BhXtPPiHC7UZ0bfIFyp34Jwio0NaUmwutmVg,33201
97
+ udata/core/dataset/api.py,sha256=jElQZuguc514Eb0cWdquEfosP1yB79hEQ52SV_SvLx8,33282
98
98
  udata/core/dataset/api_fields.py,sha256=SLuzWoPdMLPX28WQ9DRGUPKS27vlltiFeiTo6jXa55Q,17549
99
- udata/core/dataset/apiv2.py,sha256=5tBsbEbF7kjUHP0X9Xd6qDSI9bqNBgYvRaWs0n3r1OM,17803
99
+ udata/core/dataset/apiv2.py,sha256=tpnIIk4UIQPOWUSjvxfBonTO8cbBEZSHKFItg0SnUws,19235
100
100
  udata/core/dataset/commands.py,sha256=__hPAk_6iHtgMnEG51ux0vbNWJHxUjXhi1ukH4hF5jY,3714
101
- udata/core/dataset/constants.py,sha256=44uFvmL7xRF73Kag3rdaVli6CIP-3ZN8obhmsqHYJIM,2918
101
+ udata/core/dataset/constants.py,sha256=fKn21GzRShZv6pzKy3TvEK1cevQ9H3dOFE-xTRuE0lA,2971
102
102
  udata/core/dataset/csv.py,sha256=aNJOytbFbH8OCYr6hyaSaqFSa94Xb3dvBd3QGZHJRsA,3633
103
103
  udata/core/dataset/events.py,sha256=bSM0nFEX14r4JHc-bAM-7OOuD3JAxUIpw9GgXbOsUyw,4078
104
104
  udata/core/dataset/exceptions.py,sha256=uKiayLSpSzsnLvClObS6hOO0qXEqvURKN7_w8eimQNU,498
@@ -202,7 +202,7 @@ udata/core/spam/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
202
202
  udata/core/spam/tests/test_spam.py,sha256=Yu06Z3hU-XPX51mvdgH_nHQFeug_PXWy6P7QxNDaKEI,677
203
203
  udata/core/spatial/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
204
204
  udata/core/spatial/api.py,sha256=xuff3vcZjKm6z6FyFLFsuuVUDlCC8o0-JhUXkc-4pmc,5658
205
- udata/core/spatial/api_fields.py,sha256=ZEmgzRrcMr944h3EqN5sLTIeLy-a1VlvxAGDD8NXGIY,2816
205
+ udata/core/spatial/api_fields.py,sha256=6YVv7w6UyX_m7uGKNtexw8EOQk_I39GWlwCb3Qj_IiQ,3513
206
206
  udata/core/spatial/commands.py,sha256=BMYAZqueaU6QlfN4ny4fsMF9l7rcjPp73mj8CIilyZ0,6931
207
207
  udata/core/spatial/constants.py,sha256=T024yKsXuBkaZ1MAFZPfLh2X_EFFVQ7Sfg4VHCmZQM4,147
208
208
  udata/core/spatial/factories.py,sha256=-eQupRrIOQLouw9Bvv4UBqizFKQ4l4CHZBht2cv7irE,3096
@@ -626,7 +626,7 @@ udata/tests/api/test_auth_api.py,sha256=OMRlY0OQt60j5N4A-N3HdWTuffOjRlFHkz5a3jJF
626
626
  udata/tests/api/test_base_api.py,sha256=2w_vz0eEuq3P3aN-ByvxGc3VZAo7XtgatFfcrzf2uEU,2244
627
627
  udata/tests/api/test_contact_points.py,sha256=X_RWD_xCfR8WchhHfKEt5mxMHY77OmTyguNKCsZftdE,5337
628
628
  udata/tests/api/test_dataservices_api.py,sha256=fNpeHl4SMvci3QrC414X6KGorv7NS1y8LsGxcSMjjZY,25729
629
- udata/tests/api/test_datasets_api.py,sha256=0MuIxHimP24waJPu91aXR1HOyBO_YgsPp7KOkB8hkuk,94872
629
+ udata/tests/api/test_datasets_api.py,sha256=dZXx9uz6Ni1qj2SxCgcmsvfzX8eCobqCVDaYHG4VZEY,96551
630
630
  udata/tests/api/test_fields.py,sha256=OW85Z5MES5HeWOpapeem8OvR1cIcrqW-xMWpdZO4LZ8,1033
631
631
  udata/tests/api/test_follow_api.py,sha256=XP6I96JUNT6xjGcQOF7pug_T_i67HzCiOGLaPdpfpEQ,4912
632
632
  udata/tests/api/test_me_api.py,sha256=YPd8zmR3zwJKtpSqz8nY1nOOMyXs66INeBwyhg5D0Us,13846
@@ -726,9 +726,9 @@ udata/translations/pt/LC_MESSAGES/udata.mo,sha256=ViV14tUmjSydHS0TWG_mFikKQfyUaT
726
726
  udata/translations/pt/LC_MESSAGES/udata.po,sha256=rzAD_MVoV54TmN3w1ECz3H2Ru5pM7hWMVH03SkY28Q8,47250
727
727
  udata/translations/sr/LC_MESSAGES/udata.mo,sha256=EHX1_D-Uglj38832G7BrA0QC5IuY3p8dKqi9T0DgPmE,29169
728
728
  udata/translations/sr/LC_MESSAGES/udata.po,sha256=3PMnbVhKVJh6Q8ABi1ZTZ8Dcf-sMjngLJZqLbonJoec,54225
729
- udata-10.3.3.dev35091.dist-info/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
730
- udata-10.3.3.dev35091.dist-info/METADATA,sha256=xmwqnPVt3J6JKkTa3QniKR_hmPu9B1pxhmpMACyd6_c,145683
731
- udata-10.3.3.dev35091.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
732
- udata-10.3.3.dev35091.dist-info/entry_points.txt,sha256=ETvkR4r6G1duBsh_V_fGWENQy17GTFuobi95MYBAl1A,498
733
- udata-10.3.3.dev35091.dist-info/top_level.txt,sha256=39OCg-VWFWOq4gCKnjKNu-s3OwFlZIu_dVH8Gl6ndHw,12
734
- udata-10.3.3.dev35091.dist-info/RECORD,,
729
+ udata-10.3.3.dev35125.dist-info/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
730
+ udata-10.3.3.dev35125.dist-info/METADATA,sha256=3zpBEJCmrAbYIycc2NazRDRY0YMNr8KeofQ4L9bEL5M,145895
731
+ udata-10.3.3.dev35125.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
732
+ udata-10.3.3.dev35125.dist-info/entry_points.txt,sha256=ETvkR4r6G1duBsh_V_fGWENQy17GTFuobi95MYBAl1A,498
733
+ udata-10.3.3.dev35125.dist-info/top_level.txt,sha256=39OCg-VWFWOq4gCKnjKNu-s3OwFlZIu_dVH8Gl6ndHw,12
734
+ udata-10.3.3.dev35125.dist-info/RECORD,,