udata 10.0.8.dev33663__py2.py3-none-any.whl → 10.0.8.dev33704__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.

Files changed (34) hide show
  1. udata/api_fields.py +27 -6
  2. udata/core/dataservices/rdf.py +21 -0
  3. udata/core/dataset/rdf.py +14 -0
  4. udata/core/owned.py +9 -4
  5. udata/core/reuse/models.py +2 -2
  6. udata/forms/fields.py +21 -1
  7. udata/static/chunks/{10.8ca60413647062717b1e.js → 10.471164b2a9fe15614797.js} +3 -3
  8. udata/static/chunks/{10.8ca60413647062717b1e.js.map → 10.471164b2a9fe15614797.js.map} +1 -1
  9. udata/static/chunks/{11.b6f741fcc366abfad9c4.js → 11.83535504cd650ea08f65.js} +3 -3
  10. udata/static/chunks/{11.b6f741fcc366abfad9c4.js.map → 11.83535504cd650ea08f65.js.map} +1 -1
  11. udata/static/chunks/{13.2d06442dd9a05d9777b5.js → 13.d9c1735d14038b94c17e.js} +2 -2
  12. udata/static/chunks/{13.2d06442dd9a05d9777b5.js.map → 13.d9c1735d14038b94c17e.js.map} +1 -1
  13. udata/static/chunks/{17.e8e4caaad5cb0cc0bacc.js → 17.81c57c0dedf812e43013.js} +2 -2
  14. udata/static/chunks/{17.e8e4caaad5cb0cc0bacc.js.map → 17.81c57c0dedf812e43013.js.map} +1 -1
  15. udata/static/chunks/{19.f03a102365af4315f9db.js → 19.df16abde17a42033a7f8.js} +3 -3
  16. udata/static/chunks/{19.f03a102365af4315f9db.js.map → 19.df16abde17a42033a7f8.js.map} +1 -1
  17. udata/static/chunks/{8.778091d55cd8ea39af6b.js → 8.462bb3029de008497675.js} +2 -2
  18. udata/static/chunks/{8.778091d55cd8ea39af6b.js.map → 8.462bb3029de008497675.js.map} +1 -1
  19. udata/static/chunks/{9.033d7e190ca9e226a5d0.js → 9.07515e5187f475bce828.js} +3 -3
  20. udata/static/chunks/{9.033d7e190ca9e226a5d0.js.map → 9.07515e5187f475bce828.js.map} +1 -1
  21. udata/static/common.js +1 -1
  22. udata/static/common.js.map +1 -1
  23. udata/tests/api/test_dataservices_api.py +17 -0
  24. udata/tests/api/test_datasets_api.py +17 -0
  25. udata/tests/api/test_reuses_api.py +1 -1
  26. udata/tests/dataset/test_dataset_rdf.py +34 -0
  27. udata/tests/forms/test_publish_as_field.py +7 -5
  28. udata/tests/test_api_fields.py +2 -2
  29. {udata-10.0.8.dev33663.dist-info → udata-10.0.8.dev33704.dist-info}/METADATA +3 -1
  30. {udata-10.0.8.dev33663.dist-info → udata-10.0.8.dev33704.dist-info}/RECORD +34 -34
  31. {udata-10.0.8.dev33663.dist-info → udata-10.0.8.dev33704.dist-info}/LICENSE +0 -0
  32. {udata-10.0.8.dev33663.dist-info → udata-10.0.8.dev33704.dist-info}/WHEEL +0 -0
  33. {udata-10.0.8.dev33663.dist-info → udata-10.0.8.dev33704.dist-info}/entry_points.txt +0 -0
  34. {udata-10.0.8.dev33663.dist-info → udata-10.0.8.dev33704.dist-info}/top_level.txt +0 -0
udata/api_fields.py CHANGED
@@ -42,7 +42,7 @@ from typing import Any, Callable, Iterable
42
42
  import flask_restx.fields as restx_fields
43
43
  import mongoengine
44
44
  import mongoengine.fields as mongo_fields
45
- from bson import ObjectId
45
+ from bson import DBRef, ObjectId
46
46
  from flask_restx.inputs import boolean
47
47
  from flask_restx.reqparse import RequestParser
48
48
  from flask_storage.mongo import ImageField as FlaskStorageImageField
@@ -446,7 +446,7 @@ def generate_fields(**kwargs) -> Callable:
446
446
  if constraint == "objectid" and not ObjectId.is_valid(
447
447
  args[filterable["key"]]
448
448
  ):
449
- api.abort(400, f'`{filterable["key"]}` must be an identifier')
449
+ api.abort(400, f"`{filterable['key']}` must be an identifier")
450
450
 
451
451
  query = filterable.get("query", None)
452
452
  if query:
@@ -534,17 +534,37 @@ def patch(obj, request) -> type:
534
534
 
535
535
  info = getattr(model_attribute, "__additional_field_info__", {})
536
536
 
537
- # `check` field attribute allows to do validation from the request before setting
537
+ # `checks` field attribute allows to do validation from the request before setting
538
538
  # the attribute
539
- check = info.get("check", None)
540
- if check is not None and value != getattr(obj, key):
541
- check(**{key: value}) # TODO add other model attributes in function parameters
539
+ checks = info.get("checks", [])
540
+
541
+ if is_value_modified(getattr(obj, key), value):
542
+ for check in checks:
543
+ check(
544
+ value,
545
+ **{
546
+ "is_creation": obj._created,
547
+ "is_update": not obj._created,
548
+ "field": key,
549
+ },
550
+ ) # TODO add other model attributes in function parameters
542
551
 
543
552
  setattr(obj, key, value)
544
553
 
545
554
  return obj
546
555
 
547
556
 
557
+ def is_value_modified(old_value, new_value) -> bool:
558
+ # If we want to modify a reference, the new_value may be a DBRef.
559
+ # `wrap_primary_key` can also return the `foreign_document` (see :WrapToForeignDocument)
560
+ # and it is not currently taken into account here…
561
+ # Maybe we can do another type of check to check if the reference changes in the future…
562
+ if isinstance(new_value, DBRef):
563
+ return not old_value or new_value.id != old_value.id
564
+
565
+ return new_value != old_value
566
+
567
+
548
568
  def patch_and_save(obj, request) -> type:
549
569
  obj = patch(obj, request)
550
570
 
@@ -587,6 +607,7 @@ def wrap_primary_key(
587
607
  raise FieldValidationError(field=field_name, message=f"Unknown reference '{value}'")
588
608
 
589
609
  # GenericReferenceField only accepts document (not dbref / objectid)
610
+ # :WrapToForeignDocument
590
611
  if isinstance(
591
612
  foreign_field,
592
613
  (
@@ -181,3 +181,24 @@ def dataservice_to_rdf(dataservice: Dataservice, graph=None):
181
181
  d.set(DCAT.contactPoint, contact_point)
182
182
 
183
183
  return d
184
+
185
+
186
+ def dataservice_as_distribution_to_rdf(
187
+ dataservice: Dataservice, graph: Graph = None, is_hvd: bool = True
188
+ ):
189
+ """
190
+ Create a blank distribution pointing towards a dataservice with DCAT.accessService property
191
+ """
192
+ id = BNode()
193
+ distribution = graph.resource(id)
194
+ distribution.set(RDF.type, DCAT.Distribution)
195
+ distribution.add(DCT.title, Literal(dataservice.title))
196
+ distribution.add(DCAT.accessURL, URIRef(dataservice.base_api_url))
197
+
198
+ if is_hvd:
199
+ # DCAT-AP HVD applicable legislation is also expected at the distribution level
200
+ distribution.add(DCATAP.applicableLegislation, URIRef(HVD_LEGISLATION))
201
+
202
+ distribution.add(DCAT.accessService, dataservice_to_rdf(dataservice, graph))
203
+
204
+ return distribution
udata/core/dataset/rdf.py CHANGED
@@ -340,6 +340,20 @@ def dataset_to_rdf(dataset: Dataset, graph: Optional[Graph] = None) -> RdfResour
340
340
  for resource in dataset.resources:
341
341
  d.add(DCAT.distribution, resource_to_rdf(resource, dataset, graph, is_hvd))
342
342
 
343
+ if is_hvd:
344
+ from udata.core.dataservices.models import Dataservice
345
+ from udata.core.dataservices.rdf import dataservice_as_distribution_to_rdf
346
+
347
+ # Add a blank distribution pointing to a DataService using the distribution DCAT.accessService.
348
+ # Useful for HVD reporting since DataService are not currently harvested by
349
+ # data.europa.eu as first class entities.
350
+ # Should be removed once supported by data.europa.eu harvesting.
351
+ for service in Dataservice.objects.filter(datasets=dataset, tags="hvd"):
352
+ d.add(
353
+ DCAT.distribution,
354
+ dataservice_as_distribution_to_rdf(service, graph),
355
+ )
356
+
343
357
  if dataset.temporal_coverage:
344
358
  d.set(DCT.temporal, temporal_to_rdf(dataset.temporal_coverage, graph))
345
359
 
udata/core/owned.py CHANGED
@@ -41,7 +41,12 @@ class OwnedQuerySet(UDataQuerySet):
41
41
  return self(visible_query | owned_qs._query_obj)
42
42
 
43
43
 
44
- def check_owner_is_current_user(owner):
44
+ def only_creation(_value, is_update, field, **_kwargs):
45
+ if is_update:
46
+ raise FieldValidationError(_(f"Cannot modify {field} after creation"), field=field)
47
+
48
+
49
+ def check_owner_is_current_user(owner, **_kwargs):
45
50
  from udata.auth import admin_permission, current_user
46
51
 
47
52
  if (
@@ -53,7 +58,7 @@ def check_owner_is_current_user(owner):
53
58
  raise FieldValidationError(_("You can only set yourself as owner"), field="owner")
54
59
 
55
60
 
56
- def check_organization_is_valid_for_current_user(organization):
61
+ def check_organization_is_valid_for_current_user(organization, **_kwargs):
57
62
  from udata.auth import current_user
58
63
  from udata.models import Organization
59
64
 
@@ -76,7 +81,7 @@ class Owned(object):
76
81
  ReferenceField(User, reverse_delete_rule=NULLIFY),
77
82
  nested_fields=user_ref_fields,
78
83
  description="Only present if organization is not set. Can only be set to the current authenticated user.",
79
- check=check_owner_is_current_user,
84
+ checks=[check_owner_is_current_user, only_creation],
80
85
  allow_null=True,
81
86
  filterable={},
82
87
  )
@@ -84,7 +89,7 @@ class Owned(object):
84
89
  ReferenceField(Organization, reverse_delete_rule=NULLIFY),
85
90
  nested_fields=org_ref_fields,
86
91
  description="Only present if owner is not set. Can only be set to an organization of the current authenticated user.",
87
- check=check_organization_is_valid_for_current_user,
92
+ checks=[check_organization_is_valid_for_current_user, only_creation],
88
93
  allow_null=True,
89
94
  filterable={},
90
95
  )
@@ -30,7 +30,7 @@ class ReuseQuerySet(OwnedQuerySet):
30
30
  return self(db.Q(private=True) | db.Q(datasets__0__exists=False) | db.Q(deleted__ne=None))
31
31
 
32
32
 
33
- def check_url_does_not_exists(url):
33
+ def check_url_does_not_exists(url, **_kwargs):
34
34
  """Ensure a reuse URL is not yet registered"""
35
35
  if url and Reuse.url_exists(url):
36
36
  raise FieldValidationError(_("This URL is already registered"), field="url")
@@ -82,7 +82,7 @@ class Reuse(db.Datetimed, WithMetrics, ReuseBadgeMixin, Owned, db.Document):
82
82
  url = field(
83
83
  db.URLField(required=True),
84
84
  description="The remote URL (website)",
85
- check=check_url_does_not_exists,
85
+ checks=[check_url_does_not_exists],
86
86
  )
87
87
  urlhash = db.StringField(required=True, unique=True)
88
88
  image_url = db.StringField()
udata/forms/fields.py CHANGED
@@ -418,7 +418,7 @@ class TagField(Field):
418
418
  for tag in self.data:
419
419
  if not tags.MIN_TAG_LENGTH <= len(tag) <= tags.MAX_TAG_LENGTH:
420
420
  message = _(
421
- 'Tag "%(tag)s" must be between %(min)d ' "and %(max)d characters long.",
421
+ 'Tag "%(tag)s" must be between %(min)d and %(max)d characters long.',
422
422
  min=tags.MIN_TAG_LENGTH,
423
423
  max=tags.MAX_TAG_LENGTH,
424
424
  tag=tag,
@@ -728,6 +728,16 @@ class CurrentUserField(ModelFieldMixin, Field):
728
728
  return super(CurrentUserField, self).process(formdata, data, **kwargs)
729
729
 
730
730
  def pre_validate(self, form):
731
+ if (
732
+ isinstance(form, ModelForm) # Some forms (like HarvestSourceForm) are not model forms
733
+ and form.instance
734
+ and self.name in form.instance
735
+ and getattr(form.instance, self.name).id != self.data.id
736
+ ):
737
+ raise validators.ValidationError(
738
+ _("Cannot change owner after creation. Please use transfer feature.")
739
+ )
740
+
731
741
  if self.data:
732
742
  if current_user.is_anonymous:
733
743
  raise validators.ValidationError(_("You must be authenticated"))
@@ -749,6 +759,16 @@ class PublishAsField(ModelFieldMixin, Field):
749
759
  return len(current_user.organizations) <= 0
750
760
 
751
761
  def pre_validate(self, form):
762
+ if (
763
+ isinstance(form, ModelForm) # Some forms (like HarvestSourceForm) are not model forms
764
+ and form.instance
765
+ and self.name in form.instance
766
+ and getattr(form.instance, self.name).id != self.data.id
767
+ ):
768
+ raise validators.ValidationError(
769
+ _("Cannot change owner after creation. Please use transfer feature.")
770
+ )
771
+
752
772
  if self.data:
753
773
  if not current_user.is_authenticated:
754
774
  raise validators.ValidationError(_("You must be authenticated"))