udata 11.0.2.dev12__py3-none-any.whl → 11.0.2.dev14__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 (33) hide show
  1. udata/commands/fixtures.py +0 -1
  2. udata/core/dataset/api.py +8 -0
  3. udata/core/dataset/constants.py +5 -0
  4. udata/core/dataset/models.py +13 -0
  5. udata/core/topic/activities.py +57 -1
  6. udata/core/topic/apiv2.py +2 -4
  7. udata/core/topic/models.py +19 -2
  8. udata/static/chunks/{10.471164b2a9fe15614797.js → 10.8ca60413647062717b1e.js} +3 -3
  9. udata/static/chunks/{10.471164b2a9fe15614797.js.map → 10.8ca60413647062717b1e.js.map} +1 -1
  10. udata/static/chunks/{11.51d706fb9521c16976bc.js → 11.b6f741fcc366abfad9c4.js} +3 -3
  11. udata/static/chunks/{11.51d706fb9521c16976bc.js.map → 11.b6f741fcc366abfad9c4.js.map} +1 -1
  12. udata/static/chunks/{13.f29411b06be1883356a3.js → 13.2d06442dd9a05d9777b5.js} +2 -2
  13. udata/static/chunks/{13.f29411b06be1883356a3.js.map → 13.2d06442dd9a05d9777b5.js.map} +1 -1
  14. udata/static/chunks/{17.3bd0340930d4a314ce9c.js → 17.e8e4caaad5cb0cc0bacc.js} +2 -2
  15. udata/static/chunks/{17.3bd0340930d4a314ce9c.js.map → 17.e8e4caaad5cb0cc0bacc.js.map} +1 -1
  16. udata/static/chunks/{19.8da42e8359d72afc2618.js → 19.f03a102365af4315f9db.js} +3 -3
  17. udata/static/chunks/{19.8da42e8359d72afc2618.js.map → 19.f03a102365af4315f9db.js.map} +1 -1
  18. udata/static/chunks/{8.54e44b102164ae5e7a67.js → 8.778091d55cd8ea39af6b.js} +2 -2
  19. udata/static/chunks/{8.54e44b102164ae5e7a67.js.map → 8.778091d55cd8ea39af6b.js.map} +1 -1
  20. udata/static/chunks/{9.07515e5187f475bce828.js → 9.033d7e190ca9e226a5d0.js} +3 -3
  21. udata/static/chunks/{9.07515e5187f475bce828.js.map → 9.033d7e190ca9e226a5d0.js.map} +1 -1
  22. udata/static/common.js +1 -1
  23. udata/static/common.js.map +1 -1
  24. udata/tests/api/test_activities_api.py +22 -0
  25. udata/tests/apiv2/test_datasets.py +15 -0
  26. udata/tests/apiv2/test_topics.py +75 -0
  27. udata/tests/test_topics.py +68 -1
  28. {udata-11.0.2.dev12.dist-info → udata-11.0.2.dev14.dist-info}/METADATA +1 -1
  29. {udata-11.0.2.dev12.dist-info → udata-11.0.2.dev14.dist-info}/RECORD +33 -33
  30. {udata-11.0.2.dev12.dist-info → udata-11.0.2.dev14.dist-info}/WHEEL +0 -0
  31. {udata-11.0.2.dev12.dist-info → udata-11.0.2.dev14.dist-info}/entry_points.txt +0 -0
  32. {udata-11.0.2.dev12.dist-info → udata-11.0.2.dev14.dist-info}/licenses/LICENSE +0 -0
  33. {udata-11.0.2.dev12.dist-info → udata-11.0.2.dev14.dist-info}/top_level.txt +0 -0
@@ -51,7 +51,6 @@ UNWANTED_KEYS: dict[str, list[str]] = {
51
51
  "last_update",
52
52
  "last_modified",
53
53
  "license",
54
- "badges",
55
54
  "spatial",
56
55
  "quality",
57
56
  "permissions",
udata/core/dataset/api.py CHANGED
@@ -115,6 +115,12 @@ class DatasetApiParser(ModelApiParser):
115
115
  self.parser.add_argument("granularity", type=str, location="args")
116
116
  self.parser.add_argument("temporal_coverage", type=str, location="args")
117
117
  self.parser.add_argument("organization", type=str, location="args")
118
+ self.parser.add_argument(
119
+ "badge",
120
+ type=str,
121
+ choices=list(Dataset.__badges__),
122
+ location="args",
123
+ )
118
124
  self.parser.add_argument(
119
125
  "organization_badge",
120
126
  type=str,
@@ -178,6 +184,8 @@ class DatasetApiParser(ModelApiParser):
178
184
  )
179
185
  if args.get("featured") is not None:
180
186
  datasets = datasets.filter(featured=args["featured"])
187
+ if args.get("badge"):
188
+ datasets = datasets.with_badge(args["badge"])
181
189
  if args.get("organization"):
182
190
  if not ObjectId.is_valid(args["organization"]):
183
191
  api.abort(400, "Organization arg must be an identifier")
@@ -78,6 +78,11 @@ CHECKSUM_TYPES = ("sha1", "sha2", "sha256", "md5", "crc")
78
78
  DEFAULT_CHECKSUM_TYPE = "sha1"
79
79
 
80
80
  PIVOTAL_DATA = "pivotal-data"
81
+ SPD = "spd"
82
+ INSPIRE = "inspire"
83
+ HVD = "hvd"
84
+ SL = "sl"
85
+ SR = "sr"
81
86
  CLOSED_FORMATS = ("pdf", "doc", "docx", "word", "xls", "excel", "xlsx")
82
87
 
83
88
  # Maximum acceptable Damerau-Levenshtein distance
@@ -36,12 +36,17 @@ from .constants import (
36
36
  CLOSED_FORMATS,
37
37
  DEFAULT_LICENSE,
38
38
  DESCRIPTION_SHORT_SIZE_LIMIT,
39
+ HVD,
40
+ INSPIRE,
39
41
  LEGACY_FREQUENCIES,
40
42
  MAX_DISTANCE,
41
43
  PIVOTAL_DATA,
42
44
  RESOURCE_FILETYPES,
43
45
  RESOURCE_TYPES,
44
46
  SCHEMA_CACHE_DURATION,
47
+ SL,
48
+ SPD,
49
+ SR,
45
50
  UPDATE_FREQUENCIES,
46
51
  )
47
52
  from .exceptions import (
@@ -61,6 +66,11 @@ __all__ = (
61
66
 
62
67
  BADGES: dict[str, str] = {
63
68
  PIVOTAL_DATA: _("Pivotal data"),
69
+ SPD: _("Reference data public service"),
70
+ INSPIRE: _("Inspire"),
71
+ HVD: _("High value datasets"),
72
+ SL: _("Certified statistic series"),
73
+ SR: _("Statistical series of general interest"),
64
74
  }
65
75
 
66
76
  NON_ASSIGNABLE_SCHEMA_TYPES = ["datapackage"]
@@ -334,6 +344,9 @@ class DatasetQuerySet(OwnedQuerySet):
334
344
  def hidden(self):
335
345
  return self(db.Q(private=True) | db.Q(deleted__ne=None) | db.Q(archived__ne=None))
336
346
 
347
+ def with_badge(self, kind):
348
+ return self(badges__kind=kind)
349
+
337
350
 
338
351
  class Checksum(db.EmbeddedDocument):
339
352
  type = db.StringField(choices=CHECKSUM_TYPES, required=True)
@@ -1,9 +1,17 @@
1
1
  from flask_security import current_user
2
2
 
3
+ from udata.core.topic.models import TopicElement
3
4
  from udata.i18n import lazy_gettext as _
4
5
  from udata.models import Activity, Topic, db
5
6
 
6
- __all__ = ("UserCreatedTopic", "UserUpdatedTopic", "TopicRelatedActivity")
7
+ __all__ = (
8
+ "UserCreatedTopic",
9
+ "UserUpdatedTopic",
10
+ "UserCreatedTopicElement",
11
+ "UserUpdatedTopicElement",
12
+ "UserDeletedTopicElement",
13
+ "TopicRelatedActivity",
14
+ )
7
15
 
8
16
 
9
17
  class TopicRelatedActivity(object):
@@ -23,6 +31,26 @@ class UserUpdatedTopic(TopicRelatedActivity, Activity):
23
31
  label = _("updated a topic")
24
32
 
25
33
 
34
+ class UserCreatedTopicElement(TopicRelatedActivity, Activity):
35
+ key = "topic:element:created"
36
+ icon = "fa fa-plus"
37
+ badge_type = "success"
38
+ label = _("added an element to a topic")
39
+
40
+
41
+ class UserUpdatedTopicElement(TopicRelatedActivity, Activity):
42
+ key = "topic:element:updated"
43
+ icon = "fa fa-pencil"
44
+ label = _("updated an element in a topic")
45
+
46
+
47
+ class UserDeletedTopicElement(TopicRelatedActivity, Activity):
48
+ key = "topic:element:deleted"
49
+ icon = "fa fa-remove"
50
+ badge_type = "error"
51
+ label = _("removed an element from a topic")
52
+
53
+
26
54
  @Topic.on_create.connect
27
55
  def on_user_created_topic(topic):
28
56
  if current_user and current_user.is_authenticated:
@@ -34,3 +62,31 @@ def on_user_updated_topic(topic, **kwargs):
34
62
  changed_fields = kwargs.get("changed_fields", [])
35
63
  if current_user and current_user.is_authenticated:
36
64
  UserUpdatedTopic.emit(topic, topic.organization, changed_fields)
65
+
66
+
67
+ @TopicElement.on_create.connect
68
+ def on_user_created_topic_element(topic_element):
69
+ if current_user and current_user.is_authenticated and topic_element.topic:
70
+ extras = {"element_id": str(topic_element.id)}
71
+ UserCreatedTopicElement.emit(
72
+ topic_element.topic, topic_element.topic.organization, extras=extras
73
+ )
74
+
75
+
76
+ @TopicElement.on_update.connect
77
+ def on_user_updated_topic_element(topic_element, **kwargs):
78
+ changed_fields = kwargs.get("changed_fields", [])
79
+ if current_user and current_user.is_authenticated and topic_element.topic:
80
+ extras = {"element_id": str(topic_element.id)}
81
+ UserUpdatedTopicElement.emit(
82
+ topic_element.topic, topic_element.topic.organization, changed_fields, extras=extras
83
+ )
84
+
85
+
86
+ @TopicElement.on_delete.connect
87
+ def on_user_deleted_topic_element(topic_element):
88
+ if current_user and current_user.is_authenticated and topic_element.topic:
89
+ extras = {"element_id": str(topic_element.id)}
90
+ UserDeletedTopicElement.emit(
91
+ topic_element.topic, topic_element.topic.organization, extras=extras
92
+ )
udata/core/topic/apiv2.py CHANGED
@@ -130,16 +130,13 @@ class TopicElementsAPI(API):
130
130
  else:
131
131
  element = TopicElement()
132
132
  form.populate_obj(element)
133
+ element.topic = topic
133
134
  element.save()
134
135
  elements.append(element)
135
136
 
136
137
  if errors:
137
138
  apiv2.abort(400, errors=errors)
138
139
 
139
- for element in elements:
140
- element.topic = topic
141
- element.save()
142
-
143
140
  topic.save()
144
141
 
145
142
  return topic, 201
@@ -157,6 +154,7 @@ class TopicElementsAPI(API):
157
154
  if not TopicEditPermission(topic).can():
158
155
  apiv2.abort(403, "Forbidden")
159
156
 
157
+ # TODO: this triggers performance issues on a huge topic (too many tasks, too many activities)
160
158
  topic.elements.delete()
161
159
 
162
160
  return None, 204
@@ -1,9 +1,11 @@
1
1
  from blinker import Signal
2
+ from flask import url_for
2
3
  from mongoengine.signals import post_delete, post_save
3
4
 
4
5
  from udata.api_fields import field
5
6
  from udata.core.activity.models import Auditable
6
7
  from udata.core.dataset.models import Dataset
8
+ from udata.core.linkable import Linkable
7
9
  from udata.core.owned import Owned, OwnedQuerySet
8
10
  from udata.core.reuse.models import Reuse
9
11
  from udata.models import SpatialCoverage, db
@@ -13,7 +15,7 @@ from udata.tasks import as_task_param
13
15
  __all__ = ("Topic", "TopicElement")
14
16
 
15
17
 
16
- class TopicElement(db.Document):
18
+ class TopicElement(Auditable, db.Document):
17
19
  title = field(db.StringField(required=False))
18
20
  description = field(db.StringField(required=False))
19
21
  tags = field(db.ListField(db.StringField()))
@@ -31,9 +33,16 @@ class TopicElement(db.Document):
31
33
  "auto_create_index_on_save": True,
32
34
  }
33
35
 
36
+ after_save = Signal()
37
+ on_create = Signal()
38
+ on_update = Signal()
39
+ on_delete = Signal()
40
+
34
41
  @classmethod
35
42
  def post_save(cls, sender, document, **kwargs):
36
43
  """Trigger reindex when element is saved"""
44
+ # Call parent post_save for Auditable functionality
45
+ super().post_save(sender, document, **kwargs)
37
46
  if document.topic and document.element and hasattr(document.element, "id"):
38
47
  reindex.delay(*as_task_param(document.element))
39
48
 
@@ -42,9 +51,10 @@ class TopicElement(db.Document):
42
51
  """Trigger reindex when element is deleted"""
43
52
  if document.topic and document.element and hasattr(document.element, "id"):
44
53
  reindex.delay(*as_task_param(document.element))
54
+ cls.on_delete.send(document)
45
55
 
46
56
 
47
- class Topic(db.Datetimed, Auditable, db.Document, Owned):
57
+ class Topic(db.Datetimed, Auditable, Linkable, db.Document, Owned):
48
58
  name = field(db.StringField(required=True))
49
59
  slug = field(
50
60
  db.SlugField(max_length=255, required=True, populate_from="name", update=True, follow=True),
@@ -108,6 +118,13 @@ class Topic(db.Datetimed, Auditable, db.Document, Owned):
108
118
  # Useful for Discussions to call self_web_url on their `subject`
109
119
  return None
110
120
 
121
+ def self_api_url(self, **kwargs):
122
+ return url_for(
123
+ "apiv2.topic",
124
+ topic=self._link_id(**kwargs),
125
+ **self._self_api_url_kwargs(**kwargs),
126
+ )
127
+
111
128
 
112
129
  post_save.connect(Topic.post_save, sender=Topic)
113
130
  post_save.connect(TopicElement.post_save, sender=TopicElement)