udata 10.6.1.dev36082__py2.py3-none-any.whl → 10.6.1.dev36098__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 (100) hide show
  1. udata/api/fields.py +0 -17
  2. udata/auth/views.py +22 -24
  3. udata/core/activity/api.py +1 -1
  4. udata/core/dataservices/api.py +3 -3
  5. udata/core/dataservices/models.py +11 -11
  6. udata/core/dataservices/rdf.py +3 -18
  7. udata/core/dataset/api.py +4 -3
  8. udata/core/dataset/api_fields.py +14 -25
  9. udata/core/dataset/apiv2.py +8 -11
  10. udata/core/dataset/csv.py +2 -2
  11. udata/core/dataset/models.py +18 -20
  12. udata/core/dataset/rdf.py +5 -38
  13. udata/core/dataset/search.py +1 -1
  14. udata/core/discussions/api.py +3 -1
  15. udata/core/discussions/models.py +11 -15
  16. udata/core/linkable.py +16 -0
  17. udata/core/organization/api.py +1 -0
  18. udata/core/organization/api_fields.py +14 -26
  19. udata/core/organization/csv.py +1 -1
  20. udata/core/organization/models.py +13 -16
  21. udata/core/organization/rdf.py +1 -5
  22. udata/core/post/api.py +8 -8
  23. udata/core/post/models.py +9 -16
  24. udata/core/reports/models.py +2 -2
  25. udata/core/reuse/api.py +4 -3
  26. udata/core/reuse/api_fields.py +1 -7
  27. udata/core/reuse/csv.py +1 -1
  28. udata/core/reuse/models.py +15 -22
  29. udata/core/site/rdf.py +2 -3
  30. udata/core/spatial/models.py +2 -9
  31. udata/core/topic/api.py +4 -8
  32. udata/core/topic/apiv2.py +3 -8
  33. udata/core/topic/models.py +0 -5
  34. udata/core/user/api_fields.py +14 -14
  35. udata/core/user/models.py +10 -16
  36. udata/core/user/rdf.py +1 -3
  37. udata/features/territories/models.py +1 -17
  38. udata/rdf.py +1 -2
  39. udata/routing.py +3 -2
  40. udata/settings.py +2 -0
  41. udata/static/chunks/{10.471164b2a9fe15614797.js → 10.8ca60413647062717b1e.js} +3 -3
  42. udata/static/chunks/{10.471164b2a9fe15614797.js.map → 10.8ca60413647062717b1e.js.map} +1 -1
  43. udata/static/chunks/{11.83535504cd650ea08f65.js → 11.0f04e49a40a0a381bcce.js} +3 -3
  44. udata/static/chunks/{11.83535504cd650ea08f65.js.map → 11.0f04e49a40a0a381bcce.js.map} +1 -1
  45. udata/static/chunks/{13.d9c1735d14038b94c17e.js → 13.f29411b06be1883356a3.js} +2 -2
  46. udata/static/chunks/{13.d9c1735d14038b94c17e.js.map → 13.f29411b06be1883356a3.js.map} +1 -1
  47. udata/static/chunks/{17.81c57c0dedf812e43013.js → 17.3bd0340930d4a314ce9c.js} +2 -2
  48. udata/static/chunks/{17.81c57c0dedf812e43013.js.map → 17.3bd0340930d4a314ce9c.js.map} +1 -1
  49. udata/static/chunks/{19.df16abde17a42033a7f8.js → 19.0586efa786ebf09fb288.js} +3 -3
  50. udata/static/chunks/{19.df16abde17a42033a7f8.js.map → 19.0586efa786ebf09fb288.js.map} +1 -1
  51. udata/static/chunks/{8.462bb3029de008497675.js → 8.b966402f5d680d4bdf4a.js} +2 -2
  52. udata/static/chunks/{8.462bb3029de008497675.js.map → 8.b966402f5d680d4bdf4a.js.map} +1 -1
  53. udata/static/chunks/{9.07515e5187f475bce828.js → 9.033d7e190ca9e226a5d0.js} +3 -3
  54. udata/static/chunks/{9.07515e5187f475bce828.js.map → 9.033d7e190ca9e226a5d0.js.map} +1 -1
  55. udata/static/common.js +1 -1
  56. udata/static/common.js.map +1 -1
  57. udata/templates/api/oauth_error.html +1 -1
  58. udata/templates/macros/metadata.html +0 -1
  59. udata/templates/mail/account_inactivity.html +1 -1
  60. udata/templates/mail/account_inactivity.txt +1 -1
  61. udata/templates/mail/badge_added_association.html +3 -3
  62. udata/templates/mail/badge_added_association.txt +1 -1
  63. udata/templates/mail/badge_added_certified.html +3 -3
  64. udata/templates/mail/badge_added_certified.txt +1 -1
  65. udata/templates/mail/badge_added_company.html +3 -3
  66. udata/templates/mail/badge_added_company.txt +1 -1
  67. udata/templates/mail/badge_added_local_authority.html +3 -3
  68. udata/templates/mail/badge_added_local_authority.txt +1 -1
  69. udata/templates/mail/badge_added_public_service.html +3 -3
  70. udata/templates/mail/badge_added_public_service.txt +1 -1
  71. udata/templates/mail/discussion_closed.html +3 -3
  72. udata/templates/mail/discussion_closed.txt +1 -1
  73. udata/templates/mail/frequency_reminder.html +1 -1
  74. udata/templates/mail/frequency_reminder.txt +1 -1
  75. udata/templates/mail/membership_refused.html +1 -1
  76. udata/templates/mail/membership_request.html +3 -3
  77. udata/templates/mail/membership_request.txt +1 -1
  78. udata/templates/mail/new_discussion.html +3 -3
  79. udata/templates/mail/new_discussion.txt +1 -1
  80. udata/templates/mail/new_discussion_comment.html +3 -3
  81. udata/templates/mail/new_discussion_comment.txt +1 -1
  82. udata/templates/mail/new_member.html +2 -2
  83. udata/templates/mail/new_member.txt +1 -1
  84. udata/templates/mail/new_reuse.html +2 -12
  85. udata/templates/mail/new_reuse.txt +1 -3
  86. udata/templates/mail/user_mail_card.html +1 -1
  87. udata/tests/api/test_dataservices_api.py +2 -2
  88. udata/tests/api/test_datasets_api.py +2 -2
  89. udata/tests/api/test_reuses_api.py +2 -2
  90. udata/tests/api/test_topics_api.py +4 -4
  91. udata/tests/dataset/test_dataset_rdf.py +1 -9
  92. udata/tests/test_discussions.py +3 -3
  93. udata/tests/test_mail.py +8 -0
  94. udata/uris.py +28 -7
  95. {udata-10.6.1.dev36082.dist-info → udata-10.6.1.dev36098.dist-info}/METADATA +1 -1
  96. {udata-10.6.1.dev36082.dist-info → udata-10.6.1.dev36098.dist-info}/RECORD +100 -99
  97. {udata-10.6.1.dev36082.dist-info → udata-10.6.1.dev36098.dist-info}/LICENSE +0 -0
  98. {udata-10.6.1.dev36082.dist-info → udata-10.6.1.dev36098.dist-info}/WHEEL +0 -0
  99. {udata-10.6.1.dev36082.dist-info → udata-10.6.1.dev36098.dist-info}/entry_points.txt +0 -0
  100. {udata-10.6.1.dev36082.dist-info → udata-10.6.1.dev36098.dist-info}/top_level.txt +0 -0
udata/api/fields.py CHANGED
@@ -27,7 +27,6 @@ from flask_restx.fields import StringMixin as StringMixin
27
27
  from flask_restx.fields import Url as Url
28
28
  from flask_restx.fields import Wildcard as Wildcard
29
29
 
30
- from udata.uris import endpoint_for
31
30
  from udata.utils import multi_to_dict
32
31
 
33
32
  log = logging.getLogger(__name__)
@@ -52,22 +51,6 @@ class Markdown(String):
52
51
  __schema_format__ = "markdown"
53
52
 
54
53
 
55
- class UrlFor(String):
56
- def __init__(self, endpoint, mapper=None, **kwargs):
57
- super(UrlFor, self).__init__(**kwargs)
58
- self.endpoint = endpoint
59
- self.fallback_endpoint = kwargs.pop("fallback_endpoint", None)
60
- self.mapper = mapper or self.default_mapper
61
-
62
- def default_mapper(self, obj):
63
- return {"id": str(obj.id)}
64
-
65
- def output(self, key, obj, **kwargs):
66
- return endpoint_for(
67
- self.endpoint, self.fallback_endpoint, _external=True, **self.mapper(obj)
68
- )
69
-
70
-
71
54
  class Permission(Boolean):
72
55
  def __init__(self, mapper=None, **kwargs):
73
56
  super(Permission, self).__init__(**kwargs)
udata/auth/views.py CHANGED
@@ -1,9 +1,7 @@
1
- from flask import current_app, flash, redirect, url_for
1
+ from flask import current_app, redirect, url_for
2
2
  from flask_login import current_user, login_required
3
3
  from flask_security.utils import (
4
4
  check_and_get_token_status,
5
- do_flash,
6
- get_message,
7
5
  get_within_delta,
8
6
  hash_data,
9
7
  login_user,
@@ -26,7 +24,7 @@ from flask_security.views import (
26
24
  from werkzeug.local import LocalProxy
27
25
 
28
26
  from udata.i18n import lazy_gettext as _
29
- from udata.uris import endpoint_for
27
+ from udata.uris import homepage_url
30
28
 
31
29
  from .forms import ChangeEmailForm
32
30
 
@@ -75,19 +73,20 @@ def confirm_change_email_token_status(token):
75
73
  def confirm_change_email(token):
76
74
  expired, invalid, user, new_email = confirm_change_email_token_status(token)
77
75
 
78
- if not user or invalid:
79
- invalid = True
80
- do_flash(*get_message("INVALID_CONFIRMATION_TOKEN"))
76
+ flash = None
77
+ flash_data = None
81
78
  if expired:
82
79
  send_change_email_confirmation_instructions(user, new_email)
83
- do_flash(
84
- _(
85
- "You did not confirm your change of email within {email_within}. New instructions to confirm your change of email have been sent to {new_email}."
86
- ).format(email_within=_security.confirm_email_within, new_email=new_email),
87
- "error",
88
- )
89
- if invalid or expired:
90
- return redirect(endpoint_for("site.home", "admin.index"))
80
+ flash = "change_email_expired"
81
+ flash_data = {
82
+ "email_within": _security.confirm_email_within,
83
+ "new_email": new_email,
84
+ }
85
+ elif not user or invalid:
86
+ flash = "change_email_invalid"
87
+
88
+ if flash:
89
+ return redirect(homepage_url(flash=flash, flash_data=flash_data))
91
90
 
92
91
  if user != current_user:
93
92
  logout_user()
@@ -95,10 +94,8 @@ def confirm_change_email(token):
95
94
 
96
95
  user.email = new_email
97
96
  _datastore.put(user)
98
- msg = (_("Thank you. Your change of email has been confirmed."), "success")
99
97
 
100
- do_flash(*msg)
101
- return redirect(endpoint_for("site.home", "admin.index"))
98
+ return redirect(homepage_url(flash="change_email_confirmed"))
102
99
 
103
100
 
104
101
  @login_required
@@ -110,13 +107,14 @@ def change_email():
110
107
  if form.validate_on_submit():
111
108
  new_email = form.new_email.data
112
109
  send_change_email_confirmation_instructions(current_user, new_email)
113
- flash(
114
- _(
115
- "Thank you. Confirmation instructions for changing your email have been sent to {new_email}."
116
- ).format(new_email=new_email),
117
- "success",
110
+ return redirect(
111
+ homepage_url(
112
+ flash="change_email",
113
+ flash_data={
114
+ "email": new_email,
115
+ },
116
+ )
118
117
  )
119
- return redirect(endpoint_for("site.home", "admin.index"))
120
118
 
121
119
  return _security.render_template("security/change_email.html", change_email_form=form)
122
120
 
@@ -37,7 +37,7 @@ activity_fields = api.model(
37
37
  required=True,
38
38
  ),
39
39
  "related_to_url": fields.String(
40
- attribute="related_to.display_url",
40
+ attribute=lambda o: o.related_to.url_for(),
41
41
  description="The activity target model",
42
42
  required=True,
43
43
  ),
@@ -72,10 +72,10 @@ class DataservicesAtomFeedAPI(API):
72
72
  author_uri = None
73
73
  if dataservice.organization:
74
74
  author_name = dataservice.organization.name
75
- author_uri = dataservice.organization.external_url
75
+ author_uri = dataservice.organization.url_for()
76
76
  elif dataservice.owner:
77
77
  author_name = dataservice.owner.fullname
78
- author_uri = dataservice.owner.external_url
78
+ author_uri = dataservice.owner.url_for()
79
79
  feed.add_item(
80
80
  dataservice.title,
81
81
  unique_id=dataservice.id,
@@ -83,7 +83,7 @@ class DataservicesAtomFeedAPI(API):
83
83
  content=md(dataservice.description),
84
84
  author_name=author_name,
85
85
  author_link=author_uri,
86
- link=dataservice.url_for(external=True),
86
+ link=dataservice.url_for(),
87
87
  updateddate=dataservice.metadata_modified_at,
88
88
  pubdate=dataservice.created_at,
89
89
  )
@@ -17,13 +17,14 @@ from udata.core.dataservices.constants import (
17
17
  DATASERVICE_FORMATS,
18
18
  )
19
19
  from udata.core.dataset.models import Dataset
20
+ from udata.core.linkable import Linkable
20
21
  from udata.core.metrics.helpers import get_stock_metrics
21
22
  from udata.core.metrics.models import WithMetrics
22
23
  from udata.core.owned import Owned, OwnedQuerySet
23
24
  from udata.i18n import lazy_gettext as _
24
25
  from udata.models import Discussion, Follow, db
25
26
  from udata.mongo.errors import FieldValidationError
26
- from udata.uris import endpoint_for
27
+ from udata.uris import cdata_url
27
28
 
28
29
  # "frequency"
29
30
  # "harvest"
@@ -138,7 +139,7 @@ def check_only_one_condition_per_role(access_audiences, **_kwargs):
138
139
  {"key": "views", "value": "metrics.views"},
139
140
  ],
140
141
  )
141
- class Dataservice(Auditable, WithMetrics, Owned, db.Document):
142
+ class Dataservice(Auditable, WithMetrics, Linkable, Owned, db.Document):
142
143
  meta = {
143
144
  "indexes": [
144
145
  "$title",
@@ -273,18 +274,17 @@ class Dataservice(Auditable, WithMetrics, Owned, db.Document):
273
274
  auditable=False,
274
275
  )
275
276
 
276
- def url_for(self, *args, **kwargs):
277
- return endpoint_for(
278
- "dataservices.show", "api.dataservice", dataservice=self, *args, **kwargs
279
- )
280
-
281
277
  @function_field(description="Link to the API endpoint for this dataservice")
282
- def self_api_url(self):
283
- return endpoint_for("api.dataservice", dataservice=self, _external=True)
278
+ def self_api_url(self, **kwargs):
279
+ return url_for(
280
+ "api.dataservice",
281
+ dataservice=self._link_id(**kwargs),
282
+ **self._self_api_url_kwargs(**kwargs),
283
+ )
284
284
 
285
285
  @function_field(description="Link to the udata web page for this dataservice", show_as_ref=True)
286
- def self_web_url(self):
287
- return endpoint_for("dataservices.show", dataservice=self, _external=True)
286
+ def self_web_url(self, **kwargs):
287
+ return cdata_url(f"/dataservices/{self._link_id(**kwargs)}/", **kwargs)
288
288
 
289
289
  __metrics_keys__ = [
290
290
  "discussions",
@@ -20,7 +20,6 @@ from udata.rdf import (
20
20
  themes_from_rdf,
21
21
  url_from_rdf,
22
22
  )
23
- from udata.uris import endpoint_for
24
23
 
25
24
 
26
25
  def dataservice_from_rdf(
@@ -102,14 +101,7 @@ def dataservice_to_rdf(dataservice: Dataservice, graph=None):
102
101
  if dataservice.harvest and dataservice.harvest.uri:
103
102
  id = URIRef(dataservice.harvest.uri)
104
103
  elif dataservice.id:
105
- id = URIRef(
106
- endpoint_for(
107
- "dataservices.show_redirect",
108
- "api.dataservice",
109
- dataservice=dataservice.id,
110
- _external=True,
111
- )
112
- )
104
+ id = URIRef(dataservice.url_for(_useId=True))
113
105
  else:
114
106
  # Should not happen in production. Some test only
115
107
  # `build()` a dataset without saving it to the DB.
@@ -137,14 +129,7 @@ def dataservice_to_rdf(dataservice: Dataservice, graph=None):
137
129
  elif dataservice.id:
138
130
  d.set(
139
131
  DCAT.landingPage,
140
- URIRef(
141
- endpoint_for(
142
- "dataservices.show_redirect",
143
- "api.dataservice",
144
- dataservice=dataservice.id,
145
- _external=True,
146
- )
147
- ),
132
+ URIRef(dataservice.url_for()),
148
133
  )
149
134
 
150
135
  if dataservice.machine_documentation_url:
@@ -183,7 +168,7 @@ def dataservice_to_rdf(dataservice: Dataservice, graph=None):
183
168
  pass
184
169
  else:
185
170
  for dataset in dataservice.datasets:
186
- d.add(DCAT.servesDataset, dataset_to_graph_id(dataset))
171
+ d.add(DCAT.servesDataset, dataset_to_graph_id(dataset.fetch()))
187
172
 
188
173
  for contact_point, predicate in contact_points_to_rdf(dataservice.contact_points, graph):
189
174
  d.set(predicate, contact_point)
udata/core/dataset/api.py CHANGED
@@ -332,10 +332,10 @@ class DatasetsAtomFeedAPI(API):
332
332
  author_uri = None
333
333
  if dataset.organization:
334
334
  author_name = dataset.organization.name
335
- author_uri = dataset.organization.external_url
335
+ author_uri = dataset.organization.url_for()
336
336
  elif dataset.owner:
337
337
  author_name = dataset.owner.fullname
338
- author_uri = dataset.owner.external_url
338
+ author_uri = dataset.owner.url_for()
339
339
  feed.add_item(
340
340
  dataset.title,
341
341
  unique_id=dataset.id,
@@ -343,7 +343,7 @@ class DatasetsAtomFeedAPI(API):
343
343
  content=md(dataset.description),
344
344
  author_name=author_name,
345
345
  author_link=author_uri,
346
- link=dataset.external_url,
346
+ link=dataset.url_for(),
347
347
  updateddate=dataset.last_modified,
348
348
  pubdate=dataset.created_at,
349
349
  )
@@ -830,6 +830,7 @@ class DatasetSuggestAPI(API):
830
830
  if dataset.owner
831
831
  else None
832
832
  ),
833
+ "page": dataset.self_web_url(),
833
834
  }
834
835
  for dataset in datasets.order_by(SUGGEST_SORTING).limit(args["size"])
835
836
  ]
@@ -204,18 +204,15 @@ dataset_ref_fields = api.inherit(
204
204
  {
205
205
  "title": fields.String(description="The dataset title", readonly=True),
206
206
  "acronym": fields.String(description="An optional dataset acronym", readonly=True),
207
- "uri": fields.UrlFor(
208
- "api.dataset",
209
- lambda d: {"dataset": d},
207
+ "uri": fields.String(
208
+ attribute=lambda d: d.self_api_url(),
210
209
  description="The API URI for this dataset",
211
210
  readonly=True,
212
211
  ),
213
- "page": fields.UrlFor(
214
- "datasets.show",
215
- lambda d: {"dataset": d},
216
- description="The web page URL for this dataset",
212
+ "page": fields.String(
213
+ attribute=lambda d: d.self_web_url(),
214
+ description="The dataset web page URL",
217
215
  readonly=True,
218
- fallback_endpoint="api.dataset",
219
216
  ),
220
217
  },
221
218
  )
@@ -393,18 +390,15 @@ dataset_fields = api.model(
393
390
  "license": fields.String(
394
391
  attribute="license.id", default=DEFAULT_LICENSE["id"], description="The dataset license"
395
392
  ),
396
- "uri": fields.UrlFor(
397
- "api.dataset",
398
- lambda o: {"dataset": o},
399
- description="The dataset API URI",
400
- required=True,
393
+ "uri": fields.String(
394
+ attribute=lambda d: d.self_api_url(),
395
+ description="The API URI for this dataset",
396
+ readonly=True,
401
397
  ),
402
- "page": fields.UrlFor(
403
- "datasets.show",
404
- lambda o: {"dataset": o},
405
- description="The dataset page URL",
406
- required=True,
407
- fallback_endpoint="api.dataset",
398
+ "page": fields.String(
399
+ attribute=lambda d: d.self_web_url(),
400
+ description="The dataset web page URL",
401
+ readonly=True,
408
402
  ),
409
403
  "quality": fields.Raw(description="The dataset quality", readonly=True),
410
404
  "last_update": fields.ISODateTime(
@@ -441,12 +435,7 @@ dataset_suggestion_fields = api.model(
441
435
  "image_url": fields.ImageField(
442
436
  size=BIGGEST_LOGO_SIZE, description="The dataset (organization) logo URL", readonly=True
443
437
  ),
444
- "page": fields.UrlFor(
445
- "datasets.show_redirect",
446
- lambda d: {"dataset": d["slug"]},
447
- description="The web page URL for this dataset",
448
- fallback_endpoint="api.dataset",
449
- ),
438
+ "page": fields.String(description="The dataset web page URL", readonly=True),
450
439
  },
451
440
  )
452
441
 
@@ -198,18 +198,15 @@ dataset_fields = apiv2.model(
198
198
  default=DEFAULT_LICENSE["id"],
199
199
  description="The dataset license (full License object if `X-Get-Datasets-Full-Objects` is set, ID of the license otherwise)",
200
200
  ),
201
- "uri": fields.UrlFor(
202
- "api.dataset",
203
- lambda o: {"dataset": o},
204
- description="The dataset API URI",
205
- required=True,
201
+ "uri": fields.String(
202
+ attribute=lambda d: d.self_api_url(),
203
+ description="The API URI for this dataset",
204
+ readonly=True,
206
205
  ),
207
- "page": fields.UrlFor(
208
- "datasets.show",
209
- lambda o: {"dataset": o},
210
- description="The dataset page URL",
211
- required=True,
212
- fallback_endpoint="api.dataset",
206
+ "page": fields.String(
207
+ attribute=lambda d: d.self_web_url(),
208
+ description="The dataset web page URL",
209
+ readonly=True,
213
210
  ),
214
211
  "quality": fields.Raw(description="The dataset quality", readonly=True),
215
212
  "last_update": fields.ISODateTime(
udata/core/dataset/csv.py CHANGED
@@ -19,7 +19,7 @@ class DatasetCsvAdapter(csv.Adapter):
19
19
  "title",
20
20
  "slug",
21
21
  "acronym",
22
- ("url", "external_url"),
22
+ ("url", lambda d: d.url_for()),
23
23
  ("organization", "organization.name"),
24
24
  ("organization_id", "organization.id"),
25
25
  ("owner", "owner.slug"), # in case it's owned by a user, or introduce 'owner_type'?
@@ -63,7 +63,7 @@ class ResourcesCsvAdapter(csv.NestedAdapter):
63
63
  dataset_field("id"),
64
64
  dataset_field("title"),
65
65
  dataset_field("slug"),
66
- dataset_field("url", "external_url"),
66
+ dataset_field("url", lambda r: r.url_for()),
67
67
  dataset_field("organization", lambda r: r.organization.name if r.organization else None),
68
68
  dataset_field(
69
69
  "organization_id", lambda r: str(r.organization.id) if r.organization else None
@@ -9,7 +9,7 @@ import Levenshtein
9
9
  import requests
10
10
  from blinker import signal
11
11
  from dateutil.parser import parse as parse_dt
12
- from flask import current_app
12
+ from flask import current_app, url_for
13
13
  from mongoengine import DynamicEmbeddedDocument
14
14
  from mongoengine import ValidationError as MongoEngineValidationError
15
15
  from mongoengine.fields import DateTimeField
@@ -20,14 +20,14 @@ from udata.api_fields import field
20
20
  from udata.app import cache
21
21
  from udata.core import storages
22
22
  from udata.core.activity.models import Auditable
23
+ from udata.core.linkable import Linkable
23
24
  from udata.core.metrics.helpers import get_stock_metrics
24
25
  from udata.core.owned import Owned, OwnedQuerySet
25
26
  from udata.frontend.markdown import mdstrip
26
27
  from udata.i18n import lazy_gettext as _
27
- from udata.mail import get_mail_campaign_dict
28
28
  from udata.models import Badge, BadgeMixin, BadgesList, SpatialCoverage, WithMetrics, db
29
29
  from udata.mongo.errors import FieldValidationError
30
- from udata.uris import ValidationError, endpoint_for
30
+ from udata.uris import ValidationError, cdata_url
31
31
  from udata.uris import validate as validate_url
32
32
  from udata.utils import get_by, hash_url, to_naive_datetime
33
33
 
@@ -456,9 +456,7 @@ class ResourceMixin(object):
456
456
 
457
457
  If this resource is updated and `url` changes, this property won't.
458
458
  """
459
- return endpoint_for(
460
- "datasets.resource", "api.resource_redirect", id=self.id, _external=True
461
- )
459
+ return url_for("api.resource_redirect", id=self.id, _external=True)
462
460
 
463
461
  @cached_property
464
462
  def json_ld(self):
@@ -510,6 +508,12 @@ class Resource(ResourceMixin, WithMetrics, db.EmbeddedDocument):
510
508
  "views",
511
509
  ]
512
510
 
511
+ def url_for(self, **kwargs):
512
+ return self.self_web_url(**kwargs) or self.latest
513
+
514
+ def self_web_url(self, **kwargs):
515
+ return self.dataset.self_web_url(resource_id=self.id, **kwargs)
516
+
513
517
  @property
514
518
  def dataset(self):
515
519
  try:
@@ -542,7 +546,7 @@ class DatasetBadgeMixin(BadgeMixin):
542
546
  __badges__ = BADGES
543
547
 
544
548
 
545
- class Dataset(Auditable, WithMetrics, DatasetBadgeMixin, Owned, db.Document):
549
+ class Dataset(Auditable, WithMetrics, DatasetBadgeMixin, Owned, Linkable, db.Document):
546
550
  title = field(db.StringField(required=True))
547
551
  acronym = field(db.StringField(max_length=128))
548
552
  # /!\ do not set directly the slug when creating or updating a dataset
@@ -719,10 +723,13 @@ class Dataset(Auditable, WithMetrics, DatasetBadgeMixin, Owned, db.Document):
719
723
  "edit_resources": ResourceEditPermission(self),
720
724
  }
721
725
 
722
- def url_for(self, *args, **kwargs):
723
- return endpoint_for("datasets.show", "api.dataset", dataset=self, *args, **kwargs)
726
+ def self_web_url(self, **kwargs):
727
+ return cdata_url(f"/datasets/{self._link_id(**kwargs)}/", **kwargs)
724
728
 
725
- display_url = property(url_for)
729
+ def self_api_url(self, **kwargs):
730
+ return url_for(
731
+ "api.dataset", dataset=self._link_id(**kwargs), **self._self_api_url_kwargs(**kwargs)
732
+ )
726
733
 
727
734
  @property
728
735
  def is_visible(self):
@@ -738,15 +745,6 @@ class Dataset(Auditable, WithMetrics, DatasetBadgeMixin, Owned, db.Document):
738
745
  return self.title
739
746
  return "{title} ({acronym})".format(**self._data)
740
747
 
741
- @property
742
- def external_url(self):
743
- return self.url_for(_external=True)
744
-
745
- @property
746
- def external_url_with_campaign(self):
747
- extras = get_mail_campaign_dict()
748
- return self.url_for(_external=True, **extras)
749
-
750
748
  @property
751
749
  def image_url(self):
752
750
  if self.organization:
@@ -1029,7 +1027,7 @@ class Dataset(Auditable, WithMetrics, DatasetBadgeMixin, Owned, db.Document):
1029
1027
  "alternateName": self.slug,
1030
1028
  "dateCreated": self.created_at.isoformat(),
1031
1029
  "dateModified": self.last_modified.isoformat(),
1032
- "url": endpoint_for("datasets.show", "api.dataset", dataset=self, _external=True),
1030
+ "url": self.url_for(),
1033
1031
  "name": self.title,
1034
1032
  "keywords": ",".join(self.tags),
1035
1033
  "distribution": [
udata/core/dataset/rdf.py CHANGED
@@ -49,7 +49,6 @@ from udata.rdf import (
49
49
  themes_from_rdf,
50
50
  url_from_rdf,
51
51
  )
52
- from udata.uris import endpoint_for
53
52
  from udata.utils import get_by, safe_unicode, to_naive_datetime
54
53
 
55
54
  from .constants import OGC_SERVICE_FORMATS, UPDATE_FREQUENCIES
@@ -204,27 +203,16 @@ def resource_to_rdf(
204
203
  """
205
204
  graph = graph or Graph(namespace_manager=namespace_manager)
206
205
  if dataset and dataset.id:
207
- id = URIRef(
208
- endpoint_for(
209
- "datasets.show_redirect",
210
- "api.dataset",
211
- dataset=dataset.id,
212
- _external=True,
213
- _anchor="resource-{0}".format(resource.id),
214
- )
215
- )
206
+ id = URIRef(resource.url_for(_useId=True))
216
207
  else:
217
208
  id = BNode(resource.id)
218
- permalink = endpoint_for(
219
- "datasets.resource", "api.resource_redirect", id=resource.id, _external=True
220
- )
221
209
  r = graph.resource(id)
222
210
  r.set(RDF.type, DCAT.Distribution)
223
211
  r.set(DCT.identifier, Literal(resource.id))
224
212
  r.add(DCT.title, Literal(resource.title))
225
213
  r.add(DCT.description, Literal(resource.description))
226
214
  r.add(DCAT.downloadURL, URIRef(resource.url))
227
- r.add(DCAT.accessURL, URIRef(permalink))
215
+ r.add(DCAT.accessURL, URIRef(resource.latest))
228
216
  r.add(DCT.issued, Literal(resource.created_at))
229
217
  r.add(DCT.modified, Literal(resource.last_modified))
230
218
  if dataset and dataset.license:
@@ -262,11 +250,7 @@ def dataset_to_graph_id(dataset: Dataset) -> URIRef | BNode:
262
250
  if dataset.harvest and dataset.harvest.uri:
263
251
  return URIRef(dataset.harvest.uri)
264
252
  elif dataset.id:
265
- return URIRef(
266
- endpoint_for(
267
- "datasets.show_redirect", "api.dataset", dataset=dataset.id, _external=True
268
- )
269
- )
253
+ return URIRef(dataset.url_for(_useId=True))
270
254
  else:
271
255
  # Should not happen in production. Some test only
272
256
  # `build()` a dataset without saving it to the DB.
@@ -289,14 +273,7 @@ def dataset_to_rdf(dataset: Dataset, graph: Optional[Graph] = None) -> RdfResour
289
273
  d.set(DCT.identifier, Literal(dataset.harvest.dct_identifier))
290
274
 
291
275
  alt = graph.resource(BNode())
292
- alternate_identifier = Literal(
293
- endpoint_for(
294
- "datasets.show_redirect",
295
- "api.dataset",
296
- dataset=dataset.id,
297
- _external=True,
298
- )
299
- )
276
+ alternate_identifier = Literal(dataset.url_for(_useId=True))
300
277
  alt.set(RDF.type, ADMS.Identifier)
301
278
  alt.set(DCT.creator, Literal(current_app.config["SITE_TITLE"]))
302
279
  alt.set(SKOS.notation, alternate_identifier)
@@ -313,17 +290,7 @@ def dataset_to_rdf(dataset: Dataset, graph: Optional[Graph] = None) -> RdfResour
313
290
  if dataset.harvest and dataset.harvest.remote_url:
314
291
  d.set(DCAT.landingPage, URIRef(dataset.harvest.remote_url))
315
292
  elif dataset.id:
316
- d.set(
317
- DCAT.landingPage,
318
- URIRef(
319
- endpoint_for(
320
- "datasets.show_redirect",
321
- "api.dataset",
322
- dataset=dataset.id,
323
- _external=True,
324
- )
325
- ),
326
- )
293
+ d.set(DCAT.landingPage, URIRef(dataset.url_for()))
327
294
 
328
295
  if dataset.acronym:
329
296
  d.set(SKOS.altLabel, Literal(dataset.acronym))
@@ -87,7 +87,7 @@ class DatasetSearch(ModelSearchAdapter):
87
87
  "title": dataset.title,
88
88
  "description": dataset.description,
89
89
  "acronym": dataset.acronym or None,
90
- "url": dataset.display_url,
90
+ "url": dataset.url_for(),
91
91
  "tags": dataset.tags,
92
92
  "license": getattr(dataset.license, "id", None),
93
93
  "badges": [badge.kind for badge in dataset.badges],
@@ -74,7 +74,9 @@ discussion_fields = api.model(
74
74
  description="The organization who closed the discussion",
75
75
  ),
76
76
  "discussion": fields.Nested(message_fields),
77
- "url": fields.UrlFor("api.discussion", description="The discussion API URI"),
77
+ "url": fields.String(
78
+ attribute=lambda d: d.self_api_url(), description="The discussion API URI"
79
+ ),
78
80
  "extras": fields.Raw(description="Extra attributes as key-value pairs"),
79
81
  "spam": fields.Nested(spam_fields),
80
82
  "permissions": fields.Nested(discussion_permissions_fields),
@@ -1,10 +1,11 @@
1
1
  import logging
2
2
  from datetime import datetime
3
3
 
4
+ from flask import url_for
4
5
  from flask_login import current_user
5
6
 
7
+ from udata.core.linkable import Linkable
6
8
  from udata.core.spam.models import SpamMixin, spam_protected
7
- from udata.mail import get_mail_campaign_dict
8
9
  from udata.mongo import db
9
10
 
10
11
  from .signals import on_discussion_closed, on_new_discussion, on_new_discussion_comment
@@ -46,7 +47,7 @@ class Message(SpamMixin, db.EmbeddedDocument):
46
47
  def spam_report_message(self, breadcrumb):
47
48
  message = "Spam potentiel dans le message"
48
49
  if self.posted_by_org_or_user:
49
- message += f" de [{self.posted_by_name}]({self.posted_by_org_or_user.external_url})"
50
+ message += f" de [{self.posted_by_name}]({self.posted_by_org_or_user.url_for()})"
50
51
 
51
52
  if len(breadcrumb) != 2:
52
53
  log.warning(
@@ -63,11 +64,11 @@ class Message(SpamMixin, db.EmbeddedDocument):
63
64
  )
64
65
  return message
65
66
 
66
- message += f" sur la discussion « [{discussion.title}]({discussion.external_url}) »"
67
+ message += f" sur la discussion « [{discussion.title}]({discussion.url_for()}) »"
67
68
  return message
68
69
 
69
70
 
70
- class Discussion(SpamMixin, db.Document):
71
+ class Discussion(SpamMixin, Linkable, db.Document):
71
72
  user = db.ReferenceField("User")
72
73
  organization = db.ReferenceField("Organization")
73
74
 
@@ -145,21 +146,16 @@ class Discussion(SpamMixin, db.Document):
145
146
 
146
147
  return OwnablePermission(self.subject).can()
147
148
 
148
- @property
149
- def external_url(self):
150
- return self.subject.url_for(_anchor="discussion-{id}".format(id=self.id), _external=True)
149
+ def self_web_url(self, **kwargs):
150
+ return self.subject.self_web_url(append="/discussions", discussion_id=self.id, **kwargs)
151
151
 
152
- @property
153
- def external_url_with_campaign(self):
154
- extras = get_mail_campaign_dict()
155
- return self.subject.url_for(
156
- _anchor="discussion-{id}".format(id=self.id), _external=True, **extras
157
- )
152
+ def self_api_url(self, **kwargs):
153
+ return url_for("api.discussion", id=self.id, **self._self_api_url_kwargs(**kwargs))
158
154
 
159
155
  def spam_report_message(self, breadcrumb):
160
- message = f"Spam potentiel sur la discussion « [{self.title}]({self.external_url}) »"
156
+ message = f"Spam potentiel sur la discussion « [{self.title}]({self.url_for()}) »"
161
157
  if self.user:
162
- message += f" de [{self.user.fullname}]({self.user.external_url})"
158
+ message += f" de [{self.user.fullname}]({self.user.url_for()})"
163
159
 
164
160
  return message
165
161