udata 9.1.4.dev31123__py2.py3-none-any.whl → 9.1.4.dev31143__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 +119 -26
- udata/core/badges/models.py +7 -1
- udata/core/metrics/models.py +4 -1
- udata/core/organization/api.py +1 -2
- udata/core/owned.py +4 -2
- udata/core/post/api.py +2 -2
- udata/core/reuse/api.py +32 -25
- udata/core/reuse/api_fields.py +2 -101
- udata/core/reuse/apiv2.py +4 -4
- udata/core/reuse/models.py +98 -16
- udata/core/site/api.py +2 -3
- udata/core/topic/api.py +2 -2
- udata/core/topic/apiv2.py +1 -2
- udata/core/user/api.py +2 -3
- udata/features/transfer/api.py +1 -2
- udata/mongo/datetime_fields.py +11 -4
- udata/mongo/document.py +2 -0
- udata/mongo/taglist_field.py +26 -0
- udata/static/admin.js +36 -36
- udata/static/admin.js.map +1 -1
- udata/static/chunks/{12.5b900cac4417e10ef3a0.js → 12.576e63b7a990f8eab784.js} +2 -2
- udata/static/chunks/12.576e63b7a990f8eab784.js.map +1 -0
- udata/static/chunks/{28.1759a7f57d526e6db574.js → 28.1ef31a46255dc2bf56d1.js} +2 -2
- udata/static/chunks/28.1ef31a46255dc2bf56d1.js.map +1 -0
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/tests/api/test_reuses_api.py +49 -0
- {udata-9.1.4.dev31123.dist-info → udata-9.1.4.dev31143.dist-info}/METADATA +2 -1
- {udata-9.1.4.dev31123.dist-info → udata-9.1.4.dev31143.dist-info}/RECORD +33 -34
- udata/core/reuse/forms.py +0 -45
- udata/static/chunks/12.5b900cac4417e10ef3a0.js.map +0 -1
- udata/static/chunks/28.1759a7f57d526e6db574.js.map +0 -1
- {udata-9.1.4.dev31123.dist-info → udata-9.1.4.dev31143.dist-info}/LICENSE +0 -0
- {udata-9.1.4.dev31123.dist-info → udata-9.1.4.dev31143.dist-info}/WHEEL +0 -0
- {udata-9.1.4.dev31123.dist-info → udata-9.1.4.dev31143.dist-info}/entry_points.txt +0 -0
- {udata-9.1.4.dev31123.dist-info → udata-9.1.4.dev31143.dist-info}/top_level.txt +0 -0
udata/core/reuse/models.py
CHANGED
|
@@ -2,11 +2,15 @@ from blinker import Signal
|
|
|
2
2
|
from mongoengine.signals import post_save, pre_save
|
|
3
3
|
from werkzeug.utils import cached_property
|
|
4
4
|
|
|
5
|
+
from udata.api_fields import field, function_field, generate_fields
|
|
6
|
+
from udata.core.dataset.api_fields import dataset_fields
|
|
5
7
|
from udata.core.owned import Owned, OwnedQuerySet
|
|
8
|
+
from udata.core.reuse.api_fields import BIGGEST_IMAGE_SIZE
|
|
6
9
|
from udata.core.storages import default_image_basename, images
|
|
7
10
|
from udata.frontend.markdown import mdstrip
|
|
8
11
|
from udata.i18n import lazy_gettext as _
|
|
9
12
|
from udata.models import BadgeMixin, WithMetrics, db
|
|
13
|
+
from udata.mongo.errors import FieldValidationError
|
|
10
14
|
from udata.uris import endpoint_for
|
|
11
15
|
from udata.utils import hash_url
|
|
12
16
|
|
|
@@ -23,32 +27,100 @@ class ReuseQuerySet(OwnedQuerySet):
|
|
|
23
27
|
return self(db.Q(private=True) | db.Q(datasets__0__exists=False) | db.Q(deleted__ne=None))
|
|
24
28
|
|
|
25
29
|
|
|
30
|
+
def check_url_does_not_exists(url):
|
|
31
|
+
"""Ensure a reuse URL is not yet registered"""
|
|
32
|
+
if url and Reuse.url_exists(url):
|
|
33
|
+
raise FieldValidationError(_("This URL is already registered"), field="url")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@generate_fields(
|
|
37
|
+
searchable=True,
|
|
38
|
+
additionalSorts=[
|
|
39
|
+
{"key": "datasets", "value": "metrics.datasets"},
|
|
40
|
+
{"key": "followers", "value": "metrics.followers"},
|
|
41
|
+
{"key": "views", "value": "metrics.views"},
|
|
42
|
+
],
|
|
43
|
+
)
|
|
26
44
|
class Reuse(db.Datetimed, WithMetrics, BadgeMixin, Owned, db.Document):
|
|
27
|
-
title =
|
|
28
|
-
|
|
29
|
-
|
|
45
|
+
title = field(
|
|
46
|
+
db.StringField(required=True),
|
|
47
|
+
sortable=True,
|
|
48
|
+
show_as_ref=True,
|
|
49
|
+
)
|
|
50
|
+
slug = field(
|
|
51
|
+
db.SlugField(
|
|
52
|
+
max_length=255, required=True, populate_from="title", update=True, follow=True
|
|
53
|
+
),
|
|
54
|
+
readonly=True,
|
|
55
|
+
)
|
|
56
|
+
description = field(
|
|
57
|
+
db.StringField(required=True),
|
|
58
|
+
markdown=True,
|
|
59
|
+
)
|
|
60
|
+
type = field(
|
|
61
|
+
db.StringField(required=True, choices=list(REUSE_TYPES)),
|
|
62
|
+
filterable={},
|
|
63
|
+
)
|
|
64
|
+
url = field(
|
|
65
|
+
db.StringField(required=True),
|
|
66
|
+
description="The remote URL (website)",
|
|
67
|
+
check=check_url_does_not_exists,
|
|
30
68
|
)
|
|
31
|
-
description = db.StringField(required=True)
|
|
32
|
-
type = db.StringField(required=True, choices=list(REUSE_TYPES))
|
|
33
|
-
url = db.StringField(required=True)
|
|
34
69
|
urlhash = db.StringField(required=True, unique=True)
|
|
35
70
|
image_url = db.StringField()
|
|
36
|
-
image =
|
|
37
|
-
|
|
71
|
+
image = field(
|
|
72
|
+
db.ImageField(
|
|
73
|
+
fs=images,
|
|
74
|
+
basename=default_image_basename,
|
|
75
|
+
max_size=IMAGE_MAX_SIZE,
|
|
76
|
+
thumbnails=IMAGE_SIZES,
|
|
77
|
+
),
|
|
78
|
+
readonly=True,
|
|
79
|
+
show_as_ref=True,
|
|
80
|
+
thumbnail_info={
|
|
81
|
+
"size": BIGGEST_IMAGE_SIZE,
|
|
82
|
+
},
|
|
83
|
+
)
|
|
84
|
+
datasets = field(
|
|
85
|
+
db.ListField(
|
|
86
|
+
field(
|
|
87
|
+
db.ReferenceField("Dataset", reverse_delete_rule=db.PULL),
|
|
88
|
+
nested_fields=dataset_fields,
|
|
89
|
+
),
|
|
90
|
+
),
|
|
91
|
+
filterable={
|
|
92
|
+
"key": "dataset",
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
tags = field(
|
|
96
|
+
db.TagListField(),
|
|
97
|
+
filterable={
|
|
98
|
+
"key": "tag",
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
topic = field(
|
|
102
|
+
db.StringField(required=True, choices=list(REUSE_TOPICS)),
|
|
103
|
+
filterable={},
|
|
38
104
|
)
|
|
39
|
-
datasets = db.ListField(db.ReferenceField("Dataset", reverse_delete_rule=db.PULL))
|
|
40
|
-
tags = db.TagListField()
|
|
41
|
-
topic = db.StringField(required=True, choices=list(REUSE_TOPICS))
|
|
42
105
|
# badges = db.ListField(db.EmbeddedDocumentField(ReuseBadge))
|
|
43
106
|
|
|
44
|
-
private = db.BooleanField(default=False)
|
|
107
|
+
private = field(db.BooleanField(default=False))
|
|
45
108
|
|
|
46
109
|
ext = db.MapField(db.GenericEmbeddedDocumentField())
|
|
47
|
-
extras = db.ExtrasField()
|
|
110
|
+
extras = field(db.ExtrasField())
|
|
48
111
|
|
|
49
|
-
featured =
|
|
50
|
-
|
|
51
|
-
|
|
112
|
+
featured = field(
|
|
113
|
+
db.BooleanField(),
|
|
114
|
+
filterable={},
|
|
115
|
+
readonly=True,
|
|
116
|
+
)
|
|
117
|
+
deleted = field(
|
|
118
|
+
db.DateTimeField(),
|
|
119
|
+
readonly=True,
|
|
120
|
+
)
|
|
121
|
+
archived = field(
|
|
122
|
+
db.DateTimeField(),
|
|
123
|
+
)
|
|
52
124
|
|
|
53
125
|
def __str__(self):
|
|
54
126
|
return self.title or ""
|
|
@@ -110,6 +182,16 @@ class Reuse(db.Datetimed, WithMetrics, BadgeMixin, Owned, db.Document):
|
|
|
110
182
|
|
|
111
183
|
display_url = property(url_for)
|
|
112
184
|
|
|
185
|
+
@function_field(description="Link to the API endpoint for this reuse", show_as_ref=True)
|
|
186
|
+
def uri(self):
|
|
187
|
+
return endpoint_for("api.reuse", reuse=self, _external=True)
|
|
188
|
+
|
|
189
|
+
@function_field(description="Link to the udata web page for this reuse", show_as_ref=True)
|
|
190
|
+
def page(self):
|
|
191
|
+
return endpoint_for(
|
|
192
|
+
"reuses.show", reuse=self, _external=True, fallback_endpoint="api.reuse"
|
|
193
|
+
)
|
|
194
|
+
|
|
113
195
|
@property
|
|
114
196
|
def is_visible(self):
|
|
115
197
|
return not self.is_hidden
|
udata/core/site/api.py
CHANGED
|
@@ -6,7 +6,6 @@ from udata.api import API, api, fields
|
|
|
6
6
|
from udata.auth import admin_permission
|
|
7
7
|
from udata.core.dataservices.models import Dataservice
|
|
8
8
|
from udata.core.dataset.api_fields import dataset_fields
|
|
9
|
-
from udata.core.reuse.api_fields import reuse_fields
|
|
10
9
|
from udata.models import Dataset, Reuse
|
|
11
10
|
from udata.rdf import CONTEXT, RDF_EXTENSIONS, graph_response, negociate_content
|
|
12
11
|
from udata.utils import multi_to_dict
|
|
@@ -62,7 +61,7 @@ class SiteHomeDatasetsAPI(API):
|
|
|
62
61
|
class SiteHomeReusesAPI(API):
|
|
63
62
|
@api.doc("get_home_reuses")
|
|
64
63
|
@api.secure(admin_permission)
|
|
65
|
-
@api.marshal_list_with(
|
|
64
|
+
@api.marshal_list_with(Reuse.__read_fields__)
|
|
66
65
|
def get(self):
|
|
67
66
|
"""List homepage featured reuses"""
|
|
68
67
|
return current_site.settings.home_reuses
|
|
@@ -70,7 +69,7 @@ class SiteHomeReusesAPI(API):
|
|
|
70
69
|
@api.secure(admin_permission)
|
|
71
70
|
@api.doc("set_home_reuses")
|
|
72
71
|
@api.expect(([str], "Reuse IDs to put in homepage"))
|
|
73
|
-
@api.marshal_list_with(
|
|
72
|
+
@api.marshal_list_with(Reuse.__read_fields__)
|
|
74
73
|
def put(self):
|
|
75
74
|
"""Set the homepage reuses editorial selection"""
|
|
76
75
|
if not isinstance(request.json, list):
|
udata/core/topic/api.py
CHANGED
|
@@ -2,7 +2,7 @@ from udata.api import API, api, fields
|
|
|
2
2
|
from udata.core.dataset.api_fields import dataset_fields
|
|
3
3
|
from udata.core.discussions.models import Discussion
|
|
4
4
|
from udata.core.organization.api_fields import org_ref_fields
|
|
5
|
-
from udata.core.reuse.
|
|
5
|
+
from udata.core.reuse.models import Reuse
|
|
6
6
|
from udata.core.spatial.api_fields import spatial_coverage_fields
|
|
7
7
|
from udata.core.topic.parsers import TopicApiParser
|
|
8
8
|
from udata.core.topic.permissions import TopicEditPermission
|
|
@@ -33,7 +33,7 @@ topic_fields = api.model(
|
|
|
33
33
|
attribute=lambda o: [d.fetch() for d in o.datasets],
|
|
34
34
|
),
|
|
35
35
|
"reuses": fields.List(
|
|
36
|
-
fields.Nested(
|
|
36
|
+
fields.Nested(Reuse.__read_fields__),
|
|
37
37
|
description="The topic reuses",
|
|
38
38
|
attribute=lambda o: [r.fetch() for r in o.reuses],
|
|
39
39
|
),
|
udata/core/topic/apiv2.py
CHANGED
|
@@ -10,7 +10,6 @@ from udata.core.dataset.apiv2 import dataset_page_fields
|
|
|
10
10
|
from udata.core.dataset.models import Dataset
|
|
11
11
|
from udata.core.organization.api_fields import org_ref_fields
|
|
12
12
|
from udata.core.reuse.api import ReuseApiParser
|
|
13
|
-
from udata.core.reuse.apiv2 import reuse_page_fields
|
|
14
13
|
from udata.core.reuse.models import Reuse
|
|
15
14
|
from udata.core.spatial.api_fields import spatial_coverage_fields
|
|
16
15
|
from udata.core.topic.models import Topic
|
|
@@ -216,7 +215,7 @@ class TopicDatasetAPI(API):
|
|
|
216
215
|
class TopicReusesAPI(API):
|
|
217
216
|
@apiv2.doc("topic_reuses")
|
|
218
217
|
@apiv2.expect(reuse_parser.parser)
|
|
219
|
-
@apiv2.marshal_with(
|
|
218
|
+
@apiv2.marshal_with(Reuse.__page_fields__)
|
|
220
219
|
def get(self, topic):
|
|
221
220
|
"""Get a given topic reuses, with filters"""
|
|
222
221
|
args = reuse_parser.parse()
|
udata/core/user/api.py
CHANGED
|
@@ -8,7 +8,6 @@ from udata.core.dataset.api_fields import community_resource_fields, dataset_fie
|
|
|
8
8
|
from udata.core.discussions.actions import discussions_for
|
|
9
9
|
from udata.core.discussions.api import discussion_fields
|
|
10
10
|
from udata.core.followers.api import FollowAPI
|
|
11
|
-
from udata.core.reuse.api_fields import reuse_fields
|
|
12
11
|
from udata.core.storages.api import (
|
|
13
12
|
image_parser,
|
|
14
13
|
parse_uploaded_image,
|
|
@@ -102,7 +101,7 @@ class AvatarAPI(API):
|
|
|
102
101
|
class MyReusesAPI(API):
|
|
103
102
|
@api.secure
|
|
104
103
|
@api.doc("my_reuses")
|
|
105
|
-
@api.marshal_list_with(
|
|
104
|
+
@api.marshal_list_with(Reuse.__read_fields__)
|
|
106
105
|
def get(self):
|
|
107
106
|
"""List all my reuses (including private ones)"""
|
|
108
107
|
return list(Reuse.objects.owned_by(current_user.id))
|
|
@@ -165,7 +164,7 @@ class MyOrgReusesAPI(API):
|
|
|
165
164
|
@api.secure
|
|
166
165
|
@api.doc("my_org_reuses")
|
|
167
166
|
@api.expect(filter_parser)
|
|
168
|
-
@api.marshal_list_with(
|
|
167
|
+
@api.marshal_list_with(Reuse.__read_fields__)
|
|
169
168
|
def get(self):
|
|
170
169
|
"""List all reuses related to me and my organizations."""
|
|
171
170
|
q = filter_parser.parse_args().get("q")
|
udata/features/transfer/api.py
CHANGED
|
@@ -3,7 +3,6 @@ from flask import request
|
|
|
3
3
|
from udata.api import API, api, base_reference, fields
|
|
4
4
|
from udata.core.dataset.api_fields import dataset_ref_fields
|
|
5
5
|
from udata.core.organization.api_fields import org_ref_fields
|
|
6
|
-
from udata.core.reuse.api_fields import reuse_ref_fields
|
|
7
6
|
from udata.core.user.api_fields import user_ref_fields
|
|
8
7
|
from udata.models import Dataset, Organization, Reuse, User, db
|
|
9
8
|
from udata.utils import id_or_404
|
|
@@ -47,7 +46,7 @@ person_mapping = {
|
|
|
47
46
|
|
|
48
47
|
subject_mapping = {
|
|
49
48
|
Dataset: dataset_ref_fields,
|
|
50
|
-
Reuse:
|
|
49
|
+
Reuse: Reuse.__ref_fields__,
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
transfer_fields = api.model(
|
udata/mongo/datetime_fields.py
CHANGED
|
@@ -6,6 +6,7 @@ from mongoengine import EmbeddedDocument
|
|
|
6
6
|
from mongoengine.fields import BaseField, DateTimeField
|
|
7
7
|
from mongoengine.signals import pre_save
|
|
8
8
|
|
|
9
|
+
from udata.api_fields import field
|
|
9
10
|
from udata.i18n import lazy_gettext as _
|
|
10
11
|
|
|
11
12
|
log = logging.getLogger(__name__)
|
|
@@ -55,11 +56,17 @@ class DateRange(EmbeddedDocument):
|
|
|
55
56
|
|
|
56
57
|
|
|
57
58
|
class Datetimed(object):
|
|
58
|
-
created_at =
|
|
59
|
-
verbose_name=_("Creation date"), default=datetime.utcnow, required=True
|
|
59
|
+
created_at = field(
|
|
60
|
+
DateTimeField(verbose_name=_("Creation date"), default=datetime.utcnow, required=True),
|
|
61
|
+
sortable="created",
|
|
62
|
+
readonly=True,
|
|
60
63
|
)
|
|
61
|
-
last_modified =
|
|
62
|
-
|
|
64
|
+
last_modified = field(
|
|
65
|
+
DateTimeField(
|
|
66
|
+
verbose_name=_("Last modification date"), default=datetime.utcnow, required=True
|
|
67
|
+
),
|
|
68
|
+
sortable=True,
|
|
69
|
+
readonly=True,
|
|
63
70
|
)
|
|
64
71
|
|
|
65
72
|
|
udata/mongo/document.py
CHANGED
|
@@ -11,6 +11,8 @@ log = logging.getLogger(__name__)
|
|
|
11
11
|
def serialize(value):
|
|
12
12
|
if hasattr(value, "to_dict"):
|
|
13
13
|
return value.to_dict()
|
|
14
|
+
elif isinstance(value, dict):
|
|
15
|
+
return {key: serialize(val) for key, val in value.items()}
|
|
14
16
|
elif isinstance(value, Iterable) and not isinstance(value, str):
|
|
15
17
|
return [serialize(val) for val in value]
|
|
16
18
|
else:
|
udata/mongo/taglist_field.py
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
from mongoengine.fields import ListField, StringField
|
|
2
2
|
from slugify import slugify
|
|
3
3
|
|
|
4
|
+
from udata import tags
|
|
5
|
+
from udata.i18n import lazy_gettext as _
|
|
6
|
+
|
|
4
7
|
|
|
5
8
|
class TagListField(ListField):
|
|
6
9
|
def __init__(self, **kwargs):
|
|
7
10
|
self.tags = []
|
|
8
11
|
super(TagListField, self).__init__(StringField(), **kwargs)
|
|
9
12
|
|
|
13
|
+
@staticmethod
|
|
14
|
+
def from_input(input):
|
|
15
|
+
if isinstance(input, list):
|
|
16
|
+
return [tags.slug(value) for value in input]
|
|
17
|
+
elif isinstance(input, str):
|
|
18
|
+
return tags.tags_list(input)
|
|
19
|
+
else:
|
|
20
|
+
return []
|
|
21
|
+
|
|
10
22
|
def clean(self, value):
|
|
11
23
|
return sorted(list(set([slugify(v, to_lower=True) for v in value])))
|
|
12
24
|
|
|
@@ -15,3 +27,17 @@ class TagListField(ListField):
|
|
|
15
27
|
|
|
16
28
|
def to_mongo(self, value):
|
|
17
29
|
return super(TagListField, self).to_mongo(self.clean(value))
|
|
30
|
+
|
|
31
|
+
def validate(self, values):
|
|
32
|
+
super(TagListField, self).validate(values)
|
|
33
|
+
|
|
34
|
+
for tag in values:
|
|
35
|
+
if not tags.MIN_TAG_LENGTH <= len(tag) <= tags.MAX_TAG_LENGTH:
|
|
36
|
+
self.error(
|
|
37
|
+
_(
|
|
38
|
+
'Tag "%(tag)s" must be between %(min)d ' "and %(max)d characters long.",
|
|
39
|
+
min=tags.MIN_TAG_LENGTH,
|
|
40
|
+
max=tags.MAX_TAG_LENGTH,
|
|
41
|
+
tag=tag,
|
|
42
|
+
)
|
|
43
|
+
)
|