udata 9.2.5.dev32170__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.

@@ -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.core.badges.models import BadgeMixin
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 Organization(WithMetrics, BadgeMixin, db.Datetimed, db.Document):
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 Filter
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 = {"badge": Filter()}
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")
@@ -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
- additionalSorts=[
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, BadgeMixin, Owned, db.Document):
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",
@@ -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
- @staticmethod
23
- def as_request_parser_kwargs():
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):
@@ -3,6 +3,7 @@ from xml.etree.ElementTree import XML
3
3
  import pytest
4
4
  from flask import url_for
5
5
 
6
+ import udata.core.organization.constants as org_constants
6
7
  from udata.core.dataservices.factories import DataserviceFactory
7
8
  from udata.core.dataservices.models import Dataservice
8
9
  from udata.core.dataset.factories import DatasetFactory, LicenseFactory
@@ -10,7 +11,7 @@ from udata.core.organization.factories import OrganizationFactory
10
11
  from udata.core.organization.models import Member
11
12
  from udata.core.user.factories import UserFactory
12
13
  from udata.i18n import gettext as _
13
- from udata.tests.helpers import assert200, assert_redirects
14
+ from udata.tests.helpers import assert200, assert400, assert_redirects
14
15
 
15
16
  from . import APITestCase
16
17
 
@@ -18,6 +19,25 @@ from . import APITestCase
18
19
  class DataserviceAPITest(APITestCase):
19
20
  modules = []
20
21
 
22
+ def test_dataservices_api_list_with_filters(self):
23
+ """Should filters dataservices results based on query filters"""
24
+ org = OrganizationFactory()
25
+ org_public_service = OrganizationFactory()
26
+ org_public_service.add_badge(org_constants.PUBLIC_SERVICE)
27
+
28
+ _dataservice = DataserviceFactory(organization=org)
29
+ dataservice_public_service = DataserviceFactory(organization=org_public_service)
30
+
31
+ response = self.get(
32
+ url_for("api.dataservices", organization_badge=org_constants.PUBLIC_SERVICE)
33
+ )
34
+ assert200(response)
35
+ assert len(response.json["data"]) == 1
36
+ assert response.json["data"][0]["id"] == str(dataservice_public_service.id)
37
+
38
+ response = self.get(url_for("api.dataservices", organization_badge="bad-badge"))
39
+ assert400(response)
40
+
21
41
  def test_dataservice_api_create(self):
22
42
  user = self.login()
23
43
  datasets = DatasetFactory.create_batch(3)
@@ -8,6 +8,7 @@ import pytz
8
8
  import requests_mock
9
9
  from flask import url_for
10
10
 
11
+ import udata.core.organization.constants as org_constants
11
12
  from udata.api import fields
12
13
  from udata.app import cache
13
14
  from udata.core import storages
@@ -148,6 +149,8 @@ class DatasetAPITest(APITestCase):
148
149
  """Should filters datasets results based on query filters"""
149
150
  owner = UserFactory()
150
151
  org = OrganizationFactory()
152
+ org_public_service = OrganizationFactory()
153
+ org_public_service.add_badge(org_constants.PUBLIC_SERVICE)
151
154
 
152
155
  [DatasetFactory() for i in range(2)]
153
156
 
@@ -167,6 +170,7 @@ class DatasetAPITest(APITestCase):
167
170
 
168
171
  owner_dataset = DatasetFactory(owner=owner)
169
172
  org_dataset = DatasetFactory(organization=org)
173
+ org_dataset_public_service = DatasetFactory(organization=org_public_service)
170
174
 
171
175
  schema_dataset = DatasetFactory(
172
176
  resources=[
@@ -247,6 +251,17 @@ class DatasetAPITest(APITestCase):
247
251
  response = self.get(url_for("api.datasets", organization="org-id"))
248
252
  self.assert400(response)
249
253
 
254
+ # filter on organization badge
255
+ response = self.get(
256
+ url_for("api.datasets", organization_badge=org_constants.PUBLIC_SERVICE)
257
+ )
258
+ self.assert200(response)
259
+ self.assertEqual(len(response.json["data"]), 1)
260
+ self.assertEqual(response.json["data"][0]["id"], str(org_dataset_public_service.id))
261
+
262
+ response = self.get(url_for("api.datasets", organization_badge="bad-badge"))
263
+ self.assert400(response)
264
+
250
265
  # filter on schema
251
266
  response = self.get(url_for("api.datasets", schema="my-schema"))
252
267
  self.assert200(response)
@@ -3,6 +3,7 @@ from datetime import datetime
3
3
  import pytest
4
4
  from flask import url_for
5
5
 
6
+ import udata.core.organization.constants as org_constants
6
7
  from udata.core.badges.factories import badge_factory
7
8
  from udata.core.badges.signals import on_badge_added, on_badge_removed
8
9
  from udata.core.dataset.factories import DatasetFactory
@@ -41,6 +42,20 @@ class OrganizationAPITest:
41
42
  assert200(response)
42
43
  len(response.json["data"]) == len(organizations)
43
44
 
45
+ def test_organization_api_list_with_filters(self, api):
46
+ """It should filter the organization list"""
47
+ _org = OrganizationFactory()
48
+ org_public_service = OrganizationFactory()
49
+ org_public_service.add_badge(org_constants.PUBLIC_SERVICE)
50
+
51
+ response = api.get(url_for("api.organizations", badge=org_constants.PUBLIC_SERVICE))
52
+ assert200(response)
53
+ assert len(response.json["data"]) == 1
54
+ assert response.json["data"][0]["id"] == str(org_public_service.id)
55
+
56
+ response = api.get(url_for("api.organizations", badge="bad-badge"))
57
+ assert400(response)
58
+
44
59
  def test_organization_role_api_get(self, api):
45
60
  """It should fetch an organization's roles list from the API"""
46
61
  response = api.get(url_for("api.org_roles"))
@@ -818,10 +833,6 @@ class OrganizationBadgeAPITest:
818
833
 
819
834
  @pytest.fixture(autouse=True)
820
835
  def setUp(self, api, clean_db):
821
- # Register at least two badges
822
- Organization.__badges__["test-1"] = "Test 1"
823
- Organization.__badges__["test-2"] = "Test 2"
824
-
825
836
  self.factory = badge_factory(Organization)
826
837
  self.user = api.login(AdminFactory())
827
838
  self.organization = OrganizationFactory()
@@ -4,6 +4,7 @@ import pytest
4
4
  from flask import url_for
5
5
  from werkzeug.test import TestResponse
6
6
 
7
+ import udata.core.organization.constants as org_constants
7
8
  from udata.core.badges.factories import badge_factory
8
9
  from udata.core.dataset.factories import DatasetFactory
9
10
  from udata.core.organization.factories import OrganizationFactory
@@ -67,12 +68,17 @@ class ReuseAPITest:
67
68
  """Should filters reuses results based on query filters"""
68
69
  owner = UserFactory()
69
70
  org = OrganizationFactory()
71
+ org_public_service = OrganizationFactory()
72
+ org_public_service.add_badge(org_constants.PUBLIC_SERVICE)
70
73
 
71
74
  [ReuseFactory(topic="health", type="api") for i in range(2)]
72
75
 
73
76
  tag_reuse = ReuseFactory(tags=["my-tag", "other"], topic="health", type="api")
74
77
  owner_reuse = ReuseFactory(owner=owner, topic="health", type="api")
75
78
  org_reuse = ReuseFactory(organization=org, topic="health", type="api")
79
+ org_reuse_public_service = ReuseFactory(
80
+ organization=org_public_service, topic="health", type="api"
81
+ )
76
82
  featured_reuse = ReuseFactory(featured=True, topic="health", type="api")
77
83
  topic_reuse = ReuseFactory(topic="transport_and_mobility", type="api")
78
84
  type_reuse = ReuseFactory(topic="health", type="application")
@@ -125,6 +131,15 @@ class ReuseAPITest:
125
131
  response = api.get(url_for("api.reuses", organization="org-id"))
126
132
  assert400(response)
127
133
 
134
+ # filter on organization badge
135
+ response = api.get(url_for("api.reuses", organization_badge=org_constants.PUBLIC_SERVICE))
136
+ assert200(response)
137
+ assert len(response.json["data"]) == 1
138
+ assert response.json["data"][0]["id"] == str(org_reuse_public_service.id)
139
+
140
+ response = api.get(url_for("api.reuses", organization_badge="bad-badge"))
141
+ assert400(response)
142
+
128
143
  def test_reuse_api_list_filter_private(self, api) -> None:
129
144
  """Should filters reuses results based on the `private` filter"""
130
145
  user = UserFactory()
@@ -2,6 +2,7 @@ from datetime import datetime
2
2
 
3
3
  from flask import url_for
4
4
 
5
+ import udata.core.organization.constants as org_constants
5
6
  from udata.core.dataset.apiv2 import DEFAULT_PAGE_SIZE
6
7
  from udata.core.dataset.factories import (
7
8
  CommunityResourceFactory,
@@ -44,6 +45,22 @@ class DatasetAPIV2Test(APITestCase):
44
45
  assert data["community_resources"]["type"] == "GET"
45
46
  assert data["community_resources"]["total"] == 0
46
47
 
48
+ def test_search_dataset(self):
49
+ org = OrganizationFactory()
50
+ org.add_badge(org_constants.CERTIFIED)
51
+ org_public_service = OrganizationFactory()
52
+ org_public_service.add_badge(org_constants.PUBLIC_SERVICE)
53
+ _dataset_org = DatasetFactory(organization=org)
54
+ dataset_org_public_service = DatasetFactory(organization=org_public_service)
55
+
56
+ response = self.get(
57
+ url_for("apiv2.dataset_search", organization_badge=org_constants.PUBLIC_SERVICE)
58
+ )
59
+ self.assert200(response)
60
+ data = response.json["data"]
61
+ assert len(data) == 1
62
+ assert data[0]["id"] == str(dataset_org_public_service.id)
63
+
47
64
 
48
65
  class DatasetResourceAPIV2Test(APITestCase):
49
66
  def test_get_specific(self):
@@ -1,8 +1,10 @@
1
1
  from datetime import datetime
2
2
 
3
+ import udata.core.organization.constants as org_constants
3
4
  from udata.core.dataset.factories import DatasetFactory, HiddenDatasetFactory
4
5
  from udata.core.followers.signals import on_follow, on_unfollow
5
6
  from udata.core.organization.factories import OrganizationFactory
7
+ from udata.core.organization.models import Organization
6
8
  from udata.core.reuse.factories import ReuseFactory, VisibleReuseFactory
7
9
  from udata.core.user.factories import UserFactory
8
10
  from udata.models import Dataset, Follow, Member, Reuse
@@ -50,3 +52,22 @@ class OrganizationModelTest(TestCase, DBTestMixin):
50
52
  assert org.get_metrics()["datasets"] == 0
51
53
  assert org.get_metrics()["reuses"] == 0
52
54
  assert org.get_metrics()["followers"] == 0
55
+
56
+ def test_organization_queryset_with_badge(self):
57
+ org_public_service = OrganizationFactory()
58
+ org_public_service.add_badge(org_constants.PUBLIC_SERVICE)
59
+ org_certified_association = OrganizationFactory()
60
+ org_certified_association.add_badge(org_constants.CERTIFIED)
61
+ org_certified_association.add_badge(org_constants.ASSOCIATION)
62
+
63
+ public_services = list(Organization.objects.with_badge(org_constants.PUBLIC_SERVICE))
64
+ assert len(public_services) == 1
65
+ assert org_public_service in public_services
66
+
67
+ certified = list(Organization.objects.with_badge(org_constants.CERTIFIED))
68
+ assert len(certified) == 1
69
+ assert org_certified_association in certified
70
+
71
+ associations = list(Organization.objects.with_badge(org_constants.ASSOCIATION))
72
+ assert len(associations) == 1
73
+ assert org_certified_association in associations
@@ -9,7 +9,6 @@ from udata.core.organization.constants import PUBLIC_SERVICE
9
9
  from udata.core.reuse.factories import VisibleReuseFactory
10
10
  from udata.core.site.factories import SiteFactory
11
11
  from udata.harvest.tests.factories import HarvestSourceFactory
12
- from udata.models import Badge
13
12
 
14
13
 
15
14
  @pytest.mark.usefixtures("clean_db")
@@ -52,8 +51,7 @@ class SiteMetricTest:
52
51
  def test_badges_metric(self, app):
53
52
  site = SiteFactory.create(id=app.config["SITE_ID"])
54
53
 
55
- ps_badge = Badge(kind=PUBLIC_SERVICE)
56
- public_services = [OrganizationFactory(badges=[ps_badge]) for _ in range(2)]
54
+ public_services = [OrganizationFactory().add_badge(PUBLIC_SERVICE) for _ in range(2)]
57
55
  for _ in range(3):
58
56
  OrganizationFactory()
59
57
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: udata
3
- Version: 9.2.5.dev32170
3
+ Version: 9.2.5.dev32190
4
4
  Summary: Open data portal
5
5
  Home-page: https://github.com/opendatateam/udata
6
6
  Author: Opendata Team
@@ -146,6 +146,7 @@ It is collectively taken care of by members of the
146
146
  - Fix flaky "duplicated email" importing fixtures tests [#3176](https://github.com/opendatateam/udata/pull/3176)
147
147
  - Fix deprecated CircleCI config [#3181](https://github.com/opendatateam/udata/pull/3181)
148
148
  - Use proper RESTful Hydra API endpoints [#3178](https://github.com/opendatateam/udata/pull/3178)
149
+ - Add a "filter by organization badge" for datasets, dataservices, reuses and organizations [#3155](https://github.com/opendatateam/udata/pull/3155]
149
150
 
150
151
  ## 9.2.4 (2024-10-22)
151
152