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.

Files changed (48) hide show
  1. udata/api_fields.py +143 -37
  2. udata/core/badges/factories.py +3 -5
  3. udata/core/badges/forms.py +0 -3
  4. udata/core/badges/models.py +19 -24
  5. udata/core/badges/tests/test_commands.py +3 -4
  6. udata/core/badges/tests/test_model.py +37 -28
  7. udata/core/dataservices/models.py +4 -1
  8. udata/core/dataset/api.py +10 -0
  9. udata/core/dataset/events.py +25 -16
  10. udata/core/dataset/models.py +16 -6
  11. udata/core/dataset/search.py +3 -1
  12. udata/core/organization/api.py +11 -0
  13. udata/core/organization/models.py +23 -10
  14. udata/core/organization/search.py +6 -2
  15. udata/core/reuse/api.py +10 -0
  16. udata/core/reuse/models.py +15 -5
  17. udata/core/reuse/search.py +3 -1
  18. udata/search/fields.py +8 -3
  19. udata/static/chunks/{10.a99bb538cfbadb38dbcb.js → 10.dac55d18d0b4ef3cdacf.js} +3 -3
  20. udata/static/chunks/{10.a99bb538cfbadb38dbcb.js.map → 10.dac55d18d0b4ef3cdacf.js.map} +1 -1
  21. udata/static/chunks/{11.bb1c1fb39f740fbbeec0.js → 11.4a20a75f827c5a1125c3.js} +3 -3
  22. udata/static/chunks/{11.bb1c1fb39f740fbbeec0.js.map → 11.4a20a75f827c5a1125c3.js.map} +1 -1
  23. udata/static/chunks/{13.bef5fdb3e147e94fea99.js → 13.645dd0b7c0b9210f1b56.js} +2 -2
  24. udata/static/chunks/{13.bef5fdb3e147e94fea99.js.map → 13.645dd0b7c0b9210f1b56.js.map} +1 -1
  25. udata/static/chunks/{17.b91d28f550dc44bc4979.js → 17.8e19985c4d12a3b7b0c0.js} +2 -2
  26. udata/static/chunks/{17.b91d28f550dc44bc4979.js.map → 17.8e19985c4d12a3b7b0c0.js.map} +1 -1
  27. udata/static/chunks/{19.2c615ffee1e807000770.js → 19.825a43c330157e351fca.js} +3 -3
  28. udata/static/chunks/{19.2c615ffee1e807000770.js.map → 19.825a43c330157e351fca.js.map} +1 -1
  29. udata/static/chunks/{8.291bde987ed97294e4de.js → 8.5ee0cf635c848abbfc05.js} +2 -2
  30. udata/static/chunks/{8.291bde987ed97294e4de.js.map → 8.5ee0cf635c848abbfc05.js.map} +1 -1
  31. udata/static/chunks/{9.985935421e62c97a9f86.js → 9.df3c36f8d0d210621fbb.js} +3 -3
  32. udata/static/chunks/{9.985935421e62c97a9f86.js.map → 9.df3c36f8d0d210621fbb.js.map} +1 -1
  33. udata/static/common.js +1 -1
  34. udata/static/common.js.map +1 -1
  35. udata/tests/api/test_dataservices_api.py +21 -1
  36. udata/tests/api/test_datasets_api.py +15 -0
  37. udata/tests/api/test_organizations_api.py +15 -4
  38. udata/tests/api/test_reuses_api.py +15 -0
  39. udata/tests/apiv2/test_datasets.py +17 -0
  40. udata/tests/dataset/test_dataset_events.py +3 -15
  41. udata/tests/organization/test_organization_model.py +21 -0
  42. udata/tests/site/test_site_metrics.py +1 -3
  43. {udata-9.2.5.dev32160.dist-info → udata-9.2.5.dev32190.dist-info}/METADATA +3 -1
  44. {udata-9.2.5.dev32160.dist-info → udata-9.2.5.dev32190.dist-info}/RECORD +48 -48
  45. {udata-9.2.5.dev32160.dist-info → udata-9.2.5.dev32190.dist-info}/LICENSE +0 -0
  46. {udata-9.2.5.dev32160.dist-info → udata-9.2.5.dev32190.dist-info}/WHEEL +0 -0
  47. {udata-9.2.5.dev32160.dist-info → udata-9.2.5.dev32190.dist-info}/entry_points.txt +0 -0
  48. {udata-9.2.5.dev32160.dist-info → udata-9.2.5.dev32190.dist-info}/top_level.txt +0 -0
@@ -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 Dataset(WithMetrics, BadgeMixin, Owned, db.Document):
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",
@@ -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()
@@ -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.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):