udata 9.0.1.dev29667__py2.py3-none-any.whl → 9.0.1.dev29696__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/dataservices/factories.py +19 -0
- udata/core/dataservices/rdf.py +55 -4
- udata/core/dataset/rdf.py +14 -8
- udata/core/discussions/models.py +20 -0
- udata/core/site/api.py +29 -1
- udata/core/site/rdf.py +6 -1
- udata/core/spam/models.py +6 -0
- udata/tests/plugin.py +2 -0
- udata/tests/site/test_site_rdf.py +50 -0
- udata/tests/test_discussions.py +24 -34
- {udata-9.0.1.dev29667.dist-info → udata-9.0.1.dev29696.dist-info}/METADATA +3 -1
- {udata-9.0.1.dev29667.dist-info → udata-9.0.1.dev29696.dist-info}/RECORD +16 -15
- {udata-9.0.1.dev29667.dist-info → udata-9.0.1.dev29696.dist-info}/LICENSE +0 -0
- {udata-9.0.1.dev29667.dist-info → udata-9.0.1.dev29696.dist-info}/WHEEL +0 -0
- {udata-9.0.1.dev29667.dist-info → udata-9.0.1.dev29696.dist-info}/entry_points.txt +0 -0
- {udata-9.0.1.dev29667.dist-info → udata-9.0.1.dev29696.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import factory
|
|
2
|
+
|
|
3
|
+
from udata.core.dataservices.models import Dataservice
|
|
4
|
+
from udata.core.organization.factories import OrganizationFactory
|
|
5
|
+
from udata.factories import ModelFactory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DataserviceFactory(ModelFactory):
|
|
9
|
+
class Meta:
|
|
10
|
+
model = Dataservice
|
|
11
|
+
|
|
12
|
+
title = factory.Faker('sentence')
|
|
13
|
+
description = factory.Faker('text')
|
|
14
|
+
base_api_url = factory.Faker('url')
|
|
15
|
+
|
|
16
|
+
class Params:
|
|
17
|
+
org = factory.Trait(
|
|
18
|
+
organization=factory.SubFactory(OrganizationFactory),
|
|
19
|
+
)
|
udata/core/dataservices/rdf.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from typing import List, Optional
|
|
4
|
-
from rdflib import RDF, Graph, URIRef
|
|
4
|
+
from rdflib import RDF, BNode, Graph, Literal, URIRef
|
|
5
5
|
|
|
6
6
|
from udata.core.dataservices.models import Dataservice, HarvestMetadata as HarvestDataserviceMetadata
|
|
7
7
|
from udata.core.dataset.models import Dataset, License
|
|
8
|
-
from udata.core.dataset.rdf import sanitize_html
|
|
8
|
+
from udata.core.dataset.rdf import dataset_to_graph_id, sanitize_html
|
|
9
9
|
from udata.harvest.models import HarvestSource
|
|
10
|
-
from udata.rdf import DCAT, DCT, contact_point_from_rdf, rdf_value, remote_url_from_rdf,
|
|
10
|
+
from udata.rdf import DCATAP, TAG_TO_EU_HVD_CATEGORIES, namespace_manager, DCAT, DCT, contact_point_from_rdf, rdf_value, remote_url_from_rdf, themes_from_rdf, url_from_rdf
|
|
11
|
+
from udata.uris import endpoint_for
|
|
11
12
|
|
|
12
13
|
def dataservice_from_rdf(graph: Graph, dataservice: Dataservice, node, all_datasets: List[Dataset]) -> Dataservice :
|
|
13
14
|
'''
|
|
@@ -55,4 +56,54 @@ def dataservice_from_rdf(graph: Graph, dataservice: Dataservice, node, all_datas
|
|
|
55
56
|
|
|
56
57
|
dataservice.tags = themes_from_rdf(d)
|
|
57
58
|
|
|
58
|
-
return dataservice
|
|
59
|
+
return dataservice
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def dataservice_to_rdf(dataservice, graph=None):
|
|
63
|
+
'''
|
|
64
|
+
Map a dataservice domain model to a DCAT/RDF graph
|
|
65
|
+
'''
|
|
66
|
+
# Use the unlocalized permalink to the dataset as URI when available
|
|
67
|
+
# unless there is already an upstream URI
|
|
68
|
+
if dataservice.harvest and dataservice.harvest.rdf_node_id_as_url:
|
|
69
|
+
id = URIRef(dataservice.harvest.rdf_node_id_as_url)
|
|
70
|
+
elif dataservice.id:
|
|
71
|
+
id = URIRef(endpoint_for('dataservices.show_redirect', 'api.dataservice',
|
|
72
|
+
dataservice=dataservice.id, _external=True))
|
|
73
|
+
else:
|
|
74
|
+
# Should not happen in production. Some test only
|
|
75
|
+
# `build()` a dataset without saving it to the DB.
|
|
76
|
+
id = BNode()
|
|
77
|
+
|
|
78
|
+
# Expose upstream identifier if present
|
|
79
|
+
if dataservice.harvest and dataservice.harvest.dct_identifier:
|
|
80
|
+
identifier = dataservice.harvest.dct_identifier
|
|
81
|
+
else:
|
|
82
|
+
identifier = dataservice.id
|
|
83
|
+
graph = graph or Graph(namespace_manager=namespace_manager)
|
|
84
|
+
|
|
85
|
+
d = graph.resource(id)
|
|
86
|
+
d.set(RDF.type, DCAT.DataService)
|
|
87
|
+
d.set(DCT.identifier, Literal(identifier))
|
|
88
|
+
d.set(DCT.title, Literal(dataservice.title))
|
|
89
|
+
d.set(DCT.description, Literal(dataservice.description))
|
|
90
|
+
d.set(DCT.issued, Literal(dataservice.created_at))
|
|
91
|
+
|
|
92
|
+
if dataservice.base_api_url:
|
|
93
|
+
d.set(DCAT.endpointURL, Literal(dataservice.base_api_url))
|
|
94
|
+
|
|
95
|
+
if dataservice.endpoint_description_url:
|
|
96
|
+
d.set(DCAT.endpointDescription, Literal(dataservice.endpoint_description_url))
|
|
97
|
+
|
|
98
|
+
for tag in dataservice.tags:
|
|
99
|
+
d.add(DCAT.keyword, Literal(tag))
|
|
100
|
+
|
|
101
|
+
# `dataset_to_graph_id(dataset)` URIRef may not exist in the current page
|
|
102
|
+
# but should exists in the catalog somewhere. Maybe we should create a Node
|
|
103
|
+
# with some basic information about this dataset (but this will return a page
|
|
104
|
+
# with more datasets than the page size… and could be problematic when processing the
|
|
105
|
+
# correct Node with all the information in a future page)
|
|
106
|
+
for dataset in dataservice.datasets:
|
|
107
|
+
d.add(DCAT.servesDataset, dataset_to_graph_id(dataset))
|
|
108
|
+
|
|
109
|
+
return d
|
udata/core/dataset/rdf.py
CHANGED
|
@@ -6,7 +6,7 @@ import json
|
|
|
6
6
|
import logging
|
|
7
7
|
|
|
8
8
|
from datetime import date
|
|
9
|
-
from typing import Optional
|
|
9
|
+
from typing import Optional, Union
|
|
10
10
|
from dateutil.parser import parse as parse_dt
|
|
11
11
|
from flask import current_app
|
|
12
12
|
from geomet import wkt
|
|
@@ -149,19 +149,25 @@ def resource_to_rdf(resource, dataset=None, graph=None, is_hvd=False):
|
|
|
149
149
|
return r
|
|
150
150
|
|
|
151
151
|
|
|
152
|
+
def dataset_to_graph_id(dataset: Dataset) -> Union[URIRef, BNode]:
|
|
153
|
+
if dataset.harvest and dataset.harvest.uri:
|
|
154
|
+
return URIRef(dataset.harvest.uri)
|
|
155
|
+
elif dataset.id:
|
|
156
|
+
return URIRef(endpoint_for('datasets.show_redirect', 'api.dataset',
|
|
157
|
+
dataset=dataset.id, _external=True))
|
|
158
|
+
else:
|
|
159
|
+
# Should not happen in production. Some test only
|
|
160
|
+
# `build()` a dataset without saving it to the DB.
|
|
161
|
+
return BNode()
|
|
162
|
+
|
|
152
163
|
def dataset_to_rdf(dataset, graph=None):
|
|
153
164
|
'''
|
|
154
165
|
Map a dataset domain model to a DCAT/RDF graph
|
|
155
166
|
'''
|
|
156
167
|
# Use the unlocalized permalink to the dataset as URI when available
|
|
157
168
|
# unless there is already an upstream URI
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
elif dataset.id:
|
|
161
|
-
id = URIRef(endpoint_for('datasets.show_redirect', 'api.dataset',
|
|
162
|
-
dataset=dataset.id, _external=True))
|
|
163
|
-
else:
|
|
164
|
-
id = BNode()
|
|
169
|
+
id = dataset_to_graph_id(dataset)
|
|
170
|
+
|
|
165
171
|
# Expose upstream identifier if present
|
|
166
172
|
if dataset.harvest and dataset.harvest.dct_identifier:
|
|
167
173
|
identifier = dataset.harvest.dct_identifier
|
udata/core/discussions/models.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
|
|
4
|
+
from flask_login import current_user
|
|
5
|
+
|
|
4
6
|
from udata.mongo import db
|
|
5
7
|
from udata.core.spam.models import SpamMixin, spam_protected
|
|
6
8
|
from .signals import (on_new_discussion, on_discussion_closed, on_new_discussion_comment)
|
|
@@ -67,6 +69,24 @@ class Discussion(SpamMixin, db.Document):
|
|
|
67
69
|
def embeds_to_check_for_spam(self):
|
|
68
70
|
return self.discussion[1:]
|
|
69
71
|
|
|
72
|
+
def spam_is_whitelisted(self) -> bool:
|
|
73
|
+
from udata.core.dataset.permissions import OwnablePermission
|
|
74
|
+
from udata.core.owned import Owned
|
|
75
|
+
|
|
76
|
+
if not current_user.is_authenticated:
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
if not isinstance(self.subject, Owned):
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
# When creating a new Discussion the `subject` is an empty model
|
|
83
|
+
# with only `id`. We need to fetch it from the database to have
|
|
84
|
+
# all the required information
|
|
85
|
+
if not self.subject.owner or not self.subject.organization:
|
|
86
|
+
self.subject.reload()
|
|
87
|
+
|
|
88
|
+
return OwnablePermission(self.subject).can()
|
|
89
|
+
|
|
70
90
|
@property
|
|
71
91
|
def external_url(self):
|
|
72
92
|
return self.subject.url_for(
|
udata/core/site/api.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from bson import ObjectId
|
|
2
2
|
|
|
3
3
|
from flask import request, redirect, url_for, json, make_response
|
|
4
|
+
from mongoengine import Q
|
|
4
5
|
|
|
5
6
|
from udata.api import api, API, fields
|
|
6
7
|
from udata.auth import admin_permission
|
|
8
|
+
from udata.core.dataservices.models import Dataservice
|
|
7
9
|
from udata.models import Dataset, Reuse
|
|
8
10
|
from udata.utils import multi_to_dict
|
|
9
11
|
from udata.rdf import (
|
|
@@ -109,7 +111,33 @@ class SiteRdfCatalogFormat(API):
|
|
|
109
111
|
if 'tag' in params:
|
|
110
112
|
datasets = datasets.filter(tags=params.get('tag', ''))
|
|
111
113
|
datasets = datasets.paginate(page, page_size)
|
|
112
|
-
|
|
114
|
+
|
|
115
|
+
# We need to add Dataservice to the catalog.
|
|
116
|
+
# In the best world, we want:
|
|
117
|
+
# - Keep the correct number of datasets on the page (if the requested page size is 100, we should have 100 datasets)
|
|
118
|
+
# - Have simple MongoDB queries
|
|
119
|
+
# - Do not duplicate the datasets (each dataset is present once in the catalog)
|
|
120
|
+
# - Do not duplicate the dataservices (each dataservice is present once in the catalog)
|
|
121
|
+
# - Every referenced dataset for one dataservices present on the page (hard to do)
|
|
122
|
+
#
|
|
123
|
+
# Multiple solutions are possible but none check all the constraints.
|
|
124
|
+
# The selected one is to put all the dataservices referencing at least one of the dataset on
|
|
125
|
+
# the page at the end of it. It means dataservices could be duplicated (present on multiple pages)
|
|
126
|
+
# and these dataservices may referenced some datasets not present in the current page. It's working
|
|
127
|
+
# if somebody is doing the same thing as us (keeping the list of all the datasets IDs for the entire catalog then
|
|
128
|
+
# listing all dataservices in a second pass)
|
|
129
|
+
# Another option is to do some tricky Mongo requests to order/group datasets by their presence in some dataservices but
|
|
130
|
+
# it could be really hard to do with a n..n relation.
|
|
131
|
+
# Let's keep this solution simple right now and iterate on it in the future.
|
|
132
|
+
dataservices_filter = Q(datasets__in=[d.id for d in datasets])
|
|
133
|
+
|
|
134
|
+
# On the first page, add all dataservices without datasets
|
|
135
|
+
if page == 1:
|
|
136
|
+
dataservices_filter = dataservices_filter | Q(datasets__size=0)
|
|
137
|
+
|
|
138
|
+
dataservices = Dataservice.objects.visible().filter(dataservices_filter)
|
|
139
|
+
|
|
140
|
+
catalog = build_catalog(current_site, datasets, dataservices=dataservices, format=format)
|
|
113
141
|
# bypass flask-restplus make_response, since graph_response
|
|
114
142
|
# is handling the content negociation directly
|
|
115
143
|
return make_response(*graph_response(catalog, format))
|
udata/core/site/rdf.py
CHANGED
|
@@ -5,6 +5,7 @@ from flask import url_for, current_app
|
|
|
5
5
|
from rdflib import Graph, URIRef, Literal, BNode
|
|
6
6
|
from rdflib.namespace import RDF, FOAF
|
|
7
7
|
|
|
8
|
+
from udata.core.dataservices.rdf import dataservice_to_rdf
|
|
8
9
|
from udata.core.dataset.rdf import dataset_to_rdf
|
|
9
10
|
from udata.core.organization.rdf import organization_to_rdf
|
|
10
11
|
from udata.core.user.rdf import user_to_rdf
|
|
@@ -13,7 +14,7 @@ from udata.utils import Paginable
|
|
|
13
14
|
from udata.uris import endpoint_for
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
def build_catalog(site, datasets, format=None):
|
|
17
|
+
def build_catalog(site, datasets, dataservices = [], format=None):
|
|
17
18
|
'''Build the DCAT catalog for this site'''
|
|
18
19
|
site_url = endpoint_for('site.home_redirect', 'api.site', _external=True)
|
|
19
20
|
catalog_url = url_for('api.site_rdf_catalog', _external=True)
|
|
@@ -40,6 +41,10 @@ def build_catalog(site, datasets, format=None):
|
|
|
40
41
|
rdf_dataset.add(DCT.publisher, organization_to_rdf(dataset.organization, graph))
|
|
41
42
|
catalog.add(DCAT.dataset, rdf_dataset)
|
|
42
43
|
|
|
44
|
+
for dataservice in dataservices:
|
|
45
|
+
rdf_dataservice = dataservice_to_rdf(dataservice, graph)
|
|
46
|
+
catalog.add(DCAT.DataService, rdf_dataservice)
|
|
47
|
+
|
|
43
48
|
if isinstance(datasets, Paginable):
|
|
44
49
|
paginate_catalog(catalog, graph, datasets, format, 'api.site_rdf_catalog_format')
|
|
45
50
|
|
udata/core/spam/models.py
CHANGED
|
@@ -67,6 +67,9 @@ class SpamMixin(object):
|
|
|
67
67
|
if not self.spam:
|
|
68
68
|
self.spam = SpamInfo(status=NOT_CHECKED, callbacks={})
|
|
69
69
|
|
|
70
|
+
if self.spam_is_whitelisted():
|
|
71
|
+
return
|
|
72
|
+
|
|
70
73
|
# The breadcrumb is useful during reporting to know where we came from
|
|
71
74
|
# in case of a potential spam inside an embed.
|
|
72
75
|
if breadcrumb is None:
|
|
@@ -139,6 +142,9 @@ class SpamMixin(object):
|
|
|
139
142
|
def embeds_to_check_for_spam(self):
|
|
140
143
|
return []
|
|
141
144
|
|
|
145
|
+
def spam_is_whitelisted(self) -> bool :
|
|
146
|
+
return False
|
|
147
|
+
|
|
142
148
|
def spam_report_message(self):
|
|
143
149
|
return f"Spam potentiel sur {type(self).__name__}"
|
|
144
150
|
|
udata/tests/plugin.py
CHANGED
|
@@ -8,6 +8,7 @@ from flask import json, template_rendered, url_for, current_app
|
|
|
8
8
|
from flask.testing import FlaskClient
|
|
9
9
|
from lxml import etree
|
|
10
10
|
from werkzeug.urls import url_encode
|
|
11
|
+
from flask_principal import Identity, identity_changed
|
|
11
12
|
|
|
12
13
|
from udata import settings
|
|
13
14
|
from udata.app import create_app
|
|
@@ -49,6 +50,7 @@ class TestClient(FlaskClient):
|
|
|
49
50
|
session['_fresh'] = True
|
|
50
51
|
session['_id'] = current_app.login_manager._session_identifier_generator()
|
|
51
52
|
current_app.login_manager._update_request_context_with_user(user)
|
|
53
|
+
identity_changed.send(current_app._get_current_object(), identity=Identity(user.id))
|
|
52
54
|
return user
|
|
53
55
|
|
|
54
56
|
def logout(self):
|
|
@@ -6,6 +6,7 @@ from rdflib import URIRef, Literal, Graph
|
|
|
6
6
|
from rdflib.namespace import RDF, FOAF
|
|
7
7
|
from rdflib.resource import Resource
|
|
8
8
|
|
|
9
|
+
from udata.core.dataservices.factories import DataserviceFactory
|
|
9
10
|
from udata.core.dataset.factories import DatasetFactory
|
|
10
11
|
from udata.core.dataset.models import Dataset
|
|
11
12
|
from udata.core.organization.factories import OrganizationFactory
|
|
@@ -244,3 +245,52 @@ class SiteRdfViewsTest:
|
|
|
244
245
|
|
|
245
246
|
for dat in datasets:
|
|
246
247
|
assert graph.value(dat, DCAT.keyword) == Literal('my-tag')
|
|
248
|
+
|
|
249
|
+
def test_catalog_rdf_dataservices(self, client):
|
|
250
|
+
dataset_a = DatasetFactory.create()
|
|
251
|
+
dataset_b = DatasetFactory.create()
|
|
252
|
+
dataset_c = DatasetFactory.create()
|
|
253
|
+
|
|
254
|
+
dataservice_a = DataserviceFactory.create(datasets=[dataset_a.id])
|
|
255
|
+
dataservice_b = DataserviceFactory.create(datasets=[dataset_b.id])
|
|
256
|
+
dataservice_x = DataserviceFactory.create(datasets=[dataset_a.id, dataset_c.id])
|
|
257
|
+
dataservice_y = DataserviceFactory.create(datasets=[])
|
|
258
|
+
|
|
259
|
+
response = client.get(url_for('api.site_rdf_catalog_format', format='xml'), headers={'Accept': 'application/xml'})
|
|
260
|
+
assert200(response)
|
|
261
|
+
|
|
262
|
+
graph = Graph().parse(data=response.data, format='xml')
|
|
263
|
+
|
|
264
|
+
datasets = list(graph.subjects(RDF.type, DCAT.Dataset))
|
|
265
|
+
assert len(datasets) == 3
|
|
266
|
+
|
|
267
|
+
dataservices = list(graph.subjects(RDF.type, DCAT.DataService))
|
|
268
|
+
assert len(dataservices) == 4
|
|
269
|
+
|
|
270
|
+
# Test first page contains the dataservice without dataset
|
|
271
|
+
response = client.get(url_for('api.site_rdf_catalog_format', format='xml', page_size=1), headers={'Accept': 'application/xml'})
|
|
272
|
+
assert200(response)
|
|
273
|
+
|
|
274
|
+
graph = Graph().parse(data=response.data, format='xml')
|
|
275
|
+
|
|
276
|
+
datasets = list(graph.subjects(RDF.type, DCAT.Dataset))
|
|
277
|
+
assert len(datasets) == 1
|
|
278
|
+
assert str(graph.value(datasets[0], DCT.identifier)) == str(dataset_c.id)
|
|
279
|
+
|
|
280
|
+
dataservices = list(graph.subjects(RDF.type, DCAT.DataService))
|
|
281
|
+
assert len(dataservices) == 2
|
|
282
|
+
assert sorted([str(d.id) for d in [dataservice_x, dataservice_y]]) == sorted([str(graph.value(d, DCT.identifier)) for d in dataservices])
|
|
283
|
+
|
|
284
|
+
# Test second page doesn't contains the dataservice without dataset
|
|
285
|
+
response = client.get(url_for('api.site_rdf_catalog_format', format='xml', page_size=1, page=2), headers={'Accept': 'application/xml'})
|
|
286
|
+
assert200(response)
|
|
287
|
+
|
|
288
|
+
graph = Graph().parse(data=response.data, format='xml')
|
|
289
|
+
|
|
290
|
+
datasets = list(graph.subjects(RDF.type, DCAT.Dataset))
|
|
291
|
+
assert len(datasets) == 1
|
|
292
|
+
assert str(graph.value(datasets[0], DCT.identifier)) == str(dataset_b.id)
|
|
293
|
+
|
|
294
|
+
dataservices = list(graph.subjects(RDF.type, DCAT.DataService))
|
|
295
|
+
assert len(dataservices) == 1
|
|
296
|
+
assert str(graph.value(dataservices[0], DCT.identifier)) == str(dataservice_b.id)
|
udata/tests/test_discussions.py
CHANGED
|
@@ -76,7 +76,7 @@ class DiscussionsTest(APITestCase):
|
|
|
76
76
|
discussion_id = None
|
|
77
77
|
def check_signal(args):
|
|
78
78
|
self.assertIsNotNone(discussion_id)
|
|
79
|
-
self.assertIn(f'http://local.test/api/1/datasets/{dataset.
|
|
79
|
+
self.assertIn(f'http://local.test/api/1/datasets/{dataset.slug}/#discussion-{discussion_id}', args[1]['message'])
|
|
80
80
|
|
|
81
81
|
with assert_emit(on_new_potential_spam, assertions_callback=check_signal):
|
|
82
82
|
response = self.post(url_for('api.discussions'), {
|
|
@@ -122,6 +122,29 @@ class DiscussionsTest(APITestCase):
|
|
|
122
122
|
self.assertStatus(response, 200)
|
|
123
123
|
self.assertFalse(discussion.reload().is_spam())
|
|
124
124
|
|
|
125
|
+
|
|
126
|
+
@pytest.mark.options(SPAM_WORDS=['spam'])
|
|
127
|
+
def test_spam_by_owner(self):
|
|
128
|
+
user = self.login()
|
|
129
|
+
dataset = Dataset.objects.create(title='Test dataset', owner=user)
|
|
130
|
+
|
|
131
|
+
with assert_not_emit(on_new_potential_spam):
|
|
132
|
+
response = self.post(url_for('api.discussions'), {
|
|
133
|
+
'title': 'spam and blah',
|
|
134
|
+
'comment': 'bla bla',
|
|
135
|
+
'subject': {
|
|
136
|
+
'class': 'Dataset',
|
|
137
|
+
'id': dataset.id,
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
self.assertStatus(response, 201)
|
|
141
|
+
|
|
142
|
+
with assert_not_emit(on_new_potential_spam):
|
|
143
|
+
response = self.post(url_for('api.discussion', id=response.json['id']), {
|
|
144
|
+
'comment': 'A comment with spam by owner'
|
|
145
|
+
})
|
|
146
|
+
self.assertStatus(response, 200)
|
|
147
|
+
|
|
125
148
|
@pytest.mark.options(SPAM_WORDS=['spam'])
|
|
126
149
|
def test_spam_in_new_discussion_comment(self):
|
|
127
150
|
self.login()
|
|
@@ -495,39 +518,6 @@ class DiscussionsTest(APITestCase):
|
|
|
495
518
|
{'comment': "can't comment"})
|
|
496
519
|
self.assert403(response)
|
|
497
520
|
|
|
498
|
-
@pytest.mark.options(SPAM_WORDS=['spam'], SPAM_ALLOWED_LANGS=['fr'])
|
|
499
|
-
def test_close_discussion_with_spam(self):
|
|
500
|
-
owner = self.login()
|
|
501
|
-
dataset = Dataset.objects.create(title='Test dataset', owner=owner)
|
|
502
|
-
user = UserFactory()
|
|
503
|
-
message = Message(content='Premier message', posted_by=user)
|
|
504
|
-
discussion = Discussion.objects.create(
|
|
505
|
-
subject=dataset,
|
|
506
|
-
user=user,
|
|
507
|
-
title='test discussion',
|
|
508
|
-
discussion=[message]
|
|
509
|
-
)
|
|
510
|
-
on_new_discussion.send(discussion) # Updating metrics.
|
|
511
|
-
|
|
512
|
-
with assert_not_emit(on_discussion_closed):
|
|
513
|
-
with assert_emit(on_new_potential_spam):
|
|
514
|
-
response = self.post(url_for('api.discussion', id=discussion.id), {
|
|
515
|
-
'comment': 'This is a suspicious, real suspicious message in english.',
|
|
516
|
-
'close': True,
|
|
517
|
-
})
|
|
518
|
-
self.assert200(response)
|
|
519
|
-
|
|
520
|
-
discussion.reload()
|
|
521
|
-
self.assertFalse(discussion.is_spam())
|
|
522
|
-
self.assertTrue(discussion.discussion[1].is_spam())
|
|
523
|
-
self.assertTrue('signal_close' in discussion.discussion[1].spam.callbacks)
|
|
524
|
-
|
|
525
|
-
with assert_emit(on_discussion_closed):
|
|
526
|
-
admin = self.login(AdminFactory())
|
|
527
|
-
response = self.delete(url_for('api.discussion_comment_spam', id=discussion.id, cidx=1))
|
|
528
|
-
self.assertStatus(response, 200)
|
|
529
|
-
self.assertFalse(discussion.reload().discussion[1].is_spam())
|
|
530
|
-
|
|
531
521
|
def test_close_discussion_permissions(self):
|
|
532
522
|
dataset = Dataset.objects.create(title='Test dataset')
|
|
533
523
|
user = UserFactory()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: udata
|
|
3
|
-
Version: 9.0.1.
|
|
3
|
+
Version: 9.0.1.dev29696
|
|
4
4
|
Summary: Open data portal
|
|
5
5
|
Home-page: https://github.com/opendatateam/udata
|
|
6
6
|
Author: Opendata Team
|
|
@@ -147,7 +147,9 @@ It is collectively taken care of by members of the
|
|
|
147
147
|
- Improve URL validation errors [#3063](https://github.com/opendatateam/udata/pull/3063) [#2768](https://github.com/opendatateam/udata/pull/2768)
|
|
148
148
|
- Do not return full dataset objects on dataservices endpoints [#3068](https://github.com/opendatateam/udata/pull/3068)
|
|
149
149
|
- Update markdown base settings [#3067](https://github.com/opendatateam/udata/pull/3067)
|
|
150
|
+
- Prevent tagging as spam owners' messages [#3071](https://github.com/opendatateam/udata/pull/3071)
|
|
150
151
|
- Add api endpoint /me/org_topics/ [#3070](https://github.com/opendatateam/udata/pull/3070)
|
|
152
|
+
- Expose dataservices in RDF catalog [#3058](https://github.com/opendatateam/udata/pull/3058)
|
|
151
153
|
|
|
152
154
|
## 9.0.0 (2024-06-07)
|
|
153
155
|
|
|
@@ -80,9 +80,10 @@ udata/core/contact_point/forms.py,sha256=ggLhSJ1IRn5MclrhydckjAxwr4fFZxgAD4huSSu
|
|
|
80
80
|
udata/core/contact_point/models.py,sha256=NlNKureCpzgTLJuGviZPjNx-ABYRp4j2L-ur9Gmixao,324
|
|
81
81
|
udata/core/dataservices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
82
82
|
udata/core/dataservices/api.py,sha256=rjCU55NNGgCDRlurfhJUT2byBGJWN5coM8b7AApzEew,3090
|
|
83
|
+
udata/core/dataservices/factories.py,sha256=_6Rp1qGB7z3BbPcIshj8LNsxUpublq0sf4KVTjBziPI,506
|
|
83
84
|
udata/core/dataservices/models.py,sha256=zMhzjnXm1p5GHZU1lYgMqft5u7iFyX0BIvNB1hM4D6Q,5868
|
|
84
85
|
udata/core/dataservices/permissions.py,sha256=X9Bh8e0pnx6OgeEf6NowXZUiwyreUa6UY479B16cCqs,175
|
|
85
|
-
udata/core/dataservices/rdf.py,sha256=
|
|
86
|
+
udata/core/dataservices/rdf.py,sha256=YfHI60f0uJq0kSUKxSvFJzskbRH7c9yOj1ugcH6iLpA,4654
|
|
86
87
|
udata/core/dataservices/tasks.py,sha256=NOWcTPoLasMrrvq9EkwQMGlUbQQmi_l3s815K-mtZTM,971
|
|
87
88
|
udata/core/dataset/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
88
89
|
udata/core/dataset/actions.py,sha256=3pzBg_qOR-w7fwPpTOKUHXWC9lkjALbOn1UQFmmT-s0,1199
|
|
@@ -100,7 +101,7 @@ udata/core/dataset/forms.py,sha256=VJCsGtgzhQgLW-M-J4ObpQ8o6c_saC3TTc1Js33m3sQ,6
|
|
|
100
101
|
udata/core/dataset/models.py,sha256=q6AknSHYNjeUyVKoFgxEA0r9lSvMa_a4tJ3fLOzL03M,36053
|
|
101
102
|
udata/core/dataset/permissions.py,sha256=3F2J7le3_rEYNhh88o3hSRWHAAt01_yHJM6RPmvCrRo,1090
|
|
102
103
|
udata/core/dataset/preview.py,sha256=puPKT3fBD7ezAcT6owh0JK1_rGNDFZOqgT223qGn3LY,2597
|
|
103
|
-
udata/core/dataset/rdf.py,sha256=
|
|
104
|
+
udata/core/dataset/rdf.py,sha256=BujVPk510wLQzWPKZhZ1rGruQZSCr0hTDz9b1pmupxI,22695
|
|
104
105
|
udata/core/dataset/search.py,sha256=Ca23ljX7TZiKZKiAZQIH5xwS2tjXQcTDzieAJd-E22c,5314
|
|
105
106
|
udata/core/dataset/signals.py,sha256=TK6dfrOUitZZkGGOh6XmhYqYvIjzZpI70JTLV4k-JRM,161
|
|
106
107
|
udata/core/dataset/tasks.py,sha256=UYdm_O9hA0yo7cCNQFvZE9pGzUshkc3Wr4Gpel2aupU,8434
|
|
@@ -111,7 +112,7 @@ udata/core/discussions/constants.py,sha256=nbZgXESpg0TykIGPxW8xUtHtk7TwQoyOL0Ky4
|
|
|
111
112
|
udata/core/discussions/factories.py,sha256=NQd_tD0Izrm67uO5HuuClmluteACrRd9PHrb2IkQ0P0,350
|
|
112
113
|
udata/core/discussions/forms.py,sha256=daDc8vPDhaXjiEyniugiRC6pyv6OsflgIyO-KoAn6i8,828
|
|
113
114
|
udata/core/discussions/metrics.py,sha256=qtgyDhM1aPgh8bGU-h-962EKR3J44imC155JVi6jvJI,362
|
|
114
|
-
udata/core/discussions/models.py,sha256=
|
|
115
|
+
udata/core/discussions/models.py,sha256=B9tgaN6rqR4DLfHR6_jiZnaadVNZn0n08zmJRh7TPm8,4041
|
|
115
116
|
udata/core/discussions/notifications.py,sha256=1lsu8WyyOL4bdt0lx6IW5wTxmQ5gS_7FoncN53g3ToQ,927
|
|
116
117
|
udata/core/discussions/permissions.py,sha256=q3tXNuNmuXCvZhKyudROcwXF53l-IeDR3pfKSh_hIL0,636
|
|
117
118
|
udata/core/discussions/signals.py,sha256=zjuF-TiFwu9U_RcgZtHB2Z3W4oBx5hVZy6CCAl5Ekug,543
|
|
@@ -176,16 +177,16 @@ udata/core/reuse/search.py,sha256=I3FX-9YP_cEqw5GFwFv2iqLZExeaQXsak13RMSCN7t8,28
|
|
|
176
177
|
udata/core/reuse/signals.py,sha256=2vv668u8miJ9t6H-FwRgXcWwsYMQem3oLNXp36rKtno,155
|
|
177
178
|
udata/core/reuse/tasks.py,sha256=FgkUN6s1uTDFQgiImiOEfIMYYW984bxxDtlGK0uZ0yY,1530
|
|
178
179
|
udata/core/site/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
179
|
-
udata/core/site/api.py,sha256=
|
|
180
|
+
udata/core/site/api.py,sha256=nsHTfjFQMp2gdWQk2Y-CY8HDdpoDlClYcJxJcc-xa9E,6199
|
|
180
181
|
udata/core/site/factories.py,sha256=8-RwP6QRCxXqWF4UHIkm2NfWRP2p8Ktc5RSY-4jgnfk,378
|
|
181
182
|
udata/core/site/forms.py,sha256=lPnxm0MDhK7_ORzPgJLcCAPMj59hDrJ80gwRSW-jtIo,469
|
|
182
183
|
udata/core/site/models.py,sha256=96IZHtPaMio7VMNCdP1Km_SL1GlMErwBuDqgUXfzKVY,5981
|
|
183
|
-
udata/core/site/rdf.py,sha256=
|
|
184
|
+
udata/core/site/rdf.py,sha256=2yP3oUxTZBuP96TFMnh_68lO2Xuzpgn1Zwehg0POK5Y,2094
|
|
184
185
|
udata/core/spam/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
185
186
|
udata/core/spam/api.py,sha256=8tVRPorw56kxgN64kme5nLkUfh8Gai9QyqT8aNQn9Xo,1674
|
|
186
187
|
udata/core/spam/constants.py,sha256=M-wvYlcFnpUDortccIKFHWZ45vbNuMPWSvqKm2itn4w,143
|
|
187
188
|
udata/core/spam/fields.py,sha256=ppazY9bGnz7mujmDndbxG3pPG_1HDUJCbIufxyD1UNQ,310
|
|
188
|
-
udata/core/spam/models.py,sha256=
|
|
189
|
+
udata/core/spam/models.py,sha256=sGA4TMXpKbyEbwW8Gk6JpPIiV91JQ4SxWjdVtpPIyls,8093
|
|
189
190
|
udata/core/spam/signals.py,sha256=4VVLyC2I39LFAot4P56nHzY0xumjMBDz_N0Ff_kgBd0,159
|
|
190
191
|
udata/core/spam/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
191
192
|
udata/core/spam/tests/test_spam.py,sha256=W1Ck_rsnURhFi0fy5xOO0CPpW9MuUFbr-NmPZdk5R4Q,676
|
|
@@ -571,10 +572,10 @@ udata/tests/__init__.py,sha256=BezijRRI6dPPiEWWjLsJFLrhhfsTZgcctcxhVfp4j70,2332
|
|
|
571
572
|
udata/tests/es-fake-result.json,sha256=z0CX9Gs-NRj49dmtdFvw8ZKsAbMhDt97Na6JX3ucX34,3155
|
|
572
573
|
udata/tests/helpers.py,sha256=aaifyevJ1Z8CZ8htRrl8OCG5hGcaHfj0lL8iMEKds9w,6022
|
|
573
574
|
udata/tests/models.py,sha256=_V0smMb1Z6p3aZv6PorzNN-HiNt_B46Ox1fqXrTJEqk,238
|
|
574
|
-
udata/tests/plugin.py,sha256=
|
|
575
|
+
udata/tests/plugin.py,sha256=p8TZcFvlywaLeMXLQOBjZ0wgJM8d11pLYmMtLmXjtxg,11430
|
|
575
576
|
udata/tests/schemas.json,sha256=szM1jDpkogfOG4xWbjIGjLgG8l9-ZyE3JKQtecJyD1E,4990
|
|
576
577
|
udata/tests/test_activity.py,sha256=spWfhueuLze0kD-pAnegiL3_Kv5io155jQuFI4sjN7I,3258
|
|
577
|
-
udata/tests/test_discussions.py,sha256=
|
|
578
|
+
udata/tests/test_discussions.py,sha256=mNRA9PkAkUNLQRmbLjvjF2878yY5jsIuA0_wwiLCGHk,30395
|
|
578
579
|
udata/tests/test_i18n.py,sha256=BU9E7OoIkJw5tv5JYGLjDGBDsti2HuQ_3OWDKnBxnaM,3191
|
|
579
580
|
udata/tests/test_linkchecker.py,sha256=KxV1-PuuuqogkHf3jP6JhRsc2QG2dFmFB-vSHOiHkuU,10374
|
|
580
581
|
udata/tests/test_mail.py,sha256=LK_fOBbFoqbwdaIEXO9XyGPzxY9nrT9FjyD_dlglUdQ,1643
|
|
@@ -667,7 +668,7 @@ udata/tests/site/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
|
667
668
|
udata/tests/site/test_site_api.py,sha256=XIjRH-iiwKdwmX145bQflxOatfIChi5RLikIMwJSbjQ,2276
|
|
668
669
|
udata/tests/site/test_site_metrics.py,sha256=Sn9dQORwe-fvoyA7ZAnF63Cq5CwUGq1myH8Xe0EtfDA,2263
|
|
669
670
|
udata/tests/site/test_site_model.py,sha256=nAx9JjEKdfjdw1Kj5Rs7P5FEePoATtCuOYPiSXEnVD0,1313
|
|
670
|
-
udata/tests/site/test_site_rdf.py,sha256=
|
|
671
|
+
udata/tests/site/test_site_rdf.py,sha256=UeGzBkV2JW75lm7g4zMqFNHMj7QBFVI2xg0cmK25Elg,12695
|
|
671
672
|
udata/tests/user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
672
673
|
udata/tests/user/test_user_rdf.py,sha256=HrKirMURUXS9N3If_NMb8qnfJ4kE9IZymR1SPcNvlF0,1851
|
|
673
674
|
udata/tests/workers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -690,9 +691,9 @@ udata/translations/pt/LC_MESSAGES/udata.mo,sha256=iAUNwbI8ESi8MHkE3ZCYCSIXfFC27z
|
|
|
690
691
|
udata/translations/pt/LC_MESSAGES/udata.po,sha256=uTmbHfzyFWrVXUkKSuNFzbGpX7EkUuBdD8fE04d3v5g,44572
|
|
691
692
|
udata/translations/sr/LC_MESSAGES/udata.mo,sha256=1MbQHvKKNUwzMBWLNsH1qqBehO3aILhQiMhi5u1bY8E,28553
|
|
692
693
|
udata/translations/sr/LC_MESSAGES/udata.po,sha256=AAryt27Gbkhk7FntCCU8_e7HSXATfsAQhwFOFC8CAj0,51152
|
|
693
|
-
udata-9.0.1.
|
|
694
|
-
udata-9.0.1.
|
|
695
|
-
udata-9.0.1.
|
|
696
|
-
udata-9.0.1.
|
|
697
|
-
udata-9.0.1.
|
|
698
|
-
udata-9.0.1.
|
|
694
|
+
udata-9.0.1.dev29696.dist-info/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
|
|
695
|
+
udata-9.0.1.dev29696.dist-info/METADATA,sha256=7FzsYbr-nRy6TOozV0-1HW9Hm3WF03byQS1SSR4tfow,125343
|
|
696
|
+
udata-9.0.1.dev29696.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
|
|
697
|
+
udata-9.0.1.dev29696.dist-info/entry_points.txt,sha256=3SKiqVy4HUqxf6iWspgMqH8d88Htk6KoLbG1BU-UddQ,451
|
|
698
|
+
udata-9.0.1.dev29696.dist-info/top_level.txt,sha256=39OCg-VWFWOq4gCKnjKNu-s3OwFlZIu_dVH8Gl6ndHw,12
|
|
699
|
+
udata-9.0.1.dev29696.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|