udata 10.9.1.dev37462__py2.py3-none-any.whl → 10.9.1.dev37604__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/__init__.py +0 -1
- udata/core/dataset/api.py +1 -1
- udata/core/dataset/search.py +5 -2
- udata/core/dataset/tasks.py +2 -5
- udata/core/reuse/tasks.py +3 -0
- udata/core/topic/__init__.py +1 -0
- udata/core/topic/api_fields.py +87 -0
- udata/core/topic/apiv2.py +116 -194
- udata/core/topic/factories.py +69 -8
- udata/core/topic/forms.py +58 -4
- udata/core/topic/models.py +65 -20
- udata/core/topic/parsers.py +40 -0
- udata/core/topic/tasks.py +11 -0
- udata/forms/fields.py +8 -1
- udata/harvest/backends/dcat.py +41 -20
- udata/harvest/tests/test_dcat_backend.py +89 -0
- udata/migrations/2025-05-26-migrate-topics-to-elements.py +59 -0
- udata/migrations/2025-06-02-delete-topic-name-index.py +19 -0
- udata/static/chunks/{11.51d706fb9521c16976bc.js → 11.822f6ccb39c92c796d13.js} +3 -3
- udata/static/chunks/{11.51d706fb9521c16976bc.js.map → 11.822f6ccb39c92c796d13.js.map} +1 -1
- udata/static/chunks/{13.f29411b06be1883356a3.js → 13.d9c1735d14038b94c17e.js} +2 -2
- udata/static/chunks/{13.f29411b06be1883356a3.js.map → 13.d9c1735d14038b94c17e.js.map} +1 -1
- udata/static/chunks/{17.3bd0340930d4a314ce9c.js → 17.81c57c0dedf812e43013.js} +2 -2
- udata/static/chunks/{17.3bd0340930d4a314ce9c.js.map → 17.81c57c0dedf812e43013.js.map} +1 -1
- udata/static/chunks/{8.b966402f5d680d4bdf4a.js → 8.0f42630e6d8ff782928e.js} +2 -2
- udata/static/chunks/{8.b966402f5d680d4bdf4a.js.map → 8.0f42630e6d8ff782928e.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/tasks.py +1 -0
- udata/tests/api/test_datasets_api.py +3 -2
- udata/tests/apiv2/test_me_api.py +2 -2
- udata/tests/apiv2/test_topics.py +457 -127
- udata/tests/dataset/test_dataset_tasks.py +7 -2
- udata/tests/reuse/test_reuse_task.py +9 -0
- udata/tests/search/test_adapter.py +43 -0
- udata/tests/test_topics.py +19 -8
- udata/tests/topic/test_topic_tasks.py +27 -0
- {udata-10.9.1.dev37462.dist-info → udata-10.9.1.dev37604.dist-info}/METADATA +4 -2
- {udata-10.9.1.dev37462.dist-info → udata-10.9.1.dev37604.dist-info}/RECORD +43 -40
- udata/core/topic/api.py +0 -145
- udata/tests/api/test_topics_api.py +0 -284
- {udata-10.9.1.dev37462.dist-info → udata-10.9.1.dev37604.dist-info}/LICENSE +0 -0
- {udata-10.9.1.dev37462.dist-info → udata-10.9.1.dev37604.dist-info}/WHEEL +0 -0
- {udata-10.9.1.dev37462.dist-info → udata-10.9.1.dev37604.dist-info}/entry_points.txt +0 -0
- {udata-10.9.1.dev37462.dist-info → udata-10.9.1.dev37604.dist-info}/top_level.txt +0 -0
udata/tests/apiv2/test_topics.py
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
|
+
import pytest
|
|
1
2
|
from flask import url_for
|
|
2
3
|
|
|
3
4
|
from udata.core.dataset.factories import DatasetFactory
|
|
5
|
+
from udata.core.discussions.models import Discussion
|
|
4
6
|
from udata.core.organization.factories import OrganizationFactory
|
|
7
|
+
from udata.core.organization.models import Member
|
|
5
8
|
from udata.core.reuse.factories import ReuseFactory
|
|
6
9
|
from udata.core.spatial.factories import SpatialCoverageFactory
|
|
7
|
-
from udata.core.
|
|
10
|
+
from udata.core.spatial.models import spatial_granularities
|
|
11
|
+
from udata.core.topic import DEFAULT_PAGE_SIZE
|
|
12
|
+
from udata.core.topic.factories import (
|
|
13
|
+
TopicElementDatasetFactory,
|
|
14
|
+
TopicElementFactory,
|
|
15
|
+
TopicElementReuseFactory,
|
|
16
|
+
TopicFactory,
|
|
17
|
+
TopicWithElementsFactory,
|
|
18
|
+
)
|
|
19
|
+
from udata.core.topic.models import Topic, TopicElement
|
|
8
20
|
from udata.core.user.factories import UserFactory
|
|
9
21
|
from udata.tests.api import APITestCase
|
|
22
|
+
from udata.tests.api.test_datasets_api import SAMPLE_GEOM
|
|
10
23
|
from udata.tests.features.territories import create_geozones_fixtures
|
|
11
24
|
|
|
12
25
|
|
|
13
|
-
class
|
|
26
|
+
class TopicsListAPITest(APITestCase):
|
|
14
27
|
modules = []
|
|
15
28
|
|
|
16
29
|
def test_topic_api_list(self):
|
|
@@ -21,7 +34,6 @@ class TopicsAPITest(APITestCase):
|
|
|
21
34
|
|
|
22
35
|
tag_topic_1 = TopicFactory(tags=["my-tag-shared", "my-tag-1"])
|
|
23
36
|
tag_topic_2 = TopicFactory(tags=["my-tag-shared", "my-tag-2"])
|
|
24
|
-
name_topic = TopicFactory(name="topic-for-query")
|
|
25
37
|
private_topic = TopicFactory(private=True)
|
|
26
38
|
geozone_topic = TopicFactory(spatial=SpatialCoverageFactory(zones=[paca.id]))
|
|
27
39
|
granularity_topic = TopicFactory(spatial=SpatialCoverageFactory(granularity="country"))
|
|
@@ -32,16 +44,10 @@ class TopicsAPITest(APITestCase):
|
|
|
32
44
|
response = self.get(url_for("apiv2.topics_list"))
|
|
33
45
|
assert response.status_code == 200
|
|
34
46
|
data = response.json["data"]
|
|
35
|
-
assert len(data) ==
|
|
47
|
+
assert len(data) == 7
|
|
36
48
|
|
|
37
49
|
hateoas_fields = ["rel", "href", "type", "total"]
|
|
38
|
-
assert all(k in data[0]["
|
|
39
|
-
assert all(k in data[0]["reuses"] for k in hateoas_fields)
|
|
40
|
-
|
|
41
|
-
response = self.get(url_for("apiv2.topics_list", q="topic-for"))
|
|
42
|
-
assert response.status_code == 200
|
|
43
|
-
assert len(response.json["data"]) == 1
|
|
44
|
-
assert response.json["data"][0]["id"] == str(name_topic.id)
|
|
50
|
+
assert all(k in data[0]["elements"] for k in hateoas_fields)
|
|
45
51
|
|
|
46
52
|
response = self.get(url_for("apiv2.topics_list", tag=["my-tag-shared", "my-tag-1"]))
|
|
47
53
|
assert response.status_code == 200
|
|
@@ -56,53 +62,94 @@ class TopicsAPITest(APITestCase):
|
|
|
56
62
|
set([t["id"] for t in response.json["data"]]),
|
|
57
63
|
)
|
|
58
64
|
|
|
59
|
-
response = self.get(url_for("
|
|
65
|
+
response = self.get(url_for("apiv2.topics_list", include_private="true"))
|
|
60
66
|
assert response.status_code == 200
|
|
61
|
-
assert len(response.json["data"]) ==
|
|
67
|
+
assert len(response.json["data"]) == 7
|
|
62
68
|
# we're not logged in, so the private topic does not appear
|
|
63
69
|
assert str(private_topic.id) not in [t["id"] for t in response.json["data"]]
|
|
64
70
|
|
|
65
|
-
response = self.get(url_for("
|
|
71
|
+
response = self.get(url_for("apiv2.topics_list", geozone=paca.id))
|
|
66
72
|
assert response.status_code == 200
|
|
67
73
|
assert len(response.json["data"]) == 1
|
|
68
74
|
assert str(geozone_topic.id) in [t["id"] for t in response.json["data"]]
|
|
69
75
|
|
|
70
|
-
response = self.get(url_for("
|
|
76
|
+
response = self.get(url_for("apiv2.topics_list", granularity="country"))
|
|
71
77
|
assert response.status_code == 200
|
|
72
78
|
assert len(response.json["data"]) == 1
|
|
73
79
|
assert str(granularity_topic.id) in [t["id"] for t in response.json["data"]]
|
|
74
80
|
|
|
75
|
-
response = self.get(url_for("
|
|
81
|
+
response = self.get(url_for("apiv2.topics_list", featured="true"))
|
|
76
82
|
assert response.status_code == 200
|
|
77
83
|
assert len(response.json["data"]) == 1
|
|
78
84
|
assert str(featured_topic.id) in [t["id"] for t in response.json["data"]]
|
|
79
85
|
|
|
80
|
-
response = self.get(url_for("
|
|
86
|
+
response = self.get(url_for("apiv2.topics_list", featured="false"))
|
|
81
87
|
assert response.status_code == 200
|
|
82
|
-
assert len(response.json["data"]) ==
|
|
88
|
+
assert len(response.json["data"]) == 6
|
|
83
89
|
assert str(featured_topic.id) not in [t["id"] for t in response.json["data"]]
|
|
84
90
|
|
|
85
|
-
response = self.get(url_for("
|
|
91
|
+
response = self.get(url_for("apiv2.topics_list", owner=owner.id))
|
|
86
92
|
assert response.status_code == 200
|
|
87
93
|
assert len(response.json["data"]) == 1
|
|
88
94
|
assert str(owner_topic.id) in [t["id"] for t in response.json["data"]]
|
|
89
95
|
|
|
90
|
-
response = self.get(url_for("
|
|
96
|
+
response = self.get(url_for("apiv2.topics_list", organization=org.id))
|
|
91
97
|
assert response.status_code == 200
|
|
92
98
|
assert len(response.json["data"]) == 1
|
|
93
99
|
assert str(org_topic.id) in [t["id"] for t in response.json["data"]]
|
|
94
100
|
|
|
101
|
+
def test_topic_api_list_search(self):
|
|
102
|
+
topic = TopicFactory(name="topic-for-test")
|
|
103
|
+
response = self.get(url_for("apiv2.topics_list", q="topic-for"))
|
|
104
|
+
assert response.status_code == 200
|
|
105
|
+
assert len(response.json["data"]) == 1
|
|
106
|
+
assert response.json["data"][0]["id"] == str(topic.id)
|
|
107
|
+
|
|
108
|
+
topic = TopicFactory(name="aménagement")
|
|
109
|
+
response = self.get(url_for("apiv2.topics_list", q="amenagement"))
|
|
110
|
+
assert response.status_code == 200
|
|
111
|
+
assert len(response.json["data"]) == 1
|
|
112
|
+
assert response.json["data"][0]["id"] == str(topic.id)
|
|
113
|
+
|
|
114
|
+
# this is because we have an "AND" clause in TopicApiParser for q
|
|
115
|
+
topic = TopicFactory(name="aménagement sols")
|
|
116
|
+
response = self.get(url_for("apiv2.topics_list", q="amenagement urbain"))
|
|
117
|
+
assert response.status_code == 200
|
|
118
|
+
assert len(response.json["data"]) == 0
|
|
119
|
+
|
|
120
|
+
def test_topic_api_list_search_description(self):
|
|
121
|
+
topic = TopicFactory(name="xxx", description="aménagement")
|
|
122
|
+
response = self.get(url_for("apiv2.topics_list", q="amenagement"))
|
|
123
|
+
assert response.status_code == 200
|
|
124
|
+
assert len(response.json["data"]) == 1
|
|
125
|
+
assert response.json["data"][0]["id"] == str(topic.id)
|
|
126
|
+
|
|
127
|
+
# TODO: this would work with the following index on Topic,
|
|
128
|
+
# but we need to find a way to inject the language from config:
|
|
129
|
+
# meta = {
|
|
130
|
+
# "indexes": [
|
|
131
|
+
# {"fields": ["$name", "$description"], 'default_language': 'fr'}
|
|
132
|
+
# ]
|
|
133
|
+
# }
|
|
134
|
+
@pytest.mark.skip()
|
|
135
|
+
def test_topic_api_list_search_advanced(self):
|
|
136
|
+
topic = TopicFactory(name="plans d'eau")
|
|
137
|
+
response = self.get(url_for("apiv2.topics_list", q="eau"))
|
|
138
|
+
assert response.status_code == 200
|
|
139
|
+
assert len(response.json["data"]) == 1
|
|
140
|
+
assert response.json["data"][0]["id"] == str(topic.id)
|
|
141
|
+
|
|
95
142
|
def test_topic_api_list_authenticated(self):
|
|
96
143
|
owner = self.login()
|
|
97
144
|
|
|
98
145
|
private_topic = TopicFactory(private=True)
|
|
99
146
|
private_topic_owner = TopicFactory(private=True, owner=owner)
|
|
100
147
|
|
|
101
|
-
response = self.get(url_for("
|
|
148
|
+
response = self.get(url_for("apiv2.topics_list"))
|
|
102
149
|
assert response.status_code == 200
|
|
103
150
|
assert len(response.json["data"]) == 0
|
|
104
151
|
|
|
105
|
-
response = self.get(url_for("
|
|
152
|
+
response = self.get(url_for("apiv2.topics_list", include_private="true"))
|
|
106
153
|
assert response.status_code == 200
|
|
107
154
|
assert len(response.json["data"]) == 1
|
|
108
155
|
assert str(private_topic.id) not in [t["id"] for t in response.json["data"]]
|
|
@@ -110,7 +157,7 @@ class TopicsAPITest(APITestCase):
|
|
|
110
157
|
|
|
111
158
|
def test_topic_api_get(self):
|
|
112
159
|
"""It should fetch a topic from the API"""
|
|
113
|
-
topic =
|
|
160
|
+
topic = TopicWithElementsFactory()
|
|
114
161
|
topic_response = self.get(url_for("apiv2.topic", topic=topic))
|
|
115
162
|
assert topic_response.status_code == 200
|
|
116
163
|
assert "spatial" in topic_response.json
|
|
@@ -118,158 +165,441 @@ class TopicsAPITest(APITestCase):
|
|
|
118
165
|
assert topic_response.json["created_at"] is not None
|
|
119
166
|
assert topic_response.json["last_modified"] is not None
|
|
120
167
|
|
|
121
|
-
response = self.get(topic_response.json["
|
|
122
|
-
data = response.json
|
|
123
|
-
assert all(str(d.id) in (_d["id"] for _d in data["data"]) for d in topic.datasets)
|
|
124
|
-
|
|
125
|
-
response = self.get(topic_response.json["reuses"]["href"])
|
|
168
|
+
response = self.get(topic_response.json["elements"]["href"])
|
|
126
169
|
data = response.json
|
|
127
|
-
assert all(
|
|
170
|
+
assert all(
|
|
171
|
+
str(elt.id) in (_elt["id"] for _elt in data["data"])
|
|
172
|
+
for elt in TopicElement.objects(topic=topic)
|
|
173
|
+
)
|
|
128
174
|
|
|
175
|
+
def test_topic_api_create(self):
|
|
176
|
+
"""It should create a topic from the API"""
|
|
177
|
+
data = TopicWithElementsFactory.as_payload()
|
|
178
|
+
self.login()
|
|
179
|
+
response = self.post(url_for("apiv2.topics_list"), data)
|
|
180
|
+
self.assert201(response)
|
|
181
|
+
self.assertEqual(Topic.objects.count(), 1)
|
|
182
|
+
topic = Topic.objects.first()
|
|
183
|
+
for element in data["elements"]:
|
|
184
|
+
assert element["element"]["id"] in (
|
|
185
|
+
str(elt.element.id) for elt in TopicElement.objects(topic=topic)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def test_topic_api_create_as_org(self):
|
|
189
|
+
"""It should create a topic as organization from the API"""
|
|
190
|
+
data = TopicWithElementsFactory.as_payload()
|
|
191
|
+
user = self.login()
|
|
192
|
+
member = Member(user=user, role="editor")
|
|
193
|
+
org = OrganizationFactory(members=[member])
|
|
194
|
+
data["organization"] = str(org.id)
|
|
195
|
+
response = self.post(url_for("apiv2.topics_list"), data)
|
|
196
|
+
self.assert201(response)
|
|
197
|
+
self.assertEqual(Topic.objects.count(), 1)
|
|
198
|
+
|
|
199
|
+
topic = Topic.objects.first()
|
|
200
|
+
assert topic.owner is None
|
|
201
|
+
assert topic.organization == org
|
|
202
|
+
|
|
203
|
+
def test_topic_api_create_spatial_zone(self):
|
|
204
|
+
paca, _, _ = create_geozones_fixtures()
|
|
205
|
+
granularity = spatial_granularities[0][0]
|
|
206
|
+
data = TopicWithElementsFactory.as_payload()
|
|
207
|
+
data["spatial"] = {
|
|
208
|
+
"zones": [paca.id],
|
|
209
|
+
"granularity": granularity,
|
|
210
|
+
}
|
|
211
|
+
self.login()
|
|
212
|
+
response = self.post(url_for("apiv2.topics_list"), data)
|
|
213
|
+
self.assert201(response)
|
|
214
|
+
self.assertEqual(Topic.objects.count(), 1)
|
|
215
|
+
topic = Topic.objects.first()
|
|
216
|
+
self.assertEqual([str(z) for z in topic.spatial.zones], [paca.id])
|
|
217
|
+
self.assertEqual(topic.spatial.granularity, granularity)
|
|
218
|
+
|
|
219
|
+
def test_topic_api_create_spatial_geom(self):
|
|
220
|
+
granularity = spatial_granularities[0][0]
|
|
221
|
+
data = TopicWithElementsFactory.as_payload()
|
|
222
|
+
data["spatial"] = {
|
|
223
|
+
"geom": SAMPLE_GEOM,
|
|
224
|
+
"granularity": granularity,
|
|
225
|
+
}
|
|
226
|
+
self.login()
|
|
227
|
+
response = self.post(url_for("apiv2.topics_list"), data)
|
|
228
|
+
self.assert201(response)
|
|
229
|
+
self.assertEqual(Topic.objects.count(), 1)
|
|
230
|
+
topic = Topic.objects.first()
|
|
231
|
+
self.assertEqual(topic.spatial.geom, SAMPLE_GEOM)
|
|
232
|
+
self.assertEqual(topic.spatial.granularity, granularity)
|
|
129
233
|
|
|
130
|
-
class TopicDatasetsAPITest(APITestCase):
|
|
131
|
-
def test_list(self):
|
|
132
|
-
topic = TopicFactory()
|
|
133
|
-
response = self.get(url_for("apiv2.topic_datasets", topic=topic))
|
|
134
|
-
assert response.status_code == 200
|
|
135
|
-
data = response.json["data"]
|
|
136
|
-
assert len(data) == 3
|
|
137
|
-
assert all(str(d.id) in (_d["id"] for _d in data) for d in topic.datasets)
|
|
138
234
|
|
|
139
|
-
|
|
235
|
+
class TopicAPITest(APITestCase):
|
|
236
|
+
def test_topic_api_update(self):
|
|
237
|
+
"""It should update a topic from the API"""
|
|
140
238
|
owner = self.login()
|
|
141
239
|
topic = TopicFactory(owner=owner)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
240
|
+
data = topic.to_dict()
|
|
241
|
+
data["description"] = "new description"
|
|
242
|
+
response = self.put(url_for("apiv2.topic", topic=topic), data)
|
|
243
|
+
self.assert200(response)
|
|
244
|
+
self.assertEqual(Topic.objects.count(), 1)
|
|
245
|
+
topic = Topic.objects.first()
|
|
246
|
+
self.assertEqual(topic.description, "new description")
|
|
247
|
+
self.assertGreater(topic.last_modified, topic.created_at)
|
|
248
|
+
|
|
249
|
+
def test_topic_api_update_perm(self):
|
|
250
|
+
"""It should not update a topic from the API"""
|
|
251
|
+
owner = UserFactory()
|
|
252
|
+
topic = TopicFactory(owner=owner)
|
|
253
|
+
user = self.login()
|
|
254
|
+
data = topic.to_dict()
|
|
255
|
+
data["owner"] = user.to_dict()
|
|
256
|
+
response = self.put(url_for("apiv2.topic", topic=topic), data)
|
|
257
|
+
self.assert403(response)
|
|
258
|
+
|
|
259
|
+
def test_topic_api_update_with_elements(self):
|
|
260
|
+
"""It should update a topic from the API with elements parameters"""
|
|
261
|
+
user = self.login()
|
|
262
|
+
topic = TopicFactory(owner=user)
|
|
263
|
+
initial_length = len(topic.elements)
|
|
264
|
+
data = topic.to_dict()
|
|
265
|
+
data["elements"] = [
|
|
266
|
+
TopicElementFactory.element_as_payload(elt.fetch()) for elt in topic.elements
|
|
267
|
+
]
|
|
268
|
+
data["elements"].append(
|
|
269
|
+
TopicElementFactory.element_as_payload(TopicElementDatasetFactory())
|
|
145
270
|
)
|
|
146
|
-
|
|
271
|
+
response = self.put(url_for("apiv2.topic", topic=topic), data)
|
|
272
|
+
self.assert200(response)
|
|
147
273
|
topic.reload()
|
|
148
|
-
|
|
149
|
-
assert all(d.id in (_d.id for _d in topic.datasets) for d in (d1, d2))
|
|
274
|
+
self.assertEqual(len(topic.elements), initial_length + 1)
|
|
150
275
|
|
|
151
|
-
def
|
|
276
|
+
def test_topic_api_delete(self):
|
|
277
|
+
"""It should delete a topic from the API"""
|
|
152
278
|
owner = self.login()
|
|
153
279
|
topic = TopicFactory(owner=owner)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
280
|
+
|
|
281
|
+
with self.api_user():
|
|
282
|
+
response = self.post(
|
|
283
|
+
url_for("api.discussions"),
|
|
284
|
+
{
|
|
285
|
+
"title": "test title",
|
|
286
|
+
"comment": "bla bla",
|
|
287
|
+
"subject": {
|
|
288
|
+
"class": "Topic",
|
|
289
|
+
"id": topic.id,
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
)
|
|
293
|
+
self.assert201(response)
|
|
294
|
+
|
|
295
|
+
discussions = Discussion.objects(subject=topic)
|
|
296
|
+
self.assertEqual(len(discussions), 1)
|
|
297
|
+
|
|
298
|
+
with self.api_user():
|
|
299
|
+
response = self.delete(url_for("apiv2.topic", topic=topic))
|
|
300
|
+
self.assertStatus(response, 204)
|
|
301
|
+
|
|
302
|
+
self.assertEqual(Topic.objects.count(), 0)
|
|
303
|
+
self.assertEqual(Discussion.objects.count(), 0)
|
|
304
|
+
|
|
305
|
+
def test_topic_api_delete_perm(self):
|
|
306
|
+
"""It should not delete a topic from the API"""
|
|
307
|
+
owner = UserFactory()
|
|
308
|
+
topic = TopicFactory(owner=owner)
|
|
309
|
+
with self.api_user():
|
|
310
|
+
response = self.delete(url_for("apiv2.topic", topic=topic))
|
|
311
|
+
self.assertStatus(response, 403)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class TopicElementsAPITest(APITestCase):
|
|
315
|
+
def test_elements_list(self):
|
|
316
|
+
topic = TopicFactory()
|
|
317
|
+
reuse_elt = TopicElementReuseFactory(
|
|
318
|
+
topic=topic, tags=["foo", "bar"], extras={"foo": "bar"}
|
|
157
319
|
)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
320
|
+
dataset_elt = TopicElementDatasetFactory(
|
|
321
|
+
topic=topic, tags=["foo", "bar"], extras={"foo": "bar"}
|
|
322
|
+
)
|
|
323
|
+
TopicElementFactory(topic=topic, tags=["foo", "bar"], extras={"foo": "bar"})
|
|
324
|
+
response = self.get(url_for("apiv2.topic_elements", topic=topic))
|
|
325
|
+
assert response.status_code == 200
|
|
326
|
+
data = response.json["data"]
|
|
327
|
+
assert len(data) == 3
|
|
328
|
+
assert all(_elt["tags"] == ["foo", "bar"] for _elt in data)
|
|
329
|
+
assert all(_elt["extras"] == {"foo": "bar"} for _elt in data)
|
|
330
|
+
assert {"class": "Reuse", "id": str(reuse_elt.element.id)} in [
|
|
331
|
+
_elt["element"] for _elt in data
|
|
332
|
+
]
|
|
333
|
+
assert {"class": "Dataset", "id": str(dataset_elt.element.id)} in [
|
|
334
|
+
_elt["element"] for _elt in data
|
|
335
|
+
]
|
|
336
|
+
no_elt = next(_elt for _elt in data if not _elt["element"])
|
|
337
|
+
assert no_elt["element"] is None
|
|
338
|
+
|
|
339
|
+
def test_elements_list_pagination(self):
|
|
340
|
+
topic = TopicFactory()
|
|
341
|
+
for _ in range(DEFAULT_PAGE_SIZE + 1):
|
|
342
|
+
TopicElementFactory(topic=topic)
|
|
343
|
+
response = self.get(url_for("apiv2.topic_elements", topic=topic))
|
|
344
|
+
assert response.status_code == 200
|
|
345
|
+
assert response.json["next_page"] is not None
|
|
346
|
+
first_page_ids = [elt["id"] for elt in response.json["data"]]
|
|
347
|
+
response = self.get(response.json["next_page"])
|
|
348
|
+
assert response.status_code == 200
|
|
349
|
+
assert response.json["next_page"] is None
|
|
350
|
+
assert response.json["data"][0]["id"] not in first_page_ids
|
|
165
351
|
|
|
166
|
-
def
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
352
|
+
def test_elements_list_search(self):
|
|
353
|
+
topic = TopicFactory()
|
|
354
|
+
matches_1 = [
|
|
355
|
+
TopicElementFactory(topic=topic, title="Apprentissage automatique et algorithmes"),
|
|
356
|
+
TopicElementFactory(
|
|
357
|
+
topic=topic,
|
|
358
|
+
description="Ceci concerne l'apprentissage automatique et les algorithmes d'intelligence artificielle",
|
|
359
|
+
),
|
|
360
|
+
TopicElementFactory(topic=topic, title="algorithmes d'apprentissage"),
|
|
361
|
+
]
|
|
362
|
+
# Diacritics test
|
|
363
|
+
TopicElementFactory(topic=topic, title="Système de données")
|
|
364
|
+
TopicElementFactory(topic=topic, description="Création d'un modèle")
|
|
365
|
+
|
|
366
|
+
# Create non-matching elements
|
|
367
|
+
TopicElementFactory(topic=topic, title="ne devrait pas apparaître")
|
|
368
|
+
TopicElementFactory(topic=topic, description="contenu non pertinent")
|
|
369
|
+
TopicElementFactory(topic=topic, title="appr algo") # Partial words that regex might catch
|
|
370
|
+
|
|
371
|
+
# Test with French phrase
|
|
372
|
+
response = self.get(
|
|
373
|
+
url_for("apiv2.topic_elements", topic=topic, q="apprentissage algorithmes")
|
|
374
|
+
)
|
|
375
|
+
assert response.status_code == 200
|
|
376
|
+
assert response.json["total"] == 3
|
|
377
|
+
assert all(elt["id"] in [str(m.id) for m in matches_1] for elt in response.json["data"])
|
|
173
378
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
response
|
|
178
|
-
assert response.status_code == 400
|
|
179
|
-
response = self.post(url_for("apiv2.topic_datasets", topic=topic), [{"nain": "portekoi"}])
|
|
180
|
-
assert response.status_code == 400
|
|
181
|
-
response = self.post(url_for("apiv2.topic_datasets", topic=topic), {"non": "mais"})
|
|
182
|
-
assert response.status_code == 400
|
|
379
|
+
# Test diacritics - search without accents should match content with accents
|
|
380
|
+
response = self.get(url_for("apiv2.topic_elements", topic=topic, q="systeme donnees"))
|
|
381
|
+
assert response.status_code == 200
|
|
382
|
+
assert response.json["total"] >= 1
|
|
183
383
|
|
|
384
|
+
# Test reverse diacritics - search with accents should work
|
|
385
|
+
response = self.get(url_for("apiv2.topic_elements", topic=topic, q="création modèle"))
|
|
386
|
+
assert response.status_code == 200
|
|
387
|
+
assert response.json["total"] >= 1
|
|
184
388
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
response = self.delete(url_for("apiv2.topic_dataset", topic=topic, dataset=dataset))
|
|
191
|
-
assert response.status_code == 204
|
|
192
|
-
topic.reload()
|
|
193
|
-
assert len(topic.datasets) == 2
|
|
194
|
-
assert dataset.id not in (d.id for d in topic.datasets)
|
|
389
|
+
def test_elements_list_class_filter(self):
|
|
390
|
+
topic = TopicFactory()
|
|
391
|
+
dataset_elt = TopicElementDatasetFactory(topic=topic)
|
|
392
|
+
reuse_elt = TopicElementReuseFactory(topic=topic)
|
|
393
|
+
no_elt = TopicElementFactory(topic=topic)
|
|
195
394
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
response = self.delete(url_for("apiv2.topic_dataset", topic=topic, dataset=dataset))
|
|
201
|
-
assert response.status_code == 403
|
|
395
|
+
response = self.get(url_for("apiv2.topic_elements", topic=topic, **{"class": "Dataset"}))
|
|
396
|
+
assert response.status_code == 200
|
|
397
|
+
assert response.json["total"] == 1
|
|
398
|
+
assert str(dataset_elt.id) == response.json["data"][0]["id"]
|
|
202
399
|
|
|
400
|
+
response = self.get(url_for("apiv2.topic_elements", topic=topic, **{"class": "Reuse"}))
|
|
401
|
+
assert response.status_code == 200
|
|
402
|
+
assert response.json["total"] == 1
|
|
403
|
+
assert str(reuse_elt.id) == response.json["data"][0]["id"]
|
|
404
|
+
|
|
405
|
+
response = self.get(url_for("apiv2.topic_elements", topic=topic, **{"class": "None"}))
|
|
406
|
+
assert response.status_code == 200
|
|
407
|
+
assert response.json["total"] == 1
|
|
408
|
+
assert str(no_elt.id) == response.json["data"][0]["id"]
|
|
409
|
+
|
|
410
|
+
response = self.get(url_for("apiv2.topic_elements", topic=topic, **{"class": "NotAModel"}))
|
|
411
|
+
assert response.status_code == 200
|
|
412
|
+
assert response.json["total"] == 0
|
|
203
413
|
|
|
204
|
-
|
|
205
|
-
def test_list(self):
|
|
414
|
+
def test_elements_list_tags_filter(self):
|
|
206
415
|
topic = TopicFactory()
|
|
207
|
-
|
|
416
|
+
match_tag = TopicElementFactory(topic=topic, tags=["is-a-match-1", "is-a-match-2"])
|
|
417
|
+
match_tag_2 = TopicElementFactory(topic=topic, tags=["is-a-match-2"])
|
|
418
|
+
no_match_tag = TopicElementFactory(topic=topic, tags=["is-not-a-match"])
|
|
419
|
+
response = self.get(
|
|
420
|
+
url_for("apiv2.topic_elements", topic=topic, tag=["is-a-match-1", "is-a-match-2"])
|
|
421
|
+
)
|
|
208
422
|
assert response.status_code == 200
|
|
209
|
-
|
|
210
|
-
assert
|
|
211
|
-
assert
|
|
423
|
+
assert response.json["total"] == 1
|
|
424
|
+
assert str(match_tag.id) in [elt["id"] for elt in response.json["data"]]
|
|
425
|
+
assert str(no_match_tag.id) not in [elt["id"] for elt in response.json["data"]]
|
|
426
|
+
assert str(match_tag_2.id) not in [elt["id"] for elt in response.json["data"]]
|
|
212
427
|
|
|
213
|
-
def
|
|
428
|
+
def test_add_elements(self):
|
|
214
429
|
owner = self.login()
|
|
215
|
-
topic =
|
|
216
|
-
|
|
430
|
+
topic = TopicWithElementsFactory(owner=owner)
|
|
431
|
+
dataset = DatasetFactory()
|
|
432
|
+
reuse = ReuseFactory()
|
|
217
433
|
response = self.post(
|
|
218
|
-
url_for("apiv2.
|
|
434
|
+
url_for("apiv2.topic_elements", topic=topic),
|
|
435
|
+
[
|
|
436
|
+
{
|
|
437
|
+
"title": "A dataset",
|
|
438
|
+
"description": "A dataset description",
|
|
439
|
+
"tags": ["tag1", "tag2"],
|
|
440
|
+
"extras": {"extra": "value"},
|
|
441
|
+
"element": {"class": "Dataset", "id": dataset.id},
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
"title": "A reuse",
|
|
445
|
+
"description": "A reuse description",
|
|
446
|
+
"tags": ["tag1", "tag2"],
|
|
447
|
+
"extras": {"extra": "value"},
|
|
448
|
+
"element": {"class": "Reuse", "id": reuse.id},
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
"title": "An element without element",
|
|
452
|
+
"description": "An element description",
|
|
453
|
+
"tags": ["tag1", "tag2"],
|
|
454
|
+
"extras": {"extra": "value"},
|
|
455
|
+
"element": None,
|
|
456
|
+
},
|
|
457
|
+
],
|
|
219
458
|
)
|
|
220
459
|
assert response.status_code == 201
|
|
221
460
|
topic.reload()
|
|
222
|
-
assert len(topic.
|
|
223
|
-
|
|
461
|
+
assert len(topic.elements) == 6
|
|
462
|
+
|
|
463
|
+
dataset_elt = next(
|
|
464
|
+
elt for elt in topic.elements if elt.element and elt.element.id == dataset.id
|
|
465
|
+
)
|
|
466
|
+
assert dataset_elt.title == "A dataset"
|
|
467
|
+
assert dataset_elt.description == "A dataset description"
|
|
468
|
+
assert dataset_elt.tags == ["tag1", "tag2"]
|
|
469
|
+
assert dataset_elt.extras == {"extra": "value"}
|
|
470
|
+
|
|
471
|
+
reuse_elt = next(
|
|
472
|
+
elt for elt in topic.elements if elt.element and elt.element.id == reuse.id
|
|
473
|
+
)
|
|
474
|
+
assert reuse_elt.title == "A reuse"
|
|
475
|
+
assert reuse_elt.description == "A reuse description"
|
|
476
|
+
assert reuse_elt.tags == ["tag1", "tag2"]
|
|
477
|
+
assert reuse_elt.extras == {"extra": "value"}
|
|
478
|
+
|
|
479
|
+
no_elt_elt = next(
|
|
480
|
+
elt for elt in topic.elements if elt.title == "An element without element"
|
|
481
|
+
)
|
|
482
|
+
assert no_elt_elt.description == "An element description"
|
|
483
|
+
assert no_elt_elt.tags == ["tag1", "tag2"]
|
|
484
|
+
assert no_elt_elt.extras == {"extra": "value"}
|
|
485
|
+
assert no_elt_elt.element is None
|
|
224
486
|
|
|
225
|
-
def
|
|
487
|
+
def test_add_element_wrong_class(self):
|
|
226
488
|
owner = self.login()
|
|
227
489
|
topic = TopicFactory(owner=owner)
|
|
228
|
-
|
|
490
|
+
dataset = DatasetFactory()
|
|
229
491
|
response = self.post(
|
|
230
|
-
url_for("apiv2.
|
|
492
|
+
url_for("apiv2.topic_elements", topic=topic),
|
|
493
|
+
[{"element": {"class": "Reuse", "id": dataset.id}}],
|
|
231
494
|
)
|
|
232
|
-
assert response.status_code ==
|
|
233
|
-
|
|
234
|
-
assert len(topic.reuses) == 4
|
|
235
|
-
response = self.post(url_for("apiv2.topic_reuses", topic=topic), [{"id": reuse.id}])
|
|
236
|
-
assert response.status_code == 201
|
|
237
|
-
topic.reload()
|
|
238
|
-
assert len(topic.reuses) == 4
|
|
495
|
+
assert response.status_code == 400
|
|
496
|
+
assert "n'existe pas" in response.json["errors"][0]["element"][0]
|
|
239
497
|
|
|
240
|
-
def
|
|
498
|
+
def test_add_empty_element(self):
|
|
499
|
+
owner = self.login()
|
|
500
|
+
topic = TopicFactory(owner=owner)
|
|
501
|
+
response = self.post(url_for("apiv2.topic_elements", topic=topic), [{}])
|
|
502
|
+
assert response.status_code == 400
|
|
503
|
+
assert (
|
|
504
|
+
response.json["errors"][0]["element"][0]
|
|
505
|
+
== "A topic element must have a title or an element."
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
def test_add_datasets_perm(self):
|
|
241
509
|
user = UserFactory()
|
|
242
510
|
topic = TopicFactory(owner=user)
|
|
243
|
-
|
|
511
|
+
dataset = DatasetFactory()
|
|
244
512
|
self.login()
|
|
245
|
-
response = self.post(url_for("apiv2.
|
|
513
|
+
response = self.post(url_for("apiv2.topic_elements", topic=topic), [{"id": dataset.id}])
|
|
246
514
|
assert response.status_code == 403
|
|
247
515
|
|
|
248
|
-
def
|
|
516
|
+
def test_add_datasets_wrong_payload(self):
|
|
249
517
|
owner = self.login()
|
|
250
518
|
topic = TopicFactory(owner=owner)
|
|
251
|
-
response = self.post(url_for("apiv2.
|
|
519
|
+
response = self.post(url_for("apiv2.topic_elements", topic=topic), [{"id": "xxx"}])
|
|
252
520
|
assert response.status_code == 400
|
|
253
|
-
response = self.post(url_for("apiv2.
|
|
521
|
+
response = self.post(url_for("apiv2.topic_elements", topic=topic), [{"nain": "portekoi"}])
|
|
254
522
|
assert response.status_code == 400
|
|
255
|
-
response = self.post(url_for("apiv2.
|
|
523
|
+
response = self.post(url_for("apiv2.topic_elements", topic=topic), {"non": "mais"})
|
|
256
524
|
assert response.status_code == 400
|
|
257
525
|
|
|
526
|
+
def test_clear_elements(self):
|
|
527
|
+
"""It should remove all elements from a Topic"""
|
|
528
|
+
owner = self.login()
|
|
529
|
+
topic = TopicWithElementsFactory(owner=owner)
|
|
530
|
+
self.assertGreater(len(topic.elements), 0)
|
|
531
|
+
response = self.delete(url_for("apiv2.topic_elements", topic=topic))
|
|
532
|
+
self.assert204(response)
|
|
533
|
+
topic.reload()
|
|
534
|
+
self.assertEqual(len(topic.elements), 0)
|
|
258
535
|
|
|
259
|
-
|
|
260
|
-
|
|
536
|
+
|
|
537
|
+
class TopicElementAPITest(APITestCase):
|
|
538
|
+
def test_delete_element(self):
|
|
261
539
|
owner = self.login()
|
|
262
|
-
topic =
|
|
263
|
-
|
|
264
|
-
response = self.delete(url_for("apiv2.
|
|
540
|
+
topic = TopicWithElementsFactory(owner=owner)
|
|
541
|
+
element = topic.elements[0]
|
|
542
|
+
response = self.delete(url_for("apiv2.topic_element", topic=topic, element_id=element.id))
|
|
265
543
|
assert response.status_code == 204
|
|
266
544
|
topic.reload()
|
|
267
|
-
assert len(topic.
|
|
268
|
-
assert
|
|
545
|
+
assert len(topic.elements) == 2
|
|
546
|
+
assert element.id not in (elt.id for elt in topic.elements)
|
|
269
547
|
|
|
270
|
-
def
|
|
271
|
-
topic =
|
|
272
|
-
|
|
548
|
+
def test_delete_element_perm(self):
|
|
549
|
+
topic = TopicWithElementsFactory(owner=UserFactory())
|
|
550
|
+
element = topic.elements[0]
|
|
273
551
|
self.login()
|
|
274
|
-
response = self.delete(url_for("apiv2.
|
|
552
|
+
response = self.delete(url_for("apiv2.topic_element", topic=topic, element_id=element.id))
|
|
275
553
|
assert response.status_code == 403
|
|
554
|
+
|
|
555
|
+
def test_update_element(self):
|
|
556
|
+
owner = self.login()
|
|
557
|
+
topic = TopicFactory(owner=owner)
|
|
558
|
+
dataset = DatasetFactory()
|
|
559
|
+
element = TopicElementFactory(topic=topic, title="foo")
|
|
560
|
+
response = self.put(
|
|
561
|
+
url_for("apiv2.topic_element", topic=topic, element_id=element.id),
|
|
562
|
+
{
|
|
563
|
+
"title": "bar",
|
|
564
|
+
"description": "baz",
|
|
565
|
+
"tags": ["baz"],
|
|
566
|
+
"extras": {"foo": "bar"},
|
|
567
|
+
"element": {"class": "Dataset", "id": str(dataset.id)},
|
|
568
|
+
},
|
|
569
|
+
)
|
|
570
|
+
assert response.status_code == 200
|
|
571
|
+
assert response.json["title"] == "bar"
|
|
572
|
+
topic.reload()
|
|
573
|
+
assert len(topic.elements) == 1
|
|
574
|
+
element = topic.elements.first()
|
|
575
|
+
assert element.title == "bar"
|
|
576
|
+
assert element.description == "baz"
|
|
577
|
+
assert element.tags == ["baz"]
|
|
578
|
+
assert element.extras == {"foo": "bar"}
|
|
579
|
+
assert element.element.id == dataset.id
|
|
580
|
+
|
|
581
|
+
def test_update_element_no_element(self):
|
|
582
|
+
owner = self.login()
|
|
583
|
+
topic = TopicFactory(owner=owner)
|
|
584
|
+
element = TopicElementFactory(topic=topic, title="foo")
|
|
585
|
+
response = self.put(
|
|
586
|
+
url_for("apiv2.topic_element", topic=topic, element_id=element.id),
|
|
587
|
+
{
|
|
588
|
+
"title": "bar",
|
|
589
|
+
"description": "baz",
|
|
590
|
+
"tags": ["baz"],
|
|
591
|
+
"extras": {"foo": "bar"},
|
|
592
|
+
"element": None,
|
|
593
|
+
},
|
|
594
|
+
)
|
|
595
|
+
assert response.status_code == 200
|
|
596
|
+
assert response.json["title"] == "bar"
|
|
597
|
+
assert response.json["element"] is None
|
|
598
|
+
topic.reload()
|
|
599
|
+
assert len(topic.elements) == 1
|
|
600
|
+
element = topic.elements.first()
|
|
601
|
+
assert element.title == "bar"
|
|
602
|
+
assert element.description == "baz"
|
|
603
|
+
assert element.tags == ["baz"]
|
|
604
|
+
assert element.extras == {"foo": "bar"}
|
|
605
|
+
assert element.element is None
|