udata 10.0.9.dev33787__py2.py3-none-any.whl → 10.0.9.dev33847__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 (51) hide show
  1. udata/commands/fixtures.py +42 -8
  2. udata/commands/tests/test_fixtures.py +12 -2
  3. udata/core/contact_point/api.py +13 -1
  4. udata/core/contact_point/api_fields.py +10 -1
  5. udata/core/contact_point/factories.py +3 -1
  6. udata/core/contact_point/forms.py +1 -0
  7. udata/core/contact_point/models.py +9 -1
  8. udata/core/dataservices/models.py +11 -4
  9. udata/core/dataservices/rdf.py +12 -6
  10. udata/core/dataset/api.py +1 -0
  11. udata/core/dataset/api_fields.py +3 -3
  12. udata/core/dataset/apiv2.py +5 -3
  13. udata/core/dataset/forms.py +4 -4
  14. udata/core/dataset/models.py +1 -1
  15. udata/core/dataset/rdf.py +23 -13
  16. udata/forms/fields.py +1 -4
  17. udata/harvest/api.py +5 -1
  18. udata/harvest/tests/dcat/evian.json +1 -1
  19. udata/harvest/tests/test_dcat_backend.py +5 -5
  20. udata/migrations/2024-12-05-contact-point-is-now-a-list.py +33 -0
  21. udata/rdf.py +75 -44
  22. udata/static/chunks/{10.471164b2a9fe15614797.js → 10.8ca60413647062717b1e.js} +3 -3
  23. udata/static/chunks/{10.471164b2a9fe15614797.js.map → 10.8ca60413647062717b1e.js.map} +1 -1
  24. udata/static/chunks/{11.51d706fb9521c16976bc.js → 11.0f04e49a40a0a381bcce.js} +3 -3
  25. udata/static/chunks/{11.51d706fb9521c16976bc.js.map → 11.0f04e49a40a0a381bcce.js.map} +1 -1
  26. udata/static/chunks/{13.f29411b06be1883356a3.js → 13.d9c1735d14038b94c17e.js} +2 -2
  27. udata/static/chunks/{13.f29411b06be1883356a3.js.map → 13.d9c1735d14038b94c17e.js.map} +1 -1
  28. udata/static/chunks/{17.3bd0340930d4a314ce9c.js → 17.81c57c0dedf812e43013.js} +2 -2
  29. udata/static/chunks/{17.3bd0340930d4a314ce9c.js.map → 17.81c57c0dedf812e43013.js.map} +1 -1
  30. udata/static/chunks/{8.54e44b102164ae5e7a67.js → 8.494b003a94383b142c18.js} +2 -2
  31. udata/static/chunks/{8.54e44b102164ae5e7a67.js.map → 8.494b003a94383b142c18.js.map} +1 -1
  32. udata/static/chunks/{9.07515e5187f475bce828.js → 9.033d7e190ca9e226a5d0.js} +3 -3
  33. udata/static/chunks/{9.07515e5187f475bce828.js.map → 9.033d7e190ca9e226a5d0.js.map} +1 -1
  34. udata/static/common.js +1 -1
  35. udata/static/common.js.map +1 -1
  36. udata/tests/api/test_contact_points.py +36 -5
  37. udata/tests/api/test_dataservices_api.py +2 -2
  38. udata/tests/api/test_datasets_api.py +8 -6
  39. udata/tests/api/test_organizations_api.py +1 -0
  40. udata/tests/api/test_user_api.py +1 -0
  41. udata/tests/contact_point/test_contact_point_models.py +19 -0
  42. udata/tests/dataset/test_dataset_rdf.py +30 -3
  43. udata/tests/organization/test_organization_tasks.py +1 -0
  44. udata/tests/test_rdf.py +41 -7
  45. udata/translations/udata.pot +23 -7
  46. {udata-10.0.9.dev33787.dist-info → udata-10.0.9.dev33847.dist-info}/METADATA +3 -1
  47. {udata-10.0.9.dev33787.dist-info → udata-10.0.9.dev33847.dist-info}/RECORD +51 -49
  48. {udata-10.0.9.dev33787.dist-info → udata-10.0.9.dev33847.dist-info}/LICENSE +0 -0
  49. {udata-10.0.9.dev33787.dist-info → udata-10.0.9.dev33847.dist-info}/WHEEL +0 -0
  50. {udata-10.0.9.dev33787.dist-info → udata-10.0.9.dev33847.dist-info}/entry_points.txt +0 -0
  51. {udata-10.0.9.dev33787.dist-info → udata-10.0.9.dev33847.dist-info}/top_level.txt +0 -0
udata/rdf.py CHANGED
@@ -6,6 +6,7 @@ import logging
6
6
  import re
7
7
  from html.parser import HTMLParser
8
8
 
9
+ import mongoengine
9
10
  from flask import abort, current_app, request, url_for
10
11
  from rdflib import BNode, Graph, Literal, URIRef
11
12
  from rdflib.namespace import (
@@ -47,6 +48,7 @@ EUFORMAT = Namespace("http://publications.europa.eu/resource/authority/file-type
47
48
  IANAFORMAT = Namespace("https://www.iana.org/assignments/media-types/")
48
49
  DCT = DCTERMS # More common usage
49
50
  VCARD = Namespace("http://www.w3.org/2006/vcard/ns#")
51
+ GEODCAT = Namespace("http://data.europa.eu/930/")
50
52
 
51
53
  namespace_manager = NamespaceManager(Graph())
52
54
  namespace_manager.bind("adms", ADMS)
@@ -127,6 +129,19 @@ EU_HVD_CATEGORIES = {
127
129
  HVD_LEGISLATION = "http://data.europa.eu/eli/reg_impl/2023/138/oj"
128
130
  TAG_TO_EU_HVD_CATEGORIES = {slugify_tag(EU_HVD_CATEGORIES[uri]): uri for uri in EU_HVD_CATEGORIES}
129
131
 
132
+ AGENT_ROLE_TO_RDF_PREDICATE = {
133
+ "contact": DCAT.contactPoint,
134
+ "publisher": DCT.publisher,
135
+ "creator": DCT.creator,
136
+ }
137
+
138
+ # Map rdf contact point entity to role
139
+ CONTACT_POINT_ENTITY_TO_ROLE = {
140
+ DCAT.contactPoint: "contact",
141
+ DCT.publisher: "publisher",
142
+ DCT.creator: "creator",
143
+ }
144
+
130
145
 
131
146
  def guess_format(string):
132
147
  """Guess format given an extension or a mime-type"""
@@ -294,61 +309,77 @@ def themes_from_rdf(rdf):
294
309
  return list(set(tags))
295
310
 
296
311
 
297
- def contact_point_from_rdf(rdf, dataset):
298
- contact_point = rdf.value(DCAT.contactPoint)
299
- if contact_point:
300
- name = rdf_value(contact_point, VCARD.fn) or ""
301
- email = (
302
- rdf_value(contact_point, VCARD.hasEmail)
303
- or rdf_value(contact_point, VCARD.email)
304
- or rdf_value(contact_point, DCAT.email)
305
- )
306
- email = email.replace("mailto:", "").strip() if email else None
307
- contact_form = rdf_value(contact_point, VCARD.hasUrl)
308
- if not email and not contact_form:
309
- return
310
- if dataset.organization:
311
- contact, _ = ContactPoint.objects.get_or_create(
312
- name=name, email=email, contact_form=contact_form, organization=dataset.organization
312
+ def contact_points_from_rdf(rdf, prop, role, dataset):
313
+ for contact_point in rdf.objects(prop):
314
+ # Read contact point information
315
+ if prop == DCAT.contactPoint: # Could be split on the type of contact_point instead
316
+ name = rdf_value(contact_point, VCARD.fn) or ""
317
+ email = (
318
+ rdf_value(contact_point, VCARD.hasEmail)
319
+ or rdf_value(contact_point, VCARD.email)
320
+ or rdf_value(contact_point, DCAT.email)
313
321
  )
314
- elif dataset.owner:
315
- contact, _ = ContactPoint.objects.get_or_create(
316
- name=name, email=email, contact_form=contact_form, owner=dataset.owner
322
+ email = email.replace("mailto:", "").strip() if email else None
323
+ contact_form = rdf_value(contact_point, VCARD.hasUrl)
324
+ else:
325
+ name = (
326
+ rdf_value(contact_point, FOAF.name)
327
+ or rdf_value(contact_point, SKOS.prefLabel)
328
+ or ""
317
329
  )
330
+ email = rdf_value(contact_point, FOAF.mbox)
331
+ email = email.replace("mailto:", "").strip() if email else None
332
+ contact_form = None
333
+
334
+ # Tested at validation time, because it depends on the role
335
+ # if not email and not contact_form:
336
+ # continue
337
+
338
+ # Create of get contact point object
339
+ if not dataset.organization and not dataset.owner:
340
+ continue
341
+ org_or_owner = {}
342
+ if dataset.organization:
343
+ org_or_owner = {"organization": dataset.organization}
318
344
  else:
319
- contact = None
320
- return contact
345
+ org_or_owner = {"owner": dataset.owner}
346
+ try:
347
+ contact, _ = ContactPoint.objects.get_or_create(
348
+ name=name, email=email, contact_form=contact_form, role=role, **org_or_owner
349
+ )
350
+ except mongoengine.errors.ValidationError as validation_error:
351
+ log.warning(f"Unable to validate contact point: {validation_error}", exc_info=True)
352
+ continue
353
+ yield contact
321
354
 
322
355
 
323
- def contact_point_to_rdf(contact, graph=None):
356
+ def contact_points_to_rdf(contacts, graph=None):
324
357
  """
325
358
  Map a contact point to a DCAT/RDF graph
326
359
  """
327
- if not contact:
328
- return None
329
-
330
360
  graph = graph or Graph(namespace_manager=namespace_manager)
331
361
 
332
- if contact.id:
333
- id = URIRef(
334
- endpoint_for(
335
- "api.contact_point",
336
- contact_point=contact.id,
337
- _external=True,
362
+ for contact in contacts:
363
+ if contact.id:
364
+ id = URIRef(
365
+ endpoint_for(
366
+ "api.contact_point",
367
+ contact_point=contact.id,
368
+ _external=True,
369
+ )
338
370
  )
339
- )
340
- else:
341
- id = BNode()
342
-
343
- node = graph.resource(id)
344
- node.set(RDF.type, VCARD.Kind)
345
- if contact.name:
346
- node.set(VCARD.fn, Literal(contact.name))
347
- if contact.email:
348
- node.set(VCARD.hasEmail, URIRef(f"mailto:{contact.email}"))
349
- if contact.contact_form:
350
- node.set(VCARD.hasUrl, URIRef(contact.contact_form))
351
- return node
371
+ else:
372
+ id = BNode()
373
+
374
+ node = graph.resource(id)
375
+ node.set(RDF.type, VCARD.Kind)
376
+ if contact.name:
377
+ node.set(VCARD.fn, Literal(contact.name))
378
+ if contact.email:
379
+ node.set(VCARD.hasEmail, URIRef(f"mailto:{contact.email}"))
380
+ if contact.contact_form:
381
+ node.set(VCARD.hasUrl, URIRef(contact.contact_form))
382
+ yield node, AGENT_ROLE_TO_RDF_PREDICATE.get(contact.role, DCAT.contactPoint)
352
383
 
353
384
 
354
385
  def primary_topic_identifier_from_rdf(graph: Graph, resource: RdfResource):