udata 10.4.2.dev35451__py2.py3-none-any.whl → 10.4.2.dev35475__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 (30) hide show
  1. udata/core/activity/__init__.py +1 -0
  2. udata/core/dataservices/api.py +43 -0
  3. udata/core/dataset/api.py +44 -0
  4. udata/core/post/api.py +34 -0
  5. udata/core/reuse/api.py +42 -1
  6. udata/core/topic/activities.py +36 -0
  7. udata/core/topic/models.py +23 -15
  8. udata/static/chunks/{11.51d706fb9521c16976bc.js → 11.0f04e49a40a0a381bcce.js} +3 -3
  9. udata/static/chunks/{11.51d706fb9521c16976bc.js.map → 11.0f04e49a40a0a381bcce.js.map} +1 -1
  10. udata/static/chunks/{19.a348a5fff8fe2801e52a.js → 19.8da42e8359d72afc2618.js} +3 -3
  11. udata/static/chunks/{19.a348a5fff8fe2801e52a.js.map → 19.8da42e8359d72afc2618.js.map} +1 -1
  12. udata/static/chunks/{5.0652a860afda96795a53.js → 5.0fa1408dae4e76b87b2e.js} +3 -3
  13. udata/static/chunks/{5.0652a860afda96795a53.js.map → 5.0fa1408dae4e76b87b2e.js.map} +1 -1
  14. udata/static/chunks/{6.92d7c2ec6d20005774ef.js → 6.d663709d877baa44a71e.js} +3 -3
  15. udata/static/chunks/{6.92d7c2ec6d20005774ef.js.map → 6.d663709d877baa44a71e.js.map} +1 -1
  16. udata/static/chunks/{8.462bb3029de008497675.js → 8.494b003a94383b142c18.js} +2 -2
  17. udata/static/chunks/{8.462bb3029de008497675.js.map → 8.494b003a94383b142c18.js.map} +1 -1
  18. udata/static/common.js +1 -1
  19. udata/static/common.js.map +1 -1
  20. udata/tests/api/test_dataservices_api.py +53 -0
  21. udata/tests/api/test_datasets_api.py +53 -0
  22. udata/tests/api/test_reuses_api.py +54 -0
  23. udata/tests/dataset/test_dataset_model.py +49 -0
  24. udata/tests/test_topics.py +19 -0
  25. {udata-10.4.2.dev35451.dist-info → udata-10.4.2.dev35475.dist-info}/METADATA +5 -3
  26. {udata-10.4.2.dev35451.dist-info → udata-10.4.2.dev35475.dist-info}/RECORD +30 -29
  27. {udata-10.4.2.dev35451.dist-info → udata-10.4.2.dev35475.dist-info}/LICENSE +0 -0
  28. {udata-10.4.2.dev35451.dist-info → udata-10.4.2.dev35475.dist-info}/WHEEL +0 -0
  29. {udata-10.4.2.dev35451.dist-info → udata-10.4.2.dev35475.dist-info}/entry_points.txt +0 -0
  30. {udata-10.4.2.dev35451.dist-info → udata-10.4.2.dev35475.dist-info}/top_level.txt +0 -0
@@ -10,3 +10,4 @@ def init_app(app):
10
10
  import udata.core.dataset.activities # noqa
11
11
  import udata.core.reuse.activities # noqa
12
12
  import udata.core.organization.activities # noqa
13
+ import udata.core.topic.activities # noqa
@@ -1,7 +1,9 @@
1
1
  from datetime import datetime
2
+ from typing import List
2
3
 
3
4
  import mongoengine
4
5
  from bson import ObjectId
6
+ from feedgenerator.django.utils.feedgenerator import Atom1Feed
5
7
  from flask import make_response, redirect, request, url_for
6
8
  from flask_login import current_user
7
9
 
@@ -10,6 +12,9 @@ from udata.api_fields import patch
10
12
  from udata.core.dataservices.permissions import OwnablePermission
11
13
  from udata.core.dataset.models import Dataset
12
14
  from udata.core.followers.api import FollowAPI
15
+ from udata.core.site.models import current_site
16
+ from udata.frontend.markdown import md
17
+ from udata.i18n import gettext as _
13
18
  from udata.rdf import RDF_EXTENSIONS, graph_response, negociate_content
14
19
 
15
20
  from .models import Dataservice
@@ -49,6 +54,44 @@ class DataservicesAPI(API):
49
54
  return dataservice, 201
50
55
 
51
56
 
57
+ @ns.route("/recent.atom", endpoint="recent_dataservices_atom_feed")
58
+ class DataservicesAtomFeedAPI(API):
59
+ @api.doc("recent_dataservices_atom_feed")
60
+ def get(self):
61
+ feed = Atom1Feed(
62
+ _("Latest APIs"), description=None, feed_url=request.url, link=request.url_root
63
+ )
64
+
65
+ dataservices: List[Dataservice] = (
66
+ Dataservice.objects.visible()
67
+ .order_by("-created_at_internal")
68
+ .limit(current_site.feed_size)
69
+ )
70
+ for dataservice in dataservices:
71
+ author_name = None
72
+ author_uri = None
73
+ if dataservice.organization:
74
+ author_name = dataservice.organization.name
75
+ author_uri = dataservice.organization.external_url
76
+ elif dataservice.owner:
77
+ author_name = dataservice.owner.fullname
78
+ author_uri = dataservice.owner.external_url
79
+ feed.add_item(
80
+ dataservice.title,
81
+ unique_id=dataservice.id,
82
+ description=dataservice.description,
83
+ content=md(dataservice.description),
84
+ author_name=author_name,
85
+ author_link=author_uri,
86
+ link=dataservice.url_for(external=True),
87
+ updateddate=dataservice.metadata_modified_at,
88
+ pubdate=dataservice.created_at,
89
+ )
90
+ response = make_response(feed.writeString("utf-8"))
91
+ response.headers["Content-Type"] = "application/atom+xml"
92
+ return response
93
+
94
+
52
95
  @ns.route("/<dataservice:dataservice>/", endpoint="dataservice")
53
96
  class DataserviceAPI(API):
54
97
  @api.doc("get_dataservice")
udata/core/dataset/api.py CHANGED
@@ -20,9 +20,11 @@ These changes might lead to backward compatibility breakage meaning:
20
20
  import logging
21
21
  import os
22
22
  from datetime import datetime
23
+ from typing import List
23
24
 
24
25
  import mongoengine
25
26
  from bson.objectid import ObjectId
27
+ from feedgenerator.django.utils.feedgenerator import Atom1Feed
26
28
  from flask import abort, current_app, make_response, redirect, request, url_for
27
29
  from flask_restx.inputs import boolean
28
30
  from flask_security import current_user
@@ -39,8 +41,11 @@ from udata.core.dataset.models import CHECKSUM_TYPES
39
41
  from udata.core.followers.api import FollowAPI
40
42
  from udata.core.organization.models import Organization
41
43
  from udata.core.reuse.models import Reuse
44
+ from udata.core.site.models import current_site
42
45
  from udata.core.storages.api import handle_upload, upload_parser
43
46
  from udata.core.topic.models import Topic
47
+ from udata.frontend.markdown import md
48
+ from udata.i18n import gettext as _
44
49
  from udata.linkchecker.checker import check_resource
45
50
  from udata.rdf import RDF_EXTENSIONS, graph_response, negociate_content
46
51
  from udata.utils import get_by
@@ -292,6 +297,45 @@ class DatasetListAPI(API):
292
297
  return dataset, 201
293
298
 
294
299
 
300
+ @ns.route("/recent.atom", endpoint="recent_datasets_atom_feed")
301
+ class DatasetsAtomFeedAPI(API):
302
+ @api.doc("recent_datasets_atom_feed")
303
+ def get(self):
304
+ feed = Atom1Feed(
305
+ _("Latest datasets"),
306
+ description=None,
307
+ feed_url=request.url,
308
+ link=request.url_root,
309
+ )
310
+
311
+ datasets: List[Dataset] = (
312
+ Dataset.objects.visible().order_by("-created_at_internal").limit(current_site.feed_size)
313
+ )
314
+ for dataset in datasets:
315
+ author_name = None
316
+ author_uri = None
317
+ if dataset.organization:
318
+ author_name = dataset.organization.name
319
+ author_uri = dataset.organization.external_url
320
+ elif dataset.owner:
321
+ author_name = dataset.owner.fullname
322
+ author_uri = dataset.owner.external_url
323
+ feed.add_item(
324
+ dataset.title,
325
+ unique_id=dataset.id,
326
+ description=dataset.description,
327
+ content=md(dataset.description),
328
+ author_name=author_name,
329
+ author_link=author_uri,
330
+ link=dataset.external_url,
331
+ updateddate=dataset.last_modified,
332
+ pubdate=dataset.created_at,
333
+ )
334
+ response = make_response(feed.writeString("utf-8"))
335
+ response.headers["Content-Type"] = "application/atom+xml"
336
+ return response
337
+
338
+
295
339
  @ns.route("/<dataset:dataset>/", endpoint="dataset", doc=common_doc)
296
340
  @api.response(404, "Dataset not found")
297
341
  @api.response(410, "Dataset has been deleted")
udata/core/post/api.py CHANGED
@@ -1,4 +1,8 @@
1
1
  from datetime import datetime
2
+ from typing import List
3
+
4
+ from feedgenerator.django.utils.feedgenerator import Atom1Feed
5
+ from flask import make_response, request
2
6
 
3
7
  from udata.api import API, api, fields
4
8
  from udata.auth import Permission as AdminPermission
@@ -11,6 +15,8 @@ from udata.core.storages.api import (
11
15
  uploaded_image_fields,
12
16
  )
13
17
  from udata.core.user.api_fields import user_ref_fields
18
+ from udata.frontend.markdown import md
19
+ from udata.i18n import gettext as _
14
20
 
15
21
  from .forms import PostForm
16
22
  from .models import Post
@@ -105,6 +111,34 @@ class PostsAPI(API):
105
111
  return form.save(), 201
106
112
 
107
113
 
114
+ @ns.route("/recent.atom", endpoint="recent_posts_atom_feed")
115
+ class PostsAtomFeedAPI(API):
116
+ @api.doc("recent_posts_atom_feed")
117
+ def get(self):
118
+ feed = Atom1Feed(
119
+ _("Latests posts"),
120
+ description=None,
121
+ feed_url=request.url,
122
+ link=request.url_root,
123
+ )
124
+
125
+ posts: List[Post] = Post.objects().published().order_by("-published").limit(15)
126
+ for post in posts:
127
+ feed.add_item(
128
+ post.name,
129
+ unique_id=post.id,
130
+ description=post.headline,
131
+ content=md(post.content),
132
+ author_name="data.gouv.fr",
133
+ link=post.external_url,
134
+ updateddate=post.last_modified,
135
+ pubdate=post.published,
136
+ )
137
+ response = make_response(feed.writeString("utf-8"))
138
+ response.headers["Content-Type"] = "application/atom+xml"
139
+ return response
140
+
141
+
108
142
  @ns.route("/<post:post>/", endpoint="post")
109
143
  @api.response(404, "Object not found")
110
144
  @api.param("post", "The post ID or slug")
udata/core/reuse/api.py CHANGED
@@ -1,8 +1,10 @@
1
1
  from datetime import datetime
2
+ from typing import List
2
3
 
3
4
  import mongoengine
4
5
  from bson.objectid import ObjectId
5
- from flask import request
6
+ from feedgenerator.django.utils.feedgenerator import Atom1Feed
7
+ from flask import make_response, request
6
8
  from flask_login import current_user
7
9
 
8
10
  from udata.api import API, api, errors
@@ -20,6 +22,8 @@ from udata.core.storages.api import (
20
22
  parse_uploaded_image,
21
23
  uploaded_image_fields,
22
24
  )
25
+ from udata.frontend.markdown import md
26
+ from udata.i18n import gettext as _
23
27
  from udata.models import Dataset
24
28
  from udata.utils import id_or_404
25
29
 
@@ -130,6 +134,43 @@ class ReuseListAPI(API):
130
134
  return patch_and_save(reuse, request), 201
131
135
 
132
136
 
137
+ @ns.route("/recent.atom", endpoint="recent_reuses_atom_feed")
138
+ class ReusesAtomFeedAPI(API):
139
+ @api.doc("recent_reuses_atom_feed")
140
+ def get(self):
141
+ feed = Atom1Feed(
142
+ _("Latests reuses"),
143
+ description=None,
144
+ feed_url=request.url,
145
+ link=request.url_root,
146
+ )
147
+
148
+ reuses: List[Reuse] = Reuse.objects.visible().order_by("-created_at").limit(15)
149
+ for reuse in reuses:
150
+ author_name = None
151
+ author_uri = None
152
+ if reuse.organization:
153
+ author_name = reuse.organization.name
154
+ author_uri = reuse.organization.external_url
155
+ elif reuse.owner:
156
+ author_name = reuse.owner.fullname
157
+ author_uri = reuse.owner.external_url
158
+ feed.add_item(
159
+ reuse.title,
160
+ unique_id=reuse.id,
161
+ description=reuse.description,
162
+ content=md(reuse.description),
163
+ author_name=author_name,
164
+ author_link=author_uri,
165
+ link=reuse.external_url,
166
+ updateddate=reuse.last_modified,
167
+ pubdate=reuse.created_at,
168
+ )
169
+ response = make_response(feed.writeString("utf-8"))
170
+ response.headers["Content-Type"] = "application/atom+xml"
171
+ return response
172
+
173
+
133
174
  @ns.route("/<reuse:reuse>/", endpoint="reuse", doc=common_doc)
134
175
  @api.response(404, "Reuse not found")
135
176
  @api.response(410, "Reuse has been deleted")
@@ -0,0 +1,36 @@
1
+ from flask_security import current_user
2
+
3
+ from udata.i18n import lazy_gettext as _
4
+ from udata.models import Activity, Topic, db
5
+
6
+ __all__ = ("UserCreatedTopic", "UserUpdatedTopic", "TopicRelatedActivity")
7
+
8
+
9
+ class TopicRelatedActivity(object):
10
+ related_to = db.ReferenceField("Topic")
11
+
12
+
13
+ class UserCreatedTopic(TopicRelatedActivity, Activity):
14
+ key = "topic:created"
15
+ icon = "fa fa-plus"
16
+ badge_type = "success"
17
+ label = _("created a topic")
18
+
19
+
20
+ class UserUpdatedTopic(TopicRelatedActivity, Activity):
21
+ key = "topic:updated"
22
+ icon = "fa fa-pencil"
23
+ label = _("updated a topic")
24
+
25
+
26
+ @Topic.on_create.connect
27
+ def on_user_created_topic(topic):
28
+ if current_user and current_user.is_authenticated:
29
+ UserCreatedTopic.emit(topic, topic.organization)
30
+
31
+
32
+ @Topic.on_update.connect
33
+ def on_user_updated_topic(topic, **kwargs):
34
+ changed_fields = kwargs.get("changed_fields", [])
35
+ if current_user and current_user.is_authenticated:
36
+ UserUpdatedTopic.emit(topic, topic.organization, changed_fields)
@@ -1,6 +1,9 @@
1
+ from blinker import Signal
1
2
  from flask import url_for
2
- from mongoengine.signals import pre_save
3
+ from mongoengine.signals import post_save, pre_save
3
4
 
5
+ from udata.api_fields import field
6
+ from udata.core.activity.models import Auditable
4
7
  from udata.core.owned import Owned, OwnedQuerySet
5
8
  from udata.models import SpatialCoverage, db
6
9
  from udata.search import reindex
@@ -8,24 +11,24 @@ from udata.search import reindex
8
11
  __all__ = ("Topic",)
9
12
 
10
13
 
11
- class Topic(db.Document, Owned, db.Datetimed):
12
- name = db.StringField(required=True)
13
- slug = db.SlugField(
14
- max_length=255, required=True, populate_from="name", update=True, follow=True
14
+ class Topic(db.Datetimed, Auditable, db.Document, Owned):
15
+ name = field(db.StringField(required=True))
16
+ slug = field(
17
+ db.SlugField(max_length=255, required=True, populate_from="name", update=True, follow=True),
18
+ auditable=False,
15
19
  )
16
- description = db.StringField()
17
- tags = db.ListField(db.StringField())
18
- color = db.IntField()
20
+ description = field(db.StringField())
21
+ tags = field(db.ListField(db.StringField()))
22
+ color = field(db.IntField())
19
23
 
20
- tags = db.ListField(db.StringField())
21
- datasets = db.ListField(db.LazyReferenceField("Dataset", reverse_delete_rule=db.PULL))
22
- reuses = db.ListField(db.LazyReferenceField("Reuse", reverse_delete_rule=db.PULL))
24
+ datasets = field(db.ListField(db.LazyReferenceField("Dataset", reverse_delete_rule=db.PULL)))
25
+ reuses = field(db.ListField(db.LazyReferenceField("Reuse", reverse_delete_rule=db.PULL)))
23
26
 
24
- featured = db.BooleanField(default=False)
25
- private = db.BooleanField()
26
- extras = db.ExtrasField()
27
+ featured = field(db.BooleanField(default=False), auditable=False)
28
+ private = field(db.BooleanField())
29
+ extras = field(db.ExtrasField(), auditable=False)
27
30
 
28
- spatial = db.EmbeddedDocumentField(SpatialCoverage)
31
+ spatial = field(db.EmbeddedDocumentField(SpatialCoverage))
29
32
 
30
33
  meta = {
31
34
  "indexes": ["$name", "created_at", "slug"] + Owned.meta["indexes"],
@@ -34,6 +37,10 @@ class Topic(db.Document, Owned, db.Datetimed):
34
37
  "queryset_class": OwnedQuerySet,
35
38
  }
36
39
 
40
+ after_save = Signal()
41
+ on_create = Signal()
42
+ on_update = Signal()
43
+
37
44
  def __str__(self):
38
45
  return self.name
39
46
 
@@ -60,3 +67,4 @@ class Topic(db.Document, Owned, db.Datetimed):
60
67
 
61
68
 
62
69
  pre_save.connect(Topic.pre_save, sender=Topic)
70
+ post_save.connect(Topic.post_save, sender=Topic)