udata 9.2.5.dev32160__py2.py3-none-any.whl → 9.2.5.dev32190__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 +143 -37
- udata/core/badges/factories.py +3 -5
- udata/core/badges/forms.py +0 -3
- udata/core/badges/models.py +19 -24
- udata/core/badges/tests/test_commands.py +3 -4
- udata/core/badges/tests/test_model.py +37 -28
- udata/core/dataservices/models.py +4 -1
- udata/core/dataset/api.py +10 -0
- udata/core/dataset/events.py +25 -16
- udata/core/dataset/models.py +16 -6
- udata/core/dataset/search.py +3 -1
- udata/core/organization/api.py +11 -0
- udata/core/organization/models.py +23 -10
- udata/core/organization/search.py +6 -2
- udata/core/reuse/api.py +10 -0
- udata/core/reuse/models.py +15 -5
- udata/core/reuse/search.py +3 -1
- udata/search/fields.py +8 -3
- udata/static/chunks/{10.a99bb538cfbadb38dbcb.js → 10.dac55d18d0b4ef3cdacf.js} +3 -3
- udata/static/chunks/{10.a99bb538cfbadb38dbcb.js.map → 10.dac55d18d0b4ef3cdacf.js.map} +1 -1
- udata/static/chunks/{11.bb1c1fb39f740fbbeec0.js → 11.4a20a75f827c5a1125c3.js} +3 -3
- udata/static/chunks/{11.bb1c1fb39f740fbbeec0.js.map → 11.4a20a75f827c5a1125c3.js.map} +1 -1
- udata/static/chunks/{13.bef5fdb3e147e94fea99.js → 13.645dd0b7c0b9210f1b56.js} +2 -2
- udata/static/chunks/{13.bef5fdb3e147e94fea99.js.map → 13.645dd0b7c0b9210f1b56.js.map} +1 -1
- udata/static/chunks/{17.b91d28f550dc44bc4979.js → 17.8e19985c4d12a3b7b0c0.js} +2 -2
- udata/static/chunks/{17.b91d28f550dc44bc4979.js.map → 17.8e19985c4d12a3b7b0c0.js.map} +1 -1
- udata/static/chunks/{19.2c615ffee1e807000770.js → 19.825a43c330157e351fca.js} +3 -3
- udata/static/chunks/{19.2c615ffee1e807000770.js.map → 19.825a43c330157e351fca.js.map} +1 -1
- udata/static/chunks/{8.291bde987ed97294e4de.js → 8.5ee0cf635c848abbfc05.js} +2 -2
- udata/static/chunks/{8.291bde987ed97294e4de.js.map → 8.5ee0cf635c848abbfc05.js.map} +1 -1
- udata/static/chunks/{9.985935421e62c97a9f86.js → 9.df3c36f8d0d210621fbb.js} +3 -3
- udata/static/chunks/{9.985935421e62c97a9f86.js.map → 9.df3c36f8d0d210621fbb.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/tests/api/test_dataservices_api.py +21 -1
- udata/tests/api/test_datasets_api.py +15 -0
- udata/tests/api/test_organizations_api.py +15 -4
- udata/tests/api/test_reuses_api.py +15 -0
- udata/tests/apiv2/test_datasets.py +17 -0
- udata/tests/dataset/test_dataset_events.py +3 -15
- udata/tests/organization/test_organization_model.py +21 -0
- udata/tests/site/test_site_metrics.py +1 -3
- {udata-9.2.5.dev32160.dist-info → udata-9.2.5.dev32190.dist-info}/METADATA +3 -1
- {udata-9.2.5.dev32160.dist-info → udata-9.2.5.dev32190.dist-info}/RECORD +48 -48
- {udata-9.2.5.dev32160.dist-info → udata-9.2.5.dev32190.dist-info}/LICENSE +0 -0
- {udata-9.2.5.dev32160.dist-info → udata-9.2.5.dev32190.dist-info}/WHEEL +0 -0
- {udata-9.2.5.dev32160.dist-info → udata-9.2.5.dev32190.dist-info}/entry_points.txt +0 -0
- {udata-9.2.5.dev32160.dist-info → udata-9.2.5.dev32190.dist-info}/top_level.txt +0 -0
udata/core/dataset/models.py
CHANGED
|
@@ -14,12 +14,13 @@ from mongoengine.signals import post_save, pre_save
|
|
|
14
14
|
from stringdist import rdlevenshtein
|
|
15
15
|
from werkzeug.utils import cached_property
|
|
16
16
|
|
|
17
|
+
from udata.api_fields import field
|
|
17
18
|
from udata.app import cache
|
|
18
19
|
from udata.core import storages
|
|
19
20
|
from udata.core.owned import Owned, OwnedQuerySet
|
|
20
21
|
from udata.frontend.markdown import mdstrip
|
|
21
22
|
from udata.i18n import lazy_gettext as _
|
|
22
|
-
from udata.models import BadgeMixin, SpatialCoverage, WithMetrics, db
|
|
23
|
+
from udata.models import Badge, BadgeMixin, BadgesList, SpatialCoverage, WithMetrics, db
|
|
23
24
|
from udata.mongo.errors import FieldValidationError
|
|
24
25
|
from udata.uris import ValidationError, endpoint_for
|
|
25
26
|
from udata.uris import validate as validate_url
|
|
@@ -53,6 +54,10 @@ __all__ = (
|
|
|
53
54
|
"ResourceSchema",
|
|
54
55
|
)
|
|
55
56
|
|
|
57
|
+
BADGES: dict[str, str] = {
|
|
58
|
+
PIVOTAL_DATA: _("Pivotal data"),
|
|
59
|
+
}
|
|
60
|
+
|
|
56
61
|
NON_ASSIGNABLE_SCHEMA_TYPES = ["datapackage"]
|
|
57
62
|
|
|
58
63
|
log = logging.getLogger(__name__)
|
|
@@ -498,7 +503,16 @@ class Resource(ResourceMixin, WithMetrics, db.EmbeddedDocument):
|
|
|
498
503
|
self.dataset.save(*args, **kwargs)
|
|
499
504
|
|
|
500
505
|
|
|
501
|
-
class
|
|
506
|
+
class DatasetBadge(Badge):
|
|
507
|
+
kind = db.StringField(required=True, choices=list(BADGES.keys()))
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
class DatasetBadgeMixin(BadgeMixin):
|
|
511
|
+
badges = field(BadgesList(DatasetBadge), **BadgeMixin.default_badges_list_params)
|
|
512
|
+
__badges__ = BADGES
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
class Dataset(WithMetrics, DatasetBadgeMixin, Owned, db.Document):
|
|
502
516
|
title = db.StringField(required=True)
|
|
503
517
|
acronym = db.StringField(max_length=128)
|
|
504
518
|
# /!\ do not set directly the slug when creating or updating a dataset
|
|
@@ -539,10 +553,6 @@ class Dataset(WithMetrics, BadgeMixin, Owned, db.Document):
|
|
|
539
553
|
def __str__(self):
|
|
540
554
|
return self.title or ""
|
|
541
555
|
|
|
542
|
-
__badges__ = {
|
|
543
|
-
PIVOTAL_DATA: _("Pivotal data"),
|
|
544
|
-
}
|
|
545
|
-
|
|
546
556
|
__metrics_keys__ = [
|
|
547
557
|
"discussions",
|
|
548
558
|
"reuses",
|
udata/core/dataset/search.py
CHANGED
|
@@ -32,8 +32,9 @@ class DatasetSearch(ModelSearchAdapter):
|
|
|
32
32
|
|
|
33
33
|
filters = {
|
|
34
34
|
"tag": Filter(),
|
|
35
|
-
"badge": Filter(),
|
|
35
|
+
"badge": Filter(choices=list(Dataset.__badges__)),
|
|
36
36
|
"organization": ModelTermsFilter(model=Organization),
|
|
37
|
+
"organization_badge": Filter(choices=list(Organization.__badges__)),
|
|
37
38
|
"owner": ModelTermsFilter(model=User),
|
|
38
39
|
"license": ModelTermsFilter(model=License),
|
|
39
40
|
"geozone": ModelTermsFilter(model=GeoZone),
|
|
@@ -76,6 +77,7 @@ class DatasetSearch(ModelSearchAdapter):
|
|
|
76
77
|
"name": org.name,
|
|
77
78
|
"public_service": 1 if org.public_service else 0,
|
|
78
79
|
"followers": org.metrics.get("followers", 0),
|
|
80
|
+
"badges": [badge.kind for badge in org.badges],
|
|
79
81
|
}
|
|
80
82
|
elif dataset.owner:
|
|
81
83
|
owner = User.objects(id=dataset.owner.id).first()
|
udata/core/organization/api.py
CHANGED
|
@@ -63,6 +63,15 @@ class OrgApiParser(ModelApiParser):
|
|
|
63
63
|
"last_modified": "last_modified",
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
def __init__(self):
|
|
67
|
+
super().__init__()
|
|
68
|
+
self.parser.add_argument(
|
|
69
|
+
"badge",
|
|
70
|
+
type=str,
|
|
71
|
+
choices=list(Organization.__badges__),
|
|
72
|
+
location="args",
|
|
73
|
+
)
|
|
74
|
+
|
|
66
75
|
@staticmethod
|
|
67
76
|
def parse_filters(organizations, args):
|
|
68
77
|
if args.get("q"):
|
|
@@ -72,6 +81,8 @@ class OrgApiParser(ModelApiParser):
|
|
|
72
81
|
# between tokens whereas an OR is used without it.
|
|
73
82
|
phrase_query = " ".join([f'"{elem}"' for elem in args["q"].split(" ")])
|
|
74
83
|
organizations = organizations.search_text(phrase_query)
|
|
84
|
+
if args.get("badge"):
|
|
85
|
+
organizations = organizations.with_badge(args["badge"])
|
|
75
86
|
return organizations
|
|
76
87
|
|
|
77
88
|
|
|
@@ -5,7 +5,8 @@ from blinker import Signal
|
|
|
5
5
|
from mongoengine.signals import post_save, pre_save
|
|
6
6
|
from werkzeug.utils import cached_property
|
|
7
7
|
|
|
8
|
-
from udata.
|
|
8
|
+
from udata.api_fields import field
|
|
9
|
+
from udata.core.badges.models import Badge, BadgeMixin, BadgesList
|
|
9
10
|
from udata.core.metrics.models import WithMetrics
|
|
10
11
|
from udata.core.storages import avatars, default_image_basename
|
|
11
12
|
from udata.frontend.markdown import mdstrip
|
|
@@ -29,6 +30,14 @@ from .constants import (
|
|
|
29
30
|
|
|
30
31
|
__all__ = ("Organization", "Team", "Member", "MembershipRequest")
|
|
31
32
|
|
|
33
|
+
BADGES: dict[str, str] = {
|
|
34
|
+
PUBLIC_SERVICE: _("Public Service"),
|
|
35
|
+
CERTIFIED: _("Certified"),
|
|
36
|
+
ASSOCIATION: _("Association"),
|
|
37
|
+
COMPANY: _("Company"),
|
|
38
|
+
LOCAL_AUTHORITY: _("Local authority"),
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
|
|
33
42
|
class Team(db.EmbeddedDocument):
|
|
34
43
|
name = db.StringField(required=True)
|
|
@@ -82,8 +91,20 @@ class OrganizationQuerySet(db.BaseQuerySet):
|
|
|
82
91
|
def get_by_id_or_slug(self, id_or_slug):
|
|
83
92
|
return self(slug=id_or_slug).first() or self(id=id_or_slug).first()
|
|
84
93
|
|
|
94
|
+
def with_badge(self, kind):
|
|
95
|
+
return self(badges__kind=kind)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class OrganizationBadge(Badge):
|
|
99
|
+
kind = db.StringField(required=True, choices=list(BADGES.keys()))
|
|
100
|
+
|
|
85
101
|
|
|
86
|
-
class
|
|
102
|
+
class OrganizationBadgeMixin(BadgeMixin):
|
|
103
|
+
badges = field(BadgesList(OrganizationBadge), **BadgeMixin.default_badges_list_params)
|
|
104
|
+
__badges__ = BADGES
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class Organization(WithMetrics, OrganizationBadgeMixin, db.Datetimed, db.Document):
|
|
87
108
|
name = db.StringField(required=True)
|
|
88
109
|
acronym = db.StringField(max_length=128)
|
|
89
110
|
slug = db.SlugField(
|
|
@@ -126,14 +147,6 @@ class Organization(WithMetrics, BadgeMixin, db.Datetimed, db.Document):
|
|
|
126
147
|
def __str__(self):
|
|
127
148
|
return self.name or ""
|
|
128
149
|
|
|
129
|
-
__badges__ = {
|
|
130
|
-
PUBLIC_SERVICE: _("Public Service"),
|
|
131
|
-
CERTIFIED: _("Certified"),
|
|
132
|
-
ASSOCIATION: _("Association"),
|
|
133
|
-
COMPANY: _("Company"),
|
|
134
|
-
LOCAL_AUTHORITY: _("Local authority"),
|
|
135
|
-
}
|
|
136
|
-
|
|
137
150
|
__metrics_keys__ = [
|
|
138
151
|
"datasets",
|
|
139
152
|
"members",
|
|
@@ -3,7 +3,7 @@ import datetime
|
|
|
3
3
|
from udata import search
|
|
4
4
|
from udata.core.organization.api import DEFAULT_SORTING, OrgApiParser
|
|
5
5
|
from udata.models import Organization
|
|
6
|
-
from udata.search.fields import
|
|
6
|
+
from udata.search.fields import ModelTermsFilter
|
|
7
7
|
from udata.utils import to_iso_datetime
|
|
8
8
|
|
|
9
9
|
__all__ = ("OrganizationSearch",)
|
|
@@ -22,7 +22,11 @@ class OrganizationSearch(search.ModelSearchAdapter):
|
|
|
22
22
|
"created": "created_at",
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
filters = {
|
|
25
|
+
filters = {
|
|
26
|
+
"badge": ModelTermsFilter(
|
|
27
|
+
model=Organization, field_name="badges", choices=list(Organization.__badges__)
|
|
28
|
+
),
|
|
29
|
+
}
|
|
26
30
|
|
|
27
31
|
@classmethod
|
|
28
32
|
def is_indexable(cls, org):
|
udata/core/reuse/api.py
CHANGED
|
@@ -13,6 +13,7 @@ from udata.core.badges import api as badges_api
|
|
|
13
13
|
from udata.core.badges.fields import badge_fields
|
|
14
14
|
from udata.core.dataset.api_fields import dataset_ref_fields
|
|
15
15
|
from udata.core.followers.api import FollowAPI
|
|
16
|
+
from udata.core.organization.models import Organization
|
|
16
17
|
from udata.core.reuse.constants import REUSE_TOPICS, REUSE_TYPES
|
|
17
18
|
from udata.core.storages.api import (
|
|
18
19
|
image_parser,
|
|
@@ -49,6 +50,12 @@ class ReuseApiParser(ModelApiParser):
|
|
|
49
50
|
self.parser.add_argument("dataset", type=str, location="args")
|
|
50
51
|
self.parser.add_argument("tag", type=str, location="args")
|
|
51
52
|
self.parser.add_argument("organization", type=str, location="args")
|
|
53
|
+
self.parser.add_argument(
|
|
54
|
+
"organization_badge",
|
|
55
|
+
type=str,
|
|
56
|
+
choices=list(Organization.__badges__),
|
|
57
|
+
location="args",
|
|
58
|
+
)
|
|
52
59
|
self.parser.add_argument("owner", type=str, location="args")
|
|
53
60
|
self.parser.add_argument("type", type=str, location="args")
|
|
54
61
|
self.parser.add_argument("topic", type=str, location="args")
|
|
@@ -79,6 +86,9 @@ class ReuseApiParser(ModelApiParser):
|
|
|
79
86
|
if not ObjectId.is_valid(args["organization"]):
|
|
80
87
|
api.abort(400, "Organization arg must be an identifier")
|
|
81
88
|
reuses = reuses.filter(organization=args["organization"])
|
|
89
|
+
if args.get("organization_badge"):
|
|
90
|
+
orgs = Organization.objects.with_badge(args["organization_badge"])
|
|
91
|
+
reuses = reuses.filter(organization__in=orgs)
|
|
82
92
|
if args.get("owner"):
|
|
83
93
|
if not ObjectId.is_valid(args["owner"]):
|
|
84
94
|
api.abort(400, "Owner arg must be an identifier")
|
udata/core/reuse/models.py
CHANGED
|
@@ -9,7 +9,7 @@ from udata.core.reuse.api_fields import BIGGEST_IMAGE_SIZE
|
|
|
9
9
|
from udata.core.storages import default_image_basename, images
|
|
10
10
|
from udata.frontend.markdown import mdstrip
|
|
11
11
|
from udata.i18n import lazy_gettext as _
|
|
12
|
-
from udata.models import BadgeMixin, WithMetrics, db
|
|
12
|
+
from udata.models import Badge, BadgeMixin, BadgesList, WithMetrics, db
|
|
13
13
|
from udata.mongo.errors import FieldValidationError
|
|
14
14
|
from udata.uris import endpoint_for
|
|
15
15
|
from udata.utils import hash_url
|
|
@@ -18,6 +18,8 @@ from .constants import IMAGE_MAX_SIZE, IMAGE_SIZES, REUSE_TOPICS, REUSE_TYPES
|
|
|
18
18
|
|
|
19
19
|
__all__ = ("Reuse",)
|
|
20
20
|
|
|
21
|
+
BADGES: dict[str, str] = {}
|
|
22
|
+
|
|
21
23
|
|
|
22
24
|
class ReuseQuerySet(OwnedQuerySet):
|
|
23
25
|
def visible(self):
|
|
@@ -33,15 +35,25 @@ def check_url_does_not_exists(url):
|
|
|
33
35
|
raise FieldValidationError(_("This URL is already registered"), field="url")
|
|
34
36
|
|
|
35
37
|
|
|
38
|
+
class ReuseBadge(Badge):
|
|
39
|
+
kind = db.StringField(required=True, choices=list(BADGES.keys()))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ReuseBadgeMixin(BadgeMixin):
|
|
43
|
+
badges = field(BadgesList(ReuseBadge), **BadgeMixin.default_badges_list_params)
|
|
44
|
+
__badges__ = BADGES
|
|
45
|
+
|
|
46
|
+
|
|
36
47
|
@generate_fields(
|
|
37
48
|
searchable=True,
|
|
38
|
-
|
|
49
|
+
additional_sorts=[
|
|
39
50
|
{"key": "datasets", "value": "metrics.datasets"},
|
|
40
51
|
{"key": "followers", "value": "metrics.followers"},
|
|
41
52
|
{"key": "views", "value": "metrics.views"},
|
|
42
53
|
],
|
|
54
|
+
additional_filters={"organization_badge": "organization.badges"},
|
|
43
55
|
)
|
|
44
|
-
class Reuse(db.Datetimed, WithMetrics,
|
|
56
|
+
class Reuse(db.Datetimed, WithMetrics, ReuseBadgeMixin, Owned, db.Document):
|
|
45
57
|
title = field(
|
|
46
58
|
db.StringField(required=True),
|
|
47
59
|
sortable=True,
|
|
@@ -124,8 +136,6 @@ class Reuse(db.Datetimed, WithMetrics, BadgeMixin, Owned, db.Document):
|
|
|
124
136
|
def __str__(self):
|
|
125
137
|
return self.title or ""
|
|
126
138
|
|
|
127
|
-
__badges__ = {}
|
|
128
|
-
|
|
129
139
|
__metrics_keys__ = [
|
|
130
140
|
"discussions",
|
|
131
141
|
"datasets",
|
udata/core/reuse/search.py
CHANGED
|
@@ -29,9 +29,10 @@ class ReuseSearch(ModelSearchAdapter):
|
|
|
29
29
|
filters = {
|
|
30
30
|
"tag": Filter(),
|
|
31
31
|
"organization": ModelTermsFilter(model=Organization),
|
|
32
|
+
"organization_badge": Filter(choices=list(Organization.__badges__)),
|
|
32
33
|
"owner": ModelTermsFilter(model=User),
|
|
33
34
|
"type": Filter(),
|
|
34
|
-
"badge": Filter(),
|
|
35
|
+
"badge": Filter(choices=list(Reuse.__badges__)),
|
|
35
36
|
"featured": BoolFilter(),
|
|
36
37
|
"topic": Filter(),
|
|
37
38
|
"archived": BoolFilter(),
|
|
@@ -65,6 +66,7 @@ class ReuseSearch(ModelSearchAdapter):
|
|
|
65
66
|
"name": org.name,
|
|
66
67
|
"public_service": 1 if org.public_service else 0,
|
|
67
68
|
"followers": org.metrics.get("followers", 0),
|
|
69
|
+
"badges": [badge.kind for badge in org.badges],
|
|
68
70
|
}
|
|
69
71
|
elif reuse.owner:
|
|
70
72
|
owner = User.objects(id=reuse.owner.id).first()
|
udata/search/fields.py
CHANGED
|
@@ -19,8 +19,12 @@ OR_SEPARATOR = "|"
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class Filter:
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
def __init__(self, choices=None):
|
|
23
|
+
self.choices = choices
|
|
24
|
+
|
|
25
|
+
def as_request_parser_kwargs(self):
|
|
26
|
+
if self.choices:
|
|
27
|
+
return {"type": clean_string, "choices": self.choices}
|
|
24
28
|
return {"type": clean_string}
|
|
25
29
|
|
|
26
30
|
|
|
@@ -31,9 +35,10 @@ class BoolFilter(Filter):
|
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
class ModelTermsFilter(Filter):
|
|
34
|
-
def __init__(self, model, field_name="id"):
|
|
38
|
+
def __init__(self, model, field_name="id", choices=None):
|
|
35
39
|
self.model = model
|
|
36
40
|
self.field_name = field_name
|
|
41
|
+
super().__init__(choices=choices)
|
|
37
42
|
|
|
38
43
|
@property
|
|
39
44
|
def model_field(self):
|