udata 10.7.1.dev36555__py2.py3-none-any.whl → 10.8.1__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 (31) hide show
  1. udata/__init__.py +1 -1
  2. udata/core/dataservices/models.py +3 -1
  3. udata/core/dataset/models.py +3 -1
  4. udata/core/discussions/api.py +6 -0
  5. udata/core/discussions/models.py +11 -1
  6. udata/core/reuse/models.py +3 -1
  7. udata/migrations/2025-07-18-redo-count-discussion.py +26 -0
  8. udata/mongo/slug_fields.py +24 -7
  9. udata/static/chunks/{11.33f00815743ef2ea9e06.js → 11.51d706fb9521c16976bc.js} +3 -3
  10. udata/static/chunks/{11.33f00815743ef2ea9e06.js.map → 11.51d706fb9521c16976bc.js.map} +1 -1
  11. udata/static/chunks/{13.f29411b06be1883356a3.js → 13.39e106d56f794ebd06a0.js} +2 -2
  12. udata/static/chunks/{13.f29411b06be1883356a3.js.map → 13.39e106d56f794ebd06a0.js.map} +1 -1
  13. udata/static/chunks/{17.3bd0340930d4a314ce9c.js → 17.70cbb4a91b002338007e.js} +2 -2
  14. udata/static/chunks/{17.3bd0340930d4a314ce9c.js.map → 17.70cbb4a91b002338007e.js.map} +1 -1
  15. udata/static/chunks/{19.8d03c06efcac6884bebe.js → 19.a348a5fff8fe2801e52a.js} +3 -3
  16. udata/static/chunks/{19.8d03c06efcac6884bebe.js.map → 19.a348a5fff8fe2801e52a.js.map} +1 -1
  17. udata/static/chunks/{5.0fa1408dae4e76b87b2e.js → 5.343ca020a2d38cec1a14.js} +3 -3
  18. udata/static/chunks/{5.0fa1408dae4e76b87b2e.js.map → 5.343ca020a2d38cec1a14.js.map} +1 -1
  19. udata/static/chunks/{6.d663709d877baa44a71e.js → 6.a3b07de9dd2ca2d24e85.js} +3 -3
  20. udata/static/chunks/{6.d663709d877baa44a71e.js.map → 6.a3b07de9dd2ca2d24e85.js.map} +1 -1
  21. udata/static/chunks/{8.0b54a9e3084c9636c58f.js → 8.462bb3029de008497675.js} +2 -2
  22. udata/static/chunks/{8.0b54a9e3084c9636c58f.js.map → 8.462bb3029de008497675.js.map} +1 -1
  23. udata/static/common.js +1 -1
  24. udata/static/common.js.map +1 -1
  25. udata/tests/test_discussions.py +41 -1
  26. {udata-10.7.1.dev36555.dist-info → udata-10.8.1.dist-info}/METADATA +8 -2
  27. {udata-10.7.1.dev36555.dist-info → udata-10.8.1.dist-info}/RECORD +31 -30
  28. {udata-10.7.1.dev36555.dist-info → udata-10.8.1.dist-info}/LICENSE +0 -0
  29. {udata-10.7.1.dev36555.dist-info → udata-10.8.1.dist-info}/WHEEL +0 -0
  30. {udata-10.7.1.dev36555.dist-info → udata-10.8.1.dist-info}/entry_points.txt +0 -0
  31. {udata-10.7.1.dev36555.dist-info → udata-10.8.1.dist-info}/top_level.txt +0 -0
udata/__init__.py CHANGED
@@ -4,5 +4,5 @@
4
4
  udata
5
5
  """
6
6
 
7
- __version__ = "10.7.1.dev"
7
+ __version__ = "10.8.1"
8
8
  __description__ = "Open data portal"
@@ -288,6 +288,7 @@ class Dataservice(Auditable, WithMetrics, Linkable, Owned, db.Document):
288
288
 
289
289
  __metrics_keys__ = [
290
290
  "discussions",
291
+ "discussions_open",
291
292
  "followers",
292
293
  "followers_by_months",
293
294
  "views",
@@ -310,7 +311,8 @@ class Dataservice(Auditable, WithMetrics, Linkable, Owned, db.Document):
310
311
  }
311
312
 
312
313
  def count_discussions(self):
313
- self.metrics["discussions"] = Discussion.objects(subject=self, closed=None).count()
314
+ self.metrics["discussions"] = Discussion.objects(subject=self).count()
315
+ self.metrics["discussions_open"] = Discussion.objects(subject=self, closed=None).count()
314
316
  self.save(signal_kwargs={"ignores": ["post_save"]})
315
317
 
316
318
  def count_followers(self):
@@ -611,6 +611,7 @@ class Dataset(Auditable, WithMetrics, DatasetBadgeMixin, Owned, Linkable, db.Doc
611
611
 
612
612
  __metrics_keys__ = [
613
613
  "discussions",
614
+ "discussions_open",
614
615
  "reuses",
615
616
  "reuses_by_months",
616
617
  "followers",
@@ -1086,7 +1087,8 @@ class Dataset(Auditable, WithMetrics, DatasetBadgeMixin, Owned, Linkable, db.Doc
1086
1087
  def count_discussions(self):
1087
1088
  from udata.models import Discussion
1088
1089
 
1089
- self.metrics["discussions"] = Discussion.objects(subject=self, closed=None).count()
1090
+ self.metrics["discussions"] = Discussion.objects(subject=self).count()
1091
+ self.metrics["discussions_open"] = Discussion.objects(subject=self, closed=None).count()
1090
1092
  self.save(signal_kwargs={"ignores": ["post_save"]})
1091
1093
 
1092
1094
  def count_reuses(self):
@@ -130,6 +130,7 @@ discussion_page_fields = api.model("DiscussionPage", fields.pager(discussion_fie
130
130
  parser = api.parser()
131
131
  sorting_keys: list[str] = ["created", "title", "closed", "discussion.posted_on"]
132
132
  sorting_choices: list[str] = sorting_keys + ["-" + k for k in sorting_keys]
133
+ parser.add_argument("q", type=str, location="args", help="The search query")
133
134
  parser.add_argument(
134
135
  "sort",
135
136
  type=str,
@@ -331,6 +332,11 @@ class DiscussionsAPI(API):
331
332
  discussions = discussions(closed=None)
332
333
  elif args["closed"] is True:
333
334
  discussions = discussions(closed__ne=None)
335
+
336
+ if args["q"]:
337
+ phrase_query = " ".join([f'"{elem}"' for elem in args["q"].split(" ")])
338
+ discussions = discussions.search_text(phrase_query).order_by("$text_score")
339
+
334
340
  discussions = discussions.order_by(args["sort"])
335
341
  return discussions.paginate(args["page"], args["page_size"])
336
342
 
@@ -82,8 +82,18 @@ class Discussion(SpamMixin, Linkable, db.Document):
82
82
  extras = db.ExtrasField()
83
83
 
84
84
  meta = {
85
- "indexes": ["user", "subject", "-created"],
85
+ "indexes": [
86
+ {
87
+ "fields": ["$title", "$discussion.content"],
88
+ "default_language": "french",
89
+ "weights": {"title": 10, "discussion.content": 5},
90
+ },
91
+ "user",
92
+ "subject",
93
+ "-created",
94
+ ],
86
95
  "ordering": ["-created"],
96
+ "auto_create_index_on_save": True,
87
97
  }
88
98
 
89
99
  @property
@@ -151,6 +151,7 @@ class Reuse(db.Datetimed, Auditable, WithMetrics, ReuseBadgeMixin, Linkable, Own
151
151
 
152
152
  __metrics_keys__ = [
153
153
  "discussions",
154
+ "discussions_open",
154
155
  "datasets",
155
156
  "followers",
156
157
  "followers_by_months",
@@ -287,7 +288,8 @@ class Reuse(db.Datetimed, Auditable, WithMetrics, ReuseBadgeMixin, Linkable, Own
287
288
  def count_discussions(self):
288
289
  from udata.models import Discussion
289
290
 
290
- self.metrics["discussions"] = Discussion.objects(subject=self, closed=None).count()
291
+ self.metrics["discussions"] = Discussion.objects(subject=self).count()
292
+ self.metrics["discussions_open"] = Discussion.objects(subject=self, closed=None).count()
291
293
  self.save(signal_kwargs={"ignores": ["post_save"]})
292
294
 
293
295
  def count_followers(self):
@@ -0,0 +1,26 @@
1
+ """
2
+ This migration does a `count_discussions` on all objets with discussions.
3
+ It follows the change of `metrics.discussions` to track all discussions and not onyl the opened ones.
4
+ """
5
+
6
+ import logging
7
+
8
+ import click
9
+
10
+ from udata.core.discussions.models import Discussion
11
+ from udata.mongo import db as udata_db
12
+
13
+ log = logging.getLogger(__name__)
14
+
15
+
16
+ def migrate(db):
17
+ objects_with_discussions = Discussion.objects.aggregate(
18
+ [{"$group": {"_id": "$subject._ref", "_cls": {"$first": "$subject._cls"}}}]
19
+ )
20
+ with click.progressbar(objects_with_discussions) as objects_with_discussions:
21
+ for object in objects_with_discussions:
22
+ related_to = udata_db.resolve_model(object["_cls"]).objects.get(pk=object["_id"].id)
23
+ try:
24
+ related_to.count_discussions()
25
+ except Exception as err:
26
+ log.error(f"Cannot count discussions for {object['_cls']} {object['_id'].id} {err}")
@@ -172,21 +172,38 @@ def populate_slug(instance, field):
172
172
  # Ensure uniqueness
173
173
  if field.unique:
174
174
  base_slug = slug
175
- index = 1
176
175
  qs = instance.__class__.objects
177
176
  if previous:
178
177
  qs = qs(id__ne=previous.id)
179
178
 
180
- def exists(s):
181
- return qs(**{field.db_field: s}).clear_cls_query().limit(1).count(True) > 0
179
+ def exists(slug):
180
+ return qs(**{field.db_field: slug}).clear_cls_query().limit(1).count(True) > 0
182
181
 
183
- while exists(slug):
184
- # keep space for index suffix, trim slug if needed
182
+ def get_existing_slug_suffixes(slug):
183
+ qs_suffix = qs(slug__regex=f"^{slug}-\d*$").clear_cls_query().only(field.db_field)
184
+ return [getattr(obj, field.db_field) for obj in qs_suffix]
185
+
186
+ def trim_base_slug(base_slug, index):
185
187
  slug_overflow = len("{0}-{1}".format(base_slug, index)) - field.max_length
186
188
  if slug_overflow >= 1:
187
189
  base_slug = base_slug[:-slug_overflow]
188
- slug = "{0}-{1}".format(base_slug, index)
189
- index += 1
190
+ return base_slug
191
+
192
+ if exists(base_slug):
193
+ # We'll iterate to get the first free slug suffix
194
+ index = 1
195
+ existing_slugs = None
196
+ while True:
197
+ # Keep space for index suffix, trim slug if needed
198
+ trimmed_slug = trim_base_slug(base_slug, index)
199
+ # Find all existing slugs with suffixes
200
+ if existing_slugs is None or trimmed_slug != base_slug:
201
+ base_slug = trimmed_slug
202
+ existing_slugs = set(sorted(get_existing_slug_suffixes(base_slug)))
203
+ slug = "{0}-{1}".format(base_slug, index)
204
+ if slug not in existing_slugs:
205
+ break
206
+ index += 1
190
207
 
191
208
  if is_uuid(slug):
192
209
  slug = "{0}-uuid".format(slug)