udata 10.0.8.dev33570__py2.py3-none-any.whl → 10.0.8.dev33610__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 +5 -4
- udata/commands/db.py +7 -18
- udata/core/dataset/csv.py +2 -1
- udata/core/post/api.py +12 -4
- udata/core/post/models.py +5 -0
- udata/core/post/tests/test_api.py +48 -31
- udata/mongo/document.py +14 -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/{19.8da42e8359d72afc2618.js → 19.ba0bb2baa40e899d440b.js} +3 -3
- udata/static/chunks/{19.8da42e8359d72afc2618.js.map → 19.ba0bb2baa40e899d440b.js.map} +1 -1
- udata/static/chunks/{5.0fa1408dae4e76b87b2e.js → 5.0652a860afda96795a53.js} +3 -3
- udata/static/chunks/{5.0fa1408dae4e76b87b2e.js.map → 5.0652a860afda96795a53.js.map} +1 -1
- udata/static/chunks/{6.d663709d877baa44a71e.js → 6.92d7c2ec6d20005774ef.js} +3 -3
- udata/static/chunks/{6.d663709d877baa44a71e.js.map → 6.92d7c2ec6d20005774ef.js.map} +1 -1
- udata/static/chunks/{8.54e44b102164ae5e7a67.js → 8.0f42630e6d8ff782928e.js} +2 -2
- udata/static/chunks/{8.54e44b102164ae5e7a67.js.map → 8.0f42630e6d8ff782928e.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/tests/features/territories/test_territories_api.py +5 -5
- udata/tests/plugin.py +12 -0
- {udata-10.0.8.dev33570.dist-info → udata-10.0.8.dev33610.dist-info}/METADATA +3 -1
- {udata-10.0.8.dev33570.dist-info → udata-10.0.8.dev33610.dist-info}/RECORD +31 -31
- {udata-10.0.8.dev33570.dist-info → udata-10.0.8.dev33610.dist-info}/LICENSE +0 -0
- {udata-10.0.8.dev33570.dist-info → udata-10.0.8.dev33610.dist-info}/WHEEL +0 -0
- {udata-10.0.8.dev33570.dist-info → udata-10.0.8.dev33610.dist-info}/entry_points.txt +0 -0
- {udata-10.0.8.dev33570.dist-info → udata-10.0.8.dev33610.dist-info}/top_level.txt +0 -0
udata/api/__init__.py
CHANGED
|
@@ -15,6 +15,7 @@ from flask import (
|
|
|
15
15
|
url_for,
|
|
16
16
|
)
|
|
17
17
|
from flask_restx import Api, Resource
|
|
18
|
+
from flask_restx.reqparse import RequestParser
|
|
18
19
|
from flask_storage import UnauthorizedFileType
|
|
19
20
|
|
|
20
21
|
from udata import entrypoints, tracking
|
|
@@ -138,7 +139,7 @@ class UDataApi(Api):
|
|
|
138
139
|
response.headers["WWW-Authenticate"] = challenge
|
|
139
140
|
return response
|
|
140
141
|
|
|
141
|
-
def page_parser(self):
|
|
142
|
+
def page_parser(self) -> RequestParser:
|
|
142
143
|
parser = self.parser()
|
|
143
144
|
parser.add_argument("page", type=int, default=1, location="args", help="The page to fetch")
|
|
144
145
|
parser.add_argument(
|
|
@@ -251,9 +252,9 @@ def handle_value_error(error):
|
|
|
251
252
|
def handle_unauthorized_file_type(error):
|
|
252
253
|
"""Error occuring when the user try to upload a non-allowed file type"""
|
|
253
254
|
url = url_for("api.allowed_extensions", _external=True)
|
|
254
|
-
msg = (
|
|
255
|
-
|
|
256
|
-
)
|
|
255
|
+
msg = ("This file type is not allowed.The allowed file type list is available at {url}").format(
|
|
256
|
+
url=url
|
|
257
|
+
)
|
|
257
258
|
return {"message": msg}, 400
|
|
258
259
|
|
|
259
260
|
|
udata/commands/db.py
CHANGED
|
@@ -9,11 +9,8 @@ import mongoengine
|
|
|
9
9
|
from bson import DBRef
|
|
10
10
|
|
|
11
11
|
from udata import migrations
|
|
12
|
-
from udata import models as core_models
|
|
13
|
-
from udata.api import oauth2 as oauth2_models
|
|
14
12
|
from udata.commands import cli, cyan, echo, green, magenta, red, white, yellow
|
|
15
|
-
from udata.
|
|
16
|
-
from udata.mongo import db
|
|
13
|
+
from udata.mongo.document import get_all_models
|
|
17
14
|
|
|
18
15
|
# Date format used to for display
|
|
19
16
|
DATE_FORMAT = "%Y-%m-%d %H:%M"
|
|
@@ -148,16 +145,8 @@ def check_references(models_to_check):
|
|
|
148
145
|
|
|
149
146
|
errors = collections.defaultdict(int)
|
|
150
147
|
|
|
151
|
-
_models = []
|
|
152
|
-
for models in core_models, harvest_models, oauth2_models:
|
|
153
|
-
_models += [
|
|
154
|
-
elt
|
|
155
|
-
for _, elt in models.__dict__.items()
|
|
156
|
-
if isinstance(elt, type) and issubclass(elt, (db.Document))
|
|
157
|
-
]
|
|
158
|
-
|
|
159
148
|
references = []
|
|
160
|
-
for model in
|
|
149
|
+
for model in get_all_models():
|
|
161
150
|
if model.__name__ == "Activity":
|
|
162
151
|
print("Skipping Activity model, scheduled for deprecation")
|
|
163
152
|
continue
|
|
@@ -291,7 +280,7 @@ def check_references(models_to_check):
|
|
|
291
280
|
|
|
292
281
|
print("Those references will be inspected:")
|
|
293
282
|
for reference in references:
|
|
294
|
-
print(f
|
|
283
|
+
print(f"- {reference['repr']}({reference['destination']}) — {reference['type']}")
|
|
295
284
|
print("")
|
|
296
285
|
|
|
297
286
|
total = 0
|
|
@@ -305,7 +294,7 @@ def check_references(models_to_check):
|
|
|
305
294
|
with click.progressbar(qs, length=count) as models:
|
|
306
295
|
for obj in models:
|
|
307
296
|
for reference in model_references:
|
|
308
|
-
key = f
|
|
297
|
+
key = f"\t- {reference['repr']}({reference['destination']}) — {reference['type']}…"
|
|
309
298
|
if key not in errors[model]:
|
|
310
299
|
errors[model][key] = 0
|
|
311
300
|
|
|
@@ -316,7 +305,7 @@ def check_references(models_to_check):
|
|
|
316
305
|
except mongoengine.errors.DoesNotExist:
|
|
317
306
|
errors[model][key] += 1
|
|
318
307
|
print_and_save(
|
|
319
|
-
f
|
|
308
|
+
f"\t{model.__name__}#{obj.id} have a broken reference for `{reference['name']}`"
|
|
320
309
|
)
|
|
321
310
|
elif reference["type"] == "list":
|
|
322
311
|
attr_list = getattr(obj, reference["name"], [])
|
|
@@ -326,7 +315,7 @@ def check_references(models_to_check):
|
|
|
326
315
|
if isinstance(sub, DBRef):
|
|
327
316
|
errors[model][key] += 1
|
|
328
317
|
print_and_save(
|
|
329
|
-
f
|
|
318
|
+
f"\t{model.__name__}#{obj.id} have a broken reference for {reference['name']}[{i}]"
|
|
330
319
|
)
|
|
331
320
|
elif reference["type"] == "embed_list":
|
|
332
321
|
p1, p2 = reference["name"].split("__")
|
|
@@ -366,7 +355,7 @@ def check_references(models_to_check):
|
|
|
366
355
|
f"\t{model.__name__}#{obj.id} have a broken reference for {p1}.{p2}[{i}]"
|
|
367
356
|
)
|
|
368
357
|
else:
|
|
369
|
-
print_and_save(f
|
|
358
|
+
print_and_save(f"Unknown ref type {reference['type']}")
|
|
370
359
|
except mongoengine.errors.FieldDoesNotExist:
|
|
371
360
|
print_and_save(
|
|
372
361
|
f"[ERROR for {model.__name__} {obj.id}] {traceback.format_exc()}"
|
udata/core/dataset/csv.py
CHANGED
|
@@ -90,6 +90,7 @@ class ResourcesCsvAdapter(csv.NestedAdapter):
|
|
|
90
90
|
("harvest.modified_at", lambda o: o.harvest and o.harvest.modified_at),
|
|
91
91
|
("schema_name", "schema.name"),
|
|
92
92
|
("schema_version", "schema.version"),
|
|
93
|
-
("preview_url", lambda o: o.preview_url or
|
|
93
|
+
("preview_url", lambda o: o.preview_url or None),
|
|
94
|
+
("extras", lambda o: o.extras),
|
|
94
95
|
)
|
|
95
96
|
attribute = "resources"
|
udata/core/post/api.py
CHANGED
|
@@ -15,6 +15,8 @@ from udata.core.user.api_fields import user_ref_fields
|
|
|
15
15
|
from .forms import PostForm
|
|
16
16
|
from .models import Post
|
|
17
17
|
|
|
18
|
+
DEFAULT_SORTING = "-published"
|
|
19
|
+
|
|
18
20
|
ns = api.namespace("posts", "Posts related operations")
|
|
19
21
|
|
|
20
22
|
post_fields = api.model(
|
|
@@ -58,9 +60,7 @@ post_page_fields = api.model("PostPage", fields.pager(post_fields))
|
|
|
58
60
|
|
|
59
61
|
parser = api.page_parser()
|
|
60
62
|
|
|
61
|
-
parser.add_argument(
|
|
62
|
-
"sort", type=str, default="-created_at", location="args", help="The sorting attribute"
|
|
63
|
-
)
|
|
63
|
+
parser.add_argument("sort", type=str, location="args", help="The sorting attribute")
|
|
64
64
|
parser.add_argument(
|
|
65
65
|
"with_drafts",
|
|
66
66
|
type=bool,
|
|
@@ -68,6 +68,9 @@ parser.add_argument(
|
|
|
68
68
|
location="args",
|
|
69
69
|
help="`True` also returns the unpublished posts (only for super-admins)",
|
|
70
70
|
)
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
"q", type=str, location="args", help="query string to search through resources titles"
|
|
73
|
+
)
|
|
71
74
|
|
|
72
75
|
|
|
73
76
|
@ns.route("/", endpoint="posts")
|
|
@@ -84,7 +87,12 @@ class PostsAPI(API):
|
|
|
84
87
|
if not (AdminPermission().can() and args["with_drafts"]):
|
|
85
88
|
posts = posts.published()
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
if args["q"]:
|
|
91
|
+
phrase_query = " ".join([f'"{elem}"' for elem in args["q"].split(" ")])
|
|
92
|
+
posts = posts.search_text(phrase_query)
|
|
93
|
+
|
|
94
|
+
sort = args["sort"] or ("$text_score" if args["q"] else None) or DEFAULT_SORTING
|
|
95
|
+
return posts.order_by(sort).paginate(args["page"], args["page_size"])
|
|
88
96
|
|
|
89
97
|
@api.doc("create_post")
|
|
90
98
|
@api.secure(admin_permission)
|
udata/core/post/models.py
CHANGED
|
@@ -42,6 +42,11 @@ class Post(db.Datetimed, db.Document):
|
|
|
42
42
|
"indexes": [
|
|
43
43
|
"-created_at",
|
|
44
44
|
"-published",
|
|
45
|
+
{
|
|
46
|
+
"fields": ["$name", "$headline", "$content"],
|
|
47
|
+
"default_language": "french",
|
|
48
|
+
"weights": {"name": 10, "headline": 5, "content": 4},
|
|
49
|
+
},
|
|
45
50
|
],
|
|
46
51
|
"queryset_class": PostQuerySet,
|
|
47
52
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import pytest
|
|
2
1
|
from flask import url_for
|
|
3
2
|
|
|
4
3
|
from udata.core.dataset.factories import DatasetFactory
|
|
@@ -6,50 +5,68 @@ from udata.core.post.factories import PostFactory
|
|
|
6
5
|
from udata.core.post.models import Post
|
|
7
6
|
from udata.core.reuse.factories import ReuseFactory
|
|
8
7
|
from udata.core.user.factories import AdminFactory
|
|
8
|
+
from udata.tests.api import APITestCase
|
|
9
9
|
from udata.tests.helpers import assert200, assert201, assert204
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
modules = []
|
|
15
|
-
|
|
16
|
-
def test_post_api_list(self, api):
|
|
12
|
+
class PostsAPITest(APITestCase):
|
|
13
|
+
def test_post_api_list(self):
|
|
17
14
|
"""It should fetch a post list from the API"""
|
|
18
15
|
PostFactory.create_batch(3)
|
|
19
16
|
draft = PostFactory(published=None)
|
|
20
17
|
|
|
21
|
-
response =
|
|
18
|
+
response = self.get(url_for("api.posts"))
|
|
22
19
|
assert200(response)
|
|
23
20
|
# Response should not contain the unpublished post
|
|
24
21
|
assert len(response.json["data"]) == 3
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
self.login(AdminFactory())
|
|
27
24
|
|
|
28
|
-
response =
|
|
25
|
+
response = self.get(url_for("api.posts"))
|
|
29
26
|
assert200(response)
|
|
30
27
|
|
|
31
28
|
assert len(response.json["data"]) == 3
|
|
32
29
|
assert str(draft.id) not in [post["id"] for post in response.json["data"]]
|
|
33
30
|
|
|
34
|
-
response =
|
|
31
|
+
response = self.get(url_for("api.posts", with_drafts=True))
|
|
35
32
|
assert200(response)
|
|
36
33
|
|
|
37
34
|
assert len(response.json["data"]) == 4
|
|
38
35
|
assert str(draft.id) in [post["id"] for post in response.json["data"]]
|
|
39
36
|
|
|
40
|
-
def
|
|
37
|
+
def test_search_post(self):
|
|
38
|
+
"""It should fetch a post list from the API"""
|
|
39
|
+
name_match = PostFactory(name="Foobar", published="2025-01-01")
|
|
40
|
+
content_match = PostFactory(content="Foobar", published="2025-01-02")
|
|
41
|
+
PostFactory(content="Something else")
|
|
42
|
+
|
|
43
|
+
response = self.get(url_for("api.posts", q="Foobar"))
|
|
44
|
+
assert200(response)
|
|
45
|
+
assert len(response.json["data"]) == 2
|
|
46
|
+
|
|
47
|
+
assert response.json["data"][0]["id"] == str(name_match.id)
|
|
48
|
+
assert response.json["data"][1]["id"] == str(content_match.id)
|
|
49
|
+
|
|
50
|
+
response = self.get(url_for("api.posts", q="Foobar", sort="-published"))
|
|
51
|
+
assert200(response)
|
|
52
|
+
assert len(response.json["data"]) == 2
|
|
53
|
+
|
|
54
|
+
assert response.json["data"][1]["id"] == str(name_match.id)
|
|
55
|
+
assert response.json["data"][0]["id"] == str(content_match.id)
|
|
56
|
+
|
|
57
|
+
def test_post_api_get(self):
|
|
41
58
|
"""It should fetch a post from the API"""
|
|
42
59
|
post = PostFactory()
|
|
43
|
-
response =
|
|
60
|
+
response = self.get(url_for("api.post", post=post))
|
|
44
61
|
assert200(response)
|
|
45
62
|
|
|
46
|
-
def test_post_api_create(self
|
|
63
|
+
def test_post_api_create(self):
|
|
47
64
|
"""It should create a post from the API"""
|
|
48
65
|
data = PostFactory.as_dict()
|
|
49
66
|
data["datasets"] = [str(d.id) for d in data["datasets"]]
|
|
50
67
|
data["reuses"] = [str(r.id) for r in data["reuses"]]
|
|
51
|
-
|
|
52
|
-
response =
|
|
68
|
+
self.login(AdminFactory())
|
|
69
|
+
response = self.post(url_for("api.posts"), data)
|
|
53
70
|
assert201(response)
|
|
54
71
|
assert Post.objects.count() == 1
|
|
55
72
|
post = Post.objects.first()
|
|
@@ -58,62 +75,62 @@ class PostsAPITest:
|
|
|
58
75
|
for reuse, expected in zip(post.reuses, data["reuses"]):
|
|
59
76
|
assert str(reuse.id) == expected
|
|
60
77
|
|
|
61
|
-
def test_post_api_update(self
|
|
78
|
+
def test_post_api_update(self):
|
|
62
79
|
"""It should update a post from the API"""
|
|
63
80
|
post = PostFactory()
|
|
64
81
|
data = post.to_dict()
|
|
65
82
|
data["content"] = "new content"
|
|
66
|
-
|
|
67
|
-
response =
|
|
83
|
+
self.login(AdminFactory())
|
|
84
|
+
response = self.put(url_for("api.post", post=post), data)
|
|
68
85
|
assert200(response)
|
|
69
86
|
assert Post.objects.count() == 1
|
|
70
87
|
assert Post.objects.first().content == "new content"
|
|
71
88
|
|
|
72
|
-
def test_post_api_update_with_related_dataset_and_reuse(self
|
|
89
|
+
def test_post_api_update_with_related_dataset_and_reuse(self):
|
|
73
90
|
"""It should update a post from the API with related dataset and reuse"""
|
|
74
|
-
|
|
91
|
+
self.login(AdminFactory())
|
|
75
92
|
post = PostFactory()
|
|
76
93
|
data = post.to_dict()
|
|
77
94
|
data["content"] = "new content"
|
|
78
95
|
|
|
79
96
|
# Add datasets
|
|
80
97
|
data["datasets"] = [DatasetFactory().to_dict()]
|
|
81
|
-
response =
|
|
98
|
+
response = self.put(url_for("api.post", post=post), data)
|
|
82
99
|
assert200(response)
|
|
83
100
|
|
|
84
101
|
# Add reuses to the post value returned by the previous api call
|
|
85
102
|
data = response.json
|
|
86
103
|
data["reuses"] = [ReuseFactory().to_dict()]
|
|
87
|
-
response =
|
|
104
|
+
response = self.put(url_for("api.post", post=post), data)
|
|
88
105
|
assert200(response)
|
|
89
106
|
|
|
90
107
|
assert len(response.json["datasets"]) == 1
|
|
91
108
|
assert len(response.json["reuses"]) == 1
|
|
92
109
|
|
|
93
|
-
def test_post_api_delete(self
|
|
110
|
+
def test_post_api_delete(self):
|
|
94
111
|
"""It should delete a post from the API"""
|
|
95
112
|
post = PostFactory()
|
|
96
|
-
|
|
97
|
-
|
|
113
|
+
self.login(AdminFactory())
|
|
114
|
+
response = self.delete(url_for("api.post", post=post))
|
|
98
115
|
assert204(response)
|
|
99
116
|
assert Post.objects.count() == 0
|
|
100
117
|
|
|
101
|
-
def test_post_api_publish(self
|
|
118
|
+
def test_post_api_publish(self):
|
|
102
119
|
"""It should update a post from the API"""
|
|
103
120
|
post = PostFactory(published=None)
|
|
104
|
-
|
|
105
|
-
response =
|
|
121
|
+
self.login(AdminFactory())
|
|
122
|
+
response = self.post(url_for("api.publish_post", post=post))
|
|
106
123
|
assert200(response)
|
|
107
124
|
assert Post.objects.count() == 1
|
|
108
125
|
|
|
109
126
|
post.reload()
|
|
110
127
|
assert post.published is not None
|
|
111
128
|
|
|
112
|
-
def test_post_api_unpublish(self
|
|
129
|
+
def test_post_api_unpublish(self):
|
|
113
130
|
"""It should update a post from the API"""
|
|
114
131
|
post = PostFactory()
|
|
115
|
-
|
|
116
|
-
response =
|
|
132
|
+
self.login(AdminFactory())
|
|
133
|
+
response = self.delete(url_for("api.publish_post", post=post))
|
|
117
134
|
assert200(response)
|
|
118
135
|
assert Post.objects.count() == 1
|
|
119
136
|
|
udata/mongo/document.py
CHANGED
|
@@ -19,6 +19,20 @@ def serialize(value):
|
|
|
19
19
|
return value
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
def get_all_models():
|
|
23
|
+
from udata import models as core_models
|
|
24
|
+
from udata.api import oauth2 as oauth2_models
|
|
25
|
+
from udata.harvest import models as harvest_models
|
|
26
|
+
|
|
27
|
+
all_models = set()
|
|
28
|
+
for models in core_models, harvest_models, oauth2_models:
|
|
29
|
+
for model in models.__dict__.values():
|
|
30
|
+
if isinstance(model, type) and issubclass(model, (UDataDocument)):
|
|
31
|
+
all_models.add(model)
|
|
32
|
+
|
|
33
|
+
return all_models
|
|
34
|
+
|
|
35
|
+
|
|
22
36
|
class UDataDocument(Document):
|
|
23
37
|
meta = {
|
|
24
38
|
"abstract": True,
|