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.
- udata/api/fields.py +0 -17
- udata/auth/views.py +22 -24
- udata/core/activity/api.py +1 -1
- udata/core/dataservices/api.py +3 -3
- udata/core/dataservices/models.py +11 -11
- udata/core/dataservices/rdf.py +3 -18
- udata/core/dataset/api.py +4 -3
- udata/core/dataset/api_fields.py +14 -25
- udata/core/dataset/apiv2.py +8 -11
- udata/core/dataset/csv.py +2 -2
- udata/core/dataset/models.py +18 -20
- udata/core/dataset/rdf.py +5 -38
- udata/core/dataset/search.py +1 -1
- udata/core/discussions/api.py +3 -1
- udata/core/discussions/models.py +11 -15
- udata/core/linkable.py +16 -0
- udata/core/organization/api.py +1 -0
- udata/core/organization/api_fields.py +14 -26
- udata/core/organization/csv.py +1 -1
- udata/core/organization/models.py +13 -16
- udata/core/organization/rdf.py +1 -5
- udata/core/post/api.py +8 -8
- udata/core/post/models.py +9 -16
- udata/core/reports/models.py +2 -2
- udata/core/reuse/api.py +4 -3
- udata/core/reuse/api_fields.py +1 -7
- udata/core/reuse/csv.py +1 -1
- udata/core/reuse/models.py +15 -22
- udata/core/site/rdf.py +2 -3
- udata/core/spatial/models.py +2 -9
- udata/core/topic/api.py +4 -8
- udata/core/topic/apiv2.py +3 -8
- udata/core/topic/models.py +0 -5
- udata/core/user/api_fields.py +14 -14
- udata/core/user/models.py +10 -16
- udata/core/user/rdf.py +1 -3
- udata/features/territories/models.py +1 -17
- udata/rdf.py +1 -2
- udata/routing.py +3 -2
- udata/settings.py +2 -0
- udata/static/chunks/{10.471164b2a9fe15614797.js → 10.8ca60413647062717b1e.js} +3 -3
- udata/static/chunks/{10.471164b2a9fe15614797.js.map → 10.8ca60413647062717b1e.js.map} +1 -1
- udata/static/chunks/{11.83535504cd650ea08f65.js → 11.0f04e49a40a0a381bcce.js} +3 -3
- udata/static/chunks/{11.83535504cd650ea08f65.js.map → 11.0f04e49a40a0a381bcce.js.map} +1 -1
- udata/static/chunks/{13.d9c1735d14038b94c17e.js → 13.f29411b06be1883356a3.js} +2 -2
- udata/static/chunks/{13.d9c1735d14038b94c17e.js.map → 13.f29411b06be1883356a3.js.map} +1 -1
- udata/static/chunks/{17.81c57c0dedf812e43013.js → 17.3bd0340930d4a314ce9c.js} +2 -2
- udata/static/chunks/{17.81c57c0dedf812e43013.js.map → 17.3bd0340930d4a314ce9c.js.map} +1 -1
- udata/static/chunks/{19.df16abde17a42033a7f8.js → 19.0586efa786ebf09fb288.js} +3 -3
- udata/static/chunks/{19.df16abde17a42033a7f8.js.map → 19.0586efa786ebf09fb288.js.map} +1 -1
- udata/static/chunks/{8.462bb3029de008497675.js → 8.b966402f5d680d4bdf4a.js} +2 -2
- udata/static/chunks/{8.462bb3029de008497675.js.map → 8.b966402f5d680d4bdf4a.js.map} +1 -1
- udata/static/chunks/{9.07515e5187f475bce828.js → 9.033d7e190ca9e226a5d0.js} +3 -3
- udata/static/chunks/{9.07515e5187f475bce828.js.map → 9.033d7e190ca9e226a5d0.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/templates/api/oauth_error.html +1 -1
- udata/templates/macros/metadata.html +0 -1
- udata/templates/mail/account_inactivity.html +1 -1
- udata/templates/mail/account_inactivity.txt +1 -1
- udata/templates/mail/badge_added_association.html +3 -3
- udata/templates/mail/badge_added_association.txt +1 -1
- udata/templates/mail/badge_added_certified.html +3 -3
- udata/templates/mail/badge_added_certified.txt +1 -1
- udata/templates/mail/badge_added_company.html +3 -3
- udata/templates/mail/badge_added_company.txt +1 -1
- udata/templates/mail/badge_added_local_authority.html +3 -3
- udata/templates/mail/badge_added_local_authority.txt +1 -1
- udata/templates/mail/badge_added_public_service.html +3 -3
- udata/templates/mail/badge_added_public_service.txt +1 -1
- udata/templates/mail/discussion_closed.html +3 -3
- udata/templates/mail/discussion_closed.txt +1 -1
- udata/templates/mail/frequency_reminder.html +1 -1
- udata/templates/mail/frequency_reminder.txt +1 -1
- udata/templates/mail/membership_refused.html +1 -1
- udata/templates/mail/membership_request.html +3 -3
- udata/templates/mail/membership_request.txt +1 -1
- udata/templates/mail/new_discussion.html +3 -3
- udata/templates/mail/new_discussion.txt +1 -1
- udata/templates/mail/new_discussion_comment.html +3 -3
- udata/templates/mail/new_discussion_comment.txt +1 -1
- udata/templates/mail/new_member.html +2 -2
- udata/templates/mail/new_member.txt +1 -1
- udata/templates/mail/new_reuse.html +2 -12
- udata/templates/mail/new_reuse.txt +1 -3
- udata/templates/mail/user_mail_card.html +1 -1
- udata/tests/api/test_dataservices_api.py +2 -2
- udata/tests/api/test_datasets_api.py +2 -2
- udata/tests/api/test_reuses_api.py +2 -2
- udata/tests/api/test_topics_api.py +4 -4
- udata/tests/dataset/test_dataset_rdf.py +1 -9
- udata/tests/test_discussions.py +3 -3
- udata/tests/test_mail.py +8 -0
- udata/uris.py +28 -7
- {udata-10.6.1.dev36082.dist-info → udata-10.6.1.dev36098.dist-info}/METADATA +1 -1
- {udata-10.6.1.dev36082.dist-info → udata-10.6.1.dev36098.dist-info}/RECORD +100 -99
- {udata-10.6.1.dev36082.dist-info → udata-10.6.1.dev36098.dist-info}/LICENSE +0 -0
- {udata-10.6.1.dev36082.dist-info → udata-10.6.1.dev36098.dist-info}/WHEEL +0 -0
- {udata-10.6.1.dev36082.dist-info → udata-10.6.1.dev36098.dist-info}/entry_points.txt +0 -0
- {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,
|
|
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
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
"
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
udata/core/activity/api.py
CHANGED
udata/core/dataservices/api.py
CHANGED
|
@@ -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.
|
|
75
|
+
author_uri = dataservice.organization.url_for()
|
|
76
76
|
elif dataservice.owner:
|
|
77
77
|
author_name = dataservice.owner.fullname
|
|
78
|
-
author_uri = dataservice.owner.
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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",
|
udata/core/dataservices/rdf.py
CHANGED
|
@@ -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.
|
|
335
|
+
author_uri = dataset.organization.url_for()
|
|
336
336
|
elif dataset.owner:
|
|
337
337
|
author_name = dataset.owner.fullname
|
|
338
|
-
author_uri = dataset.owner.
|
|
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.
|
|
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
|
]
|
udata/core/dataset/api_fields.py
CHANGED
|
@@ -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.
|
|
208
|
-
|
|
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.
|
|
214
|
-
|
|
215
|
-
|
|
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.
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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.
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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.
|
|
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
|
|
udata/core/dataset/apiv2.py
CHANGED
|
@@ -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.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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",
|
|
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",
|
|
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
|
udata/core/dataset/models.py
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
723
|
-
return
|
|
726
|
+
def self_web_url(self, **kwargs):
|
|
727
|
+
return cdata_url(f"/datasets/{self._link_id(**kwargs)}/", **kwargs)
|
|
724
728
|
|
|
725
|
-
|
|
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":
|
|
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(
|
|
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))
|
udata/core/dataset/search.py
CHANGED
|
@@ -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.
|
|
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],
|
udata/core/discussions/api.py
CHANGED
|
@@ -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.
|
|
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),
|
udata/core/discussions/models.py
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
149
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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.
|
|
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.
|
|
158
|
+
message += f" de [{self.user.fullname}]({self.user.url_for()})"
|
|
163
159
|
|
|
164
160
|
return message
|
|
165
161
|
|